pax_global_header00006660000000000000000000000064147005362430014516gustar00rootroot0000000000000052 comment=d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89 ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/000077500000000000000000000000001470053624300175325ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/.gitignore000066400000000000000000000003641470053624300215250ustar00rootroot00000000000000*~ *.so *.o *.lo *.la *.tar.gz *.user *.orig *.autosave *.qm qrc_* moc_* Makefile autom4te.cache config.h config.log config.status libtool .deps .libs .dirstamp stamp-h1 .cache compile_commands.json build-* src/simplescreenrecorder private-* ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/AUTHORS.md000066400000000000000000000022101470053624300211740ustar00rootroot00000000000000SimpleScreenRecorder contributors ================================= Programming ----------- - Maarten Baert - Boran Car (GLInject hotkey workaround) - Dubslow (profiles) - Dmitry Kostenko ('simpleui' patches) Graphics -------- - David Revoy (icon) - Mrallowski (original camera lens from openclipart.org) - Maarten Baert (header) Build system ------------ - Maarten Baert - Michał Walenciak (switch to cmake) Translations ------------ - Arabic: Abdulla - Bulgarian: Svetoslav Sashkov - Czech: Radek Steiger - German: Manuel Schömburg, AlexMI - Greek: Nick Thom - Spanish: Dani Rodríguez - French: Mario Roger, Olivier Humbert - Hebrew: GreenLunar - Hungarian: ViBE, Balázs Úr - Indonesian: Arif Budiman - Italian: Bersil - Japanese: Tou Omiya, Utuhiro78 - Lithuanian: welaq - Dutch: Maarten Baert - Polish: Szamanx0, Vetyt Yhonay, Michał Walenciak - Brazilian Portuguese: Paulo Milliet Roque, Rafael Ferreira - Russian: Dima Koshel, Vladi105, Olesya Gerasimenko - Serbian: Zvicko - Slovakian: Jose Riha - Swedish: Åke Engelbrektson - Ukrainian: Rom Gyrfalco - Simplified Chinese: Weitian Leung - Traditional Chinese: Estea Chen, Hsiu-Ming Chang ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/CHANGELOG.md000066400000000000000000000137171470053624300213540ustar00rootroot00000000000000SimpleScreenRecorder changes ============================ Git master ---------- - ? Version 0.4.4 ------------- - Some minor improvements. - Updated translations. - Bugfixes. Version 0.4.3 ------------- - Added V4L2 support (most webcams and capture cards). - Added option to mark recorded area on screen during recording. - Added JACK metadata. - Optionally support XDG config directory (`~/.config/simplescreenrecorder`) instead of home directory (`~/.ssr`). - Bugfixes. Version 0.4.2 ------------- - Improved X11/Wayland detection. - Changed ALSA input to use non-blocking API. This seems to reduce the latency in some cases. - Added support for s24, s32 and f32 sample formats for ALSA input. - Added 'open folder' button after saving the recording. - Several minor improvements. Version 0.4.1 ------------- - Added warning for Wayland users. - Internal error messages from FFmpeg and other libraries are now shown in the log window. - Fixed blocked continue button after selecting a window. Version 0.4.0 ------------- - Added high-DPI monitor support. - Added option to skip the welcome screen. - Increased maximum video size to 20000x20000. - Added more command-line options. - Added recording schedule. - Add command-line control over stdin. - Many bugfixes. Version 0.3.11 -------------- - Fixed FFmpeg 4.0 compatibility. - Fixed AAC codec (missing channel layout). - Improved build system. - Minor bugfixes. Version 0.3.10 -------------- - Custom recording frame to replace QRubberBand (important for Qt5). - Improved build system. Version 0.3.9 ------------- - Switched to CMake build system. - Added RGB24 support. - Automatically add scroll bars when the screen resolution is too low to display the complete window. - Improved PulseAudio latency handling of monitors. - Added a separate checkbox to enable timestamps. - Provide a default file name, so now all options are set to a sensible default value. - Use base-10 units for bitrate for consistency with FFmpeg (1 kbit/s = 1000 bit/s). - Minor bugfixes. Version 0.3.8 ------------- - Revert sample rate to 44.1 kHz for FLV container, since it does not support 48 kHz. Version 0.3.7 ------------- - Added support for latest FFmpeg/libav. - Make relaxed GLInject permissions actually work. - The backend now supports fragmented recording, but there is no GUI for this yet. - The default audio sample rate is now 48 kHz. This makes SSR compatible with Opus. - System tray icon now indicates errors during recording. Version 0.3.6 ------------- - Fixed another bug in YUV420 converter related to the previous one. Version 0.3.5 ------------- - Fixed bug in YUV420 fallback converter introduced by refactoring. Version 0.3.4 ------------- - Allowed user to start recording with hotkey or systray even when not on the recording page. - Fixed JACK input bug that could cause deadlocks in rare cases. - Added full support for NV12. If FFmpeg is compiled with NVENC support, this will allow you to use it. Version 0.3.3 ------------- - Fixed mistake in configure.ac that was causing libssr-glinject.so to be linked with unwanted libraries. Version 0.3.2 ------------- - Added Qt5 support (optional, Qt4 is still the default) - Added YUV422, YUV444 and RGB support (YUV420 is still the default) - Added pixelformat and sampleformat codec options. - Added benchmark. - Improved RTL support. - Bugfixes Version 0.3.1 ------------- - Small change to ssr-glinject script for improved compatibility with Steam. - Added qscale option. - Disabled PulseAudio latency estimation (apparently it made things worse). - New muxing code for improved compatibility with the latest ffmpeg/libav. - Switched to the BT.709 color space. Version 0.3.0 ------------- - Added profile support. - New, more flexible OpenGL recording system (glinject-next branch merged). - More robust sound notifications. - Show progress dialog while flushing the encoders. - Improved hotkey support using XInput2. - Improved synchronizer. - Native support for mono microphones (plughw no longer needed). - Added source detection for ALSA. - Partially rewritten output backend. - Improved mouse following algorithm. - New icon. - Bugfixes. Version 0.2.2 ------------- - Bugfixes. Version 0.2.1 ------------- - Bugfixes. Version 0.2.0 ------------- - Added JACK support. - Added fast audio resampler with drift correction support. - Added translation support. - Added sound notifications. - Added much faster scaler. - Added keyint custom codec option. - Improved NVIDIA flipping dialog. - Bugfixes. Version 0.1.2 ------------- - Added minrate, maxrate and bufsize custom codec options. - Bugfixes. Version 0.1.1 ------------- - Bugfixes. Version 0.1.0 ------------- - Native PulseAudio input. - Show recording frame while entering x/y/width/height. - Improved some GUI elements. - Added system tray icon. - Added custom video/audio codec options. - Added workaround for PulseAudio-related glitches. - Previewer works even when not recording. - Don't show RTMP urls on the recording page. - Added 'separate file per segment' option. - Hotkey workaround for misbehaving applications in GLInject. - Improved statistics and added external statistics file. - Improved synchronizer. - Added synchronization diagram for debugging. - Optimizations. - Bugfixes. Version 0.0.7 ------------- - Improved streaming support. Version 0.0.6 ------------- - Added link to Steam recording instructions for GLInject. Version 0.0.5 ------------- - Detect and fix NVIDIA flipping issue. Version 0.0.4 ------------- - Audio previewer is hidden if there is no audio. - Bugfixes. Version 0.0.3 ------------- - Added desktop entry. - Switch from qmake to autotools. - Bugfixes. Version 0.0.2 ------------- - Added icon and welcome image. - Bugfixes. Version 0.0.1 ------------- - Native ALSA input to replace libavdevice. - Improved synchronizer. - Better audio codec support. - GLInject can record the cursor. - Added video and audio previewer. - Added 'about' dialog. - Many bugfixes. Version 0.0.0 ------------- - First public version. ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/CMakeLists.txt000066400000000000000000000073531470053624300223020ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1) cmake_policy(VERSION 3.1) if(POLICY CMP0072) cmake_policy(SET CMP0072 NEW) endif(POLICY CMP0072) set(CMAKE_CXX_STANDARD 11) project(simplescreenrecorder VERSION 0.4.4) if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|i386|i686") set(PROCESSOR_IS_X86 TRUE) else() set(PROCESSOR_IS_X86 FALSE) endif() option(ENABLE_32BIT_GLINJECT "Build the 32-bit version of 'libssr-glinject' on 64-bit systems (in addition to the 64-bit version). Required for OpenGL recording of 32-bit applications on 64-bit systems." FALSE) option(ENABLE_X86_ASM "Allow x86/x64 assembly or intrinsics." ${PROCESSOR_IS_X86}) option(ENABLE_FFMPEG_VERSIONS "Use FFmpeg version numbers for feature support tests. Enable when using FFmpeg, disable when using Libav." TRUE) option(ENABLE_JACK_METADATA "Use the JACK metadata API. May not work with very old JACK versions." TRUE) option(WITH_OPENGL_RECORDING "Build with OpenGL recording support." TRUE) option(WITH_V4L2 "Build with V4L2 support." TRUE) option(WITH_PIPEWIRE "Build with PipeWire support." TRUE) option(WITH_ALSA "Build with ALSA support." TRUE) option(WITH_PULSEAUDIO "Build with PulseAudio support." TRUE) option(WITH_JACK "Build with JACK support." TRUE) option(WITH_QT5 "Build with Qt5 (instead of Qt4)." FALSE) option(WITH_SIMPLESCREENRECORDER "Build the 'simplescreenrecorder' executable." TRUE) option(WITH_GLINJECT "Build the 'libssr-glinject' library. Required for OpenGL recording." TRUE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) include(FeatureSummary) include(GNUInstallDirs) # try to guess the correct 32-bit library path on 64-bit systems if(ENABLE_32BIT_GLINJECT) if(NOT DEFINED CMAKE_INSTALL_LIB32DIR) set(_LIB32DIR_DEFAULT "lib32") if(EXISTS "/etc/debian_version") if(EXISTS "/usr/lib/i386-linux-gnu") set(_LIB32DIR_DEFAULT "lib/i386-linux-gnu") elseif(EXISTS "/usr/lib/i686-linux-gnu") set(_LIB32DIR_DEFAULT "lib/i686-linux-gnu") else() message(WARNING "Could not find correct multiarch 32-bit library path, falling back to 'lib32'.") endif() endif() set(CMAKE_INSTALL_LIB32DIR "${_LIB32DIR_DEFAULT}" CACHE PATH "object code libraries, 32-bit (${_LIB32DIR_DEFAULT})") endif() mark_as_advanced(CMAKE_INSTALL_LIB32DIR) if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_LIB32DIR}) set(CMAKE_INSTALL_FULL_LIB32DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIB32DIR}") else() set(CMAKE_INSTALL_FULL_LIB32DIR "${CMAKE_INSTALL_LIB32DIR}") endif() endif() if(WITH_GLINJECT) add_subdirectory(glinject) endif() if(WITH_SIMPLESCREENRECORDER) add_subdirectory(src) add_subdirectory(src/translations) if(WITH_OPENGL_RECORDING) # the 'ssr-glinject' script is installed together with the main SSR executable to avoid problems with multiarch packages install( FILES scripts/ssr-glinject DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) endif() install( DIRECTORY data/output-profiles DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/simplescreenrecorder ) install( DIRECTORY data/man/ DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1 ) install( FILES data/simplescreenrecorder.desktop DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/applications ) install( FILES data/simplescreenrecorder.metainfo.xml DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/metainfo ) # icons set(icons_res 16 22 24 32 48 64 96 128 192 256) foreach(res IN LISTS icons_res) install( DIRECTORY data/icons/${res}/ DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/icons/hicolor/${res}x${res}/apps ) endforeach() install( DIRECTORY data/icons/scalable/ DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/icons/hicolor/scalable/apps ) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/COPYING000066400000000000000000001045131470053624300205710ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/README.md000066400000000000000000000271221470053624300210150ustar00rootroot00000000000000SimpleScreenRecorder ==================== SimpleScreenRecorder is a screen recorder for Linux. Despite the name, this program is actually quite complex. It's 'simple' in the sense that it's easier to use than ffmpeg/avconv or VLC :). There is a separate repository for SimpleScreenRecorder packages: https://github.com/MaartenBaert/ssr-packages ![Screenshot](data/screenshots/screenshot01.png) License ------- GNU GPL v3 - read 'COPYING' for more info. Compiling --------- Important: If you have already installed SSR using the package manager of your distribution, you should uninstall it first before continuing! First install the required dependencies (see below). After you have done that, you can compile and install SSR by running: ./simple-build-and-install In order to use OpenGL recording for 32-bit applications on a 64-bit system, you need to compile a 32-bit version of `libssr-glinject.so`. The script will do this automatically if your system is 64-bit. Unfortunately some distributions (older versions of Debian, Ubuntu, Linux Mint, ...) don't properly support compiling 32-bit libraries on a 64-bit system, which results in errors related to missing libraries (usually libGL, libGLU) even when those libraries have in fact been installed. The problem is caused by the fact that the required symlinks are included in the `-dev` packages, but the 32-bit `-dev` packages can't be installed. The simplest solution is to skip compilation of the 32-bit GLInject library, which can be done by running: ENABLE_32BIT_GLINJECT=FALSE ./simple-build-and-install You can then use SSR without the 32-bit library, or you can use the precompiled 32-bit library provided by your distribution. If you actually want to compile the 32-bit library, you have to manually create the missing symlinks in the 32-bit library directory: cd /usr/lib/i386-linux-gnu sudo ln -s libGLU.so.1 libGLU.so sudo ldconfig The OpenGL library `libGL.so` is a special case, because on some distributions (Ubuntu, Linux Mint, ...) it is installed in a different location depending on which driver you are using. If your distribution has installed the file `libGL.so.1` in the regular 32-bit library directory `/usr/lib/i386-linux-gnu` (e.g. Debian), then run this: cd /usr/lib/i386-linux-gnu sudo ln -s libGL.so.1 libGL.so sudo ldconfig Otherwise you need to figure out where the library is installed first, and then manually create symlinks from the regular 32-bit library folder to this file. Note that doing this may interfere with OpenGL functionality if you ever switch to a different driver, or even just update the driver (in the case of NVIDIA). In that case you will need to manually remove the symlinks and (optionally) recreate them. If you are using a combination of open-source and proprietary drivers (e.g. for laptops with Intel + NVIDIA GPUs a.k.a. 'Optimus'), follow only the steps for the open-source drivers. If you are using one of the open-source drivers (i915, radeon, nouveau), then run this: cd /usr/lib/i386-linux-gnu sudo ln -s libGL.so.1 mesa/libGL.so sudo ln -s mesa/libGL.so libGL.so sudo ln -s mesa/libGL.so.1 libGL.so.1 If you are using the proprietary AMD driver, first figure out where the fglrx directory is located, this can be `/usr/lib32` or `/usr/lib/i386-linux-gnu`. Then go to the relevant folder and create the symlinks: cd /usr/lib/i386-linux-gnu sudo ln -s libGL.so.1 fglrx/libGL.so sudo ln -s fglrx/libGL.so libGL.so sudo ln -s fglrx/libGL.so.1 libGL.so.1 If you are using the proprietary NVIDIA driver, first figure out where the fglrx directory is located, this can be `/usr/lib32` or `/usr/lib/i386-linux-gnu`. Then go to the relevant folder and create the symlinks: cd /usr/lib/i386-linux-gnu sudo ln -s libGL.so.1 nvidia*/libGL.so sudo ln -s nvidia*/libGL.so libGL.so sudo ln -s nvidia*/libGL.so.1 libGL.so.1 Note that the full name of the 'nvidia' subdirectory includes the driver version number, so you will need to fix these symlinks whenever the driver has been updated. If you want to compile step-by-step (without `simple-build-and-install`), you can use the following steps: mkdir build-release cd build-release cmake -DCMAKE_INSTALL_PREFIX="/usr" -DCMAKE_BUILD_TYPE=Release .. make sudo make install Note that you may need to specify additional options depending on which Linux distribution and CPU architecture you have. The `simple-build-and-install` script detects the correct options automatically, you can run it to see which options you need. You don't need to install the program to use it, but installing will give you a desktop entry and an icon. Also, if you don't install the program, you will need to specify where the GLInject libraries are located with `LD_LIBRARY_PATH` when using OpenGL recording. Compilation issues ------------------ Most of these issues are avoided when using `simple-build-and-install`, but you may run into them if you run `cmake` directly. ### FFmpeg vs Libav confusion Libav is a fork of FFmpeg that was created in 2011 as a result of a conflict between the developers. Some Linux distributions (including Debian and Ubuntu) switched to Libav because the package maintainers were among the Libav developers, while others stuck to FFmpeg. In order to deal with this situation, SSR supports both FFmpeg and Libav. In 2015 Debian and Ubuntu decided to switch back to FFmpeg for technical reasons, which means that these days very few SSR users are still using Libav. Although Libav is theoretically still supported by SSR, I am no longer doing any testing with it. As of 2020 Libav is effectively no longer being developed, so Libav support may be dropped entirely at some point in the future. Since the FFmpeg and Libav APIs have changed significantly over time, SSR checks which version of the libraries is being used to determine which features are safe to use. Although the FFmpeg and Libav API are almost identical, there are some minor differences, mostly related to the version numbering scheme. Unfortunately SSR can not determine automatically whether you are using FFmpeg or Libav, so you have to specify this manually when running `cmake`, using the `ENABLE_FFMPEG_VERSIONS` option, which defaults to `TRUE` (i.e. FFmpeg). If you are using Libav, you should add the option `ENABLE_FFMPEG_VERSIONS=FALSE`. If you forget this, you will likely get various error messages during compilation related to missing functions, structures or fields. You can check whether you are using FFmpeg or Libav by running the following command: grep "This file is part of" `pkg-config --variable=includedir libavcodec`/libavcodec/avcodec.h This command should print either `This file is part of FFmpeg` or `This file is part of Libav`. ### Qt 4 MOC bug When compiling with Qt 4 on some newer distributions, you may get the following error message from AutoMoc: [...]/ssr/src/Global.h:52: Parse error at "defined" This is caused by a [Qt bug](https://bugreports.qt.io/browse/QTBUG-22829) which has been fixed in Qt 5, but has not been backported. The file path reported in the error message is wrong, the actual problem is triggered by line 52 in `/usr/include/sys/sysmacros.h`. To work around the bug, add the following option when running `cmake`: cmake [...] -DCMAKE_AUTOMOC_MOC_OPTIONS="-D_SYS_SYSMACROS_H_OUTER" Alternatively, if your distribution provides Qt 5 packages, you can also use these instead. For Debian and Ubuntu, you would do this by installing `qt5-qmake`, `qtbase5-dev` and `libqt5x11extras5-dev`. The `simple-build-and-install` script will detect and use these automatically. If you run `cmake` yourself, you should add the option `-DWITH_QT5=TRUE` to use Qt 5 instead of Qt 4. Build dependencies ------------------ You will need the following packages to compile SimpleScreenRecorder: - GCC (>= 4.6) - cmake - pkg-config - Qt4 (4.8 or newer) or Qt5 (5.7 or newer) - ffmpeg or libav (libavformat, libavcodec, libavutil, libswscale) - ALSA library - PulseAudio library (optional, disable with -DWITH_PULSEAUDIO=FALSE) - JACK library (optional, disable with -DWITH_JACK=FALSE) - libGL (32 and 64 bit) - libGLU (32 and 64 bit) - libX11 (32 and 64 bit) - libXfixes (32 and 64 bit) - libXext - libXi - libxinerama - video4linux2 (V4L2) library If you have a 64-bit system and you want to compile the 32-bit GLInject library, you have to install some 32-bit libraries as well. Otherwise the regular packages are sufficient. ### Debian / Ubuntu For newer versions (with Qt5): sudo apt-get install build-essential cmake pkg-config desktop-file-utils libgl1-mesa-dev libglu1-mesa-dev \ qt5-qmake qttools5-dev qtbase5-dev libqt5x11extras5-dev libavformat-dev libavcodec-dev libavutil-dev \ libswscale-dev libasound2-dev libpulse-dev libjack-dev libx11-dev libxext-dev libxfixes-dev libxi-dev \ libxinerama-dev libv4l-dev For older versions (with Qt4): sudo apt-get install build-essential cmake3 pkg-config desktop-file-utils libgl1-mesa-dev libglu1-mesa-dev \ qt4-qmake libqt4-dev libavformat-dev libavcodec-dev libavutil-dev libswscale-dev libasound2-dev libpulse-dev \ libjack-dev libx11-dev libxext-dev libxfixes-dev libxi-dev libxinerama-dev libv4l-dev Extra dependencies for 32-bit GLInject on 64-bit systems: sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install g++-multilib libgl1-mesa-dev:i386 libglu1-mesa-dev:i386 libx11-dev:i386 libxfixes-dev:i386 Note: Debian <= 7 and Ubuntu <= 14.04 are affected by Debian bug #689088 (https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=689088), which makes it impossible to install the 32-bit and 64-bit libGL/libGLU development packages at the same time. You should use the following command instead: sudo apt-get install g++-multilib libglu1-mesa:i386 libx11-dev:i386 libxfixes-dev:i386 Next you should apply the workarounds described in the 'compiling' section. ### OpenSUSE This list is incomplete but usually sufficient: sudo zypper install gcc libffmpeg-devel libqt4-devel libpulse-devel libjack-devel \ glu-devel libX11-devel libXext-devel libXfixes-devel libXi-devel Some packages (e.g. ffmpeg) are not in the official repository, but can be installed from the [Packman repository](http://packman.links2linux.org/). You can add the Packman repository with this command: zypper addrepo -f http://ftp.gwdg.de/pub/linux/packman/suse/12.3/ packman Extra dependencies for 32-bit GLInject on 64-bit systems: sudo zypper install gcc-32bit libstdc++47-devel-32bit glu-devel-32bit libX11-devel-32bit libXext-devel-32bit libXfixes-devel-32bit ### Fedora sudo yum install qt4 qt4-devel ffmpeg-devel alsa-lib-devel pulseaudio-libs-devel jack-audio-connection-kit-devel \ make gcc gcc-c++ mesa-libGL-devel mesa-libGLU-devel libX11-devel libXext-devel libXfixes-devel Some packages (e.g. ffmpeg) are not in the official repository, but can be installed from the [RPM Fusion](http://rpmfusion.org/) repository. You can add the RPM Fusion repository with this command: rpm -Uvh http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-stable.noarch.rpm \ http://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-stable.noarch.rpm Extra dependencies for 32-bit GLInject on 64-bit systems: sudo yum install glibc-devel.i686 libgcc.i686 mesa-libGL-devel.i686 mesa-libGLU-devel.i686 \ libX11-devel.i686 libXext-devel.i686 libXfixes-devel.i686 Other files you may want to read -------------------------------- notes.txt contains notes about the design of the program. It's a good idea read this first if you want to make changes to the code. todo.txt contains a list of things that I still need to do. This file changes a lot of course. ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/000077500000000000000000000000001470053624300206125ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindAVCodec.cmake000066400000000000000000000011321470053624300236560ustar00rootroot00000000000000# rules for finding the AVCodec library find_package(PkgConfig REQUIRED) pkg_check_modules(PC_AVCODEC libavcodec) find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h HINTS ${PC_AVCODEC_INCLUDEDIR} ${PC_AVCODEC_INCLUDE_DIRS}) find_library(AVCODEC_LIBRARY NAMES avcodec HINTS ${PC_AVCODEC_LIBDIR} ${PC_AVCODEC_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(AVCodec DEFAULT_MSG AVCODEC_LIBRARY AVCODEC_INCLUDE_DIR) mark_as_advanced(AVCODEC_INCLUDE_DIR AVCODEC_LIBRARY) set(AVCODEC_INCLUDE_DIRS ${AVCODEC_INCLUDE_DIR}) set(AVCODEC_LIBRARIES ${AVCODEC_LIBRARY}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindAVFormat.cmake000066400000000000000000000011571470053624300241000ustar00rootroot00000000000000# rules for finding the AVFormat library find_package(PkgConfig REQUIRED) pkg_check_modules(PC_AVFORMAT libavformat) find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h HINTS ${PC_AVFORMAT_INCLUDEDIR} ${PC_AVFORMAT_INCLUDE_DIRS}) find_library(AVFORMAT_LIBRARY NAMES avformat HINTS ${PC_AVFORMAT_LIBDIR} ${PC_AVFORMAT_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(AVFormat DEFAULT_MSG AVFORMAT_LIBRARY AVFORMAT_INCLUDE_DIR) mark_as_advanced(AVFORMAT_INCLUDE_DIR AVFORMAT_LIBRARY) set(AVFORMAT_INCLUDE_DIRS ${AVFORMAT_INCLUDE_DIR}) set(AVFORMAT_LIBRARIES ${AVFORMAT_LIBRARY}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindAVUtil.cmake000066400000000000000000000011511470053624300235570ustar00rootroot00000000000000# rules for finding the FFmpeg libraries find_package(PkgConfig REQUIRED) pkg_check_modules(PC_AVUTIL libavformat libavcodec libavutil libswscale) find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h HINTS ${PC_AVUTIL_INCLUDEDIR} ${PC_AVUTIL_INCLUDE_DIRS}) find_library(AVUTIL_LIBRARY NAMES avutil HINTS ${PC_AVUTIL_LIBDIR} ${PC_AVUTIL_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(AVUtil DEFAULT_MSG AVUTIL_LIBRARY AVUTIL_INCLUDE_DIR) mark_as_advanced(AVUTIL_INCLUDE_DIR AVUTIL_LIBRARY) set(AVUTIL_INCLUDE_DIRS ${AVUTIL_INCLUDE_DIR}) set(AVUTIL_LIBRARIES ${AVUTIL_LIBRARY}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindFFmpeg.cmake000066400000000000000000000037721470053624300235720ustar00rootroot00000000000000# rules for finding the FFmpeg libraries find_package(PkgConfig REQUIRED) pkg_check_modules(PC_FFMPEG libavformat libavcodec libavutil libswscale) find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h HINTS ${PC_FFMPEG_LIBAVFORMAT_INCLUDEDIR} ${PC_FFMPEG_INCLUDE_DIRS}) find_library(AVFORMAT_LIBRARY NAMES avformat HINTS ${PC_FFMPEG_LIBAVFORMAT_LIBDIR} ${PC_FFMPEG_LIBRARY_DIRS}) find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h HINTS ${PC_FFMPEG_LIBAVCODEC_INCLUDEDIR} ${PC_FFMPEG_INCLUDE_DIRS}) find_library(AVCODEC_LIBRARY NAMES avcodec HINTS ${PC_FFMPEG_LIBAVCODEC_LIBDIR} ${PC_FFMPEG_LIBRARY_DIRS}) find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h HINTS ${PC_FFMPEG_LIBAVUTIL_INCLUDEDIR} ${PC_FFMPEG_INCLUDE_DIRS}) find_library(AVUTIL_LIBRARY NAMES avutil HINTS ${PC_FFMPEG_LIBAVUTIL_LIBDIR} ${PC_FFMPEG_LIBRARY_DIRS}) find_path(SWSCALE_INCLUDE_DIR libswscale/swscale.h HINTS ${PC_FFMPEG_LIBSWSCALE_INCLUDEDIR} ${PC_FFMPEG_INCLUDE_DIRS}) find_library(SWSCALE_LIBRARY NAMES swscale HINTS ${PC_FFMPEG_LIBSWSCALE_LIBDIR} ${PC_FFMPEG_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(AVFormat DEFAULT_MSG AVFORMAT_LIBRARY AVFORMAT_INCLUDE_DIR) find_package_handle_standard_args(AVCodec DEFAULT_MSG AVCODEC_LIBRARY AVCODEC_INCLUDE_DIR) find_package_handle_standard_args(AVUtil DEFAULT_MSG AVUTIL_LIBRARY AVUTIL_INCLUDE_DIR) find_package_handle_standard_args(SWScale DEFAULT_MSG SWSCALE_LIBRARY SWSCALE_INCLUDE_DIR) mark_as_advanced(AVFORMAT_INCLUDE_DIR AVFORMAT_LIBRARY) mark_as_advanced(AVCODEC_INCLUDE_DIR AVCODEC_LIBRARY) mark_as_advanced(AVUTIL_INCLUDE_DIR AVUTIL_LIBRARY) mark_as_advanced(SWSCALE_INCLUDE_DIR SWSCALE_LIBRARY) set(FFMPEG_INCLUDE_DIRS ${AVFORMAT_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${SWSCALE_INCLUDE_DIR}) set(FFMPEG_LIBRARIES ${AVFORMAT_LIBRARY} ${AVCODEC_LIBRARY} ${AVUTIL_LIBRARY} ${SWSCALE_LIBRARY}) if(${AVFORMAT_FOUND} AND ${AVCODEC_FOUND} AND ${AVUTIL_FOUND} AND ${SWSCALE_FOUND}) set(FFMPEG_FOUND TRUE) else() set(FFMPEG_FOUND FALSE) endif() ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindJack.cmake000066400000000000000000000010251470053624300232630ustar00rootroot00000000000000# rules for finding the Jack library find_package(PkgConfig REQUIRED) pkg_check_modules(PC_JACK jack) find_path(JACK_INCLUDE_DIR jack/jack.h HINTS ${PC_JACK_INCLUDEDIR} ${PC_JACK_INCLUDE_DIRS}) find_library(JACK_LIBRARY NAMES jack HINTS ${PC_JACK_LIBDIR} ${PC_JACK_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Jack DEFAULT_MSG JACK_LIBRARY JACK_INCLUDE_DIR) mark_as_advanced(JACK_INCLUDE_DIR JACK_LIBRARY) set(JACK_INCLUDE_DIRS ${JACK_INCLUDE_DIR}) set(JACK_LIBRARIES ${JACK_LIBRARY}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindPipeWire.cmake000066400000000000000000000011641470053624300241430ustar00rootroot00000000000000# rules for finding the PipeWire library find_package(PkgConfig REQUIRED) pkg_check_modules(PC_PIPEWIRE libpipewire-0.3) find_path(PIPEWIRE_INCLUDE_DIR pipewire/pipewire.h HINTS ${PC_PIPEWIRE_INCLUDEDIR} ${PC_PIPEWIRE_INCLUDE_DIRS}) find_library(PIPEWIRE_LIBRARY NAMES pipewire-0.3 HINTS ${PC_PIPEWIRE_LIBDIR} ${PC_PIPEWIRE_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PipeWire DEFAULT_MSG PIPEWIRE_LIBRARY PIPEWIRE_INCLUDE_DIR) mark_as_advanced(PIPEWIRE_INCLUDE_DIR PIPEWIRE_LIBRARY) set(PIPEWIRE_INCLUDE_DIRS ${PIPEWIRE_INCLUDE_DIR}) set(PIPEWIRE_LIBRARIES ${PIPEWIRE_LIBRARY}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindPulseAudio.cmake000066400000000000000000000012071470053624300244670ustar00rootroot00000000000000# rules for finding the PulseAudio library find_package(PkgConfig REQUIRED) pkg_check_modules(PC_PULSEAUDIO libpulse) find_path(PULSEAUDIO_INCLUDE_DIR pulse/pulseaudio.h HINTS ${PC_PULSEAUDIO_INCLUDEDIR} ${PC_PULSEAUDIO_INCLUDE_DIRS}) find_library(PULSEAUDIO_LIBRARY NAMES pulse HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PulseAudio DEFAULT_MSG PULSEAUDIO_LIBRARY PULSEAUDIO_INCLUDE_DIR) mark_as_advanced(PULSEAUDIO_INCLUDE_DIR PULSEAUDIO_LIBRARY) set(PULSEAUDIO_INCLUDE_DIRS ${PULSEAUDIO_INCLUDE_DIR}) set(PULSEAUDIO_LIBRARIES ${PULSEAUDIO_LIBRARY}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindSPA.cmake000066400000000000000000000006031470053624300230370ustar00rootroot00000000000000# rules for finding the SPA library find_package(PkgConfig REQUIRED) pkg_check_modules(PC_SPA libspa-0.2) find_path(SPA_INCLUDE_DIR spa/param/props.h HINTS ${PC_SPA_INCLUDEDIR} ${PC_SPA_INCLUDE_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SPA DEFAULT_MSG SPA_INCLUDE_DIR) mark_as_advanced(SPA_INCLUDE_DIR) set(SPA_INCLUDE_DIRS ${SPA_INCLUDE_DIR}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindSWScale.cmake000066400000000000000000000011741470053624300237210ustar00rootroot00000000000000# rules for finding the FFmpeg libraries find_package(PkgConfig REQUIRED) pkg_check_modules(PC_SWSCALE libavformat libavcodec libavutil libswscale) find_path(SWSCALE_INCLUDE_DIR libswscale/swscale.h HINTS ${PC_SWSCALE_INCLUDEDIR} ${PC_SWSCALE_INCLUDE_DIRS}) find_library(SWSCALE_LIBRARY NAMES swscale HINTS ${PC_SWSCALE_LIBDIR} ${PC_SWSCALE_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SWScale DEFAULT_MSG SWSCALE_LIBRARY SWSCALE_INCLUDE_DIR) mark_as_advanced(SWSCALE_INCLUDE_DIR SWSCALE_LIBRARY) set(SWSCALE_INCLUDE_DIRS ${SWSCALE_INCLUDE_DIR}) set(SWSCALE_LIBRARIES ${SWSCALE_LIBRARY}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/cmake/FindV4L2.cmake000066400000000000000000000010261470053624300231030ustar00rootroot00000000000000# rules for finding the V4L2 library find_package(PkgConfig REQUIRED) pkg_check_modules(PC_V4L2 libv4l2) find_path(V4L2_INCLUDE_DIR libv4l2.h HINTS ${PC_V4L2_INCLUDEDIR} ${PC_V4L2_INCLUDE_DIRS}) find_library(V4L2_LIBRARY NAMES v4l2 HINTS ${PC_V4L2_LIBDIR} ${PC_V4L2_LIBRARY_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(V4L2 DEFAULT_MSG V4L2_LIBRARY V4L2_INCLUDE_DIR) mark_as_advanced(V4L2_INCLUDE_DIR V4L2_LIBRARY) set(V4L2_INCLUDE_DIRS ${V4L2_INCLUDE_DIR}) set(V4L2_LIBRARIES ${V4L2_LIBRARY}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/000077500000000000000000000000001470053624300204435ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/000077500000000000000000000000001470053624300215565ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/128/000077500000000000000000000000001470053624300220705ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/128/simplescreenrecorder-error.png000066400000000000000000000545531470053624300301600ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs^tEXtSoftwarewww.inkscape.org< IDATxy$Wy/"ά>x n6ǐrݕGl ,c5 #5kX`law.l sMgf}Wue}Njʪn3kDWqw/Wt$0ڿ}W$0{XZs8^z!0 ! !&CB!`0 RʜR>@[)ZR*JΞ=wϣ,K_6Rdr0p}g#k %/8gӾ?.|Hp@loukԞ=^RJ]TJ]Rm[o+d2#J;;LLDpd3BmxsAK/vVEu]6>E3M!eH$0Mt:M:&HDRW|\.__4~ WĿ@)p߿d_>xodM#D(ܚm76__Wk !DHlrrLVZl6#N"CJI.#͒ D RJ}0z-s*$|`t\"-@)i}y?tռi 0*Uu׿go| B "N|^T*Q*(˸9f1;;||>O.c``0hT*JZZ&ׯ_8縖eQ(刺\.D(3oc*TJq9&,KY KHRD"a5!~^zG?Qǵxw]/_r^) !b h묯0Gᩧg~~y1 )%aD)e*.jXXXիܸq .py'"dtt1RT܆@r|_+Og2տu7Bf?V.^n 7K_*%{@ ?uԐiTJ}S1(m677Y__>}ӧOSO111E^lj!wu]\¹s8w|j5\.(X&?^{-yC~k[oe$A^@bob:H$@񏀜F*[[[Gy^z%#`w]7"w3`BJi8<әL}u(NS.?V&|A|$&q${5#DD |o7W:9曙&hy߬׿c۶رH)bV-3g9sT*Փ衕o6.۬NTjEA@)i$I,dD"0b||B!HL&#PC\?|r!H&!aggL整iF;~1>F۶m+  j:q${5+&~6[q_֥GZ}=~\!L_2 Q0h4,--EԲ,^~e~~Cu]JR*"mܹs+WpUVWWY__i}֑dczz'Or1H$իW?c~mR]@%e?L&S;/;Ñ:zOz+ffm)NW? 1ˀߊ4Ryg@䭷?A-)lll"J+o|.K)mVET⣏>ի\rr|,d2eY) H))J:s N<}G:&JE:?7o?}Ldxx!rx*i^(m#'OnTߖR wm6_?RGZg]w:{xVCWXV1 W_}o|u>[7n… ض}?CQ@<lllD^wG8J%8CNyGy9rTt: eP޺uo-.]b8徑fݧykSGX}>Aӟ~!w!$eYr.}5#ķD(*ۺJ~RIOLXq]2 yn6Ze^{5Owy(gYtqDhpǎcee1|'Ͳ,| jFC=ć~Ǐguuxq\L&{벴{LJ~H$ϣ0;<%U8΄˜M&_M·x& U9{պqÉ[ѻ7%X0JEeq|zˎCFJ&'nG> :Bi233aR)Μ9DCCC ,,,=G?&p}>G\zl6) xGRA)ի\ymT.OF>Gsi m) Ih7n@`)^Λ@H|dDR}պq@wH+e4EjY=q k7o)03xb䮥i|g֭[JVRȐO?n3>>a뀖CCC8C*\ZF2$NL`zzO?bȉ'̙3$I*87oޣ\.XX}<70 sTn<H)q8R_3JC;;wmN=}y8z5%"L.W=WG)*g϶7n8mH!ReØRB(8(G AI*Ejlgginmlv&|7#СC͡R0<`L_BH"28/L鿱G DZ)ܸq>+ccclnnrrfJ[NR KKB2LWI8?JR"}Ʉ} Y3Si?nWHGq|V_w߸aA}8Bo$(<go"5c'ԏ~ONN̳0>>!%0D\n۴ ) iFYI"saDPJP4un^ٳ]``* L| ~EN9 Gܞ A rZJKf'$8w.jR^4Z4/xOL}RR<߇IYp UrO8^GeXZYaG)y=$9h0nxA0 !WgnnT* ZD4u&ff3>1A*:T:8wfMբVD4 R4lT*ĉQ.x| 7a( k&Hi%nf JK!9<,k<9d/$W@L&SO?]C)j|\!E!}0B:C?M&Q'|yIr>ku4AikVl4P(FGGcrb~"W 8CZX,Rfyyk׮Q l|.G"BMZ>ge'i;.H0MsRjHXGaAs٥Rf'S!V\Gon&7 wg? ,KjLW@lϜ@ q!'~XL u(u)%f3q}!D`igfp\h\>??c%.0:=v}mx Eq׮^eum 0LL&K gRS#P0 L&PTUQ %VԧF433=7Kks777sۖ^6zuq2ui;1>nl^ ,![,J^2<AmK<0j'ONGEMvVV H%<<%N,vh;ض8vvۦmql'(SQ퀯lٙ="nq"(ӅL`/-ݳn!3Y\DLSJaH+D'̌≔?;w.ZO@.A :<@Z~R֤FNR&vWF`!e!\~i!%v|^@|0RE=dM+ lct4UFjUCB吁1Ff `}# <BIfBj0'''qJ%n7ߺI"02(D"iZ(vF p]s@R,<7Qi5mR4iҶmJ<.l%kkpR lNtL3hq/)aض/!LLSZC=s<qqBB" d_ѴyVEV!ƞ:P1^L&: P1|"BJ~8d2i0d 907,.P)Wj:.i/Rgzz0ilnnh ɕZDzIPXYJa(er]T*y,+rù c(q{ !?p~^O{ć^Phf26.9Ni#1Oi߹*ὄ~DJmg8upA 5LC`g\( 96#z߿xMjhܾ;J? ( [E ϣC$GFP6^b,tWBc&dq}F@|T`}Эl5GG9>1Is{&%CLNN(u@Dp&|@ ?wQJqPO>;ˋF {>v4qEg!#b6F)|OǗia%\PǙq:x`|g?*vM*j=oa<l4 )IXmۜ@6:"v/'dr\䢅0'}D"@q6QNLMbhy!##Jx*DS@!D jTWY:.^؈4H|m 0"3 iF[xduIQߩTR)jV}4RxRJxQ2+Muk$GF[-Fqjw|0:R(\#6 ӄV "18I 6o^';0D~xvaa*6Zض BQYYbA)[O<@UurI'tB?GJas<1fDBض# 8[ʪÇIlm,4fX1'9v2DfCX,!8*;PlrCd=\haZ}$14RfSVk ! K)v*+,} ⩾~JѸvMs~x@x9_ @!%+\P:(MN2xJ?| BGܺu ۶CoC !z/FGG<ϋND6Kն1M.mGGI 5Hӌ!&℀C+\@* ihޮȚl5CI0S*kIpQ|0&Btn{@_+Y~獈〢~Ș|: {<Ѳ-& IDATX70r`Q8w DU\ LdPJ}VD&#`^8=z4*\Bp5Ga溴e>tFX{&鰹B0iF`@-=!Fȭ{é~DZe 6n\0r3ׇB /w![,CP>O[=@uڛs:T`+ q2Ǐc&_eDh4<|>3l4\a& GRDeJR0X !,jpL^6T@hRODaYZ*M`&$54 rۍ:Ņ$ 4hBSqZ|~|W{Luͭ}:s)e8՝>?]1OD/_|`Xi6Z288ڠѭ-<#Nz$Fd%pt=zTFp/_,#Gh fqh  @`Ƣ2x:R")$Vhh-ҩ[gf$Aۍ:d>:@CCLNH,Ņ|/#+>ׯlnvh}@s3Q34 CC?ܹs4* )bH l^ל/vsQlZXp]l?uӖh d1MoHI8|(xPk?ԯ]/nkN)ݬCX"|ӹ}A J-u&{BJ~:'wdpf-\tVt;Jyfm6 L/BpaKA/Z X\af`Z`ۨVk=*/{G@W4y>nC@K%nZ,-"i"7~M||iuhZE Pz3l!d/|%3?/]j52=za&''Y__tfpdd$r àX,F jZ )!8vXM&,..F!]u#Lr ;;;}EW &+6RcZFNBqޛDynT7f'&&i,6TVV(/-R<;0o=(BA5Za (}qHX:y~O[ ? TCCdwgwK;H///) ܹs'ZZ%g٨X}Ţvu{v-v@2aVP(099 Ebʝ 2 d-r_;wXjiW*?450n7:iGﶪw i`H3lK+a!n# yѮTXj 7neV4<_ &i ;~|'@?"P*\o@׽Xd@^ge')LNLB)Ń>7n">auq2I$۶}H$R1lrr2 /bYۻGt8Q y$SsV Za~V?vBP0Qvnu:u Q-&ʶɡc{ \w/]DAa#1{8}fF- 9]v )!en`1R߸BDb[j}E6r.=,{]$92BOxRAHɡ'N$ . CanbVJMr5|>)WBEdJ/W^Dges#U]c9({W;0h-߼E-[[Cש O,wKģCuI+~B!ҳ˜T@#bl&Q &iC=A: Sk7Dr{yCd2٨̩f^0 E b `Sg^pQ5Qi$R( .t# A"NuB"Dc!lkp $?S־uF )UV +Fy2jZad@2B#:-YĨ.crr2pg*&L耠z4 ΕJ R33$bl2.,5U^.C(uEP ož VTP+±]?Nb`#G~ي#'CARE! b3]j @)hdt0)%V?=(Tƃ?GW$(GR$1:ň%d:^b~{²- 8Gg Dl򵁀mہ!0De 9|^s#'k -A^|HӻzUծ.@4UCqO$]e\BFFcص]iE3|7w/]`fspKJCa1Ra`)@/IS$: ʎ}׀ .\'PG>aSd8@[ς^G#QPEɁn1^FCJȸA``aAb(֔1Ll&qi"}rD x`.IeFyaGE=y/NL^W(Z*'m\ŗ)Gߏ|=e*F ~ ~Qva7VUJJ qo f Z>ӲouÜ#Ms2߽ HMOa "E(xmDt+V^)mh "Gjh BW?\Rbx1xHdՖe>wm|(UVxyQb %6^.Q3־uʤu0 ΗZ(HNN" ̮UZV⨨'vG;p=Ow;1:KGV?iqJd"?r~p`<Ϗ+=v$RGƎ3KQ,]{hBѵm3G7G{] ~m՛A)^ 9"@ J?=aSJIr`Ps>7]t88bpCW7a8>/. ^?@!ՉKi52 s#@RPDPcz.+E۶ d"18@zZSq4u4xgz 鹎;'!Cb^_)= zmq}v 'w#u˟ 51Q(y1zõmGIϧ{H4BJ޸z#OF :g;!vGt﫠\EQ8?|i~VJE> " \7yIжn<4ᗿ+e{Nc FeUTB?nSo6i;vg]`J$HтԞ_w"a'== BƵk://F O"͈=zjId#|M) \xpתݫUJ»D=)MP>¡ U'92ebAs pѽl&M.&LMb+D?߇Iq*g+L?,}xju=sPzi{I^[i{ntEk* ېR/r"t{HÏ?U^v $<ҲmZmIb qq>q:ٸ/9)Kō]*!6eJ$ E%Ank$FE۹ct@'sD ?u! 5>߇³-l6%{U/TP%[ ?y H$t]5y'BKQ>R ſN@X#pa;=PH l.4գ2Ѿc`w 0 kծFt Z~vF!uȼņ FW'4 ^>^ '{s҇9q gmR͵H{و^=_`"#|bݷٹu hW?=NT7Hd=+q яK^҅2nZ*vb;_^ZdQJq}gV)J{17#~}=Vɉ ^X^ cyjFij2žĽw1-I "@WFmDOY;;s<ϯ;ll1Dv7yG[bA)Ւ@?L#_ζerè/,P}&t`{qܫYsTX` &c *ߺO^G>`WT-RgSxm:5|ѣ<]й/2B)s{`ƌ`G/o=-|eV|8wTk5݈Q{Y5.B*kǎ!ok 5*"][C[h.5`WBA`k!BI i9#lqm 8{ל(JTpRtr:o}x..u67w GbVPs.W~Zվ!w)44DK#|)Q'/Þv@{;AF|^Y7՞,A&nP>#/060=w 0M#I*ǫ@)rd>}{WZb [5=e}uEV&zx A{F vv"6=u||}t(`oQ?^1n\a齎Mڭ6.۷X|3KܭHR;Į!tW!t3b+Vynf6 Oۺe낑Lnʗv}7Z!@Βkqu]VVWhm_|Cy ^9:P o[s>]qk/(#&RBme(ܛv&i%pl3,4`%zwk <7=IqDq! 83}ץa/RxP~C븞~Qm@-n?mFC[*0@PMtq<'،\#~z0-T.`X ~X^Std"7A@2ukŻíl~!/04[[T+lt-?UGkooG~CڕA8Q/49b b b쪶 @s]:8jM .9C{u-P禦8WE BO)i4Z}.w]gR,,Â7[#;ŠUٻdpS !8ВW:ej%AŏNw\ײ3spp$0-d:iYl[J%"C  @޼(nקAy>Ź.4>+Q{G5G ^amYܭM=u%SNcs/.#;AyzixR04u>RH)w1Y,!0O!)uJW$a%3\N3S(۬6Cfhǐ8tAŃ\eyJEKY6+:NjkU*6 i ߋ^L)]fERiDA}BOyTVk9\([&21 =@ョ7)E.F&l۶Yki@*v=춮"?>㟱5o"\s~1cq (ƃFxav!n%"RA̠]i*_!H&`i$SFG,pTV u864.v1:-mDò"n@NsЇ,Z[[\? JRP,zA!p@_/#k8qccc"Gv$X6ˋȀ*xQ ]::DbY,ΐ@>?^{*]ojpY*lҨՙf.E=Kd v? mҳ{+IqPG2pnni,i(]B :;;;E =ggr?)066333Z-t/`Z^jMv$r1{EҜHX,}wVWx['PCLYNvfP@SXJFq̻sxHǪ5R)C2t.l8[-##q]۶8tSG:sOݟ?E<^=\877G F04]bqg2mv}w}X$Y˵ĸ!>PҨLYan.2Y4j7?]Rpr|7Mfr9|aq`gR,f.]BX<!=5Mx2IDATRq#GbbrL;P[jI`*G&aszI],7d&{vm϶43<ʶYmV>OVE}&kkkw3O1_E(dط!)94Nz8RFVӜ3+ n p07#ۻ;> $Ah^<ڪR!@B*\> |S`h)Hm$-M8v-P'$^s;XJ;4!xgE.Ft9WsxuזWqʕ*. ze˂c;37g,TCCcQ,KRRz Yױ'8X,8kkH<@jӌt.?1Y8LzCT ;2X_dF<24(b̖PM2k@Mv ``"$ R|߇,@ Hk\',N&A5Lm,aH uJ$ K$s],dp],[U WQ`6VVVPO- 0,>(Ia9MÛ"#bW- 8)_1/@ܧrH9íj+x͓0W+i d KL7 lN]3=n}蘄}@) 36D)e 〨*vlb9E`9# =U> NL&AAI?_qH3s[-3N @t|߃,  )C|?].FUo˕s0[ĉULX$,TDʨA.I!@Tm?"2xX W mibjC1 e+p +ͅB*:nbCD3Xl3a"YqWǒCN@@pl:mRW] -ERH* S]~ܫf eۂm;b93?" NʋB!5 e6(Ih;Z@|@b Y@x :Má|1b͏JQA>0dF0 )Α7h"Xm+w#?8i<`}Χ/@cq: l œ 7lw'EEѤA !gqAvгu<TUSY*]#cv[%z=; >!4 <]-ixY<"Fa:93e NTy0BpgE}B߃aێM(|8o]aCG,|&`1!ѲD#u-Ѕe2 y|$i|A󰰙1^28x=zT~#UUqHk5 318&)Dȓ,˂eY%Յn0O8.('3WVÑ/^ ɂ1e\y|#L,k ^ŲtEJqUVDřO g'v$[;UU1Ӂ0 k(!8X(R'~lgѣ$It:M޹CsCOrVɓ0ݹ%iP< d-Td0Q㿫U<\|X&@okL,!!LERtpl]Ӡp%U C= O y0ʃ0E/I> 36<3y EN烅O}S_,+{ĉS *}33 Ӿgm;Mt+vq]=8 _(JX5 QJq;oGDo,|},:S  Tpƿ_|xLQ |״ovYT«I.RzPfÙ@&Sx߿]$B)"[7 0'q/%dϐrH?sCh@kk᚟!ϣT,y˫l6l@iq]4M<:Yu0(W9 ޒN;vcvND)}U` $0^JA6L.i ~;E &_$Oĝ#ё0M6ew=o` 4YTU8*p( ډV ']BoGʽzX-^ !AX,/] O!$o_NnC4nXHX`c5?bs?xY0{b$B}QCe7P#Vh~NQֳA D;OG=c$qvKo>'ǘt߿elUD;%s&Nl/2һ`qqT FdjVCـ vD*B7rnY"Ĩ2(H`>ivZH |Q *,}7OVQjrSDH@lnw@z+[.6Ad+$v ^7M0glZPQB!G,=LDDFE߇y!!82;(+2bz F@:F.q2-u_:] G 2ͱܚ3A bBש( $2EBaL%Aܸ\/rh}Ξ}[ cb~CLxŲg`+!Aa6N- k6M/氰0JE dҙ!v˶v\"Ot ^NfrJetLT*Ipp0 F$:g_H P: HU (u_tY;OH$+9(RHb%,jeݶ禽/z8I$~oP+/z}Pjj8}4t=Ƶ|Bl1@LAT(E6tHzpk6>:6Vh4ת,;rh> :Nv!x[bhh&YSߩi;SJa&op PJ?>ݍ# qgXgf;Nn<EuϷZڲyػw/Μ9:EiN)f%XU$Sih8# SkPu\nw]Aݘ;g]LXD* _ cSI@, vj$/" % #d?6Зlee._%]l $Xbug :1rV !cr<rcjegϞ7DnI']w`Y42 JdYFPBYVH`"+iZ9)Iw* !~nJ`8 !޽{>3ss{<^=t5m=io5n(m sK!H۩%={vDddO^l4MC2DVC"S`&bK]D$ 8 !s9!l$e3}d T#]024,gvQpLͶK/:$ M/F9dYW¾}~fݻwR`׮]}+++* g5gDX(f0 Lv$1d;и..1p&)^r.PB9M[1(<2!F@)V柊w!d6vd^G~7 nw SIvĚe휴; "F)ċbGK.a(hZcqqg ]{8}zb JpdBW^y% (WHM&"0}Ko#"Դe]&KB)r:)&T*dtc#irw8 >0C[%A# a4H83I`R__Bsm L}h6TlȲ={@0T:#LbiiSގ?J~4C}۶0ssQU(qHVI(!AD(F#wLb1aa9 |` r@J)߽MxqG.p"c'\~_CfX DbH<GXD\:e=:n^N~IFIp v#flq4J ͆Ѹ#2 HRrݲJ'MS Fsx| o[&8zLDQ q(;(-Me*4OEO_ l' o)f6-C4ZU?KϘfz+,ˈb0 cO@Fn ?϶mt]tݩ6I$د{ %Bj?>U矾 L%A6I8O(EV=?n9灯ylQ4 ֽ^F! @ LYpتDINtKp9Sn6`яOml\4.,,4#G~!{dc)Wn6vs7=?C=|P ܓlbmm JFnw{r VjT-ZyZV=}>zkK?D)2ol~SX#xًx6SƘjRiҿ7* ѝNnj>?T*gkƄVݍdʉ]""&ѣG7pC4MÇQ]ZƘza f ~yd/^]!Hpal[D!O6Νݻ\)(m x&6 .J |6#6\H H@!_|5D\ O6!wg-4z@vK W|bBsB'x~b?NjH ҡDxF޹~:\l{<#f-:p<ss~t;t \ v}4B? $/o;6U|,37wǶPWxR˭nt~D+sWcu[ ;|`='7J_ dޮFC:Z7yk_;oLw3O,W( >" vaUpҫ^϶[ƘN7) p-N$. IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/128/simplescreenrecorder-idle.png000066400000000000000000000506341470053624300277400ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs^tEXtSoftwarewww.inkscape.org< IDATxweG~::=nfY 'H %36d C  kvQj@k(J409o's_&əppM'[_ ^{aeYu!D,Kcbii|c̦zX7,H)o4/ri~81˲Z?"|Hq6Q6Bu11717|_JJ/Q>CiW_>/٭d53 DQ(Zg6Bu]lۦX,R,q]7^sIkB+ʷ{ޏ>;~z$ 3Hmll6t:m~֓?H)T*e*cccJ5Ƙ?Z˲^} <}Ykyy !.FFA$lṟc8tzjJZR066eYOբhtt:sUnݺEwqjRT!?_|mq,>OW1X_ !f n*kkkw(ȑ#<>}Ǐsq, )%ee)ef*M)5d"7o\v .+9tm󘚚bffB!^zGx!pmۿf5!ĸ X__guu~'`߾}C q^yO{8".]w]onT*LMM1118|tt_>C)`O!~!z,--jyΝ;m(2%^&yo?C )%mc6R+Ÿ  &''9p׻)gaoqm?N%C' |I žJO (duuU zl<2.LOO3;;KV.e f__gg{ A^x1柼k}^;g_Ot]3:u/~|_gzzzHRJ1ƒ `eeK.qeX]]Ӂ_)J077ǩSxul˗^|E1C@p'=R+o# yc?BŠeVW6( |K_gerrrHRJ `0h4x|2.]l~<V!h3rIN:Ń>HXP(d6?ׯo~A)fggS7x_#vxG>DŽlnn@EXOO?󌌌 )>׮]^… A{衇իWyGY[[t!qQ0j1??hgK"?0<GP(P,۷o[[@ 9*Ƙm?~V~Mb.pܹBTh9ug?Y<bNy~w^z,8b0 yqxAkM\f}}Cnzloogض"ܼyeRv'NťKh4loo#T*I돏۹!?T9s1/ׯ_nc6>,>,d4Moo~P9vZJ%&&&X__g߾}\pcǎQ."Z~5׳Lbd}} J%lڵkj5Z;v REZP(P.bmm(u,uVf'&&ؠRO2 qN~RgC~hN'>g1/(u]nݺ֚)~W~%o)%Zk ʕ+ {:[<[[[J%.B)% ˁXYY!JVLZb 1|MΝ;K.q,4v9LHo7Çp3P_3PhZ={__e||)0122•+Wܾ};~:'=957QD"]uX(PaD*P0}EG;l҅x=a@βٳgp‡y<Ӝ8q۶30 m.]STH}ʕ+4-  'Oqc̷&H)ygx',^ZYXX:ٳgy3ǯP(}q(lMg8tndZH;V6Miu;4-AQ"\m9XzU*RMh҄MW7k}0>|7oS !xyꩧp]7c(?C| rcBE~폢k=z1˲JQi6"gg9wܐ;=\VO/J:t/r)i4H)& \bgv]=GS_D8lcI6M66X` -jI\܂zq/AD?`mu8N$cuJJ Q^_eY;F~Q~!P)})eVPu]sGV?=z%`뱼1/~<䓙>(o&mgIK.%ԧ&026Fe - -h *(ePVCcvmV8VY'g)jh*p!~/y^QJ\x˲b0 xGÇ msv01z*t6mm^x!KO"/%9>GƱX\'2tb BVl]iHF$G!4Q k@4m6ֶYZ^gkI+1Qf>06:րW_gsOy +_P=}~~yaX1~?H cQe?s O?t5~1Rb{"K;i D#!F ܚ_fc#NVU}vO\(|-fmyH=D:No%M^Ç9tׯ_lfT~xXVVVhZAj/Z 7>nnn!3|WrwgΜbfcczΥK2W>RHd4k[{]R-%fg>uR\:ADH J{g/@HF&-"enN3=1lZ.r^Oh,Fbnt0ZLaK`drVj8#:;;1&+kf$5ϕ+W xjcq_V%8; hZXlO:W_*㮬0779y$~J_xxBHh ۛFDƠ-J*}?66B?DE!) +ʕ b 6FI"d8Ei66l7L8CգZju"M m1(|(qKepq92#P}>tP$J&nʪB}Zmn==`vv˩׿֚s177%-dYvX2uFX9Hz#/@!QpjRp?Svvl$ڊM >H((Pxn:2HBC,E)\"nez߿Jh^gSdzAt9)055K뢔:=VNs&''3X5Tzu2o&OΔyG%m%c"ҊLRYśa!Dd[ ˲lv7YYl ۶0Zc yu\}RD2DD*ЈJA_Aݼ͂ǰ4\&UZF0TADIQ 1D̵xRt#Qw>MRMBf3c~ u`hk0CV˪P EּZ RDˆuM6ʊSBZ"S 2 iQ)c{*aRZQ,,` yЖomjwP4#::~?+7j޿za^ (7R410 Še;QFh@t,/'u B>uujI*=^7-pl7JFQX[[Z%-/Qe(BM4B;A T*c'xRde(`[@4ICuĿjipkk+lA'1|9t| t{st} 0:=6~ B^ 䙘6F+w}E/< G#9d+mhZlln$s@F^lfΕRisRf/1&@ZJZB vJB& ξ3QA$[8e#$mSvBg$H:Msv߄1DZ XěxB2: JgěBBA |L~>kq xW8wJܘ$J0ʡ($aڦ -;i B=y Ho*W"JJ94"$#?SyPcIy)HHfch·e: *RN>Hs`vv (VvBGD* &x{'@cxWġ°^IKۤ@n H! `gI\H30f:3,ɢ&]^䔎,?`$~Ɲ"MxĮz{|cDaeex|)p]~?FxN-(.'bR[XɡQv^21La Ji~2NEXqÐ+Lrʶv):ID #~Q .ݥROƘ?`0c>r\E`H*{9"N$=[%cKJ'Y}y3~06; $۲9m0mvgh;)%APHQ\ȁ4KܢRll{ 0n0&dQn&Jc6{:U9# !8鋡BɛӶǤ_)'LPv[xy.Alf0## #VVy9NiV wG*=4 XAhERR"S )|~F*i;C RҎ.UO O8آLÈOLVL'1Fy°ɖ&Jkn-.<=i52S(dqy5k,. lcI&tPGNC~ 'T661t#KM Ƙ˖:3Kbz!q =P6h ܤQ/' f.,feu{ X>i1!@ shtsǡKv=?U:S;F:8w;/EQ*2t{,ݰR̡<%ƔuxltC;\c˩7Q$>Yb2+L}Y[Z]'뛼7b}ʗIVR R!%82}(??@)Gh@L$L`t텬%>cF/B TydJ8 R%mL;@H}K@{βJm:>,v;| 2 FhAHW" ,uLi;JM4Wzv/Wls`6Oˌ1lLEI l2ASxv 0h!Pj:m4^KW6ͭ&I ݷaЖD:. PG Hg]9o$v+?o Dy?L/~{1Vlnzc˘ޔDB  -5 #0R#$gbai˫TFez8A$Ea `E=}G^l6J'\qv|,n%RF]\Wk#$ C\7`"}}J$EԽ@kLet/[Ht[[XF &QfbDb3(⭋W ,.PJe)jܾ@՚nT{%db$QIvJD|aǿGK’B/Qފoқ1@;JaFzt71册$J CDdha2uҁx/_ˎH?GCfpYYY`UrRH"$f@Ysжƍ *}z:3I/~ؤ)aPHhN ]. %& 0q0 5U{nV˿Z@0`KI|?Җ(mڱl\l6z* hl2dzBʋɤto@3FM(%q1n}˸mfS% Jϛݝ7cN<6wT> 4IQH`W{i&ȶ1ر8Rq/<-gh67nP(P@݋k H` Mc T@$S\ e$bbwy>`Phjԉc] d\@ ( WI(4Ig\&p7휷tb'5敓.)+@J%1 dJ&HҎ{4NBͿ' D +"cb?X]@ ڵ0n0cttaӊN;{2fٺ9ȶ3iZ3 7yj ZX;J2Gҁ}Pgn"` 1qp"]7vleZ)- B/^2:WD"aiC;(}eW")|W7;f Fx<=!x7IG 0gUww`h5<|!8/Cf/l~|E,@h,)a I-dTХ(Tǹ#+L f'@9u8_^1b2 vB@p"yܫ-$i)v ~g H;@ )J&)+a!MDI;*K*{v#Lݻ1`4EˢēJx`ш^뻻<@XH*!pCw!Єal俑ݏZGt#twrR0 ӥ1 @vع*E AQ.I$EH+J6w@IOy\}JĘ}R *XڠIEILRYn8 DZM9w3fz<v+_X,@I!r ?;QYJk:Nb`qXh%,RD${-XJXƠ Im@;ώw[wl jݸdtR8J7DF"|;LµV1;+FE((nӯPRċHR@w3<5ٵ>"IxhvDI`4v[y#b86gp/H`w\tko燉ߋ,$#T=&7~.i8Dd.n:] ް-KiIL^{ q81H1aBd: J' K3g'e%p, KJ6{Vv"lZ]Fzw{%$Ҋ,r 10J|8o(JN E8@;)w4]ZO&bg˔gZVZF4&L =OAwQD+O" m,K)|qLϻwa!b.>yo?{zc>B)A@ٱcI3t"= CPGij""+?D"U;Cl)"REE枊L'~d-wӁ0 +{ŲJe ~m]Cv)ødžQtW `@Pf2v0||_6v-f PR6nzf{/R*zRN :nrrU|ߧPr 7C`0$E֙kfΰ$[:H~@''XZYhDAzF OHTɞ#5q= K}ՂbabY=;@{ѿrn97 -UB,;8,LS>Ǎ>' B &= 9ǡnZR,01Ɍ"5{^?es?rwqTKj]0{:}&ヤxnL'~t: A[(}ØzH{ wxCt:m W5Z_瀠wF8R^'i;\cԪ|f&9)&Ufl&*{U$kmOBⅻ݀ { yXw`g?XboeXt6Qi&5Ul>cL`Ziͅ%ƪU:[C -lh(tV*F6Ņ5.s{"r6cl6?l dYXNi=jL6E!(Z-$t3VtI`lX!fV}XWnγ+xTe#UV˵,l` LOM01>f 0Igx9GNt$7&f3}\ leQhn(F[E}࿕]݆}>}?MΝ;eYλwqy8nhoG$=#0i}AP% (eAɃX^zs;K,eH>V:пbai߽ ?[Rc68|@:NP)Ulew[?<0ׇz/vIZ-9p׮]k/nlp>JRa{=&nd F"D.vGqZMp5NFar<yNKJ; =3=anbRCoH~ǐ# N\&34gRD!,ɱcǸyP/_kw]A,oo` ^+OFW^ȑ#uaIXǧ`A T"WÙ+%Hޠk3#}>;L2Yv <$@^@qH[ BBpXXN&3$gY]UlڿH0t txӿ. V/l ~30+KGpZ:e;#KQ $Z,0NH$8*\PuZ_/Q,ZvD>=b"%}&:nB|L}VEHR$R |<ٯqs ,# #z$wm},dghMRK}.( ,..2;;;s qA1pĨFéG^kZcH6Ct"[]!7t([`YX|/]LԾ,Ac*ujS>l,dwe_ٻqZj,4v7k ak(K ,i{}ji{ `$ eY&ɠhF^9$a\ɨnv!a<}jG76y-,: Z`>Cz.e #(HH2KtWZ%Y3A4iA8)z]ts\˘EDvJ#F"aW쪕&pdu+LŦN'Qu gȊB2@hv:5Y-w[M,WypVsp2î7ҟC:}0|HXjTRCm :* hjf2PjZ%@6\2!'` g2ܟX7xcvϲ>>#8L&0BBW#t;=Kl%0:V<|TA$wbn;@ 60Mms~2'Ӹ_,4QU[S-"iTg CT5LA@7zh|VC8VmШk`!>{d'2$ѝKKKPTXYY\.ˀxxHP`Lk~{a'eۖEfH۷oSTV;܂]eayp,BvQ5 "So5΢zCY!&ocy, s#,S9IN&Q+ub(.XhO AɣJmt!gggQUݐ]e)˗ik2bxE_{5vvv(U'r'm݆NE$zQC7z[MR3)Z6`LzA0 Z%R[M=k܌ǂTkh54|̞[ƽO]4錌4 SvvDNk=pۛDctMVMR!J (* YzA#A:z%ޝCbOd h Rcwn{|ۛo2?&Y|H[ޞ&^T*aTUW웦 l~N2bn:&d2n3*Ѩ`Z-n:e/1TU TH,FRTXu4j0Y{L쳗 ( +Klnd.h`L8b͆,y7xӴxh<{L.~kP׮]C47x{TUR4n1ы|`y2|OH$B$LQ5NNN5ل |_#/`%2sdWA9*}>ҙ`ۥV)T*Kmmv D,#!9`:6 4edG:1C!GTKSB|^_W_'xd[O:#Tω"hU׶JFA.X,KNy~߼l@,T~n޼IE4Ͻ3O(KswgkL%NmS'Ȋ%Zr)PH$L"X(KH~?=4dZT뚈HR3ȒzMcoSLKkX"c 8G<ڻXD 'ȞdI <,itަP(w{y< eυ| N.cuu#8gEWrc$23@0UX͡i.g!bun}LC8.yó} "i0 dL,]'-Ox)aQ |= %I$ r5׌;<Aph4:~X&|>Ehh6/-70%`)`Ô@艶 -еF:v_:F4ܹC!˱֖X^^nωRD>RL |c_x?%lO\6p(ʥ C`~0ͫ R~#W BHKbBuAFFYmnp~?wEUU7QVE٬q|^S=z B~4ZF٤Z^3L jWA8&Ns||<.6DEvTF1],d Oqp!ia";[ߜll6ý{(V• 2`P(8g39iںjhZW*G=ͽ 840xE!(4~P0˲v:4Mr%U vBzѹlRHn:[b=Zs 1v:e2"EV? x.ASO,0MZs*uMP{*hb5lK 6vq.#ax7帆4>z=Q~+X=/q ৱW9{tx_x-lGMQlS^†Ӡ{4}C xDMVw pEl/`C-w0 | I W$`-IJavG؉.//$>nIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/128/simplescreenrecorder-paused.png000066400000000000000000000474731470053624300303130ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs^tEXtSoftwarewww.inkscape.org< IDATxYdy;ܗZ֮ުM@,J2)F1b&lÏq1/rأKF)#$"I( $b![U]{eeV]ν'oVWBp:Y]o˅8qw('>eYGG3BY%RRJUJ|*JEԪVR/wa,)R>,8!B[@)u}RJz[JJ{Ώ?X'>1ygBL%t]:vuvxxgg6Bbx۶IRR)/J)uBfϷ~P 3 9_B|!RTU*FzN6+ )%lL&C.cddt:mgY֋7}= !~"N|٤RPTVsdȑ# r\l6evjT*Fbȍ7X^^q; 3<$gΜĉ8q9,BJeY%DJiTE$ Aď{}.W^.wyש溲,/9<<[/B'00 裏F@VAb}}r'xg9gY1\eee}{更mrJ),KORl6ˇ?a;Ƶkרje.l)1<55uisssA <B !].u^|>///}!;C^gmm_|??biiɬx۶9|0eL&x"یk,Y mKKKlllp!͵NOOSNQ,ammz9~Tڵk+m<0OOMMO#ƍ4MvwwIR&''>?y#gsΝz/R5677QJp-*Yquɤ1$ W\;66fRP(8d,FD"A*buuVW\\.@Xŋ$ :8|{ߣj122`W)% |իT*vwwBN%tX|bddJ?R8{_ !u:ضs=s=G,3wۥZo}}kQ&''G)EVcllt:bCq%d2f{GףVnަX,$ZR,١鰳C:ƶmn޼I>V'J155Eadd7xϛUΝ;g\ӛ7ocnnOLMML _un=z4to˗/_aӏD9sKJZfDs_UFGGzJ%O^znW\<5z)ݥlr9ݾ}qſt]T*d᭷ޢT*N9~8oNbeeeZ7|N$RJ@{ <\zZFZ%bYֈ)?T.zπesS_SO=E !peoh4,T*eY:t fggyw8{,R1VVVh41n)Jqq>̡CӤ r|gVm`}}۬q5&''`qq'O#<oqWlajj}sH %AY^^Ʋ,HR(ڵkH2SS!WrggǸci#:sǏg}}b?Aǡj144) ܾ};ϟѣ  !DJ_RJ" ?^(_8=27o.[o ~24;wK.s< N¶ms-Nի!!}0* ,,,RBDTbgg)%>,O>A8 /1Ν;[oe d2C=}b/{ff|#LOOcI,0=n}kֶA+uL,J)Vm^~=J=}͝G}zxc! I&Sb1z.NnC`eunEE2"IL"e?..]m|p<3ơyٟRn<ػ[~pfǏR~kZlllⓟ$/^4\.{}Cض >j@We& ~iN8Q6!@AբlnP(&&&g #Ì RJ8zrLTbuuׯSױ,Ke&6)EyP}1ѣGmW)u鰾Rz ]_4i8.^HW[*6D"#p1=ǡhn5}ʼn'gzz\. "BbY = w}mx ETf}c׮md,R n{˫G|0&3@<Ř,Rfqq #'벺={}s/J &43VK/g>Ϛ0W6d }GOR)eGg.7?|0Tj@ b1Ξ=B/,<*Z|~~ӧIx!AH|ϥs !ݿeq]OK)-+p %p\VI(z=Jehx.2I#q;ظvY53ض?8Z ut:lmmqEr1\b2)񩑑_TJwbٓBDʤi/ зZ-/VKOMMdbnnȾﳵ_ |3%vCV#piMҹB ,i8n&JAs\\|#afznߣlit$S),ۦQMvM^GBaT*E3|'OR١hwg)%.\ڵkpac A9ض8W\ljbqrܿj*moo*<bB~g~Ç.=b1{1z.]"l6E ys DpFC/zUS)bv ʓJ%i5 ,RX4[-nQ֨7f"ɠЗRfnn۲v%Hc+j*X̤O,lR*6|!{7MRJ666Ї>{geY4McLs…W^yW_})%!o/,b-yloo~/2&( BMD2!4h7QZt]Rt:UnZbeuZA7ZA/B+hw:˻,-f-; )t )_G#Y:=l&PoԱ- ˲0V`yi9m=z#GHZE)ٳgH$1 055&Z^k9:4/J8A|dq׮]3;{,UP(z!J6gg5Xh9J<2,Z n|{Z%m%0 +z7zwJ,Ro4!OPVt\#ID&AAVWf\is] jNOO2֖ ׯ_7x<.Gkp1@.]!ĹNCVò,>O 0{@wss9E˗ GyϒdjaY6X&b:F m!Wqoܤ\WT2E.#˒J}y}L C8f@u6w845M2bwXVM,SZyb133ܾ};zN8cǐR|>O%49b0nlBC|~^?ZR 9ϳ~T2+ڭ6xͥ)|O1<2y9%~&jsyFFFHRvLCB"Eӄ .D^yEĞmB{RRyCJ%t$ z%-4Fumd",n-RtoZ884M:y(fkk˄ч 3^/={6tu:\zʈ~˲xw)!LtssJ!~"g?G:1~n"`P%-]+4++wwQڿ1B,:."X2@M /%pPMsT*9>wwnL$MX6,]c011Ғ1HCK@X^0ttt7nE (4P%USOlV)%zwy'ܟl6"N2+9?,^1#B 옮I)I(++kxmǘ`(?"#\HC:7a^s09t[(&gsfSQu\L68-?ķ`ssd2IdttrLZ5@4Vl6Êbll؏wtnǏ%4A/_lVmsrSSSҔc|R$|>UJ`[zI]L)ddj _}"1~⇿z$S9&Ƙ(&F(Z:ND5J\0e|a߽A`(?_)ɄV;T*M bϲ,.]dRC{ę0|eEKrh|( 3J;6z*\p }]sl6r-QL!obT`[1d9yrɋB"4R`I篰K&ƩT5 \zhO 2¢K14:~ ө=t]ϣH$b1zR |_044Lcϙ3g4Duqi&߰T*Q׉b:F"%sss,..FK~ |% /"J1<U%J#B|­VnKVN'8<] JSkt<2%u (N!%S ]@OM@DΜ9Y Ǐ7.хɓ'v4* JW,4rr߶mJAhp IDAT,Zmkf1ff dx"1@k`3Reg;Ch%jU'|1)]A)Eq1 h;8NNj; Xns_U$-I'SXV_mƦsIxA\vGZ-:o6Gq#Ӏn$ Vt )677I%cm (&E=ǥ[gwA>ettL:4 JiZ*]|:.8s kkkwE&&&LP(pM#IQrG,+3RR,hZܺu|~#Hiry&3_l;F/hM"Gn)R(H$qFGH$T5j^O 6vFAq0J誠+lo0VcvfKƸl;,-dv"O"C$7D!h4[scqr2X l| so6n'Hi- kIl!CJc$?"kMO*T&0V H%:Lg DT?< C1>6Fӡ[!=nj !$݊ d!1WʤOOOt5 s ;F"t{}UH)e|"$lm)vexhPVZ9q/JV #rT++> qPJ}Cl8zxLB6o7eLcMh^1$>dYKJbI!R8- A=y8zc^ubeuv#3m,..26$f}*Hai;K˷iw23?DL5*aٹy{i+foc+ r?XwBp|~P/R>^asHlethQ,xLl%S) Zv,$U=ux,piIxӼ!T*}>ZM$0 HA5t Nu˒X8fU@Xf0k f۴mzqcl֋PY*S947Jc2HƈRy+ S+&y3v@ €ptH$Tg~Kk5fff++5rҒau109odILBe8{^Ym3V(H&iw;BJ ["QaĴG!(66/ץ1ril:՟^{D wyͫ9,iQ("wh6j /OE`ԣ֧Sз$DiNտ>0nR8N c1 ,aV7| RAl,Kw`)$4x Q@( ,tNL:]Ddd[0'Ȧ L:*fk0#{U*~}~!70Zg^ V| 4 #cZjx Be|f#e~XPqbq@R3eW&puL/ fG; fwaa>~K>>e2#NIg W־QFk?z7tT*@06VV7{c;W}+t)v+"1`X>Mz:4j~bԊfҪ1Lt 7wGE0a~#u]c9QJ"|ečP`A,;p?͑a; &"w;m]Gf}cz,8ѷM^|6+k}Ɲ  0OJ ul\]~ z YamA5bhk< LA.: 8 wL{M_0re$~RUi7!$I^@ڝ.#Ú hct=ւwL pQeܾp<"+|B0 fk}=B:"?1`:*˔RA{T~d6Uf0&0Nh7P nW)E\\V_i[@Ɓ K%qD"% lߐ"=cUO)l?B! sTx(u կ"L Do\B(6F1X%Q,s9nߦ\gA)M AY V(Wm0A $ l:onWJO g50u'ᣯM)r@=Ys'4RH:r{an r\G[E m= !hw8Ri`Մ`l>'9u$177kf ppshU , 5Dۖ| `6xqh<4N("3a&.IA oN8wHKtiDe6zfTku\{T*Ձ@hll•O{IJmbB b BhKEU{oHH ` R DVC [ bzRhKp^VC;*7n, FFV p=_y1 aX"&b b{Ϲ^\`߻NA8hGU@3p&Rw* =nWT --mmK70OO/bBZשTkaFRxI?3ْ}F3G`$eQg 4|/0@M/ 21x lV~"BLH%|%7R"MPI}=E@݆B3P&n9!E\u}+F^ 0~T<%+?+'\M%tɘ%cƸ"0<_'?Bmi&qOB(!:P_3B $&ЊN gӅ}.&+êXw0e3SBhVIf!_um`).V7H"a|o~$ _e}M. 3! e M\?<?k~xQk:w[ޅ0d0Gh5(_gkL~7@"t.iIx )E#4_я,~P}e;AHl;UU>s?7'K^ *ڻL[h=0;=B <ЃzC^x"o0TѲtŶ<6B+`M< 5`J(Vs ˨pHs)XA &t(vmeݹߏ!BڅgGU޽qJ%XjStpOO$ű, Joj% ?6ZxRd2u}\ L-Bށ ?w1 ,0(La4aF́W ]<{GMO>R %tфRD<#!R|Wp9v0# W꫒K("n"J6OKJ&b@k{Uc茬.~`^YaD~Gewה$yT"*잫u\ xN"pD{0)TXQ$0 O[ޅ8pJ3C~F`Z)_ %~?͹~+|w6|\3sT- 9HEB7  R8C"FDIwBC`ds9ͦ VՑ@7AKT1 ΅޿+йQz(2=e輗9R%B8!]{bP ; +_=|Ű,+bqc_2~}BF A^J&pmPȸY(yͪ$H%x ]᛽ 6u7d7b0jH +\ qm[LMOe.n4f?r!<;B}H"C9P#(J nE%l{7a݇^׮_caaT*+6A(&X"Bu hR2RP)wIS Pi4ZY$clr,e* E;haRUVL#ã\\)**B04t2A*^]V|@~y#Ndsqtd*9VX-BAKĂ֛i7jwHœ&LiZt;=FGFg-*[K(%fh(O,[rk6RJf&2<G: 8;\  | :-iǔ 1œK4ZH&=LR=b4[;4wJ|ɉq #(fTWEtdSY]-d %x6lvqY]Y50~ meX^^ȑ#,--.ݴ1e5Cml4wV˲Zbq[KK pWW 5ON:`@&/B{jӨ5*Vuh59@@ަXP^"w\rCoi;u@@j!tk \/s3ިS)ĵ$,$JŨv[7I{ǽ*7 ;mpwHdlo"ЏE*!|z<#kۺF^ LK_)ݸ^m+_m Ywlllcj!>/%Yɩ)~Ёd # qu!tU Cn6aپ+fDA? c!䰭[(n|qcsN z?Ejl²,:DV#JXRA]|*6`O142A٤l>GRPTE8L\6iugբl1>>RNmXsC<$Q{)q]tio8RAAEŵ09 B }:`y,X}TوI8u8oFT*F d2iښ6]Sw LMNPF02GO $ }da 1۷BPVitJl6X][Ӻw.mu1/;~vq$ݤmmJ+^@!p+88 N!qEą\@BH J]{C?{~Ox:&*_iۙo~{~T?rfx<>A̘ fP@>|4혵FXRaUQ4Qգ*A0n(lmAF}Xv9C+ԏ:[Ǫ^\wn=` _&P^`?;P'~VVVt:b}ժz| ŝ Nq TUeqqw%TUɓe_YZZb88" m$~h 466G)j.pmcՕ>l6QdJJ^Mby2I:!˱7>X[[d9??T*gY2;p 7X@ `Mp9 ?'*~r҂P(tgƍv5(9 ^m^!H FSAU1QK Q}Ba`@$<^X^GR0V&ft{=UZ']duuQUIg2i2i1i髪`;14M$BaV |IYI"Si/q&JI6%Ng&՝ nmK/-V PX*,!bl%YB4qIi9)Vˠ2h5t{=^!r%ci ,S87˲& <~xBUt]RBa-gMg"c!-j51oyy~Ubm2q#r^ԩꢕ0-yP=p(vPUbx4N0`P.d2Y6ɚ&n7nѣc;21돈y )xVgRaE8>QXude àR (\=56׹tX4 lRUF-qNaEx%/C,FJuR~l.Gcv&6qi׮]ݸL&B  Ri8! $H.%@p8cggT*52KX$J W/,1iQL]u{=w "FǃHb".l^Zesh4#xb=(ny^x ߿osvY׿GXg\.~>u - oZ$IHdkk" kE^/dұ". lSqT1,2*I("  x<PV 6ir9(8| } N BH'OZ4 hZi1 ī7FI&ŧBϢ?$U:MH3-ixv,˔J%y!~UUjvӉlf)JT)'|)ς-:ifNMp\X0DRP.W|S&wN,HJow].014qrܬlL/t]Zah40)~c,X'Ơ>oVѫEff,_˜@wxp %K8^/'j8HGde;ݥ{bz/!ܽ%>c'AQTU{}` Px)z8A 9 ˔eŠ), ݗ8ɥz6i*h%*n sekH4iv8ЌDt {/G 9͗87Lw>z.1EcEoi{Np36SDhEo>•~Q7nD-I'yM[G7< sN>6"JD 82rLCf$(C ]Ml 4Iqt"?%'?{}g? IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/128/simplescreenrecorder-recording.png000066400000000000000000000502271470053624300307750ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs^tEXtSoftwarewww.inkscape.org< IDATxwe}9禗;I==pf !P\-jU]%[õUzzdie"%XHAi s ^ ֯}}{~_s$~'x0BBB>!DoZK)eNk]VJ+Je`Ik=-^,ˍx3ٟ10N+NJ) !GFnZozb]RW]}O,x≡ ~NJsGjl6i4O"RA4MXmۘI:&Ncvr\ RBsw{Ov?''|_>-x1֔J%JjJBH$\.G6%C&I~7kJ?1 ~>k~IVQ,)J%|gYسg}}}y<\ àjQ.)TU*KKK\z7oyަZEPnrhU!ėR/{c=Zc!?B Ck ,..nb̾}xG9rbRJ H^RJ_Atfɍ7|2׮]ٳꫬt8 2<22#<#<£>ʎ;:v;x}ҥK++T*rcYV|IovK;x`F '^3;;r"Q|'xgy'N`m~´(KۏBJixW׿w&ڵ ۶߻!Woٶ?iBTUfggY[[BԩS|cԩSR-{>ﳲ Efu֘8dYmbddBmI@ Ro}[| A__vqt̙3){xm?衇Nh!)ZTN-⩧ӟ4CCCLRN.\t˗/3;;–ܽR&axxQ:l˗/ֺeŧ3)g_}Յ}?Tp.B|Fa R)>я200t)%l6);\|K.Q*ۖe8e%P( X,=011C8|0tT*v0LNN|}{AaFFFbS7x ;?qO˿tuuu0>ț&׮]̙3={uo{ǎKBW^dqq1GVuyr)WRX[[#`vNkkCCCCߞ?2oC?v?~+xLk hy衇mƒ<:nk_nJµt:RׯS,RJG/jO?Z D__J:@.Z8ti:\xU&&&XZZԩS8CRA)yLNNkQHW)%|ҥKEBd$𑥥Gzzzr{;> G>bGdrrJi<h4m{u.\򣾧8rȧ_Rr9QǏ3 w]/_|V+̒y&&&#fff8}47n`mmZǓ֭[xx[jZDXdjjl6KOO+++d2;8SSS(˼4MRa044ıcǸtrRD.0!ij#~X[[c~>ӧOKKIjUyܼyo|TU Nc;v`nn]vq9= LMMQV;j[Rqxp`.3aAbVpJQ%b[5^]W7dCCC 2997߼mnaxxO|kjpM `ttt:ֺ˗_wlM?RB !r}x&:jy,KKK|<:]]]\r>nݺՖ%=|rpSNK!$RF~)"!4ZU\ U ^l n;122Ǐٳw3qx'4$4M>q%ܹ\.GԲ+W\~dY7 H)ygxG,^\癞8yw/Jqaz-,˞7wh˧ZàA{>A4!R iHL2MLƈY @x?Zz h {ƍw5vBxN>mۉ&}7L@fZOW~O !DT*__ĉ̯VF|>R" Ӳ0,+!BAj]/& 1 \@u+4=O3rT*E&5swESoi=zzz>ŎIRIfy{_j={xga /$ݻIf,=ʕ+Wp],o_+Vȶ\ T¦M1?C#F<ja Mf@)0L0 iDhG< "3<.@\\F*ZT&hynR]XVô((h6ϙVYLرc P.}fw}qŤ9bw v:(xpL&~:z:WEzdYMbd C`_/FKTj5fJ Cdz}`z;P8B5HJ4!3 +B F ~^_kd: rW-,ïTiKh Qdw2j iLi|)pK(nqJRJ~a._2wNH4::/^lb6eZP.Mɓ'3Rqn]ϳ{DϷDeY,zwjEkyV IޤYa[U7$G5oˬ4H)AVK\ccc<˯*gΜAJHC[nW{eBOAbR՛8_fqqcRyvڅ֚(S2!m[n)Z-z1291Nj [) m6.0$~EatwStw_[__^A#3%[PPS{շޥuc khkn?[^TF&4S)AZVRG39Ӫ g˞={T*ѣ8=== \.nxP(P.g;v_압+r,Z)|F{^ nc :X"А-7qgIC., 5Q}D.eVVQ4 zCN mo>I@P s ٳ' ͛7*bGPT*q8ӱ׿R'N0::Q~tkx;GD!@?Ύ]] })@UL|Ү-v Zӳ3 #2)TfЍe{>ڶ1z{V<Cf&b1iozӧoKKK &&&tJ)T*;w.>\.$ )mk@-r]d<1?940Cjl?4CaHXRPV$ަM&PD&!":!PPa^%J+.Z {HiУ>+_1 B5\1yuu5 RضL<#ܟNXo[+l BryڵC`IzT*б ?]Lʡ." %_J @ϡEkjw~X}}`aY׈~+#-`:ѭov:A[8~LAk%\/ *&j!]"d0AŠ j*sss,--Qٹs'<SSSIPkM___2孷7iI!S(#}4MY<aPTguu5h},WJ8lrR'00L {xIT#`gnݣ(4@I?z!waZZge}ؿ8RC>P?A6 @R"9"Zi$K&\U|>1 ļJ%1M)fqqd2hJe3wd2k122Ԥwy'? `ݼ˸fQ/Wq8vF4c7E>f͛E8VN H~ :Pa.@Km0v6 tm.@!ɦ .Rؼ"giC)%7("xĉR)lfjjc|\fxx5^\ )_|ŀ7cك,tvRݸ4q>E#6MƠB momyPl+`hS=Uc-4 5g2yaHt E8I6}dHRر#YBMۡm i?.~[_ Ah?@U'E>2ziJ^a ٳIxo4mSSSVBM`h=owZȑ#Iڲ,RL*ѣj5^J< }TXZ{H˸4AJ~&KAc܈< AۅN#l]G.M,MZƞPS2#=FqLPh{O}?LI fЫ3L#[ Q+++s2LUZ0D Zv6J> -LTPMdee%r9rjzblJ AG IDAT% EkڻLĦ!#HG`rXuf52Lp )yJy>\|wTC?J1 MF!m#_ F>^A͐~94HZ7,<e9.Y^^СCضtL#8]]]J%ZVL Og6yK P(B:C֍5p[n%?>ccc8yl0@Ix&Yiًt0;R"Bl,(NNZQ':F`vw?r{}pa݋wQ읻,y2ͳx<?wuzY Lz\[Z'(WA#f6V"*_#-$tRy{[j۶jJ!DJ&g}-7a16&5؏͵e68vƺjk IkicGE.Ҷ=<BRr*"ghZ.hA)αxMng>DǨL@5]֨bcSb:5hw@5asRyC ?1z .1I_,/.ibd3)] |aB. epk\ȍ'Z? $c0^enC* ThCF#|χ֡ĉexq"ئA1EfTtR= ^*UBS;w}jyѮz~^@Po୕P*Қc41(?`#`tacѼz;aCM>(͇sW]*A^ֻrܑlkĮU!!?h{"_ǡS$|ثuDRݾOGN sY< sRT '_]Y =!Hwww41) $"ׇD@0WPbz>Wo0bfR!; /|0 I۳F>L,5jyc U5? :(c@+>1O~4V#JJynO^)'wʹt&ɫ]tԽ-ŝ"q2b1j!V rTG"DZ@!IV=,C"M@4`]=@+4$P 1Bo@r*uBhl4 A#b_Þ:' V 0hTc_ܒ0Ȟ85G9ܹkPЮKT ͍8Za)1 >6NL-(d,%7v v|Z@ }>IBv:% F@]E2ʲYYZ°lcr tO"-hܜq{[O~W? =ū1P"RF@ +`jc I2v->q,!fW;ڶlݶasw" a3wH0Ċҭ]O0iXZJak2{{p^bGh1^~IK)140UcLeZ>50S1 !^A0䳍&`|l#:M@+̸w#!DS BFR2F#\ Q+<.;+kN?! L((?d4p}L6$)p}, PHL1DJ}[o7y dmB/[[$&ǐhpl,Le!M4*Jj@ Dcmoyoy5é\q$@茨}R$=ww.T}&bm "$P[:̚5!hu ejϱm-,S˸*QZaޝ0IF#3)([ôL{uřHOiu- bnkm5V҅)4 V (l߱iZfTQaJ  ZOE p OO>w?/D$t>j$p/"3@t,o;o8/WK:k#W I6 D)VZi" @$ZG-7wl`JTi(s ZFvsc2P 5Ķ>w@: n@!fSzxXB--ce&RazYyb0 T %B | Fd|nlum/k?׬|4pG(H*Ijn > ۪[J)-LBHnYnE6ʝF oyU[Yˢ`d,";ЏmF4l8LD ߇+Iz!бa^~@ h]JNl&i([0;=Ƽ Vnƣbs #= [6IFѾVG Si!pI9xah|ZZė"W ZHu|ZA@ .KMɔ2Lߨ I=[%"JVvnijKnG[#^otE3UIQ<ꮇhL+ƞqئH9Y- Ҥvx+Z~ 7־aKׂ`$ J6olRnn5 mwC #jmvKZj˥j Z5RCaVN4@p{1b댏ê_B/ҡHo5aFgȨM+IVdZ6t=mWzДd4CZw:G,9UEѤrq=a۴VWIe(S_ Ɵn M H9tKߦmѾ/*XfJQ>o9{vXE 6M&8jQ~\5Z˥riom Un7Ӑ:)Hz!$-nJ][#P EJ}iTn-l+jj]&.LF6yޖ!veFJu;mBUkv\_ Hg.MϣQ! ].]葎ʣv: *Ш@]C,ǀh}b: ީ}/9gq9ˢ.c6vm 5F$RxZhR 3pHgAGy{ D8 J;ìf>OPGM #iA<ݧIf:dEm4BܔJZ;jo#[,wKM!x˶q]ݲ\h{i2\Ѫ7Ml3ةHvDYR25tO7'O8RpP'j$-a')ŖNFw_jg\}3xxg o䅔ȬlI ?Ӟv]KBƥ9RC#a, <W0)R} 0h">4N1^| @i þ]'$]u1@:eDn/-VFv߼ИΙ+Rr[X:6fP{S DAD~7A@}yVL~"] ȶG;>~ ^ wp?7Кt/ֻuS =o-caH.a gmWZ;inծS~ko63p"΄Cyt;? ̒R\oM\quܵ1@U?MJYGmk:HksYӼr=,qG_Da\sA:H@wWAĔuY\ڷaj5VWWo7og]60 w&AIvV~*Ye}VTvJ wXXr<^ 3fA8ktNW& z$!l@\b8zGƔ~7=yR2AD A,TVJ^tHF￝zɱM{i딃'Oܺ![n1;;22OخaVh)w& `E&BIJ;@Pk5C ZwGVJS9҇PAb[vq,Ӧq]I42CLyJ:4ߤy Jt2xQsRѺ @NaRkt0"[.]}gqqO+B<'oowoE'N0 Ν{VE^+~p!O*kW[lctV֨#y5AMd6)uBdF&BVP{\4mgk?F RcFmG} *7OZ~rii[nMB^n߹$a`t B(ċK̰k.] hVe+\AbhDуx#=̾:rتւQ_}'EŤ;.^o^!YCC@R{}khzdll7nt^LNN2;;{ρ_޸s;A>W؈A^ʾ}czz:.- h2R1IU΅ h7m&c59X4RغHd:MGɞ8Zrݶ^o؎YOL<A!!ISUY08kttt:,A("Ҷ <x[:bI)066իWdC|J~t6Kmx$<}EPR}6 ^`Ri7k@Af$Yue<'x,e "AKB@ I/$/'(/ B"EHJ$Def=V]]U9VD%t]gaa].\&ܽ{7\qJ0`EJotVsF7(J 1Ur)MG/p RRmDN}*c!wnGg{ !&ADInnn>ozӛ'χh7J."aA<IDAT#ڀ ৞z7oh4Xv/eY-:hvp"Q[S5T* wV0VwV~'`=_EI&0m >X4epmj_s#n(JRiaHV٠])&za9`o!҉O h\|}l6KTj8JBR}LyV'6vD4<"riZoWU7 Roշ7XO4=AB |l}&{bH;7{)K|-eթh8AEL&C4%h ׽Nl#$>yڀqA~Y^S<%JRj`DejDm'$Q"vq$ `4d|hcD @}fGϏ]lH[xY07+8rg$̄J888=6asSQY( jDNXPJ ) :_14.lAuئi4I ș,Ņ((#Կ(uJĞٿw~ܜsi|h{s4U4P(AD3<~{.b% B Q;;;+ A6 hV8x)e$I57 nbn)Wvh7_|Sw T.HݫG~ c$_oB%_pF7 ȒD>_;LǡҮtrfff(JȲ ٍF#dj1 ۫gC]ҠJOݻrA0>]Co\HED6xO&*KwP/.}*|=ܜ O$c N~h y%qfgq]|>c*wj ܹs$ * +++Ȳa|8G?0Yw\~{9a3I[UJB"%ݩTlZa,ɘ:!^$ryv.'z{/;cuqL]HA;`XMj:MI&"`7|)Hh4$(rR/rQ !}V8B^6~Z6QDðۚeb055a6u^@sK/- GH?>=Z I$lgx瑐h}Fc5j"Xrf̧3T=ϟ?8nd2K\پyspp0JŠ;N Ca Z64M&''NJ"4M67zUܗXλ#1, Q, d{I!]Bt*nN4~1=O$Ğ]%z)V+?DAtj$159E&3I&q]rt&BMz{EQH$,,,j!6j@TbccQR?1x³EZLH$H$ihee$>G[KRI,A[x"(x h* DV cxνm쭝z@YAb,-b\8?* %o"ϭV4VwM%S;AQ* ;2_N(|=Ac[L2dyy9gVc}}\.7*s3RzڀT*uj\|VeYlmmu̟ Kfɇ44hXQպ\q,#vۥ0;2"jf TYE;-Zدo!av&nS7h4.OJRd3SA^P[: 8CQdL ~"PU%m۬qtttZ|w:m <HzڠAn㞛HW\a}}evwwIܻ$v%=Bv*iX&bR*d8i" ˲kk fqD2's;YVL4 DMiXJzo9T{uED"\x1Tvvvel6K,coo'ܤP(3?Bhd eFRH$HLOOs5VWWY__,Ag^m츼,&hD"j+YPTEu55ǡhPoX[5vOOQW0 uÆ[ /pC2h"ϏӞgx']@蟟uqy&&&(lm Pu;/j{p &}xqyyJ3tR(0ʪ+>i{ʡjqvJ\S4q%dyǧ;A3u 4Mò,dY&QTo9::|>? ~><*."K( j5r9Q0 j۶)JuLdnnu@Lo pVt:M2jv/PVýX,[zou<@fٍiqA*BUՑlQUU$s=͛7sa۷ql9ѨpUcuzr|[7+CQݸ ?oGd uMNAu b1<˲H(z2 eYL]*Q,Vg8sx#8@ {~$ID"xOó fMIALd Ck8z}7,w<Зo$Y:XB@X aeQ^>)6\% DF %g ´sq8Sa_P0<.~ă&00MS;B"mضeYgڅo >΀e"CK݆`L =n}@B+Xgf ?iA ̵QUn]>k#uay>< ||Tqf'AAy^L" W~?g9u Op_x3gAu*wQ'@8< _EX~"'I x$DQgpFۈWk )D6E`2D;GB8.OO?%( @asBIT|d pHYs^tEXtSoftwarewww.inkscape.org< IDATxweG~::=nfY 'H %36d C  kvQj@k(J409o's_&əppM'[_ ^{aeYu!D,Kcbii|c̦zX7,H)o4/ri~81˲Z?"|Hq6Q6Bu11717|_JJ/Q>CiW_>/٭d53 DQ(Zg6Bu]lۦX,R,q]7^sIkB+ʷ{ޏ>;~z$ 3Hmll6t:m~֓?H)T*e*cccJ5Ƙ?Z˲^} <}Ykyy !.FFA$lṟc8tzjJZR066eYOբhtt:sUnݺEwqjRT!?_|mq,>OW1X_ !f n*kkkw(ȑ#<>}Ǐsq, )%ee)ef*M)5d"7o\v .+9tm󘚚bffB!^zGx!pmۿf5!ĸ X__guu~'`߾}C q^yO{8".]w]onT*LMM1118|tt_>C)`O!~!z,--jyΝ;m(2%^&yo?C )%mc6R+Ÿ  &''9p׻)gaoqm?N%C' |I žJO (duuU zl<2.LOO3;;KV.e f__gg{ A^x1柼k}^;g_Ot]3:u/~|_gzzzHRJ1ƒ `eeK.qeX]]Ӂ_)J077ǩSxul˗^|E1C@p'=R+o# yc?BŠeVW6( |K_gerrrHRJ `0h4x|2.]l~<V!h3rIN:Ń>HXP(d6?ׯo~A)fggS7x_#vxG>DŽlnn@EXOO?󌌌 )>׮]^… A{衇իWyGY[[t!qQ0j1??hgK"?0<GP(P,۷o[[@ 9*Ƙm?~V~Mb.pܹBTh9ug?Y<bNy~w^z,8b0 yqxAkM\f}}Cnzloogض"ܼyeRv'NťKh4loo#T*I돏۹!?T9s1/ׯ_nc6>,>,d4Moo~P9vZJ%&&&X__g߾}\pcǎQ."Z~5׳Lbd}} J%lڵkj5Z;v REZP(P.bmm(u,uVf'&&ؠRO2 qN~RgC~hN'>g1/(u]nݺ֚)~W~%o)%Zk ʕ+ {:[<[[[J%.B)% ˁXYY!JVLZb 1|MΝ;K.q,4v9LHo7Çp3P_3PhZ={__e||)0122•+Wܾ};~:'=957QD"]uX(PaD*P0}EG;l҅x=a@βٳgp‡y<Ӝ8q۶30 m.]STH}ʕ+4-  'Oqc̷&H)ygx',^ZYXX:ٳgy3ǯP(}q(lMg8tndZH;V6Miu;4-AQ"\m9XzU*RMh҄MW7k}0>|7oS !xyꩧp]7c(?C| rcBE~폢k=z1˲JQi6"gg9wܐ;=\VO/J:t/r)i4H)& \bgv]=GS_D8lcI6M66X` -jI\܂zq/AD?`mu8N$cuJJ Q^_eY;F~Q~!P)})eVPu]sGV?=z%`뱼1/~<䓙>(o&mgIK.%ԧ&026Fe - -h *(ePVCcvmV8VY'g)jh*p!~/y^QJ\x˲b0 xGÇ msv01z*t6mm^x!KO"/%9>GƱX\'2tb BVl]iHF$G!4Q k@4m6ֶYZ^gkI+1Qf>06:րW_gsOy +_P=}~~yaX1~?H cQe?s O?t5~1Rb{"K;i D#!F ܚ_fc#NVU}vO\(|-fmyH=D:No%M^Ç9tׯ_lfT~xXVVVhZAj/Z 7>nnn!3|WrwgΜbfcczΥK2W>RHd4k[{]R-%fg>uR\:ADH J{g/@HF&-"enN3=1lZ.r^Oh,Fbnt0ZLaK`drVj8#:;;1&+kf$5ϕ+W xjcq_V%8; hZXlO:W_*㮬0779y$~J_xxBHh ۛFDƠ-J*}?66B?DE!) +ʕ b 6FI"d8Ei66l7L8CգZju"M m1(|(qKepq92#P}>tP$J&nʪB}Zmn==`vv˩׿֚s177%-dYvX2uFX9Hz#/@!QpjRp?Svvl$ڊM >H((Pxn:2HBC,E)\"nez߿Jh^gSdzAt9)055K뢔:=VNs&''3X5Tzu2o&OΔyG%m%c"ҊLRYśa!Dd[ ˲lv7YYl ۶0Zc yu\}RD2DD*ЈJA_Aݼ͂ǰ4\&UZF0TADIQ 1D̵xRt#Qw>MRMBf3c~ u`hk0CV˪P EּZ RDˆuM6ʊSBZ"S 2 iQ)c{*aRZQ,,` yЖomjwP4#::~?+7j޿za^ (7R410 Še;QFh@t,/'u B>uujI*=^7-pl7JFQX[[Z%-/Qe(BM4B;A T*c'xRde(`[@4ICuĿjipkk+lA'1|9t| t{st} 0:=6~ B^ 䙘6F+w}E/< G#9d+mhZlln$s@F^lfΕRisRf/1&@ZJZB vJB& ξ3QA$[8e#$mSvBg$H:Msv߄1DZ XěxB2: JgěBBA |L~>kq xW8wJܘ$J0ʡ($aڦ -;i B=y Ho*W"JJ94"$#?SyPcIy)HHfch·e: *RN>Hs`vv (VvBGD* &x{'@cxWġ°^IKۤ@n H! `gI\H30f:3,ɢ&]^䔎,?`$~Ɲ"MxĮz{|cDaeex|)p]~?FxN-(.'bR[XɡQv^21La Ji~2NEXqÐ+Lrʶv):ID #~Q .ݥROƘ?`0c>r\E`H*{9"N$=[%cKJ'Y}y3~06; $۲9m0mvgh;)%APHQ\ȁ4KܢRll{ 0n0&dQn&Jc6{:U9# !8鋡BɛӶǤ_)'LPv[xy.Alf0## #VVy9NiV wG*=4 XAhERR"S )|~F*i;C RҎ.UO O8آLÈOLVL'1Fy°ɖ&Jkn-.<=i52S(dqy5k,. lcI&tPGNC~ 'T661t#KM Ƙ˖:3Kbz!q =P6h ܤQ/' f.,feu{ X>i1!@ shtsǡKv=?U:S;F:8w;/EQ*2t{,ݰR̡<%ƔuxltC;\c˩7Q$>Yb2+L}Y[Z]'뛼7b}ʗIVR R!%82}(??@)Gh@L$L`t텬%>cF/B TydJ8 R%mL;@H}K@{βJm:>,v;| 2 FhAHW" ,uLi;JM4Wzv/Wls`6Oˌ1lLEI l2ASxv 0h!Pj:m4^KW6ͭ&I ݷaЖD:. PG Hg]9o$v+?o Dy?L/~{1Vlnzc˘ޔDB  -5 #0R#$gbai˫TFez8A$Ea `E=}G^l6J'\qv|,n%RF]\Wk#$ C\7`"}}J$EԽ@kLet/[Ht[[XF &QfbDb3(⭋W ,.PJe)jܾ@՚nT{%db$QIvJD|aǿGK’B/Qފoқ1@;JaFzt71册$J CDdha2uҁx/_ˎH?GCfpYYY`UrRH"$f@Ysжƍ *}z:3I/~ؤ)aPHhN ]. %& 0q0 5U{nV˿Z@0`KI|?Җ(mڱl\l6z* hl2dzBʋɤto@3FM(%q1n}˸mfS% Jϛݝ7cN<6wT> 4IQH`W{i&ȶ1ر8Rq/<-gh67nP(P@݋k H` Mc T@$S\ e$bbwy>`Phjԉc] d\@ ( WI(4Ig\&p7휷tb'5敓.)+@J%1 dJ&HҎ{4NBͿ' D +"cb?X]@ ڵ0n0cttaӊN;{2fٺ9ȶ3iZ3 7yj ZX;J2Gҁ}Pgn"` 1qp"]7vleZ)- B/^2:WD"aiC;(}eW")|W7;f Fx<=!x7IG 0gUww`h5<|!8/Cf/l~|E,@h,)a I-dTХ(Tǹ#+L f'@9u8_^1b2 vB@p"yܫ-$i)v ~g H;@ )J&)+a!MDI;*K*{v#Lݻ1`4EˢēJx`ш^뻻<@XH*!pCw!Єal俑ݏZGt#twrR0 ӥ1 @vع*E AQ.I$EH+J6w@IOy\}JĘ}R *XڠIEILRYn8 DZM9w3fz<v+_X,@I!r ?;QYJk:Nb`qXh%,RD${-XJXƠ Im@;ώw[wl jݸdtR8J7DF"|;LµV1;+FE((nӯPRċHR@w3<5ٵ>"IxhvDI`4v[y#b86gp/H`w\tko燉ߋ,$#T=&7~.i8Dd.n:] ް-KiIL^{ q81H1aBd: J' K3g'e%p, KJ6{Vv"lZ]Fzw{%$Ҋ,r 10J|8o(JN E8@;)w4]ZO&bg˔gZVZF4&L =OAwQD+O" m,K)|qLϻwa!b.>yo?{zc>B)A@ٱcI3t"= CPGij""+?D"U;Cl)"REE枊L'~d-wӁ0 +{ŲJe ~m]Cv)ødžQtW `@Pf2v0||_6v-f PR6nzf{/R*zRN :nrrU|ߧPr 7C`0$E֙kfΰ$[:H~@''XZYhDAzF OHTɞ#5q= K}ՂbabY=;@{ѿrn97 -UB,;8,LS>Ǎ>' B &= 9ǡnZR,01Ɍ"5{^?es?rwqTKj]0{:}&ヤxnL'~t: A[(}ØzH{ wxCt:m W5Z_瀠wF8R^'i;\cԪ|f&9)&Ufl&*{U$kmOBⅻ݀ { yXw`g?XboeXt6Qi&5Ul>cL`Ziͅ%ƪU:[C -lh(tV*F6Ņ5.s{"r6cl6?l dYXNi=jL6E!(Z-$t3VtI`lX!fV}XWnγ+xTe#UV˵,l` LOM01>f 0Igx9GNt$7&f3}\ leQhn(F[E}࿕]݆}>}?MΝ;eYλwqy8nhoG$=#0i}AP% (eAɃX^zs;K,eH>V:пbai߽ ?[Rc68|@:NP)Ulew[?<0ׇz/vIZ-9p׮]k/nlp>JRa{=&nd F"D.vGqZMp5NFar<yNKJ; =3=anbRCoH~ǐ# N\&34gRD!,ɱcǸyP/_kw]A,oo` ^+OFW^ȑ#uaIXǧ`A T"WÙ+%Hޠk3#}>;L2Yv <$@^@qH[ BBpXXN&3$gY]UlڿH0t txӿ. V/l ~30+KGpZ:e;#KQ $Z,0NH$8*\PuZ_/Q,ZvD>=b"%}&:nB|L}VEHR$R |<ٯqs ,# #z$wm},dghMRK}.( ,..2;;;s qA1pĨFéG^kZcH6Ct"[]!7t([`YX|/]LԾ,Ac*ujS>l,dwe_ٻqZj,4v7k ak(K ,i{}ji{ `$ eY&ɠhF^9$a\ɨnv!a<}jG76y-,: Z`>Cz.e #(HH2KtWZ%Y3A4iA8)z]ts\˘EDvJ#F"aW쪕&pdu+LŦN'Qu gȊB2@hv:5Y-w[M,WypVsp2î7ҟC:}0|HXjTRCm :* hjf2PjZ%@6\2!'` g2ܟX7xcvϲ>>#8L&0BBW#t;=Kl%0:V<|TA$wbn;@ 60Mms~2'Ӹ_,4QU[S-"iTg CT5LA@7zh|VC8VmШk`!>{d'2$ѝKKKPTXYY\.ˀxxHP`Lk~{a'eۖEfH۷oSTV;܂]eayp,BvQ5 "So5΢zCY!&ocy, s#,S9IN&Q+ub(.XhO AɣJmt!gggQUݐ]e)˗ik2bxE_{5vvv(U'r'm݆NE$zQC7z[MR3)Z6`LzA0 Z%R[M=k܌ǂTkh54|̞[ƽO]4錌4 SvvDNk=pۛDctMVMR!J (* YzA#A:z%ޝCbOd h Rcwn{|ۛo2?&Y|H[ޞ&^T*aTUW웦 l~N2bn:&d2n3*Ѩ`Z-n:e/1TU TH,FRTXu4j0Y{L쳗 ( +Klnd.h`L8b͆,y7xӴxh<{L.~kP׮]C47x{TUR4n1ы|`y2|OH$B$LQ5NNN5ل |_#/`%2sdWA9*}>ҙ`ۥV)T*Kmmv D,#!9`:6 4edG:1C!GTKSB|^_W_'xd[O:#Tω"hU׶JFA.X,KNy~߼l@,T~n޼IE4Ͻ3O(KswgkL%NmS'Ȋ%Zr)PH$L"X(KH~?=4dZT뚈HR3ȒzMcoSLKkX"c 8G<ڻXD 'ȞdI <,itަP(w{y< eυ| N.cuu#8gEWrc$23@0UX͡i.g!bun}LC8.yó} "i0 dL,]'-Ox)aQ |= %I$ r5׌;<Aph4:~X&|>Ehh6/-70%`)`Ô@艶 -еF:v_:F4ܹC!˱֖X^^nωRD>RL |c_x?%lO\6p(ʥ C`~0ͫ R~#W BHKbBuAFFYmnp~?wEUU7QVE٬q|^S=z B~4ZF٤Z^3L jWA8&Ns||<.6DEvTF1],d Oqp!ia";[ߜll6ý{(V• 2`P(8g39iںjhZW*G=ͽ 840xE!(4~P0˲v:4Mr%U vBzѹlRHn:[b=Zs 1v:e2"EV? x.ASO,0MZs*uMP{*hb5lK 6vq.#ax7帆4>z=Q~+X=/q ৱW9{tx_x-lGMQlS^†Ӡ{4}C xDMVw pEl/`C-w0 | I W$`-IJavG؉.//$>nIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/16/000077500000000000000000000000001470053624300220045ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/16/simplescreenrecorder-error.png000066400000000000000000000017311470053624300300620ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<VIDAT85kTW捙yL>$:I4RK%m~BCCHM).n+H)ԕP4PтFjIL2ɠ7c2s߽聳;?p/򽑑֟ cJq)oJr4bh۾yZy-~st4!qL{{;lgfgðgm|#nnT6"u<3݃D^ ӊca7cl6O&‘HEDOyJ) furV#z Kɉ-ޫ,+tbwkF(PP!R ;;;W\@3aLdM{m Jj ܊:fbdON(uJ:RJڸsȶhcI eJ:XKEqmZX`c0ֲȃ\D8&a:؍`@066qZ/y=TӺ-J!Dk;/ZZ~fMMN! 0 ojrrhz|K[օ}8IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/16/simplescreenrecorder-idle.png000066400000000000000000000015701470053624300276470ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDAT8mKoeo.DZVe  B{BU%VOqSb!nMR*I0ud왌cAEΑ{V#xX J+Vc9 xW)^`Y<VVV_\\UFOu\eggg.wOosnHJYqQ\%_ǹ/XXXxnmmッa$O>;OezLt5TQƇ#<~7 ßM@T)9ӐRFJ_&oy"aC'#]׽fJZy||E 8C٦qZdT[ Qf^&T*p8|ՒREnxFlsR*iՋo~N'v`"&`RneYEZ#LHj~??f[@b 2I2ʖкGem#MLJ6 2Eŭ~(48ytcPtrضM$J)5BhiǬΑj$311m27wBeh tJP32@?-f&k4+7 1 #2y5$#i#,$`|r8~ ;'j4-wǬ.9e{{)eRʏ0-1G1?}JO: %;\>,JLD)$IF-x32&O9ClqlnIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/16/simplescreenrecorder-paused.png000066400000000000000000000015701470053624300302130ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDAT8mkcU܏67d6*V팵tS#PAp%n\/(~@ Ep̢Pfl:|(i>s9tyyYt:;BJxi ")[8c#m(s"0Dʈf Zfd뱸6zF.!__=cL&m(,8v}cx3>)SOW?kqIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/16/simplescreenrecorder-recording.png000066400000000000000000000016021470053624300307020ustar00rootroot00000000000000PNG  IHDRasBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDAT8mMhce&$LatE "^Dܹp? qƕq!.DFulEjSd7]΁8ƣ<rl6wu](oFxڬ~3Je}mm}(P(+;;;_d1;;B/[[<J]@DV{a0؞]`ff饥I>seL+7->hаa~~zRZmnn~R);.I&/],0;D0=Oy/,;OezLt5TQƇ#<~7 ßM@T)9ӐRFJ_&oy"aC'#]׽fJZy||E 8C٦qZdT[ Qf^&T*p8|ՒREnxFlsR*iՋo~N'v`"&`RneYEZ#LHj~??f[@b 2I2ʖкGem#MLJ6 2Eŭ~(48ytcPtrضM$J)5BhiǬΑj$311m27wBeh tJP32@?-f&k4+7 1 #2y5$#i#,$`|r8~ ;'j4-wǬ.9e{{)eRʏ0-1G1?}JO: %;\>,JLD)$IF-x32&O9ClqlnIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/192/000077500000000000000000000000001470053624300220715ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/192/simplescreenrecorder-error.png000066400000000000000000001150241470053624300301500ustar00rootroot00000000000000PNG  IHDRRlsBIT|d pHYs))"ߌtEXtSoftwarewww.inkscape.org< IDATxy%]&+W]we]uT5zG! ².v$lٙ5毵\>.aA7*I]꺯ʬߑ"}p^*uY~fQ^d8ܿw/A߀/?O} P@8AM$3`rYn6?sX|Kn`3s=1qJ4!d}¶mt:t:ض uc .EQRh]סic(_}Wn/O*u"nYG @jYi4Ⱦttܲ~8Yn>w3C6 G?z/9/PJu]j5j54 Z-4M4M{(˲ FjhCP@PږԘI߿{Ŷm699 4A)]B@)`s.f61;;9BppOӴzi+';r.G~T&JxӴ̉r*[Y]p\*8/ܭTN}##8C%f'!L\ awh4X]]ʆ#}4Ň>!|w}N: }<A` !cXZZ›oo~8w"JattDb\𻄐_6N[/=}!?4;׮' >B_B~j;|A|J.9?gc## `k_kЋ.Ǒ} @(PUU/>ݽ{/2^z%,,,kEQ0<9?1˫ϝ[g$xO ǓKO阮ԩ~8(9h=x .s65=Ғ+}$MIN߳gG! mVWW<'?g}vÑ~wqz\TԌ1X,VZZhnKN@($e4MFH&x,o2y&^|EKh&`||iGKN*!i6.Jg4˾''~\n v=#/PusjJM<\^ IH$X> %,//~<'> |@86Z:[1??bqqX[[{lfBA<0099qLLL  ua4M%2_*\nutj9jPAǏYB$ ^/~[1'ptDU{N/A"I lEWz+HV]Ӿjsε `}l??eYsAՒ#6!|wŽ{p=,.. `׮]xꩧSOattT4M)ՙtpY_%nݺs BFFF0::*qΡ..8W䟙)L˾BI5s=3/HSs/<Έ߼W$ _FxwçêN,f#3_95Cb&X^%!&lE!R I(f#=wŭ[;`iim](كi:tt]7ėeܹsGWFFFN."ͦQs3O?׿ޣn$> ,B)^u^]|[O(f@6o]`Յ %4 ݠ0~~?SSjK+<~2J>@,(_Ǖ+Wz"ǤN+`P7o94 .]3bsn#8ixmNq|Q֭T](0kve#t&PtJ?5 b*cg&>6 8"'OJos@gcвe[ubJMLs0'ŵeIl")$umTU4My?3ܼySO<7?EL&0Пx0}vܹsNp;wRڷoߺY8{Iq8poF2D86 ޽ 8~8`ؾ}{q mS{\7HFǔ'vNTUe2P(j:Bo6߿p8 MtPZ^Ʒ^|N B~)LA40ky#?oNB;(!= xp:ݾ8?otPtU^~Blu~/]Q5E *"@~ՠH8L‚$; 0Jkﰰ0P?|vSSSv:FFF:&&&0;;ziضR%bbbsssmvT kkkH&b0 Cm9ƐL&z5`޽ؾ}; áCpEbbbBN{*nU]vݻHRfX]]47-Jz*J,Bp饗zFϟ&FG!f,/C4_%Ĵ 1Lk[6F(EزTuaa#!J#zEM }3W{~nfd?{~̑-BbLH?.udRuu,  `"w#L)As904|MӋ{$;wÇJB3)z.W{޽Fs$I;v ϟ:tcjj NG< "$)!* ns|1LQf0҅ hDL" PiuԀޡ43V[\V^[H@$ŅSC!j<H$) 6$>BG,{ԞF~B<ך텅05mCH"9lрqp1߰vFN#'ae*lbŠFm6F{E(¡CjP,8匰k.iI*FjAZĢ|۶fqT*ڵ r.\@>G<G\p9uX]]]Gu-1J?bg+fGFz^Xx,ʚL8K)E<*O/*J##Ȝ< @^ca`yy|s|ǫ BكZ@Q- a`jj H&FcB,p?_u8\e,..Gos-4MܹX\\͛7122z ǡCn!Pq[oC麎P(.g )=(\s;S٬d]>O@F[|~XUrJ>zhV=]=$%E%Q( xOCE>;A-˹~%s@uqTUܹO=:ܹtco߾8 o| X?+8XAnPT[nDדB.xgqETU߿kˈZ?pYBECRL(=(N9s k M٣;wB!Lu:=ο=zTxB J'@~*G2K^tkFǑ9u [׮ =G`yy|xc@qe333TJ_{YZzl) r )>lۛzZBx< D*J?rq \"l#۷Ǐ`xd;B^_@u؎c1up % FTT/IUU nZ~v|׮]իW*!J4ezCsġCI6a 70gsZݔ*NCS[=U).R,_=>]풠R{W\y] 4' G:USo۽[_7;wylG!1}+p1>*1^f{H\(]HQdcA#r#Aӆg?q(!mt:h[p]χXZإ\w ]4`B3go\]]śoY*`9ǏOOcOox QUpx>j낇=$༙flS&>|ۧѾh\B9p]<A?#G"sZT)4Ih4,8h4DdUx= ÐQgf:bP] >GÑ@dWoVFfVweadd###CB^2MGi{^NzF+X^YF#`Z,[?lYoOH,PpM\x c!J ϣoU]qO~༚`lK3؇?l|ڥK!fiRyYN:k4]ug C ̹C򓣣R |[["Ax%PYY#FAklMӔ Br)c8ɓǬR^ IDATwhF 3ggna&a߷Z-jU 51ܹҩ"HOJ dB ׶m4uJ^txn;=%V(H$p8J4#Vٶy\p#@è0x "աZ3 "@!ئ/гY5,.^h(" P(oT>ewff&th ٬$PyMIB%JŌ/ X!ʑд9D;bT.ZalۆJy=3(Ůӧ#iu/_P:0݃q !aiz"xR=p 209rF!_"ܽۓDA8d!zT/s`` qsPpBHy/ HH%٬{SBr9׃#+RtCY%333qJ$A^8LAoϯ#K'dn৔­F(@H1on#53!]O#629ZMoe\8G&Ɓ1m X," ([c(xpYep]?ݣrvB:q ,--ys]D"aB!4 {\„'ok=1*.>Wub Ҝ?"ղY5&[_iBm?z SLC7{H}w zr{+n QHBІ90 wΟGͿ/9&oP15 CCua} UT5sgQ>`z0Jqlضv#A,/r\BQop!^?*nܐ#|Qk;6Vz=AZ }-5sfgfJ[ח4}Saۄ˫֞PD81>*s+eBFڄD9]aax-J`#8:TFtb¾dW`c./AƐ¡8pR)m۰t=ץ@t(~VfZfƙYȡLÄe at] h4((+h4plMy5lF- h@W8$R4JKXtQ"Dg~~pիpdw+q !=3PղY5v)f|^K2uB=l#Lgh>3MP}Vi~,S:&~'*- B0 N VS( Ͼ_(\NgCk&sLÇ=s,T4<#?䔯B^:nx.IM3v:"LӀ;6ڝ6ڭ6lc+\ǁ2]D@ RPų4MiS}ujR{G/FJ%kQ7ӓÇdd ;h2Lc'4m!Ng]BA7H6۶{굊B2##';A7#?f1l!jK$6[oe~Qc#i4#eA}z:iT^)C?MTD<UE n֪ۨ /ڰ, -Ag\*;q8&jPkQ^+cX x ᐅj[8S(:ūOnQoԱ`UN2a!<8Gw`;ZDŌBiZ20e\(2hBQl#y(F^U{[L3D=@PN'qt2=WhԻh6CQ/o[Aw\PgEϤ8z?= !wmn (*T+;6FFFL@zBBR Z]FVoCtH$%LhuV*X(b<4]E"@&F^ǃPbo~"=r١!Od k/ʑ?1F~;QwYDQ`2F#ccH;B)Bz/5?<[x$,K*gvڿL³?Mߨ"xn <>m_HG0}Ty-0K ̡aDM(~ahN|_bϑGj Fh ^'! KXXZ*=ޏ~Hmty p蚆lvcYLNM /[Ȥ3HRxp^pusT\g{"%TUhW*&u̡! ~>@Ƨsb-HSy4\Q:Is={Ju]] B:H`gߨc ӄ3QX^8tC@/gXoC / <4&Ni:kuSdr ,i"#E ayu_nK#G064!˗/x}?8gM"jsQJa@3us,Ke͛ $%nJ3JGA؞=N&{d A^,ch8"z/߾F7̘eD3MR80R{u] \*jEI!7 l,DC!oQzn`ѹxЍ:a@Qu(md4rԇC7}u4NZE($,/n_8A1j:4UUŸ/fixWhqrkNu} W{7?tyZ++ , T)e4] UTUU :E.V3qHqnEzW_ϾЊv{\C]o7 4y̝:?H?ɀ`ի`2TMUu ҉'67FҮXPwV}=[##MOTbp*~1wytBZш]xOOLm{1;bjá$\"D- F&VC}ٲ,4Mض- +1 ͧSxd^<TQ@+8JN }xDB!HhX w-j_c,; f^ H.s$8F.o ާ\7)Eo$C3Gxm ^7a@O}[`.Ϋ1Xmٯg=A֋0ff\y/cؾC_A-t m$$xnƼ." U. aaJ&=!!գ"Ex*/D%[9"l- $ݽPZ46;cu(2!%~PW_޷LaIURO*{pv!z:Ⱦ} ~rm ~B)t:ԧ5dYY59xw/#e}lăxLB ุhǎ]L<VWW"HPO!in)GP(y FEQHT6(QW:FDa״ǀCw[^7 swBz.R8 @5W1I4-|B0x*H/l~P9 (,Mg2<(gBrVgR o&LRm*HD0;;+5} 5/o[&@"HB@#/ xkdD,{bY,bw4 _UFo6-9z{J=cts\o#8-E<`z$ *A㊷F^^9 h*9o){Aϳ᳢s 1 ~,&C|~S(;wHC`VattTev\ו|W$rx仃Lp8i DO:Mf#g *Q 28>R/[?~96cǎ6_'peGƘt_ F|uX;F)Fd2;w+t۷o|}lbnnLQ0c7.қŢGPѱ1fL!@GDo)Tt>|xuG\eFZ*UQ ?߿| vz.TS16]#ScrhBA@Pud->#@;pP9:>HBd{}ǃ|]p1 mۆÇsܺ5FY?E=HE\GDDt:}sewڅHDV?yP/r~b1 PJQױ#wCn !@44E x%(hXZp&d{UBc:h,†Vep>BChWu:.pt?yF ~&H ]Ӽ{ &miyVdT(В)Dt~BP,'wx+Wbbb7ny1q"#6MӰv>#i^lZB-@' _ x}AQܻwgiسg.^4EHz`oƐN{K>|س?N{~R{qy4mu!*2Xv=hTZ2 Z`US6 .1MvT)x$jj-fga&Shkܞ!H3c\ݑq=Y!𢧌~Sǘ_e/l6 $UK%| :C&%ILMM7Un۶ 'N@.Y*.,z\ښl/p`n7|# ?ৄݻw,gũqmȑ#f0MoA6,88|{"Mx Xj=rZ@uީ~or*8NGF4{QyXᑖaX r99PoR|a '>H귅_O v؝L]ap{F?~ޞ<"{]O$޽3B9ê~Q}ז IDATTPe^\.ݻXZZa<׭`!忩R5"xU(7[֥H< @f^*mz~wMa1՟*eY((_ !8p._, ةHW9$"lY Ac"CS|4"_5|fVסZ J4LUHy )B~|~d|ٱn\G}y`,hUwཎ 10a^ 1W?{nj&@xs5k0 ՚7.#=2Nӷ$fv{ F? B رcGkmTaqqqu%dCSSSعcT+3Dci)&=ш$Lm4W{ }=Ͻ|vwXN-َ^0 ;mfz*9¦(ED@#c89ͻ!)i^]ԋ'޷Ndžx:= U6 c|g/^&u~J)@U^l6(J8d2r0 CzwHA NgbM `|pP ãݺx$;~B!X4KKvb~ToGI0[ۨL*\UpdҝT"#jpMo h4  8JaڝϽ⑰cvlC Ӑ^Նh$vm Dx5D詝^(KCcOF# !pr\mdgՠOT*j!t^t>:h A, = =lL!b2@3Lbs p9{ YLXs|[no)BP3K M̏6kǘM XU@,2P˄$ZBRA QU|/ȈX}.~#2"`gF{zιY9 /`TUJ7a cQec6[Xhr۶*H)5E)As !IэQ@,ql$AM0\ƉN@% CEGZ*mgo>Ct*t<΢|"@8h?Jd՟~'.w"qCl6sV,I2\Of¨)ry1y]jZ %Ve._Kth\˶1w,ysfc {Z k(X>UDN-ئg@%<߲mFLSys$.:n `4tnBl ] a"-GB$8laﱦ^Zg1o͢|rQwa`gމ Äm;jtܹҋӐLJI`7kUB@ULV[ֵ󩇤%4q9! z G~0Dhg_|;&JffRn Kwҝ-f W*%&HoH]iY,z Ҳ[-na 1nBlrܠc^eQaZr+p3] ʛa _A.BÀy :jĪTJPhs 4M5R$c=[='6 cd?yQ: OgJ8G,zE[_)HY{s<,HO4f0)<@<0 !:nsfИ~f)"t:Ti=>'_8wP`{n&bµߦ@EٲxLj&|` Bj:G_|td,RA19ZQN(rr\t|+`q O2Lc=u!X9)UUTk5I&̆G,/70,>w~l3ˇeAD-[9ED)B@(xb<㓔z` 3~~-(VDf&y$UJ]]@8 HԃhK^(A(Oǧto!)G:^#2=q[r\YGBYO;`А'hI*I2إpR"`yy)@݅d20VVps&-'HMW01\4sq̯n0q a/ּOiЃ 3ңKs:J'_=A3 z .ќBH,Wɀj%"Η1s1F;iR99OhC`Hjgpy!x ;d]]9S״hV&L0d)"&ӳ4Lg@P% !p=4դ0jU)Qy1~5(MĽDqʠKC3(>—n9ϭ@:: NB/cqphWteNJRI<>"#^"0ӄڙsx_Ԅ  VWaֲƚaP7ydklq?}p'W'@9J\t㕴T 0hm2UD)y* 51IX#zk\]e4ܤ ;G ivvFisԬfzy1:P]9Ǟ0}/]n9wuVM8q"O /o~o']5h?%HO#%P;9Y뜹8:x: 1 ox+hmVNaLvsA%W5LCz>TM4(g][I~"Bȩ?ͧ-slkkc #k"X#lշd_5MQ .(NC,%1bxۤqiet,C=BH&+EqӲ*j>Nس3lﴹ׼=ROԙ?Cc)Z7^Cΐ H'Vi"!Ru~}r⨘fV(oh#)졏H=i ^m,F4/q]ehRN"'^@(=J1Ͽ2L6>wfI1BD+'.dIOe@aiNjZhw3sx~df)CF0_o žQ@ F)ejy͐;ok< 0SC2#T Ue6| <88P]`T*3D>d< pZAp ~"b(&4$a)Q{Fq(mEپ (ϛ@J?i)q9: iaF2BƨsS4֙@̩20Mm4=0YRK0[r*-5~<^ǝ-a76QcBWtcuO#Q\p)OM5/\O: @M` șKJ F|\tm>jb^;B)>JH`OQO^jM!mĝF,2׃z<̐]VKP06q!`P~^J(D0M D T#i -"CL1"0((@sy2KT% R/1tcK5oIx|_[ں h!uLJ^"DiS P^ƐRیP@eK~?v#DOj"@^-lbŭ>y2`!X67 .QIv*$55JTyӋMv|G9ES#WyF$I2#bD#`0P %anUP};C$"sSg~I'4 NceN G t?FK%ʥ !8܇!(:A~=o$ xz tyvKl8bm$S~?1Eo^ eBMCvvz=PQSᄳKц͢]Ljk#КnpHUYV=A^.p)sl  JOcI'J# ;U~LQ}e!|"sK#]lo&i5P qDb<@ x'1>yeLbॿ5wj/:lғ,Ih: Mw( -}N>x2H^+X74aB|hLr-oz;q[g~JM,Vm F+5.WsU?Z>20|#CH8͕Jŋ! 8j !8N ayY&b8VMN qHKo/ghP-UkXs3'yPnlbRU3C,ʺs#\eHW~54m\7_X9oR7d) }T?MrKThMP`Fe@[bI@fQI&::6$"cӭo@wgwK\(h!F‡8NxY0,1}27^/3v!E0HBxc@Bx[A I}ۻ@SK> nn 3m :gĪIa smCT#SGpC0Qu"b}wgw $2K)>yQ0po?{c qh.\22:H~BS Aa6|?@DsG!BޖJ%>]?JE^!QKL)K%GpnwNRA ໘?;A$JҔ wRlη4I0b88ű )2GќvEb4viP/RK Q/*LR*Q!U䳞gFr.L\tEcJceplT*<`O1W}Qz Av[M'03'6/b"`ci!](eZO8QL?m"? hO.iql LT&﨓}28 7kT‡rBh#ҶᏂPg{?Twh?naHak.8!6}ۜu󉋗  $>2bnc" 51~(ID2R#>k -@K1W?m?97MH԰G-``_yyJ%|x}]KBpާi&`Vh p;nO dk)S<:C uZc֭[XH5in}뛙ܞm^bחg!j4hm[K!M1eq+$S\ !B^e0 Q.2o {ahHZ:r QjԀe)e9~^I`oǞ<Ꚛhp2Me-1?!n708Iks{OCv{{HAP"b[|#!(b~&RR iPg@n8 e[0 q,x%/;FPσ޷'B.FGE0(eZZm!@@`f#?%Cɠ`_$,TdU@n^Kj2A!/ER3 T޿ ~M!i"콊 B)C(ޑ cO- 6ԹJ%jћ@-PJQ@  營L]՛ Im8n=I>ZgwoN"qϾ=y%Oy{$is-bضZ#⺎>%z pJz,1$ DϚώR8J uz8ip]G,q9JJ}Ju=|hu5T܊~~q;h}az9 ^G l+oꫯ!VeT=eT6Mؔuq  &- rc?YuZ^m/ކ( Cntd:Iaʟ[8&P#BQ_s8F'IpU80 C0 cqaۖm3יGm˂eh" !3BRzִ6#t^ISm"N @Io8,|SNB8{,0 U#B)~ec9u"bWmD Y3oG$c;TQZ^5x-F/}]f A6VcP"3y'3ed#08>l&GpFxba!i|.:ѥ*s~sI|w'9c M< cmm  ٶ2N$ϕZ!j61kpÕ Q\!1RL06&MXP!yDa~ӶA I#pչ9lIQA"/Op"b>r=X !pBP\VqCI,(@ޓt:i_;j$p1%EQRtltfsss .P.#Y_Ǽ||ha`* ՘#CHG)><#ߪg9uF/(.Hϻ KXlxp6<, _i< ?&[:]@(Tp2(Jr9# 4Ӎ z=H=N1 j&u33F`2<$g4 QT RR}/,`р PKT d-)]X2]s<2z]硞svVu9 {+R$WU*hS8ÍvkH=,TA/0-b02 ;NF'I{q8d -4q\#nޠqP]X&cvǎIÇO[`$M17("T*8&*2.¸};YZ¼!ӣfdL5%@Y5fh?n{zH `ˠbA_w/KrJ T!cic+$cRa}-eF _Nl )#Xs]t!p},nlX]]U1])REh4vqxx8MesvTnԎۑ A%Ȳ,PJOm}E,BG)g~RMĭ~-]e@}&$q0`q0ݽ˙084$uNk5U*pV? pZ831MH9xHTr "$Aq 4y">DRTT>mD0?c [[[ h:RPpIdIv, C8ͶmxZN)vFŔ&rkG'5cGQ]:%f&euϜEQA0 sqX/H@aC0g((#jPTNF ,][nacchhA6&{r̞țϿDhOE@*jr5<|  #eJFs_7;m%L]b.gh@&BY2?ƧJՙ6c5MSN(PMxjQ &Gc7s'g2 wG)YXgA4-LUhj0 CJɁB$ MRL8/|ڄ|_ٺ H&G VjFn%SFp!a D0PBp!h5`Y CIPY"/:#xnc!cyr1 ۖ|6!i<1Mrk&(p0c4y!Zm4|]&ԏ}ØVEtR`> W1a *g3?: EQp\0KR!`\Z-|,Ֆ"mժ ܞG633]TN!|,#"G8fۈ+5XFvH`;<.>5 8IZOP[Y2-_JjY@,1 CODT#N&9h (Ūe`R!_ Ns[8g~J)JbRJmR S|{ 3dmb0"O<&ۘ:7 u04m/AL ĕ*L@'G/ c2-ޣ9x?ItڨFi#"8)5~ܖifU&L~a`նJ߅a]tbxJQU>|wa mAzpRO.&YbH6f/+;; MT ^ ZE,^P+խϮ<w(!\ǢQk̯|: m_@5 v 0 P0FRl|$+AD0ZvPQ$9^  !q/`򶷽_OT{I5/`N'vO2wXN"DeY EWR  Rn]t@)+g/QC @*siJm'hy1Zkg,˄cjT/+_8sg56@''q,"Y"PѪc"/;0 ]y[Vx@*.IGa7Ōwv1/|+_7pxxy{ߛ~2Wh'xs4`țSDqn }DQz\L1ĄV*0(o—o]yL & D;(eܢ~_m/$?$ B{I?F ra^mDaTIAL<\{h%KϏ![o|7< 5gfP&BeG3yz^ygx@yo;oH ;b?!c 5̰ "HCr^ڔj}|uk ]4L'ǑĉE1d00 m \ܠ`i@sg&BV6 gְhݺ #\TQLDIfJaV yGz|ʖ1[]ιD 58ky8DX{?sW{ຮ}wo* %J.,t1$.X@"DIgf/*9Un] L,`~9&Jo}w[G XǓOZR}2 R3l+HKr1$?yxGEyԪUq:.676pe,c{ꫯbo%4P/d}66ְ3+gk/w\*af6x5T"8sFSJA*mt=ΘX288@cvwvqn#̑5!?L%,..`qqss~7o֭[EuQQUttX%aIDATۅwOrʳ<o  QI'}6EQi.`u ;X= `,N@$nw Ey!LCȴk) 2`6 ovFQ{5@| X\l̽%JjJ Nv^r X^Z21;[c; 0LDa((#}p{;;.ZG(JWV#U*вLX-fN*%JU,./E]6.uE #-_lYAF s=W,K7߿c0?$C ~LV^^ '<5 (,i& 4?5M}@};;h" 5^wiʏeVZR2 A`+hr[vJ`vF(~EM#te# a"_(+Gizsy`-rY1``~vw4Boor "3be~uaA܎rhp= 2?f~FW"Z kXY^FZ, &NXNBGE-婏C2.eh)RR)^e )F}q>:)#qy,x*Dz(UNy":- sq4B AmOtPFw8>w^yX *FZ^~3 &6^.:N#Z C; ~hj/3xG j`aqgΜj*:* j(EM{t`0 g2ۦi¶, ry``f%wNV!:=Ay;;;fffT*Tj5${x%S4ƴ9l楹ihx%~q7=蠟 И`~~.]eYǫB_8AΝMeT%d  A>yǹ_2 ?Ԙ_rٳj80O)ԧ>umx={gc^vKph 7 =uN0'A @1V~AҥK`yMozq55Sũ)uT*xߌWE|^xdP?'!$oK凗|Z\Ϲ=hɅA2ezފc|B_23eYs_mjhX 6~y44u]ž"cFR Iv^T qy叓$ׅgV4{Y{1 ?={=m$x׫\qn0ґ@NLBatq\9Ha*SiGhWbmm R F<+++X]]i888lnnHK]z߿$IT' ~c}BxSgMozqEJ%#c;w;;;h6888@BD)zF~WJ$"kH1T4 <o~ng'&d~;o|ӟ~fTa(  q< z0;#&^SJqEF-b~8677qMaW߇eY*\.ceeɟ`ccKKKhwc??SwB{1\vM-^a&G]8'EʉuaL0q]s93 "! ./o- bx8&T28ƳGG1_D+++X__ /0!g_pr/rj:-¹s簲;ePc!x_I|j!0 ϸ#&!ahNL;6G>\X](t3?{n- mbD౉ޯF{abQqׂ\1$c/^ģ>Zy /0RLu!ܹsx B"Wr&?L^n|-`5߳KiRLAm+};dLڡ'b~` Ž{`RO{޽H닋e?ѩG(Y~Vu(l)][lWΙً]c֮xH*/ER *I*% oM8vܹQ E~ ŢP(i`_pTU`#>_jO{] [ ˲<߼7oh&II<!as#M]W*WY(lK~(JT*"cf# XǏ#HhhA]\F>(n"o"-|)f8VBC X㗗n}ĸ]fXĕ|nO!0BP] 1pJ P{Z`NBPWz< 6[8RZEJ=UmU䗦 lhS@"5ɖ"%B)r Tu\g4 7E䷃RP(p8.4^:|T2fn$p#|B 3Ŕ6=~( {mN,/-M6E`=S,NeN-۹CW|SUh&/9DHϧD lz6xF[g8;]$?С6D=JfeEǒɩb*! XBoihd MnyReE --HC`ZA~ Z$y @L$ſJY'TllY;wB~'|>5:Vk'n9DA~ *ZE&BV%imU iUc='?'fffj,[|̷)餪F3p! >粪S%]/Kss'^uk~rrR%}|,#H ujVe,#uUiBHVݿ!eƄtBUuFTj:ٞp'mqllMj}M+|LȪ屇 fϟǥK@m>t˯QYvB(/b4 BQ((c3pӧOٳIv`̨kzWS^hK&T߷oqŞE'''c~/ad7%vX% M Io :UcckQ[8][@H&tP]gmkYUWSI~4'mf[z)6,/^LJCB'p>!9>yh*V' YQW:͝xeH xO|;@34؁1|̣[D&2*Rz?;+[lSR$:ȊI~;X=O~V.hIT'n {H~VMipHVU+GNvLI _xn"!3h {L~Oacl(7WGF7h-M8w_|M~]$)BVp 5[D3uƫ h63:3c~c\U-#Lx 7T;X7 scؒK~'`0ys'0vO5 2#p7NM5XR߫+F cw07_痻C댱!">}楗nRO^/ H<v`0B7F~&m{%{ɏS/T[,7N O<>fx.UyF ?=)'i:8dn"( )c#oFmBb[I9_}MԶ˃)BZ/]X.ءz)݇v)G?JIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/192/simplescreenrecorder-idle.png000066400000000000000000001124661470053624300277430ustar00rootroot00000000000000PNG  IHDRRlsBIT|d pHYs))"ߌtEXtSoftwarewww.inkscape.org< IDATxi{ʺh&EQEjDy$DwwuUWם?U (tddUvVUVsc>c>:҃>B]BR*#1!B6q2`uԊ8+JX,6{KЗ%-4R0"lOJM7Ǚmo7[A 'O9s.b7b#]oRm!) J~tCXn@{6qRBm4 L4Mm8`64 )%P0B.Txq4M{駟+I'?yB_(R3m۔J%JJZFZZhD4hb18d]׃n W:#)ϟy}_cX'N450p=wbHPP(P*V?|4H$HRL&4F"8cRʿ/J?m~kĉ}M)u!lfxb\.G>X,f#2 ht:P(D[[rZ@PZB. 6۶o3$ 4!@ڻr&ĉJ)X)Y@ߨaeeM!ݻٶm[ DRnڄH)pT4oF&''bbbK.qy&''Q(5M#Nd | R B"V>^]]!_x\8\er nGr!C* Fyo DRŋy7yyי !Htuuс[9Jd_=3Oh8rH0?ZRu5}2;wqew:ּof~pyۊoF a~~W_}{_|\.wRAoo/[9? !_|q]>J)bo)HC+f,,,P*|}__'O{СCB[18gX3o%m|nYϟg'd|||F{DRJQJW^ye}>2%;w g2o_^^fvv47'Or}o߾ 3L?mcYVoo?lܚ?C4t]c̿ձ+WOO0;;>hFww7}}}~Wh{gf#ǎ{H;h0;;&l/n}SLoYzZij1334sss,--uF$ JM?CCC 088H, 0 0H]oJ(˛>7\O^B髯E?`{'R*%?Fs4g>~~~Duij@c !f\rWrUZ`‡A===ܹ;vcz{{D"D"HL^xbg!顿oj篼IGPJ} wrӛ0_W4J),ڵk\x3g0??!|wGd]v~8@$0w |;-:twwǚ`7x~_~;RMxZ255)sIt].qzNRqlŋ>}7|s>~'uH$gtt4LMM![n kkk[ض>B AݐnvnV"B0X,+' N[׮&($R_>}"k#';`3YXX`aaq/CaumہS 0>>/[oef L&O?ii.\h9߾}B!N> p&ˡ{avvj(ΝcqэBڵjM_ݻǏ{nRA4}Gg6|=3 aSN{ iÇ#B`\ʕ++!w}7Rf rb /}yoСC ,b`pp7|k׮⵵5H&T*8a&wq/B rqؽ{7rϳ<ǏիH)9~8\zXL㰸믿kFV#N8N ??ɓh˗VVrQJ)[nىl#-wN~G) !߅Zo9'O$%!zbHZ%O} .PTnx-=z9lۦp8"[922˗1Mx<(au-l {ի,{r%4x3Mk׮a0?U3FbHVcaa?#SSS[j{?~Y^^& 333X0333-mkkc ŖVb1BoKKKm߾=xfڶm <4]vqEd2(R0??[o'x۶9vX{QW^yi:::D"jAg qN8AWW/^ XZ%0 a۶;0}$R'>Bo;w8rH`/jr9x ~8z+ B<˗liVcǎ9sGr%h^7|.u>H&q* jCQ, ڵEB===,,,066F^g~~1btww/I$LMMQTX^^n N8A,X,n)+++`rrN à^>FA;d~~>mR}BaWWWvii!~)@ؿTJg>L&VWJQV)˔J%x }Qfgg~g6^3<<̹s(`JA&'')߿FjKHJ SSS4 :y4mmmaR,Y]]t:r|>OOOwfddp8o88pSN +++ɮ]XZZP(׳sN\BGG}}},--~[5yYYYTKf9v\p!,j&IAB|cii >"җi abb'N4˲(˘ӧ䖌ߎa={6 B8J),+hGDuv4dYVK|˲Mf[|Z2"LNN233E"2A2$qa|ADJ`nn)XYYmۃ/~ :}}}Xu]`xxr"@s; x~~CDH#Gb *V1>>ޢ]˟ɟݽ}_י{OaCK<'ꫯޯٽ{7?GGG&ϳJ5񫤥rt{QTPJN9|0:0 1M3P*Ǐ'H}8LOOsܹb5M"DǏFeF_c;::1~=,?Oht1e* w&qW,"ܹL&T],u&)h0>>N<x<[o;wP(ofIR ^\.sa&''Z[oE6ehh(2JxB!H(utteYΞ=KGGd˲رsV؇F~+Լ/f2|(-k׮N)SBC@Phlޓ$_ضmۖ%iu+/u/}g\.sfggYXX@<sڵRDOoan{TwDGX:A<@ BiSJBeBqH)"˓T7S؟D".\]vq5= gΜam}cJ9f7ReNkNM(x:BP`tt4eSQpee{zz׾FGGǦ*iR,yǶ,P+$so6CCC ؽ{7˜;wBe._|0oW~v:BX/RDaH !54 MR@Rضm;XqmmXn(tTy\c *Q7syY]][om ed2)bllB<sN/8[sqMoz]4 ۶뮻vZGo-/:v5%Rq4hwGJ>Gnuu}+h.:R֝_D!Rm)SiRgHw аmc+gAl\kRnP8?ŵzͽ. ~Y3 a#,}Q|5TRYW\>0عs"KX/mhvo?L(4>}'7iM8|0PW^yq8z(|WٳEbqi-B(?0$h&4 t +eְy~E*jiw导>عsqBP,[4 ?0px4q(#M'6 A8{ԩS,,,DۂNfJ&<_W^ "TͶgff޺m۶ǀeh\F7j{nl={_ȑ#,//_wƯLycdU]HJ!r>HC'ݕA Gh86I!hhjrt.J@aH4$JhK"C:j*+k%ٰ +`M BN,="N:A2Ƕry- StvtbY6,/tuvLOϑ]{ t4MX(cPdC///:p??Z6 oi_3ٳ]vq`Zݻ2߭@'"jhKV%j#%TF,B2QRtӲ[^Fhݽ=$ړ VP^S)%,,gY\QL$HwzB|p^V@ .lqWBZ =r)+IPfvvJFXAbe 43C$k ˯;Yp}SOP~;OD^gddM}۶m㮻T8hyGa6_ޘo޷~۷osBT*O}Sϛk'&&ZR[Q6 t󟘘`{! UrEO&0-jNVXC"`pxd|jRJ?xمE&ghK144H,grfo_+..Q(Vl%@tJnRL שFxq{{ ^hxXan!˥+LLh8lbxhjƹ/Vvgwu<4CCضZX^D,G`RǁA)Wd0Th>uy?޽{y饗ŧ +W `|_BDSh _zN&DB@0ߧݻwsa` Y i@l6hn4l2b>4C !\ xZqDHhaD)CCDQ&ghXC3sT5F ?Gv%W21jLv<Wjs=WrtÝ1r:_ MC`k2S .`"ӑfn!<=]=]3;@wg.**VF)հ4H8BôL:a#@C9$5Le4ymӧOuLd-av?`w_j"\糲%`O>1Mdu]СCRM5J\.G8iV FlYM#c+h8w42]zzXɭ/6H(frvQ1J"$+iKx /t ]p X>c޽>4MNo[['ٷor .F9|0r9kzB!߿ˤJf*#FBmmtvuGZc)chxXѨ"u79&Jl屔cWY7[:@!ǔѰȮRL'ضC_?bݽtJ kRCVæ^3778"B(pHLX 2 cp!=n{fy&#eplo|?A&p}K5=w_*UY[+1ׇrte:iX652LP ¶׆<8'w`rk/f޽߿4f]LDwMf02p+ƫ $R޳;BN8iD똦믿}˅B!۱czl67nnO̮0)\@3s(!FF4%_%ӕ!d_ް).Ok B!, o q&_.[_\ޓo !-v%~W0tҦtuu111CH,--1==M6I#W=QR`6ZG7"Ah#P*O&P&],򔫵@+)I%H1ZëLx::庽nUޔl$d6}?"|l` BIzJFwg'bR$ "#.CHr߱l$:R[@WkUqs_iϞ=ܸRV#ֶ),xpr.O) ެoPxQ3gl9GSyӗs=G,ѣ,--yp8mKtR" p3()эHRB̙3[.Z&d0 #X.h#eڸ?\,[i9. k%F$L^LjD6c֊^4a:2hPkLkh}1xA1~P %z(Df{-,&'I7á eAn`#fFd!Pߪ:xVuh8J\%+P)܎2WxQJ G+k ;,@xwc& 3667,h4B}__h4\Z9Jb7Ӳ%>i铿 pzkC:_NK"­촔CrN^*>pzD 8޹xR˽0D::;&gҁO$4/,2qh||pMӯz"l4C"*=uh<_V9Z$09nPDyB!p9[5Pl6tuu!W_}fdD3RJsI=T*߹ihoooB|  !(JVk&5sεh08rkkkE&''Bpw na3*^}lX ëih8;MC4Xb)r }FukQ! dz 낳 K5'pv- C{9M%MF41yov|~}wdСƩAFj(P Ѓ$eD,DG9r>}:`aRDooo0gx!iW۶ؙH$ST6@7-x>Xsj߾}_|eqQz{{Y]]eyy UY #jUO -3=v_Un 2mp4J4`$֞j,"9/?$*[Cv4ړ:;c&IF8c8) gGmqڇ 芒q,I4oknU>7:Ь#Cc h8FfI=(RDZ<($5JJU]?]݋+r9 sqbJ,N󓪎_m Rj;N'4]ͦ_OYoi"O<?~bٳg7K)?j#vlz{{eW!L"1 l@s5b<#LtcBx{Ua<($c$!NG %kbѶ8Whi!l hï؀c!X Df1mzSYr:S¶brD[ kwY c+pZVRi^Dc$2- vs iḡPݻw3::Ғďy4E hYI^7'nMYL&sR_\.sΖ.#_YY4vϞ=R@‹"hF<4h:ʭQҭWB#t}QxM/` u P,'і`DyS!ֱ@A;nHTP9+$%drNűQ!g6,/̩{ViGXp4j =]bh/ &KE"C֬U&KkUlRD! Aò&6vYi60MjRQb*VU3iTM9ΐNÉͺ3j@h4MfQ)kKUjui{^C6GԺup>xѠuAl<[ aqc===l۶D"0۶mرc-8X,A~q` 0j]-@*W)헨6'+zzzp'(sZjguu#Gގ{.qiH(]vi!qOul&}k8ٺߔdBBH7̧ Ԝ*fR"5RhB  CRDL"N0:Bw!Fޠl6X.V[5Y(0 0mhpR*s8Wk5_J_ӯw@X3BD0!i ʝ\- w`[x*@i0D$hmZXXe\vmˑKKKRMyamR~@/EsFѠ)@1n멧=Gao=hXz@BMh&qcm!VT49ɞ0D"!tg)j8DD!WP3=E,s gW ,P x(Lg$H,.šF\y.0P W0<aYSdRw9x%D]l=6+҈QtpsӐmr@ Vc7~˷g_CCJ%JyדL 8N >8yA%$H$1ƓwtJ^J(Q$yϾ|Uy>]_R/=Q/T*opu677?3&l4F#\H󘚚JF#;Ŀ+*r Aog4bG !}vLǏeͽ1Dߴw: J8|\&H%@n3 LR)DB0U)pPX[Z\.q( r IDAT-z=$R s"%QHT3I%Z^G4Z~#OZ D*Gffcfrím Bhuz}^jBXFX:N?;o/2{x**OO$krcR0hW( t>) ~}zrEuʮ)N!"EUGP|Oqq"Ie{/{iq689imk@ %O>g<OZx?9&?Lz ̲2@r (A',~OcgA,,,a&HX,& ֕Rz4M,sB^.)J6#uWYgvsNc5$QaTT.Roɔ-` JfV++%d)r )J^(/ i6یqtB?Iq{WnX#Wsa@'R(;_3<僇]O~!3**L$^"Nng@TZd5"}D]2Hݣ̠qy_mcc0dA&V^+  w&\ ag#-J43Wr9t- ը܎ \?KBA{7 ONt FBi:ܾ^hnHbi(>0{O.#!npӖHh0@C [!}_8C?/lfSl4ͷ:O* 5 =3& BzB<3HP).!d6*`;7PfVh4ۓL*VJ1 055qC/=)e'%KC'fw:"_ޟI1U C,:NfRT,plb Q1v {}#)q"_`8nф ݂h8kwpw??V.wmxM>?^yk#Qfeeɗ!Zǧ G()mGdv͔R+jI:-8v"ЮjP3y,xǻ AXa:1k躅Y5qN_} Vq\'}LOiuIkzxOxtj u:):'53!G'' GCM g ^Vu #R&YDkw&L0 ` Ԉ|"۫ʨX*Ed *Sǵ=U͌ЊP%\gϟ8 azF% u9Kfٸq WI. DÃ3BvKc[+)Nߧb&$Mfe^J[@F*XGV[*6HJifA3F@ _ 奀5=2#JU=@\Psyp]A2rhq頸]/EI6%RB)+3:`}ӧLOMQ.0qk Wtzq4A /`^Ma|Cn |E)G)OT\@0G)? y.h#PbJsSf;MC/B3&gMoc"X.3JGJ)\lH?m8K'(8FO6J*ʌ~`USqr٬^cIG/ 9$LQ$jTe W=hern<)g:?PV@p44^1O7{69^1rSaF:rX [raH+jO%V~DPJ}`.3%/ Woȯm6kղF#G[P̔t,N̥"Ai?Dd8 N%~FM@G޸.v7El&U4uDjX6މnq:>O)R.ޣFG4(jx{D`:nX!-3!fDjMy5htkSf@B/ BDq21o]gx}ǯ:[1c~brq]u桚bGŒ91FRrpṗ[YoʓNȫV$?8XM1#@, w[Oy횗k i4 W>*P{YRK[74#D;#V8t_,9bI!Wq 7x_2dpf/0R6ҧtNnQ)tSoQyEG q~LOO%qݘn"Rw۬&+OE!e#lAn'1='3a"˷nHp~<&RD.po0#k?)*OvΜ"Z5ꓪD9)`ntͭmN(|H"^t&ྲ,R/[WeiPI\C =`r:Dp:aiB1 OdJ x==cv`yFJigKU`ѾJJ7%L?iU0HN22uE*n%R7gzC@Al<&ހVB!A&uR~J`">{g/^AM)c=[tb/F~<D'&H5H10yV7Ho£m `M=eyo#0 7̱->̀ #*pb\$F);QFi{`aq>ޚa))ҏ6bo5IE@a bOК=%u-1 `Rjþ !6`#H6#^\ n3XPd6<-o1Tá^:VQVh: lns#3 k:o̵{Gof:X" VBwG~|y$|BJ0sQ&4ˉWRUrjbvs8}[فی1iJ y `SWQe x蛐L6XdY8Qql@﷈bbT.hw  iu:|cN'e)4Rɳ^ HNY7d1!(B\fGus/ٺu{!{|< Y3vm Q)`q&w\&[0//ˏ_L8#Dt'?@kj.dN!1u}(%-\*pjFE AnJGtAVkW8m)6_Hs\nHsB M"I.P#h Sɵ*gI˃Rq8MjtTS8aETU=6̄8*gls-J=IU9qS&s}]'{y꘱2=Bڠ ZtM2~Y(K,N<1KEmu+@FȑD t9Fq!F%BZct:Vykrx).ZnOJnH#OBMkA<'{uxw@{[[d4-Nno%U?8Ibۃad\5by'7qMl `Ľ,TkҁmQѸ"N20RL@rk|>o¼r4dPJqMW/MᗔAZ`$}(2w:($A0T~$i {: "|ϥX(P,xd71Pqg,fzCPVt:="7nɓMђ[ms0B"|# ې;QńlR d"RY߶`HIP  WLj/.D9L gr$t5A0+)SE? 1iDtxIW($077)A (ހvCJ0E8fQ?scI+|Og< v:ǿK^% quCl=qIx90*9::s^5LPZ£0@.#yhM< R)lL:;i?5 (>hiwi 11LllM8~Bj2Mb5(vz($B #]"$V'KsȆ\dĐmeAlO-rARQu|_g1p-^ Hn)J8BAsuPNu~0 *Й-}UH(kwK.B Qt]=(2F\;q˅aH$ߓW>c@y^"X{8bo[n+`~삳 W8᥌`qЯ|% p<3^N3I=xѫ&&4*"@P( y(wzWĦ5H.#q l UͶ X}_ qF~esS&ίb4ô!N7\"H& ̙y EQ&e`V{;!Z(A|ϒ> $ P$// @y\=lf"[fWS[aP8Jv *6(Kϐ@A\XFB;fTTFb98J] 8:C̠lj!3P0a0<'I)Wq:PtmN"쇾 @nj8Vi3lڟUeS-耒26C/Ԉa$:7GN~X]>u , &+䇃EJj"JE@n?&._˜\'^BIKW+t&] @J|lַ^LF 2I05,))tFQ*EdC x"pD`pNWH #BX] "A~8c|s*:AiI&T*jA IDBWo}tvsyIv޹6 @|K;BqRK30Q^n(Ð G 1RTd40VR*7f!#钁B;H9҄=+hp:-p5un'䙈82q5~cׄ1Cl U(A3Z 8p!!(0F#{tU,W/ E!PcV\&շ+fA<D8*b:$ws!곩f|MqFQ}Mz + } 9.*"Q (k H'uG/ZER"IǧS*aGs8dR7 v8t qR5 *ghB1[.w{t Ʉ};䧚jVJei}؉3E9*%o~.aD)Ƀ@(^,/rR:f&X,YeEY"{Fvee8e+{! @ΜR}zN!ߍ9N48C|$g̰fc~@lB)hzzz)oxbW l#2Myi*]!^$!흫_ F\!?O& ec`A_^d$bNj]gA|sGE@#`+ A7 a!i`  =UOl} ~QU*4Dd"0_(ZLg}=&9Jp^ @^z شO&愲`r .cXǓг"ِÓY!!F~/zш?hxqހZ17)y̙TIH2H-k4:T(eqBBE}3l +bOS^*<{i/ wTJ*9 D#ϳ `q@e}2)AŃZJJpA CPG#*G,]F 2;U す mOwVʜ4Z8("Θ&2Hn-(hs<\ b! „oJq']6Y'2wzլp+ ~>Lɛ _ l"`}2z[ d(1+QIpo-ϫ?gq{uƷ}ϥ11wg"e$f~oYef ^rBMrFqAd)^ ӕ ӕ {G'h:N?i%ln*W~I'~JhUQ)x9/`0H. c$UCAP񒫌j~ 4ݰ>xE #J$w}꾇`jZ5 1 3O٪eWK%֯hs bAo./1קer~ِ{\wԪe<3\|/h3K—\K> {f1;2-A䷉GoCf7 ;0D!.2R|,S臌3ϼ+)kKVx |*neB9bR2_t틜@\Ɛ>v濉U\A0Zǚ yWĿ_ "#rY}cǧ#zs?9Yz~(%mmNVu|H fv:?x3S= HAOn{+2!Q!*Y)jeɈ0$R+??_6]ZLPK5WtEB'v rD _<6wJ?"Ƕաx8 h*E廭l|`Vw!' zqDbb!%Egqf~d Hc&33F1/  :/jU/Bݮ^^NO.{gC˶p^P*'pd c,edF9ȟ;KPs,"C FvY_ZdaI%7V{Pb?xwnO1Zז?8vJﺬl?gv?~lobKWh$R|q.%@#3G ױ⋜ t Oݢ<[Z| ۣS99>V"q>w^[qCGP |lK ]g|#*?x-كo9D*._/h:n^'` 84;n9ܺ:Ov/rv"LG#u0;]G(?6EB!&\e}6~hEwR2|.|~O  CZrL0蒆-"s@9(iǹHQwC[9 2HwnGM$H=ʥ"޹i]/CT{wfol1kU^X v~f.VXZX ڭɈAL( lz';6.O>a_Efyi<%IBxV><6s3U32\DKh=H'1ZzJ7*bs\%6~$H*" w_lslSEv{CVe6ϑJĻ07?NqNO87P.U(x2 T4{_|o^gm[/)dתxoI"&$B^4IW"˨wQ8`~nzcSjgs`/ ."?B.zE'bH2%ʥ~wBAɔY|8FzU'U x}}rowb7{>ז?Qb%"|JjJEFAD7Ga~n;)Kl>OYOSp5lb`uOY}a@e10,{.Q ʟ$j$?ltt.+Y?^D]4K=ŨA҅e2[[[%"?c +ԧj8[[eRY]Q.Tˆo ydq~x?7!uE;{<KRoA =8fÓSon%LXAPb2 \[^B8.C>OH:!_VI#$?VHÉ2v3z?6H?}N;<$/c7ן?@ p]pme{zNW,qj;b/z1jd77X~/贛q]ZKboװ$+&V9X^{D#x̗i7~m\ C&\EŤ.=kCSz$l`/~tfg$NfBj{y 3* +++|O^g0k muGC2rc-P>qo.E0n#bbʪn^׮7oECvgzrF ,;X .&k|Vͷǜ4{] Nƫ2yY\E@E;j7(ʌ&j'5bq0LFSB !9CT ÐZ-2BEܼy/^055EVc}}gϞqttD^/(Z$HWEAsZ!dDw0%G`{?ߒ: "%9lx?dys3;maAW%z{> fׇu` a¹H1oHJ##|' ">l62|z^c?.WJy?iO(G ~a6."@ A^T-t;Lj. (@@UF̔,"0Hefv{2o{e5huzDR^>O?t<}u&F K5ݽK^;Nɖ\@DFO(ƆmC,d:~MUGn|n!FC\G>{ )T*P((( tG!ǽiD)^=M*qf)) [v _ZJ "WU*uȿ07<2ҪTM^=ńnSVP(j2x0 ?_eU #TRR%>>&enܸaʡ(f)65E^DQ#~H" ZZ+x5n-}5~n1e">NK_ˆNo@QfNwhdM4*Ej*褮g"IF-( T+Ua)K{u.Ffπk>F6{.|Fi". sIm*;EBᥖQt:3T"GD.ZbLZىH2- ׋Gql*{n GXHG{5vqS8VL\,dDڪ V896rLP2] ty_dwPUO(G#udΝ;8Cӡ18==R-!f+%^~QQTt(nbmmU ֭[<>eyaNGHŹy} #<59>>\v-ߺuLj3NMO&\te XLe#LgL3!q5Zn^ Fn1IdԧM{ AHJs4ZMHo'dL,  aq~sMo*gĿ%b` U(JcʣnQ[ѪX_hphq}yFNt;o8.F;8D\>(z92o&qo z& t$B돀s/K 8*+Pic 077믿^%󅈠۠T)Sי73^ i[,,h۬]G Eo0Ȫ=Xȟ7!FzT:B)7T 4$ܖF:@JF%2_k58<8f4 ^+x9>.txP'E3㰸<| f7n0;;&6򟜜pttDݾT\/Oa+ݢaf`qq7n &&bD}LuNVcf~QeVCe~ibċ]촑 gb!dȎuRWV1țʐ o0 d4!<az s<ܦV1?;nU",,4:=3Kۡ?D! { u.;]gqiV]Ϋ<.nϔ ^W)2on#=dU!6@ gϩ>Ⱥ|㵍uVy927;`o0)'7@ y)n8d)EC~󘚚JFso߾^ΨEn7Ijeo'''$cW*zqEA>~*,Bm=ztVcxD-SSդ&\} 9i4X\Z`aiͭmw߼{X-hFA)VRiR9F0@R$52]67e[E~ pcoqã^qk&Rیݴ_ǭ7QJϳ+_SP_#xTwޡqzzܽ{b"ŋ1 <{h4$/r 4.{p8q윳;= O,,-RVYJբ`Bqhyk1==[oKT'9HSHd  X y'GɌ/e(*5{Q1;3í챹6#ʘg?)ss(OCq`9Dw'~m>GGG.f*qH)y)P]m rAtoKl.;;;'Хw(&.,PVYNTd`_+%h0pH\Ν9m#*.zFUD2I2Ւ!>L}`<\!y5'yڭ >|>A_>s]nv_c^'?_d8P`W8E>@1z-z~?ٶi4ͫz}E+笙 b#\B355 vw6RYYvcl1jtouv×_?\ﱸ@I:nAJ\SH?#,aƸ `uywt%|շ؆VO~Fp|td5IDATa|SF7|uvnؠ뱾NIq8ONNߧ^57W(!ӗh/ B>#zɝ)oy()t ,./Qs%(Art|̋=x;t]> 7OzbK2HcV Y.T '.`aaw~㏨+ׯq5`kwOJ%Ya0Axdiſs߸U9>iwbyiԪu<ѣM@+tX]Y'4R:4N|״]j=;-A~S^(xaii)`Ԟhw}`0.Cq~ Ɂ,..8ΕVV 1\ YXX~~!z ^_~HM-γmnnd/ۧ2Uc 7nH G'4mA Hrbw4 ,/0?; 3S3[XY^MǣGO;F%nNL@ |#C1Ϟn L<8e>b{gy|7Ix f=Ǐ9>>+ \σ@or,,,\:wO?euu >cR,--jǧńP)nT]u7yP@9~oxlS@j%%õkƍ5밵֋w > u%gt*Dx(.,28m?cp:k׮olOnI] ">7n#Tmk^@pZqe%{'Ov999GOzyE.:*ҮQRܕ333lllnjF#>1Bx0 V !ތYs b?'<}T@$*PX[_c}}kkkU'''hwtz]{gF_U(IնV/p;(i =@҇ddNA.9&A9& r! !@ $ #ْeYRkDH;rX%V5>X{lfCijBu.O O y}xU/L +|n}A„ ;C++lm{6O,OB)nVy9ŪUӝFmb?WVV̈,O G= ]Tc\.W9ښ?SEw嫯"133Ã_K4t|FGwwޞwp]AI<sDⱲ&Rٜ1\2K{Ok *6ˉTH E4DnNyEFch%ԌeSia|t7o(Hlmn377O8Ŗأy{{6wntttGsiqD-$pzI8O5D%tۍ+RV_FuLղi@Yd|=7J;zk1vPtEbϞ1L|*+eS['ؠF W+JF6ok122IMtvbeyأi\alf,Kl6|>DQ4o8d 9߭^x/sTpz%$ K,wyEuܝ;wH$q.+$]5n142ƭI5Kmnr mQ4S5J1(Gv+\Evw1220}>$YA+VTr%/aI T,!qM/y&mQu]fggE?SWS OPC YB@ rgbbŒMNNrʬG[թ3<6ح |mm$ R 7noN8LI*vW+裏~}m4]B_KYZ\fa%y{أIdw^+' dy&=:~8?g}}T*uD+(P[^rzC JSSSix{{{3<<_]cn0Km=304Do݆H BI׈'vb⻢aol.G&d䱕Mˍكŧ>Y.4 Sk[,*;;Xe'=A)hjtb^-2o6h`0x9P~𯭭NkA`&?7\ :mBÁ|N`޽{S(w><"w)6+衷7ꢭT dI;. SIfȔJ 4JRIq])4P PJcp%EÖ'&&̺\5xa{"Ip VFVc "B^hf+p8$lPUi EN'+H*>ZthEU~ׇrtgUa/G.W W%FDD1+)oSRNv.M>͐[Y+I1/AABH:RN>#TҕBo*333~94M?ѣGf#  Pʿ١P(FYU"&'P E@X$I: ~)N'oMMMD"g ge _xCPfY]]>`gg^|l6VVVL!W^Q,hF("xjN4Xh^3 "GQ3U[댎r vvv֖Jxоzr9'Jk]2;ݰO Xtp8ln*aeeE{=sRd*6rXʁ3sW&N|>o#YH$o67-u]'F6%!?Np\`5 ?qHcU $IvDø\.里ɤanE lv`=hnn6/ў|>ύ7bppnn,;r7FP`kk4y`(-.eݕB#Nt:QU(0KKK n߾M"`ccf577nlllehhN$>EQ O>$199Q)DQ4M#HXeoF!5љ  |@ !RJF We(=?008ZflinGQ|fppop\LMM7߼ZjQ9$(LT֣u!S(̄3*/ x/W Ǚvimm5zƒ$Gprܽ{/^1y{# zQo*s!I:Fu2 d,ѝJ=1YqDxEg(x<7HZ;NdY&͞{$ L|>o ~*/ԉEª$\Τ&LY'9/.0" |TInA!N# BqAMY\G,GX.M@PTSωE1h뺹r^EJ%sP֯c-="z:*!3cr͆< Q$q@ffg=:Qup Tj(8N'vbdrX+Cpt+<-hFĘ Ԅn7q}jQ+b컕??4DʟG_ |_b7.I4bhq70p6G\h$U&;_4U79Mmzc+._/r 3DR t"/qpuN1^h)zn'#FAd&WNG?# 8 n)1 LIP{QBd^F* q*;l P(zV5G[Tb,'3R55D䪁hhjA/IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/192/simplescreenrecorder-paused.png000066400000000000000000001075421470053624300303060ustar00rootroot00000000000000PNG  IHDRRlsBIT|d pHYs))"ߌtEXtSoftwarewww.inkscape.org< IDATxyםyY]}hb$@H"ECR#ɚ#zc#z;z?6#aFlͬY-g )C) 8}ﮮ?EI!]˗{3>3>跎įc=@CJ!BEzi%ԤinH)Ӛ__W>M#7 5NzHJyVe7B}I)}w4Y.^.rܹx9sf4B@p+Sߎwbt o )!;RʳRʳ/@ݖ>C=4GEn}ah4ua`&i`稪(|>~?>B RfL|AUg^|kN5x?R>(~=AXX,R.VT** LJJTU% [8&4=61 4)/l58}UUHbx4) y(*2mMdcc AǞ={صk  Qe&@Qi"th4XXX`nnyfggv/_fnnRUU$R)W"xQJ>?@ x\n[nZpid2d2eN?Α#GطoDe1w\ »X,rU}]Ν;ǹsXZZe$I:;;iooGӴe)_} SK099B.hercNM)DVNYYYX,x~oo/gΜȑ#|;1iaw~u˗/+3333B!zzz%J)o1O F X;?R[m,..R׷ə3gx9pkw;v0uo?lݼPUMܿg̿Ӿ)y{9tuuK ت'l* /Σǯ'*_Q/7 Y[[ۆs=|k_Hw][\P9Lm&lL&CXP(P((j5תS. ÀIB`еb1ɤ:L 8˜L[-=Zm[ߤR) [Ѳ[;ا~`rr( xȋ `yymH$W׾5u]VQV(]VYZZbaa7olM#!D.O   i C6g'd}}}};::n%o !o0 >,oך0Vyoo/_L(%^w>uժ;b !HLMM1==4KKK-0WA݌1::(===@A gn' zgի-BM__VAꟽDǏR w2 7o܆7TU@wFz)%s ^ʅ X^^* RJW۷'OrMbX:xܹ"|Z}h``'˟JV>GU8:7nh~?_?S7rGhP(Va~~~ޱxMgg'W^u!XR!- Ο ӧR>J)v l6L ;u{ Vd2{9o;^su[ZV… ?~k׮qwuȉ|wYYYg~~MessX,F$\.ST8rB@ |>YYY`||Z2^'2??O\f}}{ 8}4pBoll_9R~ZL[a``SNadYE!80*&/!~CӗY^^v1(<#ɟ X he|)%JRDX'dqqqG|DxijCCC\tRDww7~rsssJ%w366eh6ܹslllׇ%uӫN8A[[+XԌbN.*JWWWs|J?5o|CUU 0ٖJwӧݑ pGuJz?Jqx :ta뺛877i+ޒ)I6EuwL-CZe}}UJuu9XXXp;3~bLcǎ ġCXZZb~~y6660 67)g+_ZFoo/R?LMMQ(\+xî:<88_y&ڧB&''+++OJ)wZ2332߿]]]k|MhD"tttoމٳg>`17o$ͲFKv2im\*lq2RJ$ǎܹs:tzTNgSCH$r劻?ϻB)Onll>BR8zh[^Nyg_6m~??6u:-,,O0== ~^8kkkLNNFr/k{cvR(X&1nLaY]]euu58qΜ9[nk*g^˗[M i.]ZP  p?'y%Nַ:0;s[s^zmcGGG_Xݲw^i:z4uБ6K,@,j*>JT5$;,.4.]ŋ Xww:u'ODUՖ:篮<\t=/gzNI ;;;kwrk_}U.^⚦&giUU1 ￟7nvtOvltgg'<L0.jvYzNZ^b&4G8yG;9Vk"~pC(j M糕ǰƛouŶ) N|TG%:O>${N"*)_R/ (' m*Fw076Mʱc|fLOO۷U бh{!8z(''D(&n֪Ke*2Zf\ZPnD„akvSUBR,TNT\. R#   YáPa,%Rȵkx_Bx|+_qìq« dN"׿7n }"066'X !( -#?8@`[44)J5\S~7FJYBޮ} KI_6;4X/yaaŠ`T*MRR.yꩧnYK>Ͻ(?O.:;S.Y pNα̙(QVM)ʴEQQa[P tAS1le&) rՕU._̦}Ux,F$JRxo9tttN8m/ |{s:UU[,i˰ B!B%=cmm͝nUU_:===-X2<6EQm7'r ^jݷS'OwR,>T.jRlk &'3 t}~T~PVR.S,TҴrUK8|~@P(L4!P5zLSRT+~ϪTkݰP(.jT*Y}\APUYZ^^y(a100 s7MصkW\0 7%9Jb_.ء/e:ܹ\%̙3:tERg{zzz^qF>|͡vWg4+l E팍@ q$,uFCFTMQoPרUk4tӴH2t0m8"PTUQPTK|ƀ2b\ZC~ /W*i$Ϡ**j7EQEŒ=:=υw/fo=B!NvS傰u:x _җ)`u /:ka&ҥKX}}yp0[ũvw-w<_#  i4Q,of,#S5( 5B!K GB?6r!B@^GutݰjU+%ټ1I$D! +qf#u@jJXR6fjͅ6i 5xնV.@zwy sX,ƣ>ni7x=2.XB*q{xwM]v=<C;I`1﷘7r߾}R)^{5&''Y__e㭏]܁r4M##D^CA $ #m$ ::4rM6 4 {Hʼn'(JTb#a#!Q,\pVme%  m2 IDATD{6:S)baldϯF4Y^^aumMӈ,GY.\*Y,Ia T5E]@RW^aeyӰUN>; ǹpaYĪCɏcy'ϻE!ĉٻ/}044_h뮽9nVxҾ}n={a;=y>?vbNPtNww7d; aXbQ[" ՙ"Hg ZBY\^fuuJyѝw\[ DA fffoꦫBͅE9~\>GXD Z Vk,--a&mU^Ν{{b^%^Z0]vq0ZO:wE[8rKf ؔݻw2MB?@Xl_q{B-(N͛7],;)jO4eFNXĔ&p~@PUݐdMӈDm%G(biyRv&1}.D.}KJna8 c47ȥKWQ366Z/oK__/ hO!Ip`^CשG[&]:S;Z4Mؿ?;I)ݠC|UU[0#Nvg@t4z_[[|#ש ~Oqpk;g;z@E!sTUMp(NI؍F~m}s:t餔VBRm$&UE%i>rcp`MΞ X[[ z&n&'ʳEQ q 3}cٹ9?Gť%fo bHh,JcH$A8! [0a4Nv<-`yy. -)5Bumvmmvzw ꏵZ-hlw}vyy#Xq>n`Z,s+9@zOmӣi9rD"-F\.dl]}衇C[*ufO/~fZ@¡0Xri ¯XX\BQG"Wx2鍌|kSTU^f*.[-m轈hav#D~Fק0{ 7fL'P.hK4iH$V*erxˊ3kI n]_144(pmeWVV17oIVsbT*677kwAYIjBNh2C<p K9( ǎ# [nFFFx׷5]Q}Y7lK$ iYhF4hPHuH,-DMRazP3WA~e۶'**rthT zN4!;͞20 F&h=c%zazٹy>Pf~id76(NxrK/f~ނ/==֬L&YZZbffƅN6:::^} ='HxC%ŝ#`_!Hq0mSz8<T* 8vRŗ[cz|~_WvM]ש+ucwX: G(J~(+ld2LLF|* P,l<'ٖ$ |(JsV5rJs![k#;>ޙ WVY]]c`p~OMa:v QvzM$( jzTbUSbyeZ;{ A+ tff .2ß>}z[J(tttwf1 zB>)eOP%? J)z|ޝB>[!M(JnC>SNiN>͵kؠ\.399ٳgl#{1p• }1Dx"a;,! GBJR#a++tRfv&7ff=M'hO&݊MUiӊl|(.3[Yd;(ێ{-> ]w<qIڒ|>BF nQ009{ݪ4L.766$ XR)]A2 ]8F_**# @8.߉{\&x뭷ea܅\[Xdjjf&ʣ>Bww;7:FeDcJ8Fo nr}{'(\>eb(!8T;(V*t{V#w^k^Fv=o'kP3@EX h+ ʲܲa< LLZcf b^|DaW4TAnYw+9e㝚I)`hh % w]]gRC.}h; K{.sUT{g[}vPd2ʉ'Ts  /\:B MjB!*>vY" }L̲NXB]]aPL(ބbbRF 5g2wGZP~)%M62Yvzmf@44%iYU!j )M4rL|}[C)3449x zt:-ܥ^#&IiuY)F/K%}hD"/0 v8->}z[rMӨ;w8U%6::JV#m-j߷'0 ~\ϒjv@X,J ޠ@ ̌o2rG{;j^FsѶfFCo\FkPGqkm=nyF ^'sD1lp8 UU%STN~Tg's׽K?Ri.^‚[,]]].Z4ecm:R,or%_|>20ccc7Je^ddffaz{{=%O5{ pyڒI@>**h)X,k0lťe;L@ HOOu g5*QT4HB$Jf'XmʖѺeD2ЯiJo8 W|wϱ9vM6C v Bf~TE㢬Q\)cvȶP@$&m+$#m-!8u[B2QN9Ug93 8HzT9}!IJ?8njo>y\666vHD)#pz-7DH!uH4J$IT5E7  NoO/׮OQV@JtXD E) w` l+Ѕ v)U}꫄a?ښ9M4G}s-|fiQ(C7Z:Xgew /w;H֟f .4ua#ǶP@7 zd6*{ Zi/AXjFZ!ifug>RZ0LT/$-?ϩS`yyUU[y)ϻխBYCuG"kR鎣EZBLa-H(8qu4kfg!i/~7!311 XXXpvS'ޡ]$H2* Q*5q"gm=&Y͞nB˼B"^|/leV8kYؐ\@Q4UmӢ [8e`eHJW\׹@BD]ej ZV]e[>|M* {孷BJVVpj&IK(x]Gy+2MwRPg'ءŘFR!' jkks|v 1:6h]iڒ-/SU5@r"TiH}Dqj }OxnSqЫ(*`{pY0z dNcOHu9:;:4%[J"DtMkkkAClA,ü;[/|r:ݞy4;;.h4\PUD\v.ᎎ666^nu$T; ʒX!ØٲT(,,,}._͛7{j?~K&~NS*paW,pgGQ Ӛ:c0l|>caYI%e4a۬n[ zh4J0 SU!ّd#apu,-.yboΎ9BVsKa0??Ho@JkAׯשj#c&.BXIY7?!v;hѨV+xܢ;e~`˛n~RT*5V5h*窪!)$XY k6ؽ{7ryw:Ezzz!0 ch/BR[ לyTpo<[mֹsqzzzr3;;(v_zz9>92aXuy4M=7<3D7-3'ZB 'de'apa+erG?0W(<Ձ?pC被-H_g Fw2H_*Lo{8=(=2d c{W+$E4|JB0`=ӻJ4C4VZ~4M7 ɠ( CCC>~Xߟ<,ٌn9 "HjF H0mtz]idnfglv8q}>{add5Lܱ2"~e%ZuXv;L)g8p_ ѣK/q .\вFCΊޭzuu}%=#ui)Q I8`HR!8q===LMMh V7qroF,o9鐒 BHXp(B>)[g6e"/ >t܂_V\jvT/K n4&%N '<A:W&-xo*T)"P/ Fi1AbUB\N {ksɺl4>,BN:ղ0N9cL4j;$ ;2j57uk !~Q>.`Ojd2ڶKz4onnR@H$I)3YbtBvZj'199I[[ꫯb&Je[(gb>) BL^r|- D.4Nt.x4L^eaqeR@iH YN8~+!5ggg)TU =C4_|\TV룽/ºm%2#J]U Pl6O~@C/˓:BM+e`(D\no5X]m8+++<3ldd7nXsmmͮafff'̝)5mMw2|?ْ ܤ9VX^x67k)5Ot||h$:וԲqO-hXfZ8䵜44M#d7Lm{N~\tzW֩׭57UUUB0ɶ6{8x ~.lSrգ>7?O8fbthS۰-XkL+x#x ½ĸ[Z ht.USI$2Rme wkCATdm}$X7tDY&fF ,..i*d}162B0\*#R̥K% lA6#hkuemEZ1;-lKJlD" }T$%tmT* @ D2gwm '¤8k<9btb1vZVbA?m9RlBQ̘[ؽnWA<V/eNa9677&h~M7)7"LPH$F.}kC7eE^*W)e>rMӘ'6yY`kקfbwfxy=綾7sJ J Eړm*yHѸyͯuL&Z @{{;7nyN׻!sneP(D0la Sw7p?&Lأoځ@bmHK t+.;bd?@{2rUUô-g\M󳱱A7m5YЭCͧ NRk|>v 0Aj:Ɔ,i@Jg0;4{$CM=@x~h+HV"Aoԩ$:= bU"f!x*nJO*P   Zφh럕n9>3Xm\sl`⪯iX,R,p]sAM VEPLձݢ ?ڀjp0VpSu _&L:2t xwi<}M!!<- << ;ԄG\IϸyL,!e ٷ4pdSE F6t yK3\xY Z>+ذ*f ׃HJFH}(׎ lVHT)(@ܟhR3Gtz}RZ?iST6 OmI;J5Nʥ^QT*UVW6cpjG:J cN2Ex^Ih(]3]5hźg@ LI5htv@` ༠br"vFr~BY ic%cR)_#:i"G/L&m!C0N =3O?ek j !F~#-Y8"x:h&s:ɗﱲ0Ϙ[,)<=o2YM5`Ӎ*%QYNgKNq q9_6Q?쳁y,--v҄i AJxKDk {;*DH  G38'8u6a:V[/,/-JՖv6s,ӆ'm+ɬ$aBQTsanHS91JZ]5Q 8ǃUByG;RL"xnp$_hYP(R".Ng AaquȑLC٬Zb폪UZ6~g,0{4[-Q o4"LꔝI"l)3bGQK~tJIExqqi6rS\F7U1,rץyGoLWwӣlut#1dڸWw}q0X@UW722i3wՒ0cJ]SSJVlÏ>泇%`g+Dr*S!J}_BjRJz& 9A|?Ar {N폌$"iS.W0_cDw۱r=7Fhx\%;#h>(d37KqX݃J[-LI)h4ZTiw6@4[h{ƒV˜U/^:rq8e8:HftC,M0 v'r#!VRL1 p~^`\/t-&)\ضk>% lF GUUF!fG1=^IZ>gwwZ-]9i9gld12,wyDhn,!iZ쁔:Ao-vOh;p!GAo1)` 1q{m{[0Q@DE#Y5'9.(5{ja-Q~^+F^5gUC_i?验 "'q2aY*rhϨuT8{` &/2 yNI., qLt.Ǔ\3miI[ԟTj|RJ)"Bu#^g׍i: z}- ÀZNz͍8`gk[dD58i@ U6 7}~LEI=c'|~2LNlF%OB5! \r1Fa^4r6?i4,Hl6@v]FJFȞ=H Z)9RJ@!J=K,:TM'0dI}:ayi xHz~OTY v>ߋy33 Tl&M^Q1;縷R$diFAI42d(ݱQ[fF=.a{dumd$cܓY1}rɰpRO q%;w^ ` `l@ΎpĐ(+(ͨ~f(6 EHٴ}k4*ob ; %y8Ty/ߣjŘȬj=2DQ&OxkPB}'JD8v ='=L"O)9'v;^N2 Oi 88[59OnN<$ `R \" ߳ acuRO9lAjB*IRkj ItM?#Nj/ۘgBFaRFdÔo|i*P8LW:&@/ C(zVtfF26sщGabn:]R1D!/=oшM"pʻc*C2Et|O|1TGcAL> s?A%hGz <@IiI81ge] ,fENӣ̀y·\}DTVy.;.1(Oxj&g/oD81M8l@eV#ҥ%Z4\$Dp5 qDSxR0Byb܇ U+RSKfy9)@chd;YFkt%X^)O ^cz%x5Cat0ڈ C/: n= CcfR'TZ=a#C"ua`U-Bx#ya:17FmG=Ɛ^X۶ g #tBJR?>p2$@ '"E5^`H2?Id'MaI 8>6[煮"}+z{H ę NJ$xzjZ[j8U-LE}I!^q#Г*L$ Ii]L-r;I{jPNg@I p E  )~O:!ڐemGS}W5WSr|9)Q@6P DR9zȃ->D6BO!hR;A QB|\|-GC.RLWxahq )$rլh?@p6`#M#X}I-q#\RHtz{\R)C@K@c[c;o) 3EhaZal6PfM5*ErRRVt{*yj(ݺF5) N| }g&ČajGNU~:cLZ/HL^!Av+׌]^ZVbR 馭> "k@.ꖍ@K׾g=g3a e(>)WӴ`G yB$$=8u z>HE Q?9$ Vp4"Ri(J Ώۉ1< VvʪD4*} i%.El(L<#wL`HL}mZwrOC&1 x^'{Ӵf\ZZ_Lrp(@IkȚfwH:% Đ!Wu}[7& _8!M=6BsAi PW6%eh4'p<$8YNb8$*ݪ$RAکYIm9 4/$ GDRCi! $cY A4"Ðҩ P@Ƞ' V#? ]'&*@&$CHj]ds:"Ie#Wu3% $ZUt*ֹ_PgS'&8\>!*, #TKj20xuH%?GQ刅Rq$Pg*GSe0i֟xsk!z9е$X"R0_*cAXUK8s4įMiR,QyNS{ 3ϒN"#IZjREHC:q \N;qGȆ{\TԚ}EZQqӡP(}n=_8[ 3 hAhM/@X`yyT0YjW~~QB+P10@ݻw 26\4Ң`vl\U7t{}t^%c&1h8hT*ju6Ucb)^8X;Z \6&NQ.z6F&HRi݊X)Xq}.'6&@6V%b\.sU~grNs J\rUmN"ͲP.t]Y^]ǍY;Bş9hsᨣW5G!fԟY]f< N4-ؑSO?E*lؓ$4E*TċPc zQYb @JxxxD:fue!{pe6:g3]WT,--2s\93ujaØd|Ǐvug5{zVam%Ak5 F!tڽlv9# K B@T,hZraǕzqV#ΰ~NKgd3Oiw8hggbkW)d@HX R9<5\רt*%Ğ2c 6?jL|#5 y@]0רR%hg>'d<9.S("e PwxۯtX(gl]C@m &h)duyFBPt`0]J"ׯ_R=>6wܣX,pimϿHvN88NCݡϱ|~E nGνt=Kʥy~}<|%BAL-/pxT{uAh foPBl6mLK){IxHf _'<(GzQ&<; .]=aУሏ>ovrL^*z gE45u)M 4kbPA:IdGu<׮_gg{hM:Poh4HT*qe8SVw3﷔fz2kk+\ؠժh68TBA = LR]Z98E-O埬GR2*N;8 ᱸUZ7؟i@Ll'r o3Zܐ妖[[[QVyWy뭷{s-:]/a4Nu"&j0:- Hsn笠rmVѡCDqz888=r<x,rIRu]988_D#I~L2{-.-P*;;!i@\6JVL&=Ot;=r' $w] NNC;J\XJsjNW_}y4h4_PKQ{~u&KKKӉc.7I:73.j15HՏp^R1'`oFDxyxp0&KJEQT=cmmZg;Q$uhLBb0 9Հy"T6WʀH nf3~ܸqs3uRͦϧ]0MSt$Ν4)IR\~9ƤÇqtxH `4:8AN:뙔 CRNEHzã 2O=4 q&P nnmQ,/:^IBqlf,}G<7N|5F]4ġ/X[uat]2<0ݻG?0 *LZΧwxӴS40Hg2 CU{GHW|zbybOGOqI$ͲqԕvC^paLTe-ʱqG{t2!A@z?;^*DA4NRj43\VmUI |'<34 I9AS GR|a^ >yġ?1Ĥp.LysO(PnK+D =)Ȥ}!NVnheJMW:uo旿%I}͙w9!' 8ϙz* jZI*Hd2rQEw0XK~r6J5f }F 0 a$ qq%M!u^ioTйG`Drx+3va #eI$G沴-Z^yk}p@?3UrT_|Nsppr̿/𐑞i/a cAOt?7nիlll0q>!${\vU8y#X)Z;>Auu, a6CFXRXV T, "?\LڏgZսuz%x"j7ːNg~\`VG}qriPʐ=Q׮]VGdoo-B̲ZRլ 0%l _&yU࿟`@;3uNH..H}٠R$\ ZJ)%wƍ!h4p8++R. ! ]47 l6磢4шJBRX,8:Y%T,/-+AƉE (ܻw45lLSS7ZdatXصy!#iBa%ZPrpԈ\tc0_qkX35/ ޿?KT..ٸ @^W~GՒIo|11LSי_YU PVW8 }L"Z-nܸA6t~ H8\@@R%N'"&PUIF\6_*,&>n DZpDCDp${o"=\!|:ӖS,`Rq 8(yT*6b^DEyK"6GբV;fܿwVuUC nU._`0 r =zKmb=loovg˨<ju?Lsp8$t]|evN‚-VP\*ܝG6Cc<@ Ћ]Bf8tČAr ik)CqBLa0 $K|+{~FJ]6Ove\wR ÐB>ͭkx£P1 ݽg9Bzň.]3< X__X,mܕ=QVgq,3?p>nM(‰H)7J |7o_oP-b r{:*˜VS(V+ux ?PꎔD%BWCV19:g^E1^V0-<%Li)jtnw-..Op} "jU1", ܺt|~f<,..r rٯV4M-3y%Qa\<d2SUFvN*׮]C;'체eɤ3J?vRҨ *Ǖ,-.r||YIxJY&9:uEaw QߤRoYmX~RF:Կ77r2~00z|`hJ,K3#gʬ.\$/L{`0бE]BK/Q,O~q;Aʐ)c:E6Uɤ4IRԎkHIRuizU`q3nảD2m9UD6 ݎ2/ +KK|՗v{|G,,_ 4Cmme[6\@xxv͋Nj,Non˯~+0t\ISNIl&C6Nss9]!Q]v;o0! zq+W.S"pgT Q-b:FnK= @t#w9-C6KL& ?kjǬ./h47Q!u^^W^f}wޥ!*T*l7oޤRQ9^>SD x?T><L{CߟjrMvvvNY!~V*UrQ0^h 2 €E{,-+/Ψ293TC "N\P15){o"|vwϳXj}gn=:~aDޠr|bLBX(:a(C.V^& }ps ) Op^ߡVf2RJhZѽl'nn~i ڭ>hѓ3>vu&[[[t:677i6M==Oݞ5YoJ"Pz*io0L^{Ng a!l`4b>'Ͱ֝K-=9VկާԓO2NX<|HߟU_p֛&`% nM{yIs1Uoc2*rK+T睝]*:i+zBrDu7o੧b}G m+j"41 "O!,KM$QEtܷlm^gc}C>c>S2 KK diJf6 \*u|~m5鰷nBG~s\.S*a[jw0ovUMv :LpttD>ӎOn%}IBfy饗HR|6]{dݨJe+riEÿ; tSFP*uo6lmmo"dmmF9N"oKˬ*Fr-ͮToXX(Z*Mӥ0? VY[ĥKX\,9 O)F!`d3Y~O^gwo}8;֨3S.) R)zzK1^z +KV;>>fwwW7=UlFw%s=go?雓kjiy[O.%͒N)<?:*4ԅNOϜ f ͲȚ.dlGզ^h{Ns*zr?D8](Oxpς/ 0S|>o#G%l6&O=o&jg}n˃f'cb }n޼&RU::6GGU17qX>Π%tk'#{,..bxGzt;S)J<ҵ^z%ժ? _VQ3D/8 |_&*'!Ju|< ?xh4z m|2[7X\\\*:Rl6רn"-v>+^PKywW漠PgqqѽBؙՃ|rN7n`kk_m{]&6Stɔ'S<7N_Wx+WΝ;,--q--z~| ")J\߼KEYZZ"Xuq(CZͦ ٠0X+)F\ h ^_Wo2 uvyTJLRdirs92,ssYPIU0k:Vz!:ɞקoy/2jAZq'!Çt:Aُ /J}/zc6eqqqy:~!_g'}|X]]aeyB@XT*17Wѥ*1ndx^k"6` ztt7LzM9Tl)ݖ[\s,SO>[1dO]   <@ُg\wa2 G'7ilY$]r ݳS2/& '|? ,..R,) '(GZ6SkWap0`41 zz=zAv[i4W#sdW2 qr rfk<=:׮]\.Gv?Wvy? L/@I˳>KKKءY\.O\joh4X__gqq?tar)BHSSi24R\0DQdv:hWvX\70 -z-wz}%y6OZ% >_<"K(TGۿr⋤R)* ݋Ve 2O>$t#ov İ{wJ+"0}I$1pT*T՟ɟf骥`ÇzT*9AyyW% '*¯<&T*Yqqc!?裏&Ƿm>3$ pI<[R+L<(!7~6Rk6/qa53UѺ/~ {oE\T XRɭspYiL``j=.ǔዀEјyy cLMZ~oSv:*t$O/0q׉PE9~8f:h-u ikF&51G?ce׍@ePK a-!BfEo*1 ("0cTs e~X'| Tf?E4Nr1bp7 \oM< j\p4%/Vq}LQSj:O6%ٔ&.|URu_}1Q~.rt5~ܛם ̣| sg!Y)F5 &?zBW-i!O?ΗnBt". a2>o_2=9->k ;ia{ |ׁCTamQt1<UTWP_-@ yͅ ?Fŧ|HU<_Q 26jFz{Ƞ~_O #◹ Q oPkwiBx}a f8~UR ):W#(T=o֊:ZlS(G7p|C*70|C|a} {9剼-e|o'vPi>Ϩ+++[UU% *2 =B~\nHJٰs޶ [Yuvuu'\6ȑ#m// D3~#_O`*(xSJyTJy^H>pz7۶( !LfYj00 jeYضmXoTUEQt]' 뺿 @Jٶϼ W~,駜>zR>B)c3eYy|x.{[1mr9,l|>OK0DfZZZH&z"cӊu>ϳs+UU RB B#K)rd2VVVr>tSjkkT*E4!DP\.f)Ji2XuBH$hmm&E@!W^yۺyqEM)mmt:M:fee`L?۷og۶ml޼_D"(Ұ!P?mH)} TUdjj \… |KTUVRͱN $t]s!=XxuuWznpmd2d2en8={ؾ};>Cߎك߃ 3Pn%|˗/siN8' !hmm64MȸRP(/ ޷3-oB-!D'3~Xdqq[jd2}<-?N¶m8~8/2ǎ#lxoFww7---Y? !cǎ-[D?{x,"OPq'o9C=Ğ={u1Fo۶?x1Fp, m뿛Ʌ x饗x(tvv6@$)e/R/ }C?S022NR_о' a4#G#sN'L}ea?_PUMOo-oڵk,Jٴi&"zx4'WoƟo->zؿ.EQx8-bnnonn瓟$wiRT(kr,ܼyYYYYy.ۑf:;;M6G,' D"hvG°3Oh4zKA\5e]׹~:u0'A]]]elBww/HH$Ù a=z;/_;.z{{3UU__{oGJ8pa)ۃp'pg?Y|ATU}K44q/_̙3Q2d֭k."/ P-ݮǏ+_ W^;io /݀ߊ4M#gq8SSS(]wEGGRJ8{,e( ===-~;( s=ݻH$p8L,[ Ûoɗ%nܨGQH$6׀K)o:u"~S#{٩(_pg~~:_%qB-eYQ 0>>kٳg7 =#Jx0 QTUҥKuܹ]9uL&zJÜ? uVJ w|c:ĶmېR Foi@|_feeTh ܛowCRp$O !6y ׮]cuh<{K3ȆaFP qQ/277wGy7{!b}}}>}7n⵵5H&E8b{/GP(`6۶mP(pVWWСC\~EQ8tL14M]d6 8q7xrLkk+mKܱGAUU^lRL&.5K);|]w411`۶mɾH)!;??T 9rh4 T*rRe{9կr%me``0;;eYp24z*a& }u]w5 ;v>1M;vpZ[[M~]عs'x\.su=2NTBJ RpQU۷s!n޼{77ܜIEcA2_JITP(ygyꩧ{T*8&'') QVY]])B__SSSTU* mmmJSSp!r9VWWy +++tuum6 Üٶ,z|3QUU/abb/~Ç jp4M ap)199!㷷s= 8wڵA,--a_899i+YW) iryyv(,--@P/,,099$~"]$GP(D2$ɰo>N>}}}(,vbvv)HXEKK_HR4oi {n]F.mx<Lsw~h ؿl~~))#e;m:;;JկrԩqRǏ1m6ssMVVVX[[#N]w?NZ\e& kttbVlj'صkP ʡCH$ ݫGmsMΟ?Cj-RH$8tmd&+++DQ.Rn,#mmm) ݻa|KJQ p*G'bPL^Q "۶m#k.V0MND!JޱsWUlj9{,kkkf9y$477f蠣GR(طo\.sYGOEQvEss3/^gY?@ BN({Eϋ/Ғ[˲xnW)冂ukdYl|'RV봿WD[[-eΝ;G[[d47 زe ϟGayٯ8]>JN"X$x֭튢!UgT!/ "9t5aݝ}\1Tp%^6 CSSoNP`j9rQRN0+.\Pw ===+}B088 BO(^Y^^s#}(c7|ȑ#|{kR) /*qƏЦ>~5Jlt WkNKD6.X(*h YD5JML1,Ppth%ݼ^uݡCPU.4M'|K&N቉&x/@7!G###گ( ڗ_~s5\4߅<3u*eqs ߏ /Uڲ9 L0*BըslxѸ.j BB!BzP8L8EԠr6/e8vqQi``}cB!ecPL䩧o]/I)?zڵ=CO4x'u}Ws5 Ӫo>t]_Ƕm8 ׯ_۾}; r9?P#B>غEl[8 *FrTUj U;Lx0᭣h-(ͨ$Z2]](`+b k-rX+W#a+B8イ]9YgY1MR|Ӭ׿:E* xzϾ r: OASSS] 0,\xM{{;A!k׮w6lٲ^:sT᳟,hA{lO?- TxwC8fttD"ŋ)u]qfffrUU3}}*0MbHTƶjL!Dvn'48pP[[:/%vF?;_zsҥ_=NeH$J,C 3^%Cc* F b1>Z+¡'|;wII~O+X`˖-R +עÚ|s#H4tb~gn޵k7oƲ,]FXdϞ=|^ƷX?_]5p ZP.)TC$#:JN{uŃImXeT ۲PTU+(\bhĿOaWUjZ=!HRu3r|_ueU۶qx~l0<>7ޞ>>FZu|~rRnM$>zmHlW+>+k蚎EL,Ɩ6PhEPT:몊li"- iZHtX, U( %ՊqrtFh՛3q}]([}pMW9_L(i6D#>_&L"WVaz{{v7/"4j]ב#GصkW{J%60yww7;vlCw]"`~ F)Uz}(jT 7{ "-Uˎ15RuӴ0-i"MIfXEӱ*r\6Hr6M)QEE(E}QCa'S]bdtUAW5tMŪer% TS>uߏX4VuW(r4&ܚ(Z*Νsq{7t'V;mیqt^-EU>!l.RR*1 U냛=4F7Ñ0pgXB8F TF5,P;PPc1x(0J$_Ae*UUB+W̮Q_NE-[q5**K( PaR \vF 7:6ku1KcHnp8?̛oJA&6oߐ8hy'}H(;hSSSmѻV044ٶ !B|ퟘ oD˾6y󟘘7ѧz߳yBP5 bJmk#wV8#+U슁.%M0*DF)O 4[(NMSxh_͔% HX4eJJlBH4a-gL ӲT_]MrZNIJ,N:UWZT0 xH[[="b1,J74vƽennGjM~=NĴd2wȃ>aax4={ܐ^,d2. j4vaaee;Q\˲Fhl Ԅ4(DbJPHn 5&w2ȇ;V_xYk .4%rz-0Vjh.U9r?|,IGyaܹKDۉ S̬R-I$h@(,[к`u jP5( h$'8iWٶjtL[l!q~~ Sy?T*  Bnmm~#͕Jj >16mԀUU sN .]"o> /ƿK4 KieY~MӋ2>Ι3gXXX>}T*( T Q `Y B!]JٝqKIH)wl6qhzi ?u#]׹xW~:ʕ+i"ѣìOU ½l.P{7#DUTۄS"e(:Ke D8Lpo/j?ۤw,xX^BÇZknĉu뺜f*-[PTXv3[ʠH@lwRud@kKb؎{ B?PT}`{?90swY Vgh[zv-aPExkjPp7Idh]Ćw%hj%Thӫ4I6i&:Ml0C6i;vD0UC㫃Lԯ{:Amb=Ն9dm\Q1dժ+PHt^pf4_L&( p!dJoimm/8Umv*BJY,J/ o=8Tyy!k-p6UUYXXgeӦM:t\.ǹsjE, I P9M(^ULbeH?}e>omju3 }P\A2"nC'fWR8skv AHU) p"iİmMt/l#^XpjuelۃA/FIBtQH' \53hM+0[P`qnǺti }-q3^s\]ٶm,..b?wm@BPLJ%8v;RԽRnP(922RWYtinn%<1>>nF;+Y׮Dtv/psz:^uFg^Ah#{jSթd\<7#-;MbIzLoYTl0-JӶV J++d]eY%BwEjUݩu tDޟF\BaUǶiM(2V===M7oŋڋvonn0D9]`mN[HHND"7r,4{^rp:!8x \vmC_pX{kk5e婚PuS/#O zGn%]1`vy?#S`/p/$MMTUu%q4e;6w SJ,vmeTY>j4G|o lյeTQ!wQam4Z2J4*-7jot%'ʕ-cjoQ]^^.BᄎI6mJ;BFۼRZ8S ;h4@\AT[ QT*i,..呯 ccc,,,4dz>|(Qk" lFxUA ;ZB>65oE@ڃPڔ$tvWX>}r:&\BP-1,amY.M00JebZmU-")xJ__%l3x.5dtQNey56w㵝:iZ5ƗAI +MƳؼy3D6o*c;qb/Mܮ\CПT*[D#@ssRZA Ruuua۶U}m4*痢EQx.p:'=Lڂ;4/~ j"N7MrZJx6W^qa=(t0ݍ Ab1^IjQ9C~b$;e{mťtM+y$>8> .Eq|BKKbC[:vZk3ZK5IEq+4xFt[W3anܸaKE]]]`Y.uEQ> E\p`=g4<=?฾yZZZ1:я +hB6?ثF@F y3o3yQ#q&Кc#L8I;Pzl=$7nj2*O01GrVhTMCon&EltΏ>RNYx8K?|Ӊ|ұteiKWYg]JnߢA!.8h XIP!6KJCu տ eSv6k2·En^H$֭[ٴi<󷜘puu0PUn4&=a\TGZSS<™[^!oݴ*pbLsܹ*/]}^9.YA05=P4P婸Q\GRj9 .Wj9; 6zؾd+VO!ѷ];J&}!77}⽽d.^dy U5EO& 37۸OQBњћP VVY9^$7yO}G>L Lm.Q\[T{{_8W~B\#m{r Յ%%jiƆ2@BǼqz.h/Rv||\t* hjFal_#P:M|)A ~^Vf(ߜcn&h{w;t,jPFQu4cUg0:$Rp EӨf8]ǹ\!%B B[_b-::ޯx}"ksם@ NB=Mκ0r IDAThkE.xɒ-N%) ț' i%ɺ0_xt]C ;{CT{{;M>#8DX]]L.'7 j)Z !BaSYjS5ዟx@Kap=>~B!AV_Z^!u^h<B~rSg0KEHUװM #*-20DeTjJ$PB!Aiv?H(\@,aAyzÿZz+uvVg_Ih6$%xgIlpl!@*Z(0!5MT A4wMwI)%FcH 2 #&R@)Uu ?< W=@IaCE/v7o[KNaVZt{0*Dk$q)ǟ䤫bd$H">zN wya$1c 6Q!J7[o@ DK7Q`חz@bUFFk7B( ~XBEܵ G+?*35zhc?)# qYUEâ d5Q  ]6"( X5}i4:pZflWfZ|yޅrQ Rg"dNfsFi@iڀÁN5}9a`6^ֈԱw#tQ.; Ҭmp(X߻Ĺ3"EMx\gI?a4 ԏ/JoKH( Ja+0’2\|+o&YmL~ ?b7ŨQCZj+T*@ub5g5BFe>ɛs?3E  yk-T%@iKWoN|EJ(b2 c+BW"pIT±lZHoYWs%(/s}"&EO+] ђ(ݤ K#GP*R LcnqQM&BCpZBƎ8{IirzoqBn+8@mAAAFT J;#iYx+'Lݯ  (!=.}Ծ){Ii NnxAɛ>`l!}?nMƧ"%V>N.(NJ?QKV  e|0#x~66ΔJJD4%bJ^<0Бkb BSZ\#55Dn#O< 0y=@ TN[bM"$;7s54~]ŹyDg坩AB8l%$*e^M< E֒^6a`A)&"rE8 J1 7%V;?3ኧ;x(#Qz ^E@)1sLUA ߻q75(i'|1Bq KȻ 9kNqY &5Db^T97ǢNI?oV}qGmNҏ=ا* 9#X%6]$Pػr wsޭ.Ixb;j@A@w hqBTTP -[X5̂i|͛N0"OɨC'0.iM>ġ|AFv@2 ը@,:8tBf/p_ldb F8|"dT:&U**SX!T*7@7<P8~‹{ɿE)Ikw 6#uF-C\ }ny}J/qJMcsTkE1ՠxPz; "=zxIG.&;DBŬDK$8yf88:7FPQC^&tw$zR@J97neϳ[#;ehR}2N_!4vCW)Jze%42 {Z0:-0M{tp~yhD ı,v@Q Q#m >KI7gg'ebY FXҬu;,p")x"f_Fv@0[~T.$YYe[ߡ* ,$kKev]ZqW' r|DJ!ByXxWpfB`Ԅnbb#QAi v/DY"H$B"8N$ = @= RwHDG9J5Nen#e^JwuUA =dNuuA)h] ABWDDx52, ciB`RPLC~ '=.0} Gd!%Rɳ䴼\^LH)Zِs٦F҈Y20Uz= ?2C AgiL'P2 _JڷnLP hKͿ2 ^DQ^FS]-bǴL,4 pd?QU)zNB '*(_BQpN1}(oU_Dh A`h"^!ԟ:OQ=7S)FB@Aռ?ٵ]kO=7u #@ "v0t8I$!!gteORs}};m xE㒐Rj&~N!&tHɠ)C`4x,eLzۺb>Ji! @Hߠw W}ɿY:ėX;;(wgB@EK"=$݆ ~D>ѳ%דp2oU `s/Hܝ„-@`ʲ枇Q?T;mxܦPth߸Aqf<6 BR#a0۱E:\{nLOпqg_uz-G^"f{A~#"ǮVP(1aْ`Nɿ@{DRW !p <"1%# Yy>ɋdݡwdTĹ;Ms0J:fA Ī(T&&&)T١(t#fO7ůbLӻr[;2|p4$Q7hPd0SQ_ ;ؖ9Tm8I{6]!}kEc,侐pK0HJ ,T +4r ;Xfdsb.=Y,ޕ&=%]hZ ۴PB࡯5VF8ǏҿysKJfomwe-m9^i7qTI:O0YSiP0M$w\ՑB/ki#X?[ٯ9pg@x DLCW6b|4L+Ӭ5וݭ-<}x<87onc J>HK@g$ ӊ)Ȍ 40Mf2 ]lc,88#1IXWa0lG9!x~(& ;T>p;:Qz i"=Lİùq!RS"ac tW6Sʭ[['ŬZf5Ci!H!&%TBw;@FaƆ!]%lӤh89Kzȇ%T4yT\w ^ Q?!P`maFѺ"B @# .9&~⠗ 9&h5šSn%f2Yݾ~bM %͔`!%@Yˣ5&JO#yiXA0RS85,Kη +|:@rg4#߰('t Db_}FȗJ^\04Vo /(X3Hvϴ/ĦNF@Y A*NOPC8ΌoƧ@`:ϲt9M$2*@( N1h"2G2҃^)MpC6;YoGχ^CI04R"|𢤴YJn8{!Dptp>)\̳0 lQ{Qo}Hקh`QF!, N ita).1NA27LiB~<#*/GW#D7)i㷶7 D ¿h8;{u FmP~" w5ԋs>s[XSa T S*L!Y(T FJ@ ᑷy(zVyv0G' *M0TB!e|7)ۊa)*e A`V _ɪ@crH0( iK;o)y8 % qGw4F){{ clb؆`YO>iLI#\:%~B+}ߟ$TH3Ōj (# iUH0A-;}jDR$[u۷1{BT('ӈ1x\w `R6Ҹ}gwf# l&a+/x/o[ᮭC ]Y2t 0MJXƥ4 оz)\pэTܪFW*- /e)l 1D)5/"w4d'HI;8vx}[$ky^lDq:,4IBOv>{L \CcYmG7-S\ad"tG:\)DX(}@qD %#GimqF@V,ĻRF="X42wlZT)>$0nGHgY]&2`8|w}haw,QJGE+MYi"76=FPF2^ľAV|ĐFzEZ)JGҾu[gR@W o(FS;a:Fwnmg1k"ywVV$'&"eCr$XoA( }@y eƜ? (#SKa>c8 J`!lVJ> /%`I`)z{+d$Qmo-;# "q p=Gs / È])ax97:,?my􂀞v]UJ]0nxoS>~!D,*4 jPTfZ%:ژW*l՟hq*c4>{SsMKWPD*-`Ug5eHJjr#~TR ]U_UtG5N$6U$~ g7L6B:G50vv) sbڥc5(V>Z5Hdh_T#"c2vPTuNV$ǵKEjc؏!7zX4[{A%JHK@DŽkɍ</\w7 fR ]wf*32+x': 8+lzO{kI`(bSC/P$ "5H*ݪAk$` 6#ƫ*tnBe! Tͨ1 z CeXX\q7xۛj, 0V A\ _L X kD$p 9 is]M~mnnbjؕ 8P.C@_Yĕ[ 4^LN(s˚8snO"!*/wm"Dhc/ޟq$xwX<#8Wۮpmk;`FðJ!<;ɸ >#Q.߄Js={vR 6SU}E1Ge$U4!D/rF=|-Z ʰjkkOK9b$c9zͦ)Y0M`9cTXm?*xvHC)D'1c K ~PuQ}<ˢAxOV z=._ع/%40 IDATq"}HGQgJ1yx;{"PDJ꬞#dl9z@  \ z# q j|<|+qX,ÀZK{>JSNc|NeRέ[(CP[<Ą?b*,W$A#E(9B;/e" A 4D@R;Bf6K$O2'wz80& 1]GW? g\Xh5'q),>jycbJBC8$ Fal(; $Pz-JID;0UA()+{! N̐BJ;S!'f_r{F!7q/ o|x11nโ0p]5FA%)&mGc+Tkx \?A@oޢ|02N޴f%wY _B) X3,_pd`{7F\jxÛ=I'O"'0(l ; ݅ғc^EdQQ%f+l "-jNf_e4ِHhR.SSpvߣDPBpaiY.‹lW{oEQFp VYNƯһ~|Ӷ=ۨfh ܤv}_F 4ddW1(۶,lK'<-8q_Au_^ G\YAT`MapiaYBIjumY ;p {`x=7h;K֞ Jץ|}Ñ?i]_~?lQ<0*e%JrIou {~x(:ϷJEN>IxtUAFL:oUB˓iIeo Z.7C$L:M9 r؏64vv'])%b 5ҏ1K[mͭı4kBOD|Gsiډ{<}^|ۜꗘ~+V;{y`5L—1*>8fL\@,VgK"d/yޢ"aѨcSuͼ})NQ2=:5|H:v AWWJ8]Iw2ۧ8[( $ Nj R%CwH!xE)MbOl7q[f$+x{*\Ώޢ{missݽP;33L|%ǏWϱ NSt~ޕ X4_rӟx0NW ?jłZ"ϧ \)Y)nZ___L߼_9/e&''Z jϯwɉI0Mދ2L!| S7oӻ8y"fdf E@/~k^dT<^j<?Fe_~ +L9'z({%=V¬U1% 'k4_z;څY*gfy-=3FF( ~⓸Ͼ wOA*P"}j:Ҩdm`ng2译֟5 ޼vf0{ށv0sw102CC*S ,ۦ?9hpWWo bD0kaXT? 6T> =+JJk+P#3{%A!$8kr}'g15r 57}R 0'j^1)E؅BZd 瘚?NQQ;%:^iTUJL*H4*(DQkkkFȈBObP0??OTbyy s_NSdbrBKP'ܹBX"H_Ao)NCR!Iib(Ӽ"~VC6B),I|i܏%z^t`oQެT(8N佔<0MzW 0gLR)TDg~qca>cjȭqN; aql0}v-*=3Rw}~ȯڵkZ-s_ |bq?fbHmq`qq9W^P=fdtc۲긳OCR 8#6FpoGX5_,7s 2aY҆f3'ci4[t?އWs g-BP~qc|8'{rlm1+edaC+1n'UsN)InzJbIU'fY#8v!kzktmy~t,Z&ߟ+~ʼn^XŹsRbY<ajS)rpY8 \.tFP*xg8y$ǎcaa8y$KKK1Ǒ޴>= ^%y:mZNsm:@ _R2T y\:1lh#')K@R8Fi,ӿ|cnKE=R`ȷD?+++4MVVVX\\DJIӡj [[[6pMr2?Nf8 m@)pΛo}Z{{{o6m{}ףR`BJb"e)۲^(u8k8ևS<=[${mH s׏8A)$Z$>uMoΑ9z~@ѶM19Qgr{MI*)JnKզcoGk9֐Cevv{'@pj7n܈<92^do>S=П[,_V:h4b:Nps^rlNN۶qEKlRTomS'=D)GM4H5YͳHBDIx< ^B.ݤZ-iw9Y:b# *?9)7 Yt?~)nݺ61%kk a:>SÜw7;:# -uB~бdcR!/nˡHpp=R%^#Žu;P+κFaFg tm23FХ* s޳cwZ͖nj̑#ylo )c7gΜҥKDHlnnr1zP鱵M+Rs2ޞA;;;ARRh6ho,--vk.yw;58=Hm|I.?RX Ppf CEGT$ |CJDFujO=IqWVP>O2¨tÿi L|iʏÍ7FJm)96VT.Q|K߲b顧.ri|"1EܕUS=&Ja죞{p4=|+!uK]mzn[(Lda~je6_ԸVJOXG鰽mۜ9sB,ms!z\~u#AGGA;|vXn˩Su>@bhۢ)Hϧ\`)E0C^'$ۯAa^_Fsr7>+bĐg ;`r ۢ'ů";- {Ljm8!?@X;;{4ZܰhU3y.lnnb&hFvݫW<'#= Xxxd ,# >L 8yVǼi1?D"loﰶ`ofPVT+[:A@?4g ”1 pAӪ>5t()hڧStyݿ!EPUtzZW IfgAgsc xbTp-#5 BsX[[cgggrePc㔇C>+*w"#砌q8mTLgGew2)%* ^/alis]nGTd^ûq^ža23|}+Y=`H $@A!^D2}2:זUQ(wt{T8{055^G0UBb&v#Gl6Y\\p &|"buuv}\^7{R>*O{뺱=p '}~PpܻDz>rq4Mza"P(hLN--m|_eyd3y RiƁn!(Ɨ?U ^e}k MIxx )]2]m/s%^+e4oZc\**-geu-{ðOTURlz'S{MoCGoѻ4+Fq&s ǎR9S$=2_VbͿrCIDATf=|bR.sd~B}}NᤶlaK}[n177W{R}\z=666\G{W8|qt1 쬎2*gϞ… vj5|3'?=?Cqq‰O9v#7i&CJMR\.tuno"33kU)vi6[ߛtx3tqFgBAPò,.]7'!ʕ+t]677;BaN~vsm z3gꫯ".\@){{ʹ]1"?>HjL2пN:Vspd5JjB\athw:tz.FS')Zx)yJ! m#]օ⮮ѻ|އru ecST)KAݡ2Tژֈ?D mf&=Ul}'%?#x|ᇴme"7k#昜dqq{uܹs\p!eOBϺ>`i֪GtX[[j$u˲T+T+e i> ϗ¶QԳXcS((8c;2jua{$jٙYJ<Ӊ͂Nڬiy_9}4/^nuvիWi6hr@OXcJ0pO?͛oi?^z~ϧ?i^xB\!R;Jž/=&jM3~!\黣TV9CPX,P(pl-cmH.>^OmFGmfbbIJblkߦm5î t'N0;;.۹6RJnܸ{{{wi{{$|7~u{{ln}^tL^B0J<'Odqq_=n[Uu=䓼[zyy9{{{?hm࿹ǁoB;U=JT4 Μ9{y^2KB1Ou=>Jf8BKdn^?uo ĺ(JJ%2r g |٥qłg 50==ӧun|W^y%5~jjӧO[GY^^j ]sr(0"{bTbrrP!zٳgt:?~^xv N_N;62?3Y7iI&U4E6*YV+BbO GVB qq8q ꢠviMfI؍f<7;v#}e{SJ6\5-^+Y\0,̡hUJl`۶+̋Y:i=wiTU+ KMd.?ll6K&!ΐXQmtgPI@uF|{rqUUz*۬-wUAx-{Q \za(9dffD"a077w (}iq4b :#ab(т"P}RJoVz-.Dr٬8x\6665sNI -x+ LLLiZn2a\g B LnVǍ#W3T!Nww( hxx~zI<gzzYgqqNS!$'`9F+b&BM.xPY>CD&t[ǶP Y:mNK!fh(QeM(@MZUI)š*B/ߋݻeYܾ}۱lll6771 m/_䈔 Yz(q"Aܼywy~p+WbyytPCK Գg2>>$IVVVq H$Bu>|h?4X8uOR^}%r0 -Iׁܨ6`~mҁS۶b}}D"A `hhJvf#P-nbLOO3??UU?wd2RA)HZ{+Ju~BI&TW b>dqqk׮9)2s*whp{W.Mp$HnʷD@6/-mfggUr[[[~͜ <_Ь@3/bXUxUEzD"N ڎz{{$8iXFGG NP7 ]Y(bR7֞B bxx1"O2tNwwvve3 O>Y԰N%q̹b{p8L<m%eii 0t{{{>N6u0w/wR8Ia?EjaGZI! ׍P(D$y}+(qW&m025ב!k[#h5PX=oizZ_,B Z = zҊ Ad~$n:pLܜpwq\\.WU5>j)ZY@*MBF0X&pмYmt՚ObwKNy󸴺HԀ,$[g~9iNݭBppc!\W#|Z@2 JMQD:w4ibYi:y)rC`l 6\FS9 Ge9O~#?3_SQ%vp{ۦ~8m@B+"ROg9?A4[Nc~6ؾقvS۴&|Ald!"M@.p5iֳYR?FmӦ*7к{'%EO줼iCx+F /osmOIE)"j2/#*|8ȭm?bc=Ȇ}Exfm,;No's!҆",WmڴiӦM6mTgS IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/192/simplescreenrecorder.png000066400000000000000000001124661470053624300270300ustar00rootroot00000000000000PNG  IHDRRlsBIT|d pHYs))"ߌtEXtSoftwarewww.inkscape.org< IDATxi{ʺh&EQEjDy$DwwuUWם?U (tddUvVUVsc>c>:҃>B]BR*#1!B6q2`uԊ8+JX,6{KЗ%-4R0"lOJM7Ǚmo7[A 'O9s.b7b#]oRm!) J~tCXn@{6qRBm4 L4Mm8`64 )%P0B.Txq4M{駟+I'?yB_(R3m۔J%JJZFZZhD4hb18d]׃n W:#)ϟy}_cX'N450p=wbHPP(P*V?|4H$HRL&4F"8cRʿ/J?m~kĉ}M)u!lfxb\.G>X,f#2 ht:P(D[[rZ@PZB. 6۶o3$ 4!@ڻr&ĉJ)X)Y@ߨaeeM!ݻٶm[ DRnڄH)pT4oF&''bbbK.qy&''Q(5M#Nd | R B"V>^]]!_x\8\er nGr!C* Fyo DRŋy7yyי !Htuuс[9Jd_=3Oh8rH0?ZRu5}2;wqew:ּof~pyۊoF a~~W_}{_|\.wRAoo/[9? !_|q]>J)bo)HC+f,,,P*|}__'O{СCB[18gX3o%m|nYϟg'd|||F{DRJQJW^ye}>2%;w g2o_^^fvv47'Or}o߾ 3L?mcYVoo?lܚ?C4t]c̿ձ+WOO0;;>hFww7}}}~Wh{gf#ǎ{H;h0;;&l/n}SLoYzZij1334sss,--uF$ JM?CCC 088H, 0 0H]oJ(˛>7\O^B髯E?`{'R*%?Fs4g>~~~Duij@c !f\rWrUZ`‡A===ܹ;vcz{{D"D"HL^xbg!顿oj篼IGPJ} wrӛ0_W4J),ڵk\x3g0??!|wGd]v~8@$0w |;-:twwǚ`7x~_~;RMxZ255)sIt].qzNRqlŋ>}7|s>~'uH$gtt4LMM![n kkk[ض>B AݐnvnV"B0X,+' N[׮&($R_>}"k#';`3YXX`aaq/CaumہS 0>>/[oef L&O?ii.\h9߾}B!N> p&ˡ{avvj(ΝcqэBڵjM_ݻǏ{nRA4}Gg6|=3 aSN{ iÇ#B`\ʕ++!w}7Rf rb /}yoСC ,b`pp7|k׮⵵5H&T*8a&wq/B rqؽ{7rϳ<ǏիH)9~8\zXL㰸믿kFV#N8N ??ɓh˗VVrQJ)[nىl#-wN~G) !߅Zo9'O$%!zbHZ%O} .PTnx-=z9lۦp8"[922˗1Mx<(au-l {ի,{r%4x3Mk׮a0?U3FbHVcaa?#SSS[j{?~Y^^& 333X0333-mkkc ŖVb1BoKKKm߾=xfڶm <4]vqEd2(R0??[o'x۶9vX{QW^yi:::D"jAg qN8AWW/^ XZ%0 a۶;0}$R'>Bo;w8rH`/jr9x ~8z+ B<˗liVcǎ9sGr%h^7|.u>H&q* jCQ, ڵEB===,,,066F^g~~1btww/I$LMMQTX^^n N8A,X,n)+++`rrN à^>FA;d~~>mR}BaWWWvii!~)@ؿTJg>L&VWJQV)˔J%x }Qfgg~g6^3<<̹s(`JA&'')߿FjKHJ SSS4 :y4mmmaR,Y]]t:r|>OOOwfddp8o88pSN +++ɮ]XZZP(׳sN\BGG}}},--~[5yYYYTKf9v\p!,j&IAB|cii >"җi abb'N4˲(˘ӧ䖌ߎa={6 B8J),+hGDuv4dYVK|˲Mf[|Z2"LNN233E"2A2$qa|ADJ`nn)XYYmۃ/~ :}}}Xu]`xxr"@s; x~~CDH#Gb *V1>>ޢ]˟ɟݽ}_י{OaCK<'ꫯޯٽ{7?GGG&ϳJ5񫤥rt{QTPJN9|0:0 1M3P*Ǐ'H}8LOOsܹb5M"DǏFeF_c;::1~=,?Oht1e* w&qW,"ܹL&T],u&)h0>>N<x<[o;wP(ofIR ^\.sa&''Z[oE6ehh(2JxB!H(utteYΞ=KGGd˲رsV؇F~+Լ/f2|(-k׮N)SBC@Phlޓ$_ضmۖ%iu+/u/}g\.sfggYXX@<sڵRDOoan{TwDGX:A<@ BiSJBeBqH)"˓T7S؟D".\]vq5= gΜam}cJ9f7ReNkNM(x:BP`tt4eSQpee{zz׾FGGǦ*iR,yǶ,P+$so6CCC ؽ{7˜;wBe._|0oW~v:BX/RDaH !54 MR@Rضm;XqmmXn(tTy\c *Q7syY]][om ed2)bllB<sN/8[sqMoz]4 ۶뮻vZGo-/:v5%Rq4hwGJ>Gnuu}+h.:R֝_D!Rm)SiRgHw аmc+gAl\kRnP8?ŵzͽ. ~Y3 a#,}Q|5TRYW\>0عs"KX/mhvo?L(4>}'7iM8|0PW^yq8z(|WٳEbqi-B(?0$h&4 t +eְy~E*jiw导>عsqBP,[4 ?0px4q(#M'6 A8{ԩS,,,DۂNfJ&<_W^ "TͶgff޺m۶ǀeh\F7j{nl={_ȑ#,//_wƯLycdU]HJ!r>HC'ݕA Gh86I!hhjrt.J@aH4$JhK"C:j*+k%ٰ +`M BN,="N:A2Ƕry- StvtbY6,/tuvLOϑ]{ t4MX(cPdC///:p??Z6 oi_3ٳ]vq`Zݻ2߭@'"jhKV%j#%TF,B2QRtӲ[^Fhݽ=$ړ VP^S)%,,gY\QL$HwzB|p^V@ .lqWBZ =r)+IPfvvJFXAbe 43C$k ˯;Yp}SOP~;OD^gddM}۶m㮻T8hyGa6_ޘo޷~۷osBT*O}Sϛk'&&ZR[Q6 t󟘘`{! UrEO&0-jNVXC"`pxd|jRJ?xمE&ghK144H,grfo_+..Q(Vl%@tJnRL שFxq{{ ^hxXan!˥+LLh8lbxhjƹ/Vvgwu<4CCضZX^D,G`RǁA)Wd0Th>uy?޽{y饗ŧ +W `|_BDSh _zN&DB@0ߧݻwsa` Y i@l6hn4l2b>4C !\ xZqDHhaD)CCDQ&ghXC3sT5F ?Gv%W21jLv<Wjs=WrtÝ1r:_ MC`k2S .`"ӑfn!<=]=]3;@wg.**VF)հ4H8BôL:a#@C9$5Le4ymӧOuLd-av?`w_j"\糲%`O>1Mdu]СCRM5J\.G8iV FlYM#c+h8w42]zzXɭ/6H(frvQ1J"$+iKx /t ]p X>c޽>4MNo[['ٷor .F9|0r9kzB!߿ˤJf*#FBmmtvuGZc)chxXѨ"u79&Jl屔cWY7[:@!ǔѰȮRL'ضC_?bݽtJ kRCVæ^3778"B(pHLX 2 cp!=n{fy&#eplo|?A&p}K5=w_*UY[+1ׇrte:iX652LP ¶׆<8'w`rk/f޽߿4f]LDwMf02p+ƫ $R޳;BN8iD똦믿}˅B!۱czl67nnO̮0)\@3s(!FF4%_%ӕ!d_ް).Ok B!, o q&_.[_\ޓo !-v%~W0tҦtuu111CH,--1==M6I#W=QR`6ZG7"Ah#P*O&P&],򔫵@+)I%H1ZëLx::庽nUޔl$d6}?"|l` BIzJFwg'bR$ "#.CHr߱l$:R[@WkUqs_iϞ=ܸRV#ֶ),xpr.O) ެoPxQ3gl9GSyӗs=G,ѣ,--yp8mKtR" p3()эHRB̙3[.Z&d0 #X.h#eڸ?\,[i9. k%F$L^LjD6c֊^4a:2hPkLkh}1xA1~P %z(Df{-,&'I7á eAn`#fFd!Pߪ:xVuh8J\%+P)܎2WxQJ G+k ;,@xwc& 3667,h4B}__h4\Z9Jb7Ӳ%>i铿 pzkC:_NK"­촔CrN^*>pzD 8޹xR˽0D::;&gҁO$4/,2qh||pMӯz"l4C"*=uh<_V9Z$09nPDyB!p9[5Pl6tuu!W_}fdD3RJsI=T*߹ihoooB|  !(JVk&5sεh08rkkkE&''Bpw na3*^}lX ëih8;MC4Xb)r }FukQ! dz 낳 K5'pv- C{9M%MF41yov|~}wdСƩAFj(P Ѓ$eD,DG9r>}:`aRDooo0gx!iW۶ؙH$ST6@7-x>Xsj߾}_|eqQz{{Y]]eyy UY #jUO -3=v_Un 2mp4J4`$֞j,"9/?$*[Cv4ړ:;c&IF8c8) gGmqڇ 芒q,I4oknU>7:Ь#Cc h8FfI=(RDZ<($5JJU]?]݋+r9 sqbJ,N󓪎_m Rj;N'4]ͦ_OYoi"O<?~bٳg7K)?j#vlz{{eW!L"1 l@s5b<#LtcBx{Ua<($c$!NG %kbѶ8Whi!l hï؀c!X Df1mzSYr:S¶brD[ kwY c+pZVRi^Dc$2- vs iḡPݻw3::Ғďy4E hYI^7'nMYL&sR_\.sΖ.#_YY4vϞ=R@‹"hF<4h:ʭQҭWB#t}QxM/` u P,'і`DyS!ֱ@A;nHTP9+$%drNűQ!g6,/̩{ViGXp4j =]bh/ &KE"C֬U&KkUlRD! Aò&6vYi60MjRQb*VU3iTM9ΐNÉͺ3j@h4MfQ)kKUjui{^C6GԺup>xѠuAl<[ aqc===l۶D"0۶mرc-8X,A~q` 0j]-@*W)헨6'+zzzp'(sZjguu#Gގ{.qiH(]vi!qOul&}k8ٺߔdBBH7̧ Ԝ*fR"5RhB  CRDL"N0:Bw!Fޠl6X.V[5Y(0 0mhpR*s8Wk5_J_ӯw@X3BD0!i ʝ\- w`[x*@i0D$hmZXXe\vmˑKKKRMyamR~@/EsFѠ)@1n멧=Gao=hXz@BMh&qcm!VT49ɞ0D"!tg)j8DD!WP3=E,s gW ,P x(Lg$H,.šF\y.0P W0<aYSdRw9x%D]l=6+҈QtpsӐmr@ Vc7~˷g_CCJ%JyדL 8N >8yA%$H$1ƓwtJ^J(Q$yϾ|Uy>]_R/=Q/T*opu677?3&l4F#\H󘚚JF#;Ŀ+*r Aog4bG !}vLǏeͽ1Dߴw: J8|\&H%@n3 LR)DB0U)pPX[Z\.q( r IDAT-z=$R s"%QHT3I%Z^G4Z~#OZ D*Gffcfrím Bhuz}^jBXFX:N?;o/2{x**OO$krcR0hW( t>) ~}zrEuʮ)N!"EUGP|Oqq"Ie{/{iq689imk@ %O>g<OZx?9&?Lz ̲2@r (A',~OcgA,,,a&HX,& ֕Rz4M,sB^.)J6#uWYgvsNc5$QaTT.Roɔ-` JfV++%d)r )J^(/ i6یqtB?Iq{WnX#Wsa@'R(;_3<僇]O~!3**L$^"Nng@TZd5"}D]2Hݣ̠qy_mcc0dA&V^+  w&\ ag#-J43Wr9t- ը܎ \?KBA{7 ONt FBi:ܾ^hnHbi(>0{O.#!npӖHh0@C [!}_8C?/lfSl4ͷ:O* 5 =3& BzB<3HP).!d6*`;7PfVh4ۓL*VJ1 055qC/=)e'%KC'fw:"_ޟI1U C,:NfRT,plb Q1v {}#)q"_`8nф ݂h8kwpw??V.wmxM>?^yk#Qfeeɗ!Zǧ G()mGdv͔R+jI:-8v"ЮjP3y,xǻ AXa:1k躅Y5qN_} Vq\'}LOiuIkzxOxtj u:):'53!G'' GCM g ^Vu #R&YDkw&L0 ` Ԉ|"۫ʨX*Ed *Sǵ=U͌ЊP%\gϟ8 azF% u9Kfٸq WI. DÃ3BvKc[+)Nߧb&$Mfe^J[@F*XGV[*6HJifA3F@ _ 奀5=2#JU=@\Psyp]A2rhq頸]/EI6%RB)+3:`}ӧLOMQ.0qk Wtzq4A /`^Ma|Cn |E)G)OT\@0G)? y.h#PbJsSf;MC/B3&gMoc"X.3JGJ)\lH?m8K'(8FO6J*ʌ~`USqr٬^cIG/ 9$LQ$jTe W=hern<)g:?PV@p44^1O7{69^1rSaF:rX [raH+jO%V~DPJ}`.3%/ Woȯm6kղF#G[P̔t,N̥"Ai?Dd8 N%~FM@G޸.v7El&U4uDjX6މnq:>O)R.ޣFG4(jx{D`:nX!-3!fDjMy5htkSf@B/ BDq21o]gx}ǯ:[1c~brq]u桚bGŒ91FRrpṗ[YoʓNȫV$?8XM1#@, w[Oy횗k i4 W>*P{YRK[74#D;#V8t_,9bI!Wq 7x_2dpf/0R6ҧtNnQ)tSoQyEG q~LOO%qݘn"Rw۬&+OE!e#lAn'1='3a"˷nHp~<&RD.po0#k?)*OvΜ"Z5ꓪD9)`ntͭmN(|H"^t&ྲ,R/[WeiPI\C =`r:Dp:aiB1 OdJ x==cv`yFJigKU`ѾJJ7%L?iU0HN22uE*n%R7gzC@Al<&ހVB!A&uR~J`">{g/^AM)c=[tb/F~<D'&H5H10yV7Ho£m `M=eyo#0 7̱->̀ #*pb\$F);QFi{`aq>ޚa))ҏ6bo5IE@a bOК=%u-1 `Rjþ !6`#H6#^\ n3XPd6<-o1Tá^:VQVh: lns#3 k:o̵{Gof:X" VBwG~|y$|BJ0sQ&4ˉWRUrjbvs8}[فی1iJ y `SWQe x蛐L6XdY8Qql@﷈bbT.hw  iu:|cN'e)4Rɳ^ HNY7d1!(B\fGus/ٺu{!{|< Y3vm Q)`q&w\&[0//ˏ_L8#Dt'?@kj.dN!1u}(%-\*pjFE AnJGtAVkW8m)6_Hs\nHsB M"I.P#h Sɵ*gI˃Rq8MjtTS8aETU=6̄8*gls-J=IU9qS&s}]'{y꘱2=Bڠ ZtM2~Y(K,N<1KEmu+@FȑD t9Fq!F%BZct:Vykrx).ZnOJnH#OBMkA<'{uxw@{[[d4-Nno%U?8Ibۃad\5by'7qMl `Ľ,TkҁmQѸ"N20RL@rk|>o¼r4dPJqMW/MᗔAZ`$}(2w:($A0T~$i {: "|ϥX(P,xd71Pqg,fzCPVt:="7nɓMђ[ms0B"|# ې;QńlR d"RY߶`HIP  WLj/.D9L gr$t5A0+)SE? 1iDtxIW($077)A (ހvCJ0E8fQ?scI+|Og< v:ǿK^% quCl=qIx90*9::s^5LPZ£0@.#yhM< R)lL:;i?5 (>hiwi 11LllM8~Bj2Mb5(vz($B #]"$V'KsȆ\dĐmeAlO-rARQu|_g1p-^ Hn)J8BAsuPNu~0 *Й-}UH(kwK.B Qt]=(2F\;q˅aH$ߓW>c@y^"X{8bo[n+`~삳 W8᥌`qЯ|% p<3^N3I=xѫ&&4*"@P( y(wzWĦ5H.#q l UͶ X}_ qF~esS&ίb4ô!N7\"H& ̙y EQ&e`V{;!Z(A|ϒ> $ P$// @y\=lf"[fWS[aP8Jv *6(Kϐ@A\XFB;fTTFb98J] 8:C̠lj!3P0a0<'I)Wq:PtmN"쇾 @nj8Vi3lڟUeS-耒26C/Ԉa$:7GN~X]>u , &+䇃EJj"JE@n?&._˜\'^BIKW+t&] @J|lַ^LF 2I05,))tFQ*EdC x"pD`pNWH #BX] "A~8c|s*:AiI&T*jA IDBWo}tvsyIv޹6 @|K;BqRK30Q^n(Ð G 1RTd40VR*7f!#钁B;H9҄=+hp:-p5un'䙈82q5~cׄ1Cl U(A3Z 8p!!(0F#{tU,W/ E!PcV\&շ+fA<D8*b:$ws!곩f|MqFQ}Mz + } 9.*"Q (k H'uG/ZER"IǧS*aGs8dR7 v8t qR5 *ghB1[.w{t Ʉ};䧚jVJei}؉3E9*%o~.aD)Ƀ@(^,/rR:f&X,YeEY"{Fvee8e+{! @ΜR}zN!ߍ9N48C|$g̰fc~@lB)hzzz)oxbW l#2Myi*]!^$!흫_ F\!?O& ec`A_^d$bNj]gA|sGE@#`+ A7 a!i`  =UOl} ~QU*4Dd"0_(ZLg}=&9Jp^ @^z شO&愲`r .cXǓг"ِÓY!!F~/zш?hxqހZ17)y̙TIH2H-k4:T(eqBBE}3l +bOS^*<{i/ wTJ*9 D#ϳ `q@e}2)AŃZJJpA CPG#*G,]F 2;U す mOwVʜ4Z8("Θ&2Hn-(hs<\ b! „oJq']6Y'2wzլp+ ~>Lɛ _ l"`}2z[ d(1+QIpo-ϫ?gq{uƷ}ϥ11wg"e$f~oYef ^rBMrFqAd)^ ӕ ӕ {G'h:N?i%ln*W~I'~JhUQ)x9/`0H. c$UCAP񒫌j~ 4ݰ>xE #J$w}꾇`jZ5 1 3O٪eWK%֯hs bAo./1קer~ِ{\wԪe<3\|/h3K—\K> {f1;2-A䷉GoCf7 ;0D!.2R|,S臌3ϼ+)kKVx |*neB9bR2_t틜@\Ɛ>v濉U\A0Zǚ yWĿ_ "#rY}cǧ#zs?9Yz~(%mmNVu|H fv:?x3S= HAOn{+2!Q!*Y)jeɈ0$R+??_6]ZLPK5WtEB'v rD _<6wJ?"Ƕաx8 h*E廭l|`Vw!' zqDbb!%Egqf~d Hc&33F1/  :/jU/Bݮ^^NO.{gC˶p^P*'pd c,edF9ȟ;KPs,"C FvY_ZdaI%7V{Pb?xwnO1Zז?8vJﺬl?gv?~lobKWh$R|q.%@#3G ױ⋜ t Oݢ<[Z| ۣS99>V"q>w^[qCGP |lK ]g|#*?x-كo9D*._/h:n^'` 84;n9ܺ:Ov/rv"LG#u0;]G(?6EB!&\e}6~hEwR2|.|~O  CZrL0蒆-"s@9(iǹHQwC[9 2HwnGM$H=ʥ"޹i]/CT{wfol1kU^X v~f.VXZX ڭɈAL( lz';6.O>a_Efyi<%IBxV><6s3U32\DKh=H'1ZzJ7*bs\%6~$H*" w_lslSEv{CVe6ϑJĻ07?NqNO87P.U(x2 T4{_|o^gm[/)dתxoI"&$B^4IW"˨wQ8`~nzcSjgs`/ ."?B.zE'bH2%ʥ~wBAɔY|8FzU'U x}}rowb7{>ז?Qb%"|JjJEFAD7Ga~n;)Kl>OYOSp5lb`uOY}a@e10,{.Q ʟ$j$?ltt.+Y?^D]4K=ŨA҅e2[[[%"?c +ԧj8[[eRY]Q.Tˆo ydq~x?7!uE;{<KRoA =8fÓSon%LXAPb2 \[^B8.C>OH:!_VI#$?VHÉ2v3z?6H?}N;<$/c7ן?@ p]pme{zNW,qj;b/z1jd77X~/贛q]ZKboװ$+&V9X^{D#x̗i7~m\ C&\EŤ.=kCSz$l`/~tfg$NfBj{y 3* +++|O^g0k muGC2rc-P>qo.E0n#bbʪn^׮7oECvgzrF ,;X .&k|Vͷǜ4{] Nƫ2yY\E@E;j7(ʌ&j'5bq0LFSB !9CT ÐZ-2BEܼy/^055EVc}}gϞqttD^/(Z$HWEAsZ!dDw0%G`{?ߒ: "%9lx?dys3;maAW%z{> fׇu` a¹H1oHJ##|' ">l62|z^c?.WJy?iO(G ~a6."@ A^T-t;Lj. (@@UF̔,"0Hefv{2o{e5huzDR^>O?t<}u&F K5ݽK^;Nɖ\@DFO(ƆmC,d:~MUGn|n!FC\G>{ )T*P((( tG!ǽiD)^=M*qf)) [v _ZJ "WU*uȿ07<2ҪTM^=ńnSVP(j2x0 ?_eU #TRR%>>&enܸaʡ(f)65E^DQ#~H" ZZ+x5n-}5~n1e">NK_ˆNo@QfNwhdM4*Ej*褮g"IF-( T+Ua)K{u.Ffπk>F6{.|Fi". sIm*;EBᥖQt:3T"GD.ZbLZىH2- ׋Gql*{n GXHG{5vqS8VL\,dDڪ V896rLP2] ty_dwPUO(G#udΝ;8Cӡ18==R-!f+%^~QQTt(nbmmU ֭[<>eyaNGHŹy} #<59>>\v-ߺuLj3NMO&\te XLe#LgL3!q5Zn^ Fn1IdԧM{ AHJs4ZMHo'dL,  aq~sMo*gĿ%b` U(JcʣnQ[ѪX_hphq}yFNt;o8.F;8D\>(z92o&qo z& t$B돀s/K 8*+Pic 077믿^%󅈠۠T)Sי73^ i[,,h۬]G Eo0Ȫ=Xȟ7!FzT:B)7T 4$ܖF:@JF%2_k58<8f4 ^+x9>.txP'E3㰸<| f7n0;;&6򟜜pttDݾT\/Oa+ݢaf`qq7n &&bD}LuNVcf~QeVCe~ibċ]촑 gb!dȎuRWV1țʐ o0 d4!<az s<ܦV1?;nU",,4:=3Kۡ?D! { u.;]gqiV]Ϋ<.nϔ ^W)2on#=dU!6@ gϩ>Ⱥ|㵍uVy927;`o0)'7@ y)n8d)EC~󘚚JFso߾^ΨEn7Ijeo'''$cW*zqEA>~*,Bm=ztVcxD-SSդ&\} 9i4X\Z`aiͭmw߼{X-hFA)VRiR9F0@R$52]67e[E~ pcoqã^qk&Rیݴ_ǭ7QJϳ+_SP_#xTwޡqzzܽ{b"ŋ1 <{h4$/r 4.{p8q윳;= O,,-RVYJբ`Bqhyk1==[oKT'9HSHd  X y'GɌ/e(*5{Q1;3í챹6#ʘg?)ss(OCq`9Dw'~m>GGG.f*qH)y)P]m rAtoKl.;;;'Хw(&.,PVYNTd`_+%h0pH\Ν9m#*.zFUD2I2Ւ!>L}`<\!y5'yڭ >|>A_>s]nv_c^'?_d8P`W8E>@1z-z~?ٶi4ͫz}E+笙 b#\B355 vw6RYYvcl1jtouv×_?\ﱸ@I:nAJ\SH?#,aƸ `uywt%|շ؆VO~Fp|td5IDATa|SF7|uvnؠ뱾NIq8ONNߧ^57W(!ӗh/ B>#zɝ)oy()t ,./Qs%(Art|̋=x;t]> 7OzbK2HcV Y.T '.`aaw~㏨+ׯq5`kwOJ%Ya0Axdiſs߸U9>iwbyiԪu<ѣM@+tX]Y'4R:4N|״]j=;-A~S^(xaii)`Ԟhw}`0.Cq~ Ɂ,..8ΕVV 1\ YXX~~!z ^_~HM-γmnnd/ۧ2Uc 7nH G'4mA Hrbw4 ,/0?; 3S3[XY^MǣGO;F%nNL@ |#C1Ϟn L<8e>b{gy|7Ix f=Ǐ9>>+ \σ@or,,,\:wO?euu >cR,--jǧńP)nT]u7yP@9~oxlS@j%%õkƍ5밵֋w > u%gt*Dx(.,28m?cp:k׮olOnI] ">7n#Tmk^@pZqe%{'Ov999GOzyE.:*ҮQRܕ333lllnjF#>1Bx0 V !ތYs b?'<}T@$*PX[_c}}kkkU'''hwtz]{gF_U(IնV/p;(i =@҇ddNA.9&A9& r! !@ $ #ْeYRkDH;rX%V5>X{lfCijBu.O O y}xU/L +|n}A„ ;C++lm{6O,OB)nVy9ŪUӝFmb?WVV̈,O G= ]Tc\.W9ښ?SEw嫯"133Ã_K4t|FGwwޞwp]AI<sDⱲ&Rٜ1\2K{Ok *6ˉTH E4DnNyEFch%ԌeSia|t7o(Hlmn377O8Ŗأy{{6wntttGsiqD-$pzI8O5D%tۍ+RV_FuLղi@Yd|=7J;zk1vPtEbϞ1L|*+eS['ؠF W+JF6ok122IMtvbeyأi\alf,Kl6|>DQ4o8d 9߭^x/sTpz%$ K,wyEuܝ;wH$q.+$]5n142ƭI5Kmnr mQ4S5J1(Gv+\Evw1220}>$YA+VTr%/aI T,!qM/y&mQu]fggE?SWS OPC YB@ rgbbŒMNNrʬG[թ3<6ح |mm$ R 7noN8LI*vW+裏~}m4]B_KYZ\fa%y{أIdw^+' dy&=:~8?g}}T*uD+(P[^rzC JSSSix{{{3<<_]cn0Km=304Do݆H BI׈'vb⻢aol.G&d䱕Mˍكŧ>Y.4 Sk[,*;;Xe'=A)hjtb^-2o6h`0x9P~𯭭NkA`&?7\ :mBÁ|N`޽{S(w><"w)6+衷7ꢭT dI;. SIfȔJ 4JRIq])4P PJcp%EÖ'&&̺\5xa{"Ip VFVc "B^hf+p8$lPUi EN'+H*>ZthEU~ׇrtgUa/G.W W%FDD1+)oSRNv.M>͐[Y+I1/AABH:RN>#TҕBo*333~94M?ѣGf#  Pʿ١P(FYU"&'P E@X$I: ~)N'oMMMD"g ge _xCPfY]]>`gg^|l6VVVL!W^Q,hF("xjN4Xh^3 "GQ3U[댎r vvv֖Jxоzr9'Jk]2;ݰO Xtp8ln*aeeE{=sRd*6rXʁ3sW&N|>o#YH$o67-u]'F6%!?Np\`5 ?qHcU $IvDø\.里ɤanE lv`=hnn6/ў|>ύ7bppnn,;r7FP`kk4y`(-.eݕB#Nt:QU(0KKK n߾M"`ccf577nlllehhN$>EQ O>$199Q)DQ4M#HXeoF!5љ  |@ !RJF We(=?008ZflinGQ|fppop\LMM7߼ZjQ9$(LT֣u!S(̄3*/ x/W Ǚvimm5zƒ$Gprܽ{/^1y{# zQo*s!I:Fu2 d,ѝJ=1YqDxEg(x<7HZ;NdY&͞{$ L|>o ~*/ԉEª$\Τ&LY'9/.0" |TInA!N# BqAMY\G,GX.M@PTSωE1h뺹r^EJ%sP֯c-="z:*!3cr͆< Q$q@ffg=:Qup Tj(8N'vbdrX+Cpt+<-hFĘ Ԅn7q}jQ+b컕??4DʟG_ |_b7.I4bhq70p6G\h$U&;_4U79Mmzc+._/r 3DR t"/qpuN1^h)zn'#FAd&WNG?# 8 n)1 LIP{QBd^F* q*;l P(zV5G[Tb,'3R55D䪁hhjA/IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/22/000077500000000000000000000000001470053624300220015ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/22/simplescreenrecorder-error.png000066400000000000000000000030671470053624300300630ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsϐtEXtSoftwarewww.inkscape.org<IDAT8MoT3g_hj,P MTK"4P/U*UmD}Hԧj` vSn)>xi;ȁX9txVEc Z#҆*0|a) 6qǽ{l$2 a(&9p+Ҕ3Q}St:EJAȅ½ӊ(R$Iˢ(UJ$ #Tu).a @)Y04,\2i԰qh(Z(E{"~`A@R:.X#%~*oFrLQcJ8s$9!B D)8`OH<ϧ\.)Byx 5CYtQ8HưÓOK)Ð\4p%2A{H<b:]dś{l;Iyvfs({e#y4!%(,T*\*WPTXô%jNCQxO,SK%)V)9.do1e9`U=Y*:V. yQ5F6SrGt)%1(d Rwx7/_}u[I2BJP( c ZK$Ԃ`ut}Rz|@:Jq&IC0<2@p]8f2-%y!Zaجd*M^yebү'}eL7ܑy$I80DJIh@l-ެzw*?-?79yz4.J___Wqc!s8&"YtPN3&=4ShzN؍x;G>0 L f.[ӧ20:IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/22/simplescreenrecorder-idle.png000066400000000000000000000025511470053624300276440ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsϐtEXtSoftwarewww.inkscape.org<IDAT8}=\Wޛ{fkvcˊbR/)M% $4  E4DM F&6כu߽#::G H[X]]vx񢷵ŝ;w(0Ơ<Μ9*.]2ͻwcǎt}}k׮Fy/\YB ɓLS.\h4֭[??xrntč7W;|D==@wV;;4 ,_>u|ĉzegϞݻGÕyI,z>uA*|-=Ľvn˃LE`s1שּׂ2 psD+@7x;a&?Dn+đC+Scwq}!Ǐ^o-M_)@*EYL)K+4-Z= Yr*$=vww B +A"Ib&n?ݐmE2|xY-2'3zb"q!rYF7qHB[,1&IB$ho nH!\$_d"T'4-H`bt}v 4ЪM,RFtV+Mh)% 1=ky9cG21I#!R1AÐ4M6(`-H9f,/y7N(!AS e}G;.<` .Z$w|$B$L@,LS%pZi֪9a l6D{L@fB5  GtEZk'|\uJn(XTNLSzN-|Hz=?gY~QoO-7̊)q6Ag3Su}BK.sm M`|} (xK'+ 9yv$H[DxsMoo?Bj nE%mĖNgk-ܿk/ORj5z?E ,* k}#O1vzW42VEo\?yUZRJ1H)}SNQոzv?{/^ ~orrgOfii 57q,?q:wǏ山gffo,#=ksZ]'vjhY[[{^#pٳ{p"GK yQ18 O?}F,&MFc>h8K.1a0A/ ^2(rjV}Ň9? ~"I%IeYƙ3g;:“,\1fB1Qg^1[ 0D8l6QJzMJsEѷ([^'NFDQC# r3w{j\tRŃGueVETJBk(82A)Iq(pȇw^'eEC~D~Fk<Ͽ#>Z>CAy8VexG֚r9֭[EFXCTb||?A"T֧\.)Zk|W vqj5fVb+wn}|)hlՉ7o tE $AJoM A1Ŗ(kT#,//b9ࡻVre <␱*I)"*r"\uX_uz۷oA}8?T*>Xđ#88<},˘̧ͤ3Lc%F0 7]t#ꦻn(T*"T\6 EJLEI;d2ӹ&&s?zqaZ_q8?u'Ilg(3ƼYf򻉉\rgⳁz0 s%lCE)1籺J^˦x͟>WϞ=ZkjH.̲E( e uh[p8ŋz=_󃃃[b{{A6(?;7mʭ^=l<^fwwVڧ॥ZeΝ;ߺuVr^0y9L!C@ Kn`GGn'vĖMY^^f</}z8n r+I3/-.!U#p<"N1@07W:#aX\\4M\.\.q`SO$bgz}l@aƘexS'8N`I$IRJ0zQ$)k~S&^@+퍿rM|#KԨ#dQ@CT*^Zw$I k5 F}}ΤqNhL@(6d筇yCyYk! l~<1NvpZ*NL l!0$"EqZq)9 cH #(-@x>Q$8<<04gb13S \#r,2ޕvRp9!M:E&cT w)nȓEJS),̔3Scx,|\45E"rMA$8[X]]vx񢷵ŝ;w(0Ơ<Μ9*.]2ͻwcǎt}}k׮Fy/\YB ɓLS.\h4֭[??xrntč7W;|D==@wV;;4 ,_>u|ĉzegϞݻGÕyI,z>uA*|-=Ľvn˃LE`s1שּׂ2 psD+@7x;a&?Dn+đC+Scwq}!Ǐ^o-M_)@*EYL)K+4-Z= Yr*$=vww B +A"Ib&n?ݐmE2|xY-2'3zb"q!rYF7qHB[,1&IB$ho nH!\$_d"T'4-H`bt}v 4ЪM,RFtV+Mh)% 1=ky9cG21I#!R1AÐ4M6(`-H9f,/y7N(!AS e}G;.<` .Z$w|$B$L@,LS%pZi֪9a l6D{L@fB5  GtEZk'|\uJn(XTNLSzN-|Hz=?gY~QoO-7̊)q6Ag3Su}BK.sm M`|} (xK'+ 9yv$H[DxsMoo?Bj nE%mĖNgk-ܿk/ORj5n4^p+r/V*e ? /]{?L$JRbΝ qU)JWof/%X,z7c*Q`ZL+So+>g̷6 2l"t:5vNs{QLͺX\~ٜVX7yԾ}?//s]I{77_^2.]718hRزAף7o >/j .ϟgii )%^;wU~RV|ƍ{\]]yVҥK>ggg B>VU6/_6~Z>z(yٳgˣΝ{oaaMqL Z( (5<ի+{wobii飍n$I8s~ٵ,QYlK~oB=>d,iۜ>}+WE  O.-|g<c~ hx֚ۄh$ȈE# bE[dC\ex1QVgumK!O#͗IUDh% Ә(I( )%Qaezo4xc1P](ɰ`q~ʜCbS6/pmCT“ 5kz=n޼CeYfR8XoM>/).֞Jш۷oEѧ 6 lۦV) eI <~(z _8rjj qPJ[qZ3 L&q p8Q?qdJgѩIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/24/simplescreenrecorder-paused.png000066400000000000000000000030231470053624300302050ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org<IDATHooƪr\ *[t VLKQY}G)D" ^DD#B6v8b\vU)/ j% Y{y{1|YYo(zw]Abŋ .099zΓ'OxaBr~~b7o=ܤ!<*j5nݺee^x`aa!txkܿ,cٳ4 뒦).]u]ܹ篭||iif-0+7; SU4 ޽Kj5Ν;۷_fY@eV?p8dkk ׯ_g~~ ~~Ge؎ ZN^GV+mnnuLPT&ֿF4c'p*18QTelz6rHr˗B4A@nEDQę3gc.ߘCKAZ_3;[#?26ڲy3'?i68X(󼫶m6~Zy K)#yrxNOɐ0h78=qNC<':9VAHԐh%HӔ~H._@)MV&N"X\q& 4m !q,K>Џ˲XYY!C0 (4SXERdEyt]APz,$IA. /pR&Nb$3c =^KOcI۸2Q-?;rsr !AAg$tߵXN4*?&őu >Aފ eSIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/24/simplescreenrecorder-recording.png000066400000000000000000000030511470053624300307010ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org<IDATHoG?3}wupa$[BDqᆨđ=q/@zhoP ?$!Q4]'f6zw_Q)$Hsy>3eZcRZkP(Ypk׮;/cRJs1==vݻwyf0 sƍ_Rv%kwwl6!Ii4\|lmm\k虀bSSS\~5J)4㳵 ,!ygOO,-->W^}X*^wwWVVthZԈW|+r|qie?f罻i͝MYB̙3\re]kVu`ONN`nnqj(#ސ.aly --nn ~ƋØD99R CX<E[Oz}ʶȝB5TS?yYc,4JPmҽ>Iw@Yp/S4Sq<8( M$I\;ywS ^(W* Gd建o`kkYR? |5I$(D(xYB6Yg p+]E-XV3VZ#~Zt:<[ð& ^GE|1Vȉc181g9v4'`m;DNB%G-6XI->^ DIdYq9C II4[Gl)>"z33V!z4l 4Ŷm,[mWKdLPGGPUJ]}h~oKf}X)+%R؁o cU_xmnݺmk zNPu]g9ߪ֘Ƅ`zl 2RP(sq?'!BVq* I8XdffNy8󟲀7o+2bQp]7+ m<m! ?S_8'H8|fIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/24/simplescreenrecorder.png000066400000000000000000000030241470053624300267270ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYs11(RtEXtSoftwarewww.inkscape.org<IDATHo\IUm_ +a  Cg<Ha3@HD"+V( !Nwq{Xx8)}:>]718hRزAף7o >/j .ϟgii )%^;wU~RV|ƍ{\]]yVҥK>ggg B>VU6/_6~Z>z(yٳgˣΝ{oaaMqL Z( (5<ի+{wobii飍n$I8s~ٵ,QYlK~oB=>d,iۜ>}+WE  O.-|g<c~ hx֚ۄh$ȈE# bE[dC\ex1QVgumK!O#͗IUDh% Ә(I( )%Qaezo4xc1P](ɰ`q~ʜCbS6/pmCT“ 5kz=n޼CeYfR8XoM>/).֞Jш۷oEѧ 6 lۦV) eI <~(z _8rjj qPJ[qZ3 L&q p8Q?qdJgѩIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/256/000077500000000000000000000000001470053624300220725ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/256/simplescreenrecorder-error.png000066400000000000000000001542451470053624300301610ustar00rootroot00000000000000PNG  IHDR\rfsBIT|d pHYs7]7]F]tEXtSoftwarewww.inkscape.org< IDATxYy.[U]w A &2--Kd˦,Ywĝ4˝<ܙ7-Ye )%n  hڷ\l|F ЇFeĥ~H7?U$pIV J",G}Lc yӌie0F!wĽЖ\\~#HI' f~.#o~rX\gvB&BЃoE&QWa]6 s=wϟh ,P ~.O;$Я(Rp߃13[ǑbsUUI_KG `!\ > N0>d,ÇF_QW&ӑ B\wUU}-'H8,PG8fo:R~#H_ !+=.R4MMan=_E0 (E$A$wǴ[1vr̲bء7x:}D"^4}Ěxo (es;ݏRZjO$ <@p_k"@»!oDJ^J ~1Q*:jj^4h]a撜,.B!aH$J飒$c B;:$'4BR !ivsoe)Ug<>Q$auSm_!?I$ /4k_v0P.QTPTP,Q* K% "!H  -cw!`ώ9rӻmm;" jOo6@I$h C1TLH`Ϟ=aY_,뛔W2=c J|BBz<%J)8RRR,,%R>ysLtE"CὶR^L&R6@VU{T򑀮KJ^!(E5afF6mH'ذa6n܈!lܸk֬, n6JBmӸr ^n޼hE$ttt{^2p_[;/D"^Q}H$ִ+Jwå〟߾^P(o>u3>KXE"bwߍib |B$@v<2 2kV/DBz)l߾۷o3`K Ou296?WUmrWHRT0ߴ Uw@ï/JxrܜJ/O? Q 0 {=\࿽/N*Pd,}]۸x*I M@cWQ?hk ~N_nB'>- }RCL}I,݇#˲333w޽ rL&iSJ144/򗿌P(Xݲ,jjV4 JrR R=gWu BQ @QD"X,> ˲\޽{x7_rN¥ }}}sAy޽46Ah ?mb{υ X?=|% j>0MS#O>1sqH B$w= %P(JNB=$166Ju짭 _W/^3{>yGUU 0ji9Roz6_}w[t; 8!5ѹEܿNk_^~eG iN1MeYp})IJe˰l2,_k֬A2lp/EA((ظ^uly(e :c>e#_PO4?dO|40XԴτ>IEC,L󂟋K`  B10EsT*Ezoaxx ,<gi6!jw͛7qmeiсkbXnz{{xL'˲ ;7Q, Eg|1jYj23áP3e~6$p$P?V$P$I"ުSS*{zZA` tۄ躎Id2&;ꫯb |N7>>?ح{f-q_7oSO=H$se݉Zj??7-KBetww#NO Z?SHR܁'> X<~.. ܁>8VN:!MyeӦMxWSO-ʿWU^8w._ȵ$I_#%f199W(Xr%n݊m۶!J pH$Ќ|M?g%b5G8?~JX,%;w-܁O1##bPJ@5hݲb?NNz )~>\ 皞18{쒀>H L"|>qn$I4MtwwCu=zgƍ(rrʼ)JOOvڅ;w"$I`^`X,_/~)"x B(sWSŊ!7Lȑ,,) ,%e˖& #>^#$[[,?ĉuąqT<%LW^K/0 TU__,qY=ztA /1 Bn:\t n5/xgk5MrJ[›vߏp83gΠZ۶mÇd؁;wbffƝ0W~.ױgl޼-! әs<&''qA:u 1D+]1 V:z4'=|y \$~ $ ̿˂,[@XEײPh()I ;,\2]~xT&&0@):Qj5Qoo>`EMiBMP,Q.a&Lŋ_H=֬Yx''УJћLGع{:b$u j5XUUqI?ƩS.oE*Š+|3ۋ{N<ؘ=5ߗ/_N%޴i^l۶ ׮]kYشvZ30MCCCr `ժU0 Ν"|e0)wرcs{躎jw,BT>b͛`mckYs5ML;"DUd1B+WJpSؼ|^l Ȕ(YJeNY<β[էMHLBĢ(逻(:3cйw/,^3N~gnI F.ƪXP( Jzj 4\ǎӧ]>! 5k`ݺuıc\~hh=T*^[nE*B[[}]sn>ׄi&m&Hi*)ގ<.k׮ŝ;wuVW&6 _rW\A__ۇ-[qY PT\ÍGn6E "s8l .f&fYՍȘ1SMN* @+PD۝g2x60-tXPtTGyY7޽{ J%IlذvB*it: 0Ӄ^cppeu(X~=޽i(X1 ڵkM>)ۓ$n޼ xgFގ7իnKnǝ;wk.*R W\  4dP|SfqjIs4 1;f:zIXw<3(*eU(ujifxr)`xWB"5DX,VoԈiȽޒeJ˂ " ]*ԙDwRH4-kע:5Q@L[е|9B(LDZEZuMطz R/VXl6sι:v'lقWF[[IΝ, 7nĉ'rAB!w7btt%>1Zm۶!ϻ>|$aݺu}%`ǎ$qIB!lڴ N(JPՐ0<<7n޶mVʕ+8<,B2t0*<4{I)>US ~LBdҘxkUL4C˗Kީ=$"~-JwH jb-6Xђz{a9z>ؼORbi&\xovS<`Yz{{Qq%l޼ {^1 6l:s޽jXnV\! app!͢\.X,b֭ؼy3֯_+W((ƚ5kPDuVDQ ϓS!B9EYb `|gܡCK~دS\]AT!j&۷ۋk7o4(A;f̸~,ݍz 7ov-H$t:6|g:|+˗/G>wCUUd2zƍz*._WR-;u^+@^|9qI֬YZ j;vkC:ڵkq}n4M_;v 7oބ Ll76RVquLܿի.dsm4#ᰛf(x3? IND$. f'J!200 |QQCBi F<4?o"DqIHȻN2͆Ȓt[dYUAf6XMεX7q]h:A"hkkÁى7nٳ>0j5<3Xz5Rpܤ~:L8n Id4 Ao"LrݻцVgR̸0 \Br֭inƍA͛7A={0::yܾ}۽x]@&i7I|zNAd@PBvne/ BR=VL`r /_.Q$|ԔMεBү%&<ᙙB *P!%$ } _¡CUm 4FH(/& yz8F2rV-EƁTq^w *sf$O>p}pB$TUiN:|>S^>|?11M0==tB~5gT3 `ȝ:IRE(⮧ T X2cU<[W'00ؽ@`P"Dp-آH`!@{wHϖ i(/pA1B!r4?7/B)›ڽ^$!u}}(NLXҔ @$AWW&''vZݻH ˗rxPժ;UU7w.) R*Fyflڴ Xj.^vall ǎC\ʕjXj:;;[oÇزe p+~&v^4;}Lq B ӄiY-NJo!!P(yqz IDATU1ujʵ˗&$0Y^4}h {LQ (c?(KmmHP UU]?Sp2 0#XLo4 Νs& |_ǵk|n,׿w}mmmXr%BΟ?op8Pv_o$uE)ooŋ!VBww7N81'><;D).@d\+AϘT[@$-X%eYcB_ 7L%>z4ڰPߛda7nc:,E4Z4V8tg2Ċhwr"Tjv3!OU:9 Qn\3σ7ݻnP:F&ӧ4iHr{cǽZ*/֭[T*\.4ƱfXr-޸q# 33M=7~ibT'& I;}}iʝAA)Y42%jXI3lh|TKl n`(BQ)5BEJ{ۛEl;lD/BCioGǞ=foe *LJ/} o[i'@]QT|8 OApɆYc ]O幟ynժUx_@M0sP[]]C$ eַsƹ{OgydCx|>֭ .Z!b1YR w100Cڝױڮ]_5MZ.<{4~ᇏMQF{vu}H6ͅ_X?q߄xKpu4%F޽C!?xd%@DV4._ ]ۋaʕ+Lޞ x@:A*b_xbu>c`vp|v-0X^2sO:2 ݻ˗//pU u[cݍ彽(\B)-:b`"Ze=xl8pcǚ4A^VV ~8<7w7 똳CXkRHA6u47_VCdM݁CCٳg133)7;wʕ+DT^@] I`.lRpp ,;52{dg 932L&7oҥK Hޒe~tt it{({s.72bómi 0VM[փ@i!ل~vXiիW;=Q˲|~vinX={#Gjp~i~>N,*k&*ʼsB^z(a:3n0QMC4=!6l~9h/LӄineiYX)Q K`/" "AMߦi\.cddNZ ĚjcR @p_۹D6ogk 2zDffY> X:;6 bM('׼)/^Ѹ~Y}ƞnJҁfBDpJJGǢI@ Mի1{==v^,u0BU5MY;ӻ'p J؇d/ )P''W;z;^X>TAiczN+db`R$I qV1JR4V@Ȳ IbH޸,mnܸXU$FA)cNB"DҷP'yX.އ6Ϗ'3!j&hv$ww9{=X$+dl  aݷ7n$Jzuη'8ԪUkuw^8і׻aK{ PE !! @%6}^C<ظq#r<^ .8|;v33M|#εeDMd$P4)2G7xͲ1R7#zwLƚeiP5-VJBQ\{^5flхJwؿ?&?:z4v^V(vQo(gΜthh'O\,+*Ia҇> (S0{tޠ2'̕e(D"z|@b;pOPwH`Uؾ};Nc .!5v ϯ֮Egg'6RmDl{cx'x' Sj(JN}@##wov$~-!9uhĶؘГ, ZݎU&''qYԊ% 5zfNZ2{Q$L(Jcm5& ^M5?ja(Zu0/_<[z@ |g~_3 , tYnBk5!?N}׮(X'ˍ@DznLFop.H`ƍCGG/jt>/> ViQ7='uGbIܸqnnGGPIP/qQ \#`08Ahh,"jeO ;w#p6i $0܂Ǐ9H@'D!dy\pD"WNٿ[>9UyMӄ$IPeεt;w6;趨(X쳈0 ;u<:DZyf/[Ύ[ {8=)@~Oyɥs t#}X `ZL.2w!`ּgmAI\v#n݁a 0R ix_۹7fȞ:5p8 WޚX EQ0~k֮EG{;xD8 >fs(z ]{!C7LNE+{ٟz")gܕ08^q2fTS?Na\MO~/EkJZm܄tD d)WyW$Irkj!-t={B *'q,@Tйe Ĉ}h6 tm81gT$KDYϩs%\[Ɯ&DRBZa80X'T1(@'piAt7Xj*EQ\CTݻRA~.a w Q] pl61H`m)Հ GH .o_cLqPWilSiޢ9/'mL J)(v$nuqlAk,6n݊4 :OVc1(M$٩JY(4lMF  ! A%ȲY|AuSZ8o ,B]U ^GZEZ N,^|T,ZN%T*t`,PA@q: =vbxen=¹sв95EF*|V}%xA ֠L<3ʘh{CD~,G %d7\E+o.(vynm4mAmR\Tʗ&>‡P46R]]q!v /If:T]0- $HXU@U_4lۼYZ!Y"ːdp'Ũi:JNWb$IBH! VSQVA]uz0 C9ʼn^k޻37)+fp$.#0 (-!ɝ=o!nj5MLF>S7{VMYz!"2¡P, b2c B "(!x  ,o{2(bϪ4DL}R XCTFT@Pr+B[*P8hĮ3 ŒAPʫKuU"Ep!%dkHmmH}rb d3v2G:݆T x RSSS;RY G(2É^ٽ-˂iwZ3o3 ,FQ;,u1X`Tj(BIޱÞoK)ʴ@W xDP #T64`yYԜE$"9c-nBeY ~A..I='O6`J<]FֆĢq 9%eщD0(,CD|""Hϼ0̒NsxׅJ DܹBzԩI,(aV՚nG-G8҆H"Q6u]weplv[b2- # R(/|X3BTL"Jaho'˸ SӘFV;9V~%JϵՅe}n蘞KE ]D)ݰ\Dv8aDrhϹjaWVm!ri6xmwB9C54 Νk((!Y3y/bË?Ez < wmsm&,DhK;?`Z רBDіl6nG%ŝIm)$lqfK&$F1o%V@WWӨTܻ?I%0bgow@3D`p{NI݉@(ToFm_e4 {G罖#/|j& x-J~j5X )˲nTڷo0%_ΟopxI<yt]@X\+j?AB[( R烽V=RK||Eoj}{:[cO? -[AE-7Ϝipp;ڌr{059 M!23 %g;kF ^.iAZjJ tvva/Tِ=~n7Da KP̸+XKm 0f7>Oi(/QAkp8 Q;#a/ o][޹q-RZ{<8ݻյkʖeP,0O`0Pdth_Ud3Щ\e,vEMm%,f`8n%d7b;a(NOc}p$Pw7B% ,WL! y[oJG/X^TDT`,Ħ똺}^H0?;^$G8+Ր -ݷ)1x=%tCwz K{0 ?R ^޲Pz V`fzfZ9Cr(A`^#)jL˅ɤ]+xf+h5dYp˒X"I <]Q?+[I64wwC6BB0e^fRt}yq ,&br5Rd̞:K$K 5n e RAe&2 C*H$p(dWʊ3FA"h:=4!><84qIjjx_߱iB(0sP33*0 1(J!Pnw`b/Kcؽ d's9ǣ3^DgNzػ@)<{%U8$]/0Kal?7- A0MYl^vJtuA/m v{ti0O $'jZgaXf+:Z1+,aeb\w77W' (2]l#&jPzaZ5KQfq4P|0 lk(لacyl<"no>-cv~ d@<3fjk@{j:ի@M7&BEسx69vl5Pa'{: 4BY1,.u)DbV;sQ5- W X õrs>XblE-\ + DMJ33;"! )Vɲ ^J!`Gyӆ!坓d:WH4@r l[v9$ATQLC耷]`_[;ѱ|H&XvN˲'4c6ޔ\&Vt] PE|LuE>ر,Kg2'Bm-c%ځW{z3mZ ;=q21Vm轿7W4M0o19(}Ȱ,DVs m,J@/Ձ,[,O)n]ב-0S+{i(e2go 5G-P>E  IDAT (|i"YQ|ޯXh /$3Ʈ[DfUH0!ظ\100uڗ;P0X% ̍q;k~Tȑw |}̴Pc799*0"ϲx@[kyt]w}6x1;F Qjsxk"4:vEQ ;{{\0E(]K)m sW󒀎۷йb5MIg5[864;5VB/>w@s-ިD+qaԛ;w0-/_7<L&NYV`t-ٵ f-6hs}_[| 51F{P(49ە6m.s߿("vwtt|>hK){`ka^E,YEV~i6 80QEQtrnP@H=;w28׉[MB΋:X [P5\ /Nb/m0-~NmsS̛d|VER@~CGP1ˇNS$P:crһ~~@1w?MK8gpsl͟ڲ-Ukk@q0 R.Yɶ8D"aÌS}vlH*O4<3u==0@<1/ xlBj4+b5y J)Dڳ,H2E"ZY*7sȒ@GGGp?`wSq;vs% B"!.4 )!uv¬T B[ZypAd$j}OQBt| 0UY,فA:k}VAj"z \W]M@kN,FFo'{ЃSz!vSD t9,Y̺a.!z?Wi:05 T I{V~uN5YT6䀟[8F.C6;nx2R.P_/ UVa0KŢm9o4]Ko#"RAf{Ǚ;1>u, b&7~_E@|_UYr,g!>r9bh&o@c =U0 @o4mBÿ~+b8}>?, +W` ͟{u7~Ο9of \Wdϓ 9gNc]ŃyΑW9r?бcBfs?~vs,#n4T2R)ܹsa{ HR?,J^\+KB+c=oEE 7F$ȩBŸi(f!ݍj6 ѩQ\nqBh%HŬY 8~pbRpZfJX]Y>A#(C#cutX%@)D%Wsfww55,q+{=iׂ__3-jn D)[A}J];v:Lg.]j(a"q#Dg)~]\$IB^:z0 T*_.D$F1#n:59LBd| n xH ۋzݍ6_|~ Hm-7?ْerN}޷5 ) 5,y?GDOgv~4Hb#,.ih,ݍg-YUu{oTS[VVV~*c&vb g-0rҝxy!Pkw>Pou0 )8miۛtdFgV#{BcO(SrBH2QR z )E^p" aVF1nkH ,)n')``ii!BH뺔8|iEa7n+#Ds q#%V,C*" (#-#4JDN9k X|.lǫ6I"d$A88Ɲ.aIx̔"BU> C/%/6s{!ڲ&dJiSA=##s}.H":)炒/Q_z|1eG8~ R!;6q!$Ib]`gl6/߮? /^D4vb 67qZfO쌥؄F?PvoDPBr#h .;u@sBXBnpOF '{P/N!܃78$bI~>'?B~+ȇK9c_NqAGQgȣ.[(L>?ڙ3`ȥ(nk4sss٢3]ե+WZK xq4iyk49%v%^3ϔ'ДTq;$q8|8#™GdL>[OH]̷K?h%aٔ(}h # 8I`q8 /"+zc,əXT8ӌT ddZ O{ j?pJ@*NjH$'PV:6Z>*=n1^y睂_! 6 8x`%pWB$Ibj?bﱛ4s'߮rI8p l5AiZcm[X2 q"J|@[{!?B(7eރ4eȯV -L3M! ,,Œ('W?D#phC Br A88ʸ:nk_ K%X(T+x{KVKڼ)p\$u{I@2 _pQ!رbTTnkm"tpʕ@`/i4'u`4T(xKuTΝ;vZ!ZE88pH.JS 66p\e$ JwwJѯTGGf jO;豎qvH YFxVK*@oaPށk#3Xhh?t nЀ-Jul[W 9Q6pտD:X.& a ;/ ?V/ݮlz9N"_[H}@L' oKF*@Hq M8( e1&Xf# 8$CU4QJ1 |ՈB'M]p+im#!G9ǻロD`ii KFa)$AS"%j~Wr xۡ*DV$(ha#P~R.g90.…EXI,I}:*ʐU84e>~/J8}s\CE[[(1R9nhQCh>TlY yi1YW;2BpNԑYX)lի=J=O {x8/vM Vql;wt{amm-;FSNS7u`)BjKKwv@4_<@3?!%ŅpW3 @lw`aQ30#Gp ?/%9W(62=jܧt1˗/OqiWV0}DH7'@:1նyez, "OD@ R!ULl;Z[9X0JU[F0Kb=:XXw[7qmq}x?_?8(91(Y[*" 8I;Zgs]'C~=Z>Yć,g@Fԉ&3BZ$nc[3N!'Q{~i=z7oD!oVeZEX^^zAPtmSZ-95ѨPT1 @룏>*׾}k㏍s;;;"i!Yz@ AMRFqi.@xr]}\gǁ)u U#gk)!pY :Ý ghkX;\A!x4M2}#֟tGlf?HIw"a5aγW>c1g *oXJ mw~1C)~xmZnOXZZ•+WX\\O~򓱫 Gy^9ǽ{{T͛QOh483XXX:Z+.Gq<3qʕ/W677Cɓ'tmlqiPo}?pi  `cPM6qoSfD1O<K8#D@KV©`r2c uKϵ|&8 YD4!@P=+!8YF&Jn2途 V8.--UBrKJ/6^To6LD~1,,JdFk׮8ćPAVsQ.@>ZS:ױS8???C… }kbqqիWp"@sg2Hi\K{IߨLцJtb,--nիp?< ׯ_ӧq}a(8ХԈX"66pF =&>ʡ…m Yњ pQ$I3 "$0L]]9K IkZYAgkK di!??^i"BP pue|̸˒gv[I=v3gAUrQ /]c$""B=>|piUJnsn\u+7xkohL`nn8AeǏ/+u]ܿ?dCExl6qYp,ӉN:Wf~Ԅ9ׁkQNdq H]JZk_V%G~C;(\* K%'Y@K~v8׃7\^`c7yw&6?]wΝv.@~S:$i( {.YX L$d%hT ERio9#{!~>,_52S]~.]|ׯ#9r|6+FaBJՀZwy]7 Ms?pNz\jW\Lݕ$ ְ^7x#XYY˗scZ!Psg2R oal0Ìb73YNAȾk'D3pԫB X0ʾMQCmqs]|O>L/ cV/O=VˊPG&zm66lȈ:fp|3Pm%-Q\:Ξu\ ߿po O"q\ígk6x/|7įJW8t2iY786"؇ G}ׄLްm///cqqpn~؇a5ܺu+kh_"~_c{{[ ms&CO8&nXRs9r8Fp]'sQl JgUi0BERQYs\^$gM *6o2r&7VWx@A!Xs=Jl $OSW(IB}8buD"b## rWT'yB a|>B#._M̍˗Odc\$GQQbcs AiLE=6SEu %Dmxg:ޗQv| URÇ`ass36s\ f̜Fݮ)Q%ۆNSAYB^#?l,SDR*-R4 ߆'Kk[oAIx6ZMˎS5Oc,3Yסp)VD~OB=mT.<@#6c DzC oPfNAQ'OmGϙ4%)U}$I$M1T u; CPI泊SP6ԡHvz]$ c]xgLj/~TmEra`0DGۅ[xlnnAw*WLp[-4O܎;r:R\vR- @ܔ4)\@ѡ=c"m߭Ji'IRw/@)#G'"l5 yy!  J)'Ni ]AB1u 'ʇ_Qyj'" Hvz!ec=A;hw"@Eslnm a+ |テ_v=x )ݿk`|uCPfƪmQ5|c|"` ͦa(1{z-l_+tqoM@oq< .Ƨ?eAPEGY)kyq^LJ'«p=OӋ f]!&]< ;;hZ"J~}Z".7E2*@CnywUxSLr9>lvu.J r U4>Ɯ$@|^oJFcȿXYYAsrzPDv2"p[EI[[M@Ҕ4NnсUo5C$o<׭97Tj2xex rPu;A%b)CogfnYRnQ"ck'k\=eV# xwz XF u(MSܸma5\m+Felu0ޘ-@uL-C0:@SNSJgK~vkVT~ sem. )P_)wp]gH΄ 槐o7N{뛲ګe}ID 咀ww@Etx"PD *p JzU |-0:/ .8e)eQqp'W4`8B' &G>Ŭm8uR|o"|kb?t%+q^O CYˊ~sސMԃ)$X%nVL,W4q~aL׷W̴jRǎiHp,I5-*2́RU*1Q%9i¼w[/yte$Z-X $VN)>ӃqKDb8*T{*23n4v6'j! !8p@HFPF hxZX"w07'$ץM@w:׈6qK8HYyE&P9/]5?9cKcIv9k\UC0)ڊ1o1U1c4hJΟ3x>'%ϛ+5b\Htii,,eJʲnY;7<'sz,u]eKK@q&%㰢V/xo~>ᔄtMR8nI #JEyz])N{L nB4a Oa8[foya(r Tlyd(xb5~M#!Ɖ%hjh42lkvMDXp&0,sWIe@ɲć{qy&]&$¯"Eq8mE: pĩK\C(}bMYJ+k"dS*f635_ ]6)yNSbF^^9y |BZv= >*\.,# 4}JP>7`#?!$a^fW+ͦ(G yi Ǔ$ԁ4YѣI (yEPm{|Ģ٩+:0ƥ9MTɵ˯"7 k9Ly#sM4O$CX}i^b8`F'm)@?K)ˑ,+*T t"2ycKc1`'6đr'&6NRBe`suIjz8!xc{ ߿pA9ѣp[fCE,p*f>I#[GT)n%)83q 4K[ [$@Eyﯬ%`)\J'fmE1|>h C26Ikjc)N Pd; 8;BL(EZJe?PsŨp`nO|$_low.h".cE(IBUg:)1~SA5тIJTͼiS`87#"R{Yu &+G"E} IPׁjqm^_؜< s50 <7YL$nUt7_f༐ 6U%+kPAeƃ\Ȏ"‹~(Vᘹ8$$Q&:2+ obie͎1.AP&T2I^^HHR'kEQW/],s //}9-o_ƄrB2 nvp$(~[pm 5c%۝C(wv 8G^%[y+#ۂdmFP4AfW$!I#p6۲ՙ f:]*)rAJ%wMԏACn>9Ϟ# 'S $ µZD2I`:{玓j u z=t*Ρ575UzBt32`}n䟠h[L]RԎ<*\bU GIVp[g ̊ Ėo\N-+ N@-ppcEc&$ ίzae" iMdIXsE-6!Hj-xkK@5XНUPPf+4-]we$#bYm8sxOw`{ /)ru~#;+r}1aT$Wr@վf0]I@:cTv 8FF#Fp@|ُ._*}?Ұp ' 3d8ZjZ9  >TCW(:&$h4~>SD?3u VtG;F.J&^|^ƹ >ʵ.#T .*D2IG0(WnZ2〗sby8&ǤMιD~<LjRa"2Wj>DJi~%#mN1ծo*"cI_-68hZ̩-ݣ5?4Wj  ߲(~ k!~$'DfMeRN(Z=&3Ys6 FaXYQ;̷)nTHńgG! &ۼvb)s+8?ёT"bAfff]~084]XXJ${ˢ5Bl65ڛS&}tMR+e'l $5MP6A9h8Y*nJ_%D(IEO[}=ڧ-4E5u_G~U)# (2o2nmG;W* 7AE:'@JSPQR?B*.hdbz&.qf5΄ ' 8FLG'o[{Rm&<.d :գ,pH"'"Ib$$0 ޟ˂ʎ?)D &Gd/#B.8%(2'e%ePv ZRKSOkK6`9RjH׮@A|Ͼ/M&ITC4(0@mRP}WsS ^G@%G Rf6f- ;;=ad F>9Q(;ǎ )]T}Y ?[(T+|Jq" b=8gZQZ5 {/ bclѦX@M:u9#xi|c{=6 -WtC \GłQATLKzT?2twӴq8}DJA=[9~֥bsB5Sk[z}~f&'sVU =DՠHQajY%"ƺ~zw' Lxwe1 h%' ."g :t4,! e}مc2gѣ k_-A~ǯm>a!P84qQ6'n_LЍ6డTaOi2W4rBLВqPp.|w DmtYLQJ}'I(1BQ .D~%SA9y!UYp>9ӊF>Z ѣ"6ޯ~a+qj5s~V2=ept`Ĵ(ԎD"]-g*<XVDƼ2'VF(!m|w@}˗Ѯ.0H$WKKb*v,%S&9X*$Յ.j;:Gl8:c_} p]\8G\K9qP)'!(8;{'eHo# "%Bf^݀BǥCA: ԧG_V=)&:ke&4 Dn7moX* A*7 +ۙy>F}@p(\Gᅬ_lA>_Ñ/?`~^fv\Dž"]F*DN!1%pqRT*T@? 1fxH:DܼBR:]t?,][PzEa8*tT /߀sMlr`h~0@KpްH9?<(!`)j*zu`R#C-(Gjظui5$TT>Ah*.BQp$mU@P: s!ŞyhFn'i`Q!]Qi Tdqr(uH3qqDO-Tޙ0_>xhcT3!3缐ن7LѾbLa'G`¨m,?[`)kT![ERR`ѵZ%`AQkcaIyԔ՟Y^1o'g $2?0rN'.qUb^uuGHʇgmӧMݘ1ۺ8{nvS@yLF`uƌ뿾v{0 \lcLd6#[l*~ǭLqۻ(ЃP8`Pd6}k=q)8cH! ͢(ϴ*!~#3tO0OW4!vt0up+WWy}WѿqD[-0q8NNEaM\:lݩ$Lp>r)БJSIcSV!TJs i`H nHO~Īt 'Tqh gϚԛsDkkZ">3в0V!ׯ,;QNU IDAT(.-2Z$|,gG77S\# a40pNQ`OP `gg'k[v#nVClᜁ́pɰor OM3hm l8 9"|2NnT얾l W_r}ĉ@F fj2oǍH/7U!b92 MF}nTSMǬ"&Tp*7 ìgJ UYOg|m08u|dPg!DƑܺ>pVs1i$Ǘ4ũ;K6^#`7Y&>PHKt75R CS eV&Y|^*.lBxʈ$*@ C0 C$2;*T]o朙/+\G?~7W؍ } l ΐq4V h☙anP $u|Sl;9++/GoԌz>$w~*rq9˜U@'#vy}L'WcXq^Y]~-\p$yQmSZ2b[Qxm PPspPxRN]?yf pA~ҷl>쟉:oafo_܅ `ܯ"'TF }ZC!iD UTv@~6 ~ք\uXDIJJί8._(u@G(v˶viss ~_DwOH^p@f"k Rcl ذjp$Í_]9޺t84L,SW&Djb4E11&%jzHR5Ep[祽rdD($ՠ˶[@_D2O3'b~?O;}_|l" %Ԣ `#@)MXkg p- tD:zqU8rn2aK巧$x˒8}#ǧ{2JVgQUVMݛ n+(YK"KxuC 4"`O2 97[u-J0\[^@yWf־.$3MBQ*CXN_?yҤ-)Gx&vW?RDrhdB2@fL-%bPTA6Jj$cpS>["_Q|Ԫ ᭛`áeSפfߒ$eϱW6(˞Z DDࢲ3!!g_²s ޼XD_[R $_iwf35qu>n2A՞,y'O0V^rd/* p.2L':>z{gL<o\} @E/n=մqBERZAWvOi|'\jDX‰,mq8 Cnr ?PD)geIc2ثO܀pi7Oyusv[S2N[:nu©UeE3WʙV^ʰ9\O~ d]T㷱M݇' v}ue7o"P(>!y b~q<& ̜YάIPPV$M2b{{gQc*`^qs> OZ$i6U`ߞ4BA))cjr }, 5vgn) Af Ȏ&q:Ne}L-`̹fM\o.m9TbC+BA83s $AL"0fieO$I,8PkM'8'(pTNe{@̗EW5(mmWn#}q$9cL!bQb(*?h̠uH4ˤR/8{}JfeDX$Ȩ/BF+mS`PI Ě*%lTR`>3`jA2zPBd ]}T/D!hsbw7OHzDt*?7Ktd: dp&3 PAgəbɳ*Fgt[F^<8"cd9 * [|F3@ lۢTR8"@^| k.)4<~j bkk{"O@ @M _1ܹ D!D` fUo79?G|6ҁ#IaIp.RS͆)(B UoO?Q(:z%ܟѝ;4qDJ;%:Vy2 0DsW΅0 9K5f{`);1j7OCT FDriɚ 67K?(8ip0gĴ4e 0ռ,ex,s)$F-e|Dɀ@7qQ0 5qok 'ZxL$%MDF]/@ŹeٛOPM=*U{W^< DG.678ׁc aX b_]-A$/-q8I4q2<@*w'|`0F9T}ϫ p/ULh]9W) \`bFYuE9$ GdׁV]ՉiJV<,vwAqgقK4өumw-sΑܻEٌ#߲ S zeMe8Iz> L0BՄy݀D j  D;N(EQ6Jm׫Ty! f$:.'T-)$m5L䏢jgss f1bQ K:T`@I|0 3Q4ro8a> ,"Xs"Dq u5;|FG4ikXhoy h9V(0VEx θX4S62@" &-HvT 0S޽^}\GQYߌ;9fp g}2c VkU!?gW$>zHdq⸩#_Bqzgnvb4Qd;a&c @d$r4ZMDi}#%}A j !xlߕ^h e)!R&O(EV(AR]׃狕`64cFc.7jΌnu Z|Wpו+ḋhxɾcvk綶0m/!0-Y9G^ϥvNEAU!4:4`0Ȃa{8!ēFqW%J$QhǏu#Ś1.V_(93qiՁ;wdޅ]1mb eD* @x䑇`W_+zS\r~C/+|Ӿ_~drB$IGte΄0bFZz@~c8 ! 5H@ayI.GwUyp=F^QuE!F.F!n6c> l1b G~TjUU0܋.0xti44 xp++h477poǟt͏ ]_NJ:4?ǫgϸX\V$Rt5KdUJ)Aui$C;mtF^"q_A,pjq z y]C%QI }4 B1Vޙzz&fINQGZxp8MK!8XQB)ԍ>hp[9&E6Y{9GVg l4Y@ "V-lx׏7` :XQ o}:Lmf*}$OM@02roo!QS  !zG:^!c81*P f5l1+"b!$v G)7n۠ҋsu 0ܾ}{>Y5<A!y 0-L X.]]k$y}\=f+B)!h=E\muKdc(&Ϲebih`hgZ/h۩.,q8蟤x+eL m:bm݌"Da\0oVC#Xw]ז'0&ADkqFX1j>&8LD@;MSܺu 2)`p7ͅO؅CC_೟,~imPJqeau<裸?jGHN$qΑnlweAMSJ5U6M&I"FB*hSwqQfOk˝Cx EbYvI2&)|=џ 4#FP8r=ɉnp$@8G# j`28RȎ]&ollgх\8+"߃- ab!GEO~lmmng1PA\/?Pv }0fBPN7s~r:~1ƈ3&x=;8__Ar\]gwUhl IdRHcfƳ1u8bX9Uxڎ wb3KCǶ(x!A&h}]?^YU!c$ѕ}_X("$]G(l6 Hbޜ7am?ߞe؀,|y>o?΅Ȟ h~T4>|baXIF]RHLEs^djj j5Ȇ4Mña?a>g_}L RE'`6X0MXÇh4011u}K"84On<hY氺 UU1;;K.ajdʥK"*圮CeY vA#yV{qյKP03Ⱦf ~K c~ GG"mlb6/D) ME:VC9^j?*OlN8RBl!!*!k؟6HZ?t^L&MP!Ea\VVV`YNKn<Xx)P%zbL&ӧOСCXXXT*Pgm.h Oby uZ)˺n~;z¿&##A2߹~oԪP|gDDQAG|Mz˶ eTEHвX: ӽkh'O$31",cm|މ\! :?0`S`eeŏ( 5Ų,}[[[8`)m d$|īA?O&VVVp5TVW1"Odq 8VG@BчWvQw-B4 e<|w:^CH5 >a87d3WBJipym pv`LVUU7rcc9R`"ŗ? ϲ"XwrpL~M@xFUI ,BՂ,8z(d/~v,.,8Z,G >7 % s19H;8l=|n<%$ DB^x4 (k*"ȋq~H/f݋Q ǭ=jBd! (<{o~)~mkmɽSepLKݽgI,I<쁰!"=?pZ-PۆR(gRxbfdqo %e,_{Q*011qq?I; 6?Qu;I/JOT(@ ?/ h|F F6FVwDs}^ZLQux'̈́y²\Au\!]۷Y5( Ж IDATa{t6&.ԃU>u='$OrP]ag.iBD+*˸g]cǎ… h4p/J)W^7yWn0??~?gxbbŸa ~i0q\wJ zGE1_4 (/ˋc| 4C;zڎ J=xԃ8plBDQ@6ŽA˂ip0]2@m7qύ !,ɞ=D|&x"y^Eeؐ #W"y1q V]S6X[[c >bA[g|ߝvB-dgqvoPT:4LT8r-@ nLvo)KUG"lU l6cNz}HC|;q͍϶_GйvQ"lht]\0u];Q MF\- -_H`>ȾgMJ8~BȏiyaZσL)Td[wcYXBxuT*3x7q0???O ;N89ΩT |>ts{aTAzf p%‘$@)#D݉t QUf(㹞 [Ewϔ D VV 0is 'ۑ@{8.~$j{~MG{"IB) Z$ay882׶C$@(VNX#Kf VNl6P?T~1'u Ve$y(,A<>¬w2rQ~,X`J?~m LE-& Hm@a<L  u ‰Biwϫ{qX΄*Q+j $@Z9J|0M+++eju'? yIfqVn* nP`)tR:(E;!xhi @0OQm0ğG SxOO>HWCyVBB\X@QፎC\! 9ӆ9]3FjLwPB ɒ¬v2ߑf+_z#Y?jud :T ++m;eY?1 Z505d[0)ix {|b"? &,wP@ g .6r 葤9]?(S.i6D<*rqK8DNJ#$@SAQ 潴L@y1 6oZ/_.$ha6$@?:ՒN!+@^"eYs]7xĿH?K~MD^O=cU}3@6 '`J1.IXQm ײS!q2z|6$4 /rgܼ?~>p51\ׅ8cP<ȁY$a_:=( |=NݱS"8UONO#gkeN:OQyPUIFI &A# { :X yd=/,jW; g/}IUXh䳟I(H#'I M|sen'"O,h8#\"?LҊZdhv! 3Dz8=R$ iZhN_۱ NO*> AHQ5ej d)ҥGg}yie9vwʴE~(dxW$PT8 hGI@ZHav 5@Ck_Qϟ= ?\?N/wRj x}4lŠv HOXdN$"ͪ=_ SJ$ǢV @4!=XgQJ! ">mKw9$ãσ"+P55= c~/'~{Yvw|dtV&nZ뺐%Pm1HqebVKaI VcޗexA$&ٳfotPq!h؏+AvȻq@<\ĐF@urr1qe/#?y'a y$sp2?|-(;P@RDQ }O~' O,~C0Hm om$@[ j e< EArlj*~}<7d[#t߃<{SAH`3`Cw=299y7EJiHO*>X,@)e#mwΥȼv|F ?Ka$q\(:JtPT怔ؿ.lC^1?*} 4/\븐>G* Nel$YH I` Ӵ`YЫn>!7]w2zg:2`cE& u818^À8X t#*/~1XGRgayy󪊟kZWMU4LMD!gs fI,];KI>a'{Ot#I lg2b0{O1Մޏ;HlHpvُ%Ç2lQK^o6dÝ^ ~-q*lc P`3GaN ixPR8ZOAHP?C&uFK,qYl'(Y6\sB ?O~{޽x +k:rA^63hOC<3'ih;D|]Ht9|fFGڿ"@.'er)nI{q8nH9s۞ `s "$ϓ#M H rRoƐw䧇M"9sf4N ݟ˅Q~.6N$IB*BlP\t,.QRYͯHPM"8Vġp݇y.޿|/ťŴ?9#E svG៉=@=F*cG=ޙ… ?؝LM!JRnβAbG}Gm6zj>;]YӴ}0,` @l׈{TÀQ(…$TQx~Z> ;N;O}O:,t@4QQ7PU8 #)LMab|c+i*(&fНAddTyT*X[_:WŅ%olde2(eʰa:ңmΑx0S~wWub 8\8 R?b,IQ$ ș3gF$Iĕ?|ٿ"(V=6E v( >#L8!9MS_6RzgPmT*#"o{_uMG>G.A.*lF+j'#I# "OQUڦ5}-ȲYjZr2AA)tT tHQPPTQTN,Yŀ$dۆq]֍Wy<9= 5KƝRx?atF ]7I.'Ix.?7"^H`+1j?~ NĥTor)ݱ[X8s .?}KX(^w w'px$5ngd:KBH6+E՚Jr @IWyCϥl#=}?2 U^Ft:R:q8;y{(\*RԔR-llnb}}>8G$ ౏]7ۖ`rbclfG J+hllB +Ri& xm {yWⶹW 5΃` ҥKZr%8yl˪eMUgdaxXaf#[mQS)?p3{bN)mzMK%jP<@$$H`? lpPU(" ($UT4wY"XS9#Nj(рfNLpB8,K0ide"Ő@bRJΞ=ȿSXhg}7*S3LHPu('߲QNg B0wLb|l¨eH@`J* f0x@ B`f~"x}@%syF122\.dzDwJ%\.CccbiBpz$J q>? ꓶ߅T*7o}GIG?LibVQ vH t7|sDUՎ>p~.Bߡ4aF3ORdlYf,roE|,Jq_۱}Ba/8?&&!+hű"#_R^oQ^hh\?ęb4L&l6L&|.T*MQH qj5lnl`ccu,--^#9XxXj+&tEAKN1 #d "7 !_/I "X[ƐmC=ĤPs't:"SzJK ]wXyBuRa@*rp=/U~V^o!mB X՗1LOOcjr @4Q5B"m 'u킹agej¶LayァP9D| ş̡h:R~"?y#;?)Q8gmask 66} @ϋr[^$AquD;J!>q@(MTynC?3D'S򤦭,)@]IDAT7cHӟ_ՊN8a t?TN7>ɉ A׵ U8!!o[ `F`rJYzh{뵙[A\?MjZrGz[666O=!$FLmMBcYQ ;+C5Z-\a,Cީd>(m4( |rrrfS$1ÿ9:RUS O;̀%-ˍqM[YaOl! N4MUa;^bĢgL3iH(鸉?8%,//l61 [wN#J ?#`I4`VzFe#6uX]jP)Q*WN 8',BbK{1e%IxOq% k-<^,BSY-@۲@dz*ƀ-C$sZӖHCby1^rm/5MI.S,?(( 6c26)h`PK^Q*6jɶǶz{=;@dR* J%$Dg͡Ze?7!%aV+V+| * &Iaij0RtMcԣ*X]B&nߣ86,ߟiBQBV)TP^ @ Rf,c||~iB!s&R EidMf guWB=UdHt{zF#\p{4-9sf4]?+B#tx' ) e $?"oq~R-_eu(8x io}/" xli.;F&p_:GG;F&m:L{`cS4p "@*%E96X*[^yBYf=SN#.=݈~zsVpe꫘=sUt~ }`OӰ,cߖυطo677kVazzPy\dpow Z*uя7J;TUUXZZ=͖DrwX,̙3 `G+iÄ讋δt7=JflpPQ f *I$fǁV*O`ll,j _xgOz}kk0A`*%EGAdL6'u}TD@xO63:^/Y)z+++`kk |@•+WBx,,,Ĥ5PJq%+q{b}=~,//w\wd2|F/ut#:0ܛJT@|s}}{Z'T@J)oll/d!J;5pqP1͡h M-pG_ƷuSνՈx;'48* Μ9Ue?x ⋉~iˁ[dў78uR)VcիWj آ&iՎY4 di((o9|{]pGonlt _;?'ӅBGаH{&7ZQ'&&`6j~~7H"YTU(C-B4 ǔpRվw$4 Vz= ;DL%BP3<  % ^~ck^cAD${o*5k-]\Uj?Tծ)W\]. }fIR`}}=ȆL&x3*VWWQ.yJYYA+b_,A!д塀_n D$iC!wȁf$hBzi [~Z 7JFFFJ033|tXDl0r6~Z-`~~>X.F/,.N Ȅ83}lq7\?0 J9ܗ4wШ9NǛ5sʶ_hM$l|_ʲ\.0Qh$Q 8Ɛ:"sssu/^eY7I7ս~@h>ϯ:} *~vf︦1E]'C{ cQ&dg)B ~`Ij|.9wwA1>Bia@ueӜ JI87}_f!"n,ɱcǰo>ضK. nSJa&z(wVL2W JW Đ@^QJH?sȧ?iN H"BM4YKbS J7AP.&nAA/nϏyZV$Y& Hxv?[c7؝$IBUF6,k£40 ~QdYF&êD'A/ iЧma)EYVvd7 . a"Ij ml]RhJ \ HtT69Qomn L$ɔj7]$$ݓol{ 6*2 RT'8;RL3+Ǝ6KOr椮go }G~0  7<5z={>-V>J?:'oЋzϫD nKzD!'Ŏ0ommr7[D}ߥ⢴i!iv'ۙyn"fo$@aM$H1f=5Z?!dS>j|W˲>G4Ek \Λ;Z~%Q OOÙ/Qϋ6-}"B?} `![?,7;" !wwߧ߾}K.NR SUCӴe-A|kڍ[0M3\O^*ʧ?7~7w~ȵ( wܹW|H  q$gK1$P:]OI}G<]~`I$޿R:",ݴ)ވGel{GT~RJ677sQǯ;tL\LEO)=??r6>n+5E~9#?o֏RJsj5H:d\v k.G4Ov#pamođ@&v*]' q>/y7?*Y? }tvX^W#[.I_~ {m~`睶9p{?NNAVJ mdsjT>ܡVdoN,?go_~O6T<A?`o3ɓ'krnsOn hZ S7/)c R#BK)7ܒ/x ng7Lfi{ۄC~u9^I J$} .H)j w}}{zm|M1w}w !!!zI`5$+=AץI)_u͓'O=p~˸{ !RBnkFVR V3}6 )%( 6 ݎn^jnMJ┮'|>ߋO?tat n~l6{(H)~\.˔J%"bگT*T**ݒq; \.n(%i!į_|wnɠocM&OR| f]~ajr9<|L&C6e}t:z|>AJy]OʙO|p>!>|p8u< x`mRIRi4R-(~BP@ `!]G?SN<7/n-w%]+?B ~6%HXBgvI$rYj;ﷄ1RVHRH)I&R[:C0$D,߃U?iӧO'nMsOV}o;+ }V#JL&\.몪J[[===twwˆ hmmX,F4p!hR"DZ3;;(###ܼy7o2::J6XA#B h e߾aX7noGR~xT4/'ZD",DbIhm-en[ne``ñḒI`9BX,.\ŋvՏlv(kA ^ߟ8q".v c@:t>EQZλ9,z=p\ر{w^C$Y"k jCx[NKZ˗/osy}N~k&g4߿c `rBJWB- 155 vfc=z244|5$(Կ)!H)ԩS+ήnZ[[immnePJ_`&5w^?fI sZZ[[9rG[.+@ZG!ҥKꫜ:ugϮBBkk+Piu)7|ʚ? `e(xTQ lgBkXcǎsNE|5u2MӚOQ،ћwh$!sSU7 VOR+׿MvA,[-@BӧO~/n28xayfL&WP(ıcǸ;lLlZiMc]qf2ln!$ y~_qܹUnikkpX7Q߯H)f}^Y_w ڪ\!7昘XUW!z!z!\.o4NZR|+5;$ܕJ|>O.#͒f1jr EӉ|>Y(VʰS͛<3\pE!ѱ=H/~Nj6hR B)B8&F㴴FWW.kt:q:s󣒀r̫3<Ù3gV b1:;;W$!ćz7ߨo|Cq_B󍂟d]U;;;׾qAWTMӚuactt@I!EWW===  N˅4W㩧◿AQ(]]]l6"23g/,۷o;a![l}hho~k cWUK^Iܷo=7o^7%>2>k~ ~͛7}vv؁i2L4'֫ ~ӟtŴdAgg'hto[?>([o߉Pk׮;E;.;yS͞frrrm۶ccǎdߗekoT/_̻?v.ZVяWׯ7H$@"`zzO#E]v{nBPSvxBH3<O<KLߜ,Dww߽bAZFPh3 gΜW_Bs#djj !6m VKK wuO>߶m8q)񦥥y=wޡP(ݻ9y$sssۿ?Vj`~Ç}v+Gʙ"]י?!/q:X&-{w=FBԵ_Ů]ؼXO\vmYǑ+_ wٸq[瘨T*d2riΝg?O>$/_X=ebR)>fffB͍7c˾\tvf1}vy,Zd27Z׉lܸd2I__;v`ffc737 qݔeSm,Ȳ6a0OO8xgr_(u!dSNo/s!~BشiXSNYВqk.BpzT_{&DRfx 5&ZZZXsEaƍ\~]v*/^dttt߅ p9r;wZ~ÁnKJٵk?я,jʵk׈Fttt, J)s_iB;رaEQ~ ,gggYR`"ȑ#|ߥr~\&R*Ygxꩧyw`-[pBPlDj1>>NTbppp>͛q8N,R244ĥKU;::V􃘱FǢy> rUك֭[xR^z%_N2j֋l6˅ xp\q+40\"Ҟ={ʕ+KkP NzZ]]]]<99]!cǎB-orx4//x-s{i\L&s=ǓO>^N￟ 6H$xw-gZΝ;y饗'399I8n/[;:[n_ҕq\nȈEfubݻIRL$Jaٴi׮]£By7p\l۶zI$ZZBX$LrA\b}ݻ z\,pgϞnjE_MDXoq]w,6a:TU].\EӴvttjjjsݛsO7ntvvv/(Bk׮-۷˿lLoϲWmƹsv횕g^Su)Jl߾I2lr5VeƬ?,bjjbȦMehh!T*$ rL]v}v6oLoo/d22 P*x<ڵ oiz9whFggg߿K.5J!)˸\.N {!S.P*|2/^Sf'w/^lD_T|M$R>1}^&n^D"ȒMb_JIRP(Pu7|5}100e;;v}vKxm۶Q(V,㷜ǭLH3yk%mm۶q…&CJI:FUU+~^_n>we˖_ !9!R7n,η-~&mibњϞ=O~㍟zjx<8lܸ;cUU)q\](\rgw5iK[fx^oζms1<< Q1N:E.Ɓ58q8pZ_a5TU.]]]]8* [-Ж#`0Ⱦ}M2u^TjuU3U*mB66v뻲b|n4GOH)]w*b|||M800FsU~o:>x5׮]!عs'W\AJsM$ann~{̭iH!wb_G-n {mڴP(D2\100 (֌k.E끦i\~16l؀n%m@͛ B\xif5swt: ~H$>jB|LQ/[sNocWʹ]0s=׮][x82v;>(O>XTUv;x<}]n7J#iD"~|/Qu$ZU/V(Kge̦HLKBX---QUǏrB44ʺ.tr1lb ,nHxr $EˬWzٳ~[<lٲQǀe痵Ϗ=#>˗9ӯq8lÆm=[uFJ*B{@UP:MMӌ:MP|LtX+L\a~r}{|>dY_͛gEJ֭[q:ܪ*۷s=X:嶩)nhYRc/_L} `pp'B+`/`zzzIf(|_g+B;ꫯRRU]vD²AAJc9l6Koo/SSS9sJBww7`xx .QO( =lGZ¸N4EAlE،cU!M( "4 R3^7뺎p\'#u4Yɍ%2L>r+MǎҥKo͛7$-aկZ˯loL_̙fk"[Jyt>xܹڅYؼyBYT755*bÎƴ`=qĚ*Yx%Fǹv|`{^{YTU]%w`CE?. )@jئ"bb3fSBAUPUTEE*ƽtV-% Z X|D#Ts$.M0U\Z^KU_ *&:::W՚pp16n܈͆f[c2TIl6(]A!X ׫yf^ګgJm&UUKo%x,'j$PxǗ9c"^r IDATo^/>D|`֭횦Rnh^c=$߸k !8wܪYj~%ifˬV9}2u]s+CwniTC W Ř%H&K&e>$W(`ʬ!2RuV/+dq#XL24ϛ PPHHHmnޤnX Dߘf=|Ǣ|---|_*|9VO,dۭ>дO묈 tttx<ϋB9!&v|;)RJ^{5e6o0ϟTcǎdx7^p8x衇 XuĹ!:7{!>c3TzCPAQL293| Y %uPm6|?xh{Ͷ:_xI**ZMZQT)*F!L&,Tb4A} ʼnEDZP;4qР0OMmާG^Ǐ`-GsJBEDRJ?Ծ q'AsbUOUUoiӦeXZu~_Y088Hgg'[\ҥK?]|9rZM]wh )˜񥪀PHLNM253KRBz]% EDh$ .^a2\OPK5suMGʺ`]- etL*sJ:CP "cmT{]ԌBq5J¥8N|Alq6_L9عrZ\N\RڵSY ?hTz{EQxG\u#'ĉVJxG8yWUp8L>X,,E-Cy&ܑ B5OP6B(s&\Xj^WJ[{#a ^iPE bD  E=V$uԥ!R"uuХ!9YWd݆Vk3Y2s Fn uba6b-qPu@jd/_gŗ(W DN7* ?p|5\9UU^r9)N7n|LJ/. BQ gbuAWE50T~S3K$ݘ-RMO"\@2f||ɉt]T,߂@%5J0g֯\c(gW|D( {/14' DP*-sab;W,OLLJ}}}; !<9]_y=pСߌ¿V6=fcjj/2==;v੧Z+ߺX<—m%ڦ#,_~Rf|bY4) l@4# +Z1ČH GT(@jn!T#'a+ JؾTk:FSUV5*JZU[ :)^rI@ PJHdzjY7)dn?mmD"Q:!^|/:!җd~ҬAd}b1h "׿ |+bB7\1wwC(&V*}ُdfY@ uxffGM`Bq݋ÃƍJӾ`J3Rʇ/N_z i8qbEn{]UU9rsssV{+߮w p`Ql| &fIS{{ îRzݎVT6";x>/nEMӍJRB\ըju@`Ql66(ծ'U5VըUk.v˅ta9u2|($4]1R,cjrCR ;Xh@IAPdsRUn~2D`@__U1˓O̳>* Yl޼Νkl [nի +ߢ}8pGE]*5337 ,hObzvJM#%H#pذp4%/065y|MLruR >D7)˴[hoB=иnQy IR 2N E΀jd@ aIxhmmebb@ ѣGq8Kdddd"L&X,LLNN6I\tZ BO\5Bºd2%]}v́k]9y䊉>y {q`b[oestA\JWЄdv.ARl}׃$W* .Ucw9imo# HNk[m+&f' 10G$!W,226R+<hC]P8gy`o5Xo04E(D#-tJPatlkFv:ډ㔊FoN0H GlD:c7H&S)bVR`STL]_ p .r(BRS̿r ޽{Z6U6b۶mV;BpQۗ-2\.O~$!F[[[:Gڑu]D `ƍRo@;_@U4dΗBVe6izݡ :Zܣ#hj}TbljJJ8W,1:9E: }=nnN01=M25Z >l񛯁@( &P_uw@<'2$j@kEWpm099+74:;hK'-G/9IX1>SW,?TP.W17H758 )2gB5\_TUؕ {%/QMv~|>X_dפccc׊ ^30Zr5 f) @3??ĘH#G4?s m6l6Dª?|>݆ W^p=C]h)/E=g?ɐ-m)Z͘Q I{G.iR X6,ãc(696drF' ݆0 9}(U6J!bW,h V  5|}Z>J 9 B 1|þk0JlD"E0d)ã bpt:NxֶVJ Ө6;]]6;drzjgf~+FHgs4( VOc;a /}MBM(! `Eԅ(ꂰ.Qp7>6E&LaJ\x sjuI[3Lキ|gww76l^~޽V^:fzz!^{/%;;8*Ѱݫ I*\B(^5maG|%B d.$Nǣ$3&g lO\̧2F*z A afr ,.( VV4iF.jnT.h d ?/5YBR٨ G3JU(a ʋ[[C͟۽ltJ4rzj55M4Orw:q k)g]t|>G](u]__v7νȈ8墭O_Xի R?/+z]a3Kҙ,Z)yC7oM%bs8ip9!_,׷P()Ƨf9ڬ6;~`ȏ7£AQUJ.TY~e[k$ fR,D|Hb>㣻b,vx4F3;@D-h5I\Wb4"QTZ@'y IDAT\EQM@ QQ[&c]DFgn;88;p8ƍ\x_̾gl;gd*#NL2VN Vl!G;w w/vQv5K=^I,\!Y%u(f48Zi) >p*~hOlCנ A{deΜ:!,/[Ў\YY\pPI$cTnYYi4"YyuF74 a+0_t^{7xӧO3qF$'ZkFΝzPpyyRZk>^.`kk)7waá YYRM}!w*(eeeW^y7*2\rΘ*l1rh!YZ 8vYY[a0s4N,Q32R5c]Hnl\14Gyeoa+Q56*_2j̠݆қ./&}Y{Gbueh`0bק2O]=i(OQ#ET܇=Ө|v$W~WRr}]~s]*.n-` ~.}vww+#d,np8g 歿֚ʃrsi믿ΥKr ɤ;;;ܻw/˜;w[nͥv2kLl:MIsKE$mW3Yo>iQc_xxgX:*/lm_#BqUP+`P475\"P-PVkMpJ^k0pXK\sec~4I9E$/}K\p])ׯnH/o<$4^aNǎ?^^pD21vhw>}}v]tEu(| "_uPXv NB=T2Pgu_!C`J`6 g;eҜ'rV3ѐNCa: 0.aȸd%&Dϑ0Bz)_|ot:]0Y-,[|%8E #s&iP[t'_mkG:geme {GG,ʃ=c{+fuuŖ6syxE* ~ҺJov]F(b*X(Һ/]Hw$:ΫP7 pnVpLame},\YYJGZm:4+"2mP82+uL-}t?|'sKԁ͉9w\VWWYDc3?Hq|q`h|9::bii .r}Y^}U>䓹uAݧf4&JZ (VBc8byu\k#VVm٭;{.YH,b Wڻ~z> +.Rdiw\L4xP (}[ 4B3YQ`pB6ht_h F$Mf.^G)Z"0?6$ onAx1N}҄=#Y@tTZ?\x'CxgEOHVWW_e( /1}ׯ_AzVDdjaO&E^8.)dJKKdZs4Bf{od>wIEg_*)@P̸( ]j$/Z @1M5% 1 `]Ԗa<"E$c0Mh$ql&?t"n"[T AtH:[h.]|ƍ<\x}{ ylnn&W`uu7n̸5Z&Msy,8sLi d<W|$Ix7PoooM|7pɄ ^~e^~e1\x>@T+URWx K/NZ Ji t&iF5~a8L$kSkv]6֑q",xeT 1Q@`oG5nva)c&F<[+(Œ7PvߜBŷo!L\,b?F0$+ ҩ6)VH1BDvSW@+"BAi1@Oxǽ=n޼Iկ~wyբkƕ+W,- hq3I^cͰ߿>Xʕ+>}XۄE!{1LARJ\½{?S>#vww}LA:|:ߔGۆ)֮-ؤ v>[35v{h`4HZ->PRU(} [Zb0.P%9C xT CO||"TK~2 - @+K cLtQJ3f6Uq>)H5RZWcTWmcgϞŋjF Ct:ܽ{[{2Z&XYYƍRVc^FG(8zPC덾$DQLܻw۷ose^{51}YCeYwˊQDqlu_!\!3Z6BFS:qpH,,RRwG)NRv#-(e#]}6@p2""&ʹA7S"P({E)=n~Q Pw d4͐"3MPiw2JF R vc l!\~s%:;s5߿yW}6Ӄ,) tyb+nx8{A~9u…ȿl279s .1v7o2 *Zދ}=ƣ1YV(F&q@ 3V Gq+0ɵ-Zi ~`Ki~M8`/-(,|zR2N!wB(< _Xy߃E/`K0 W.ӯ*ڭ6i![nPbH< !%H8B@(ϟWW 7h:آ)c{P#p~<O / bOeLgyc s\|^zW3߿W_}gy&H7WyEts['Ϝ8m;\Yf'ǔ6\ʘvpĨn׿uzLJ/n~:sKz=N>]:o<@'o\`<lnn7 OB{?^{_~?O>,v_'c+G|2gLq<,4G&PӔGC!7VVye6G ߿ 7 AS7a}{k`V 6T2.+Zl6(ΐ[ǧ!8<_׸r w)\s]4M9uX@3S`[~q}}?:::Y| d_vy^L׮]k lllܺur΃>4֭[Q^g$bBre3Z2i+I xX][iU?EQ?! 3E/tLp\szD>sV,v0TW`F0Z#B9b!2!L!d9$`0rxe-넃a,WjZJ@բ`"pFC'*~Ӷ#awuD1{$FKDL~ز]xo}[>}?lmٓxaMdfQ9xًRDmyd29q0]_7\7$8w\q^|/Y/7?A^+W0Nvׯ_ B<% E[ޟU\;lsZBVW-8-r_Vy awDaow[;Hf\=nJ(BDk$h[GidAAICW} B)5"[g!s &>V+k|㭧scʏ?'M*71`h BDBKI\I@>H_8 c m4CHէr6`vE:7EH?`Cf~=- >Nywl*gΜ9V|+`X$1"֘?\ WR*ЩS,LRQv_Ȧ9|t๋OKG Gvp}#QSj8@ŴVIZĮxٕ]+M"Fb?Om7pS$EFy=Nc;;Hhiio}[dYt:E)ٳgݭLDyꩧ, pٙa>17C_7Ƭn3g(`7Mvz7|URE1x56.0)tYt*xVY*xۥiQZR}X"("嗂W%W?\Ɖuo"cJcpWSR `MJ4u9i%tvJ"2eiCSW|eYe\VdZ?[|K?4wI+@;?S{Yҩt[-ZqRvcÿ??|[eva-Cjxx B;wK,zJt:LM]p'.nSdqz#ECCTd·m^xvvv^N"˲Bj#ss RTaWM)6nMB nWB]}?7:+|{풁횔e+cqVBivڜZ^2~1*gOSۡ -"b`]D'_n%<ŗϞo ~?v˖L@(޽o&]|i1Py\ D8NkMRFteR;{f\A y`T|c [<3ܾ}?? wQ_Or %ˮ͗vs$0 IDATe$dʇWo2IS$feotف+It=svO٧w`$hI64%NѓXkB6H!S HI'SO_|ĵ|j~u=}pQ*jA[f`r͛wPِ/swG3_J4P C2Fi3NbI"NPt/}i]w{E?OvZ0EkH)ɲlf.8٩>!V鱩'v_E՟,f~\|yOq$ݟ\+C"wԀR"Un~] dD EEPh.b?[+߿/gs$⥋osӻ)9ӛ,{.OSr_jr,/?`?=) w^iNS:Z~"c6^tXk -&SYbow*Ia(n20(X6 \H$c0!| T_"Em@x-c:,Nϴكƴߓ1n[zo_Z{jZcLxPG1UK$Ip8\?NKPj](DqhE Dn,m[J]QU:xe/B^ʧ>Ξ8`.Ie4Ϟ?dz`onpXğ")&-L)g G Gc1hp8f0s88ph498fۥǠ4Ԏ4:8𳏯s |/sGÒxd4}o"powZU 1",P׷ sz y!2p!S (e,pj;e׵:$IRL 'gs"XZZz{uӚ7N/ʘ;q#zh?DiEQv[WzZ|޷=@<@#CH(r`Bnż|ynw:֋l?~EK8Υs9<[w8O w6c:-ۘih4Evsw{+1J۔"d s4c\Z&``4Q "gكqU\`cmqy' SN͸`*"vww+ Fu7i/Ή I@~Yk]7B.]H0;w</Fzx& jh[9~<@h= n/ EJZW. 9{ve|f}ΒJG&)C8&b F)6 SDkcrh߽ט<'fFciF;ĉh܃c"!_&G;;{>*⣫EV7R+.`VjLp jY3MA/Yp($Gb)ZOxt: ݀d2cT}*4oP]NVB[,qϽ]?5[l¼׮3#HM8Xܲ5?6^*@6x57K,[\:7Ë-L)2aOZ#~ve`(dWW̥fv),Pf|ߵT.>`\W+VuG9I-N,.,-wpt:-B6O>EEL'SF1Z)i R1HZn~N۔PDÏ*+U[|*,AMS&1Fib!@1?woA(F`S3~b (JR}* >aWlر{W}qR`PL ' ?KKKEX 8E8@/P|_2wjsx+I+qq^gX<@\s[hjpvKW~kL `eOWЊ"nmҊbNƦJrNCd`/?2v*?!>c0Be 0Zw~¿l P,WZQ(t&]>WY>,+@4kj;{ Gbv9eT(y :h.8Kg?/-Ν=E`4"IBWXg$z3h0`wo(BCey#ԼKAM?˕o@Ϗbx3G$Bk?NKB'q<>j*p8Z~KKl&^z&vU(w;˞#G?Sg2?T|ڹ+QJd: ږQ$ 5?6ih:Țk@_ OXg^o(o+FʋmIbg.{Pg4"\|l?Ξj<>y3*{]se{_ ڙӛ\8wڂ((zݯW._}*)?L}7Y=Ň՛߿˗(i{\60C@hz' utw ?ӅPz lǑdBe>6: x]@z8o-BݲEYs UB?EOTvOq'S6׉hb~! L@QGfyPӾ(\S\yq$?;c'Cʂt}4#/vZ?{\2: n̍8 ,7|B~0Y^y'-x. fJY1@JEX;x|G8x>~@D `Nw% N;%3wu;("qqx4,Csi4s%_~v ~1wKLӇS֫(8& gy/#ZFg?)?(5_h(y+*4+օ,zu<)h&~jVּs/c/'pOZ6%?EB 7e ,܆"Nu!= {h7[rOOo˔y"AhIZSngnv<nܼ?hڋ}@L{C%};|`_|T"~8|ι>ee[z?5aFr?UF"S+& 70ky8>"g)o蓦B^оoޗ ^FϩhDe슕Ue/;ܔA6G;Fc XY^h0DyPvxorҩ46+ +/pss{TSxg@ /I)%| p?eG7s\I,}W`Sf1f.G@`-QcG&I%R7M)a1F&IHM/'G<℥ h/ٞ& Ik~yRϰ+?]4״k'u{q*|3y L&Z=ߦ>^+EїλNc,bM74!HEe4qɍ;\xd XY lԭ~}}m9TFZ Pm>#kP 4/! c g}ΟE*i\ ʘB'?/vjЗk1y}+ W`ރm[#h}燕/NbEDgwZ'bps,_{iuG1D;QrN*-w"(3IiB(׌b7ĜT9%{3m :ԕ>Sh mT|zBPNe oH>@O~|T|ock TY㭿S[|~m ɔ;ë1M ha?_ֆo_wSG3x ~ Ӛ@$Z:cE@KWTU#X_;cT恂=6WdAnd`71E, RgJ.bg +)SQ0B.hJ[p~Fm{Ttp(1Bhb wrT΍[r%,)r2n`0b{:4M{ #!/WUq2`2-  ,Al vl*pm}k9PP2-xV[:yn@a>l(xiW<+\%i6k5 JKRJp|Jߕnv@!9 N*G(o5WJ?}/XWغdy (Xt{]DWd@T\/| ֺ1u0k!*n@dЦ+ <Qʮ(yO*Bh ʆve= @_ID uKns0NGjQZ"֨1,/=8h\jC\q>%UybݓO@DPS. .J p, 7Y=L.Jŏidn`vњ9nϬ.O!E!7ɕ>\Wj?ZzA&|@ǧQ/ ?'"CjpE[yV*w첝ae8ɔ*{k(ֶCOO?{ReA*֟3BJs6Dq 2b0  qL֦Y\hp=SXE6S,REn"{j<7WjF ?i_R5+`}WC8ض4W&'Abƭ~{LT[INɝiƵwg{Nܸ)_pI_@ %6-$\ZNeuh+)\~: QR8?8`ԃ~V}M6p' ET( * Q's sSzW!B βmn~#_kLh] z]c7Vkv0>x*! ӊ#ZL}U؀E[vs&(Ġs$)D\~cDM`[BԀ=ܯa%m`ճ,rx/b~]f}FRn.:& 9o݀]nMq^@ئFƔvN 7ݧ FYv4 ML !p^~JQ?BCI$4(%6FSLvSPm$"-@?AMg~9&yF!Od2!z`f晩+}- txe((N28?\ߛ7/7뚞n]#h7Ë֕/EuiW"jX7j4qiJQ: )2Dqd>&Hg,Om0nܾK_7D$|~o{:iɌN",z;RzQJK(ypsEt){/p ?Bc32n/q`_%:C+?O{ {+e-R__0?Bf~Us6‹Ż"gL,( ޴K<䜔ѨLs>N-yocP$GG2`ȿΏZBȌnnyWq>vwmb,5+ɔì=G<͏4Td),1 $L[ŏD`\曨nYBgL?Py}]{p CB o~4ʇZƠ|5ט<hK,״ݞR-eCn GC:wvxO;YNS<'cڭfe @^{vf 1<&IAկ}uV aiO7vj8(WTY)@RyFEɎ<8Q@bô+7`0;%7̍<H<hY. ~QOots9l޾)(2LRtxp4Hɔhu #IB턪0>ĽU+1[[[\r(% d2 Z-NKEK椺axk@a~^N5!J,A, 2 D B츶Nj1Ұؿ]/rϝTt{笟E4%*"ܽDQ1 IDAT\kL~Osъiz.$t$VnȜH46vuuCUC@}7qיg)O<8';H Ӑ$[(a oՔlÛLO@ +PZpP`{BYIVXg\O\ Ӳ>D?s1:sGRu~t3HfbW6 RH" k.t[h b4^x&D N.j.)4?+ZIBL@ؚ wSSa#QTumcԙDmH_=EOb~dДE>΂JrTچr>X"HP8W|d <ȅA:V%Q``]Ppr}ʿXL~~rWܧ?Sp1UfHgwֿ{Z7@N_ 6%4Vgw@NƐbk DyJ v 2gIѿ|`}+)10U1k3LU\YnNWE F5eWiQ]g8qQXXR }8NД(0^,$!ƕMQ&eOG Кĭ) L] rЦ ߋ7A0QcF0NyS95sP3Uf0eϥvi#J1cܘ!rXap8@'b '[qe % ×om~Bbt~<֤2"ӊv#acEw#= b'= %L10̃@ "ZR)U~{^ݩDL6y*n@;P'RJ.щ \Vt{.:")7qà <ca UG)H5Z hETݬJQ\~[{\9\bAѐGyO:'?7 {ML ߎ"\v ry FVAHԷQ>y03ݙ9N Kq_#b'b ݞ;RM=iB'%5d9/A_7|:([R4%"_Ηt !0z%  %f(#cxf"ye6WiC?Ix0Q^riNBT_"(wx|$%ˆ  !cNSs<$h$>δ*c0sr [W>Pk1R)8!i+_oBiH v{a)kud^FeDd_(RX?n1=Q SܖvȯA 1NMtN$3khta  Ty",;c+7; kﮬ5!u{@=8=C9 ǖiX)pz5izu%w8l}` kK"X(I0T9{㱥X!pQ&9Yʉ$Lu2'1?BEqρc,XgJ 0vH$3{{ϧDxgPGUN~ >^OJ} F'7EȶgtmV;d%eML_9̐e͟F8ˁ{>OPiЊT+Ib+7En @}@uX~@:mG*v<&Js A-&m5͉}yތa ƍB&ȣ*B6y4߾qCo+q 9R$FCnbaOP7 X%{ˑ.lVG \u,wȟ ֒0D1z3Mrcn*?6("e>O c 8ܟsܙhF&Il P>$r:ԭy}]_+ֿ4Iˆ!(ñVc0֘UEJrz\N^b`CY`8ZL}n:W+Rao=k f>MC3S,n HzN1rbl@rqddxPQ5qe`[;5r`eals 2xF:|=sjᑋt_O E;ُ̽$׽9gUVVU־/]nI$Rw;: dž_ {l{R#{u)(.kʪ=3NDfuҔ|X'@:ɤ7UMWLK4 D6$8vyIoc]@?+@1&P AˋB1u}y9tW16-0M# aP>i!@TےT7`%w<Bm#g%Pv%ݎ]Q` P> @DNE˗p?2هJ箒Q4̀iA/ceq@16z%z#~t?b3m}H+,hI(G4´B\Qֲl%(h~ӫzPiP/a,ϓ &ZN]Q 4 #>N{~rL&!P3 qHTנo||y|۞g'0L`( b D؇ҕ||8- J;wVsG,Jbq$JARTV@ 0uE0җ}/H0_gz9ı;0"24F?Ǥ|FU)9^iogq|/@&2x4_?0}۞d. T A*Y<'=2󬮆iF?bpfPl_7L5hz- ir2\ `a:wH>ȅ$؜ Ovq<*6gďmN$ZpJҍ%\s{#={ˤP<4N^ !^ՠ/*Wzj@Fv^AfN}eYnq lqň$qk{Cz} R%w? D-J?Ý_`m~ӳf T2mZo{+PL u@H?_.K{F`^<4W꼬*p^z *ėzbyn`]e?Ѩqf8aE#r'hR1k:$,; %Ʊm.c4B:#^G*#`)טg nOIM&ȦRl$5az^9 3tpGDT˞7v, nf.\\߿0X0ҫ߆'n'@)O Yei2zQ)qlNL·BזeaLLӤJ#E7^N1 (f11:8?G eӴ[mLF15JiUnAT62rJ},pom@\+S;@vI|XA_]jTPTt#t:=64q:%׍HNzGLZlj.]g()97?=; ` -%UEV$c\ZW_?t'E02l!`GߑG89C˿O1Q Δnz/% iw #}m`Pf0A?AS@OUX|.x\0W0F!ꗋnW Ox\Q Հ/pN B /[<8g X_Cԑ?Xô8޵YLe5 ٮ=`Ze2#M$s|“C>| `I@Mq[b s7WY_}nK(vv|_n J'(6\?BjJq`s;e4&}5=0G]~v^Eҝ[e{`k=s$\5CI5 ޡ1"?|\,x9}TSEBz,d:HQwmVN-RȤAJʧs~x.3^߉:DL$we2_b6LO3 ZvHOͱw-)+FA_=1}ݏ30AWzS41Xn.CZmr  >) \n/j|IT*r'@ rGgYqJ zS@`̈́3HĈ1MeGF(AB n. 4^o )sF};GMg~fVMP$BZ^#?| 0rC{(F?:;X3 `$tOwU$-kWl c<Ŷ\]r'q$7wg)7K?=Jqgc>bg v$FYa{f'jBW`jD6;nO؃@O% Z?s>>.ݗhq='\'BK$znD^èe^te^q^!0VKޑ&RkuF=1B(е omz zI09IXm["{peC ۡܜYwC\ռ =P7[t{wW1mfeZݧlwbmyZV~~ A^d+~$l{jTuTkγ;Xşxbleԣܨ #+6XT/\FdG)Ef)-fRC *?躿`ߏ'5VSwc\;tQ0~٫K)d2raa ulg z=븂OȚP <~E!{=_`#:M MH0JYw-(x^)%n) pVo3LZ6oQ=w{I$TXx |wݷ(U>Ǒhk+ lp\;<$f'\f{nclj\=?ec}Ȥ|q1P> %3?;;oAopSXZ\dxto^Tg>q6>]9F*j.Om{&tظQ7i.g\DU%~[/xl%fGw29]ayqv[ ?)[o]Ggavѡh[4mp[̎p^R>>>Zn9;;*eo*wQ*HR("aE[Ql6V[f IDATq?[s~wбuJAX%~w!54<826UwyyˤHol'<}ay6J$VajbhL&M:!I ;]GNt~?KXcX[afr?|" rs~bOZt_E[e0zz@'cn` rvpxxjR6^U21.{'155P*s\MR=aW6&\)`| j&g~5!u*@DU:^/uU"Jz!u_UÕ$~;W2|+@z K$ccEXYgbbjI'*v^Ϡ3hu:$S)2 鴫G FeXv$/g{];/EO&ܽ}T~_siɨ/Ede@{yx '}ҕ2 +h8 YF>sl6+SiZj+ {/^Urp^G&si"ǏT*ܽ{%B$I" cE@PkcW&MJ~Q~ߌkQk굄r K`N퍻;]}]rqg&x GrdsYsM١hnw9qxt'[WN1 Q_oA ܺls }HGW_jowOm}ݨ|'jZO 8t1 qjovj.6E ^?^VL&sa)@u~m(j5={w(྾ 6eqzzJij(V: 1)D;Jh3W-AszA X ??<}%m|"Q*tTk yUˮ0>VwޥW{vJdANPoq :Ŀw4M ㉄R`0DGnht!M?!OMMh4e?N Bj5juj,]͞/zޥ YZZbxx??\.x۶='RSR"t*>RPv] ɨTe<l6Bwbe:{qLj]([;q/w|r/s(tb~?e#c*@+\4V^ʅ[! . z)t6ۧin߾څqZE~?^U"tR|zALEz:sssLOONy7999\LnXMX@jT6CB"H06 DB xԨi5_XhD?PB%)MwdRu ÌHn[~ʀ X\Oiz_?2^竢PX|E%|jop;;H,IB=DNM0ZaLΪg Ahs߸qVP?ZH){ -|x(YIe/$[%LƣG\.tddFz $)4 =/7 C`?t/0M:=#$|%vb w;OMҡ|N)~7X_pvVǖm8{?>Zf_>:'ewxU1:αUd3 )!b+I0 ۛ4F`#òt<9`YBjLf~A6qeڕ 6t&iuڌfhw;XJ!''XA.u[ʀ ˢFD$(wD~*~2 P$q(uxyafaa?X]^$3M/w T}->^a?|{=9(rs#7&=4={G!(`R1Gm:ɄN[Q떎"6*.;b y*?_  [,FĶm`m(Ͳ4?  KIX`O4O> ?==M;W,+0 /e+\>?\jjZ0::Ml]g.$ldKdg{۷FGGi]}Rd*cYA*s' UU, 16Aׇғ "zq0+ @ --9:mP>m @#zB'p}ĥre߯U#]=>2ôZ 0 fffx].iz%s;SC? bmJ.LNNR,I$XE"399A[E`Nwf8̤[sY $IBH%iu:w@O `y^s$"` 8=4 ҟd?NYPq.!UkGDW\ȹJ*[aP LQIIC4mw>Urwn@ S& ^0?? ?L石C\X,FV0 Ldooap]* 5p*m&]J u,..dΝ;,,,8 Iv6Vh]P&gӱI3INe[iz=0BJTI\]B C B)@"'b9'H "E%HU P#.o]^!Ҭ?಑2H ~)IgTHh FF 4mzfߚH$w6d F!ΌghZT4wL&}6_|E`ѷmJB"`uu5p,tvF^:]7R0#p1i(Z)4MLs mGOYHf.sK`vv͝mFÒҡn-4O+,'3RQ+0rA#/Y.A25cׅ!k}p $­j D(w S?&A؎% KB1[T@Bg8B&\EtƊ#0,D8וwgli^eWG5M}.?;\4#~o{{i KGu+h}yŤ/Kc}T6MRR@>P((1n2 z1^M-G+ ifE*!;G#^D] aYrY4{C3:Vgpt_oW9 @|- TS-zup@}_Q 2A ~CDDsUjxU^^ 2(JG 1U'JS>:cc4[mv7n31>R8moR Q#*G諫T*,+J4N乳a `窕_l۸bȥ8N*t:nݺE:wd2䣏>4X:)!)lC! ZB'?q#$eHNΐ'Ʊli 𘻈 th%LDx qPĨLA8B*~3@Hj= R Qγ~'y/R5fIh IRL&h6MME][Zdav;VcogN &!}_fYXX4" R)x@ַkD_]2>x|\e)%'''6O\{/ 7 k c$Id0g 5$ R4Z 4Dhvw's _&"6rsI) }WuŸy@p>VǏy}j)f,#r,%gujj8?tpP~V4xĨ|:GUUFFFX__ܺu~o{{;s]`: U*^zjH$( 叫uDB:ifS1^*'$I!jP>ܹd2:mJ%2 v۝g "+rw\# Mؤc"*o~;1 pgQ'"DyԺcs(߃CC,h888bX46)F;Dsܾ?|LưܥdcMXXX`}}i$fS:SSS1<<W?^: {U*!p,+6y7YXX`{;:I7p/ڀGhi$&3'Jhtl>OQg8-%G2 cc4Z-NkgGnc9vCWmU_tÀeFs|ܭ*9UW#% Q܊q ?7 3SS135Eax#ZNb,w6n"O+Sf|wu]޽{i,yfggT*4M\}ޏqjz ?{,W(q1^,] Svvv쌥%"r9ZI`3IHj팉 4=A:fPwp٤j'LNM`c 3XMԣ?}`#>C t}NH"jUGF^:G+JGD(AރdEO |G"5Zwyq ﶧ'h`e|_ evF4EJƊX]6dեEzfX*7i]}ʴ+4eM?k*D~zz h4\T?UPpW"U*A6tU~e>={͛7``In7dA]NϪ&' R4#5W~]ӠlRgtlJq唱1ff1,v>@2>'A\_U >@}P%%G)Dsa#*5&$LOӓrrrah; @-/V'l}Thyff4"WA@uFFFT*ޣE^ <uPӷu U*>P(z]{/TN;A)k9=N*'LLM'$cq&e"4F,ǢzV'07?iYla;E7ؾmE"mAhqxO2p?e"`WB *@pصU"<9P{/G3SܾuӴxars}|/9=uBГ=ggY9똉oе?nO&,..R.b$ 㰹^]g/S_Í-p U* y>C,O>ye08RH䄱q3>1Nۡg=NPk4s sF 8<:bltյezq!wo) np <ޮ WvR."{2PT/ I*9yA076(xt=&'Kjux+L|}ƃe!i\{دi 4MǙ N7n;赘0 䚢?uo K%^w!B>#&?/0@J%a\4q4=IV);WTN)8<ڢj0`{ bԘE:p9~P Eb"=U1V?L&u ]/O7WWxZͶR_Bc:6D=NT?Ƕm}={Na!/gP3%ӿ'HmwqՁ+K311 } P(O99 .$?63" KK֛h ˑoɧĩk%hkLp|RON(YY]aumۑlbkcAJ*DSOkD_x[cgDb H3,-, 'g6HtX9m ~}_4w/x/z^`OeSz^`4fx /KMHF ^z-5 Q(~!ggg|;w`?\ɓ46"oEO%] u>Sww%E . hH N:2/wCJ]cqq OO9(AF-U](#8}wL*M077ӓ$i.,/-H*>HG ]޺s!H["i匯OF7 |>_V59iZضu~pgٺ"WMMx%N#### ݻ?O sH2iʂаCnhShkln~i%Ey\ 4Ybeu%R,{lGդ8>,s-1>>Fϲ8:ptRVzVӳN;". D"I08"EJc%&'J=};`wm9Mϰ0\ݽ2ϞmPW %wI˂^7j9&ar?@:KWV>'''$㺍\$YUA`zz7n'⮥X*?@KRd2sm!`bfvF$%Ӽ#D ~ϨTi^*ÓӬ,(H"095$S&J2>>ET:].mV9v)DC:T2ir92,D ۶[TTN9*pP>BhLM3;338xb/Ub#RInߺ;o#!-law{_?0,tfCY{-?Hf|_x xKwxFπ. B}],vy~_3j}*?xICᎮMiU oΛwIf2.psg|;;yR>}v #E[gnѝB䄣 'T5,MchhPl.9of=$IHE3LӢjhh: ]Hs(?LilI&&,M09QBJ~^!* R4o{o]#ql(՗i[[;9&W)2}SSST <H$wvtt,,!6t Wu4RD"*H$Ν;?ο;dY>,--y#)tV23o3TtIo[ofiM̯~O>t6e]D`P,GerrIgg. vZN^th;Ca#Hhz\6G>'͑e0]1eʇ_vF5ੳ. Ɗy.7nԓ8Rz1 OkIXK ?N$ܾ}`9W~EpWE^~?-o.~W3iuRƫA&G?}ggglll033OSLdbb|;| &EI2捻wy{d֓4 :^O/~Q !TAwgc2:6F8!HR 4i#0 ^ϠPoh4Z4M*3NOTNϨ5ȼǽ*(q =o޹܂;8s!Y1 @pG }###ܻwSwǏ)/Uc!5?e5t6<E4?OiZ$ ]Mq Et';,wȍ:$˫y-Enq7q}NN@j Џ @;}n@;kxD8k""w6B:V->_}uS* r ]>zyyA;55ŝ;wO8˸i0lѸ~z|oIW.xkD"AT Q ݻ''b+w!ge>C9&8Ÿf}6_{FgƱǎعtҤM6iK[u^hǑ/?\U ! !?B $=Gў쭻&isuu|~|8cGxb~^$Zu{^í[O}bϦ(?I(t8&Zr188h5mU koc~W_}>/888ȍ7xwhjxC3!\`jzKs1d I@=.B577ٌD؈ M35M>\(e''w$SSS,//J__nΝ;GC/" d2}~?%J(]nQ [ȮyXq"} 믿_|a|4yiksJ= 8wϟgl,rO0$]. $$Pfv*E,g;$˒˩3jbB\Ʌ Hx< >2 ?k1 IH"%dYfppЊhU@(ZP۷o&.T@rg=hYWadt1'rztEQЫ!,D$@d$ȒXRȒxT 5CT̿k"S1:kk`HH+^+>\!Kv``9z{{NAw]Y[[#bT~|r`N wNdea|>я֘:?w}w)'}h?04(Ff+% -cx CCfxx3g|}x)-QS d TkK;Gv^bDbOD$Cͩ`Hy y'Yƕ(!%il9fT +Wx%&iyp%Ğp9PMY]]P(Xi5|J>ATCY& |îɂ `}}VPK.石yܸq\.(.^@)d WOeL_(DxpOOYaHOTi:FP\рJ.#βLNW# YՑ2rFGTH%W`Y3q$ܾ}n1^/+++͛d2sKѣGVaRښU,i#)ǀ.]h<\c2Kكw ut$a.^{:ӧfӭ+A\O4%͒嬿J%fgg>LMM1<44D8fii/ 322A DѺft]'Z{ DŽBl#R{IP u [vMZTv)ׯsYTU%2;;˽{a >}t:]gI!IR&Fy!ٳgzR0==mYq٬̣zS |l0LBO7հLK}ծ @UU 8R/Fԗ/_fjjl6k{{{y7ؠT*qܹk׮qU~Ջd||i߿oիV4ER&m42%IAn7BL&$$Y7J%,$* #:xX,T*&+t(i9i DPqmuKR ݲ5`YrVy1EaaaX,Jk#x<%^_~:'UT"311A8f``bhgjK'V@4LTJ-Ţ|)\.G$ld2ie.$*+ظikU \p}>J*2irMuuUU`iiO?k7;z%N-~7?rTnofGH;&opO`Ή,BP((uJյb2daa^zJB:?'JYנ\.J(ӽoς#i *}}}d6C(( xx\+Nv( dzg7ZA?b\T*Q,)JP@ uD$sv bu\,zzz,ev1[5 LQ4ͪh.^ 2Gq^h|0===nK!\.\.׾B;uiF\RP.??K*Hd)YݞZa:逺0ݣVO"= NmgE}x|OKXpqXErp)/;~|8>"S_}|DCOp|cDhCW01", qޡ?3s=wi' /b}@ I"|,O}q/DA7%y6g=Gt+E&]D8O X.9#zk+q.Q'B $;~> 9U~\ٵ);8 BA3 7b_}~D HW8"1^31YyL|PIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/256/simplescreenrecorder-paused.png000066400000000000000000001536131470053624300303070ustar00rootroot00000000000000PNG  IHDR\rfsBIT|d pHYs7]7]F]tEXtSoftwarewww.inkscape.org< IDATxyי'{yT}u}F_@ 4N@$gDIcƎX;1rĄc1ڱXYkg#iŕx/t꺏/#FG$i?!!d?J}# QX*1yUHt^(~zĆ;=19rrrX}f0 y\$e5^0 $IMӠ( ÀeY,%u$!(vofjqzLL>5($I̝fdNN\.\.|>A6&I bkZAQEI$^ٔN?u1| t1W.{ sƙy$ $I$Ib1Mc"``t:aZa$I#Rr̙_|1<$ڷod2EE $!L"buud2_n7(T(7(ӧ~y=zcd:t(w+BEm_8=s ٬p84fh4 IDF7/`Zrzz5ۃJkg ?pBx;5 ta7/!}j{%yhH+++f4Mhhh@SSlق`0*~|>M 'hm$I$IEy,//cee+++XZZ&&&099ILMM!{ n񠪪 NЧ YwgϞ}M6L $I!!^yG8pkm=֭[Յtuu&1zqS1JmKKKs/,! {fSN%b1|"{=JQ_zƻe,//kbFl6Ν;zKz=_k[8gO9(HT ŋ7|9= ROOr7$!d^籸Q\?aرĞ={􅙼܌ g/ $aaa/_ƹsp,--mh- X]O=$IO>ɿ֡d2}Em4!Z?% 8pñ _ {Կ}a={Ν5BPUU`0m{Ѷ(I+(O?޺kJ2Q/PgT*Y,--+~=zǏAQb^EA00EQwGF!C4|!`h43gÅ  b~X+q…О={^t?cffHdsn?~GŶm0̦1<A0l g jT MZȰ @x͛k+˲d$ |~1h޽]({O[˘]Ş( ===xg3l6]Ey-7ic%rH&H$Zտ|ll6k84 ݮ}}*!D 6LU!&''[o᷿횀KQjkk3|#z $IAv=DQLOO#ix׃a Ez>]vJd2 ${zz/bϞ=iz̮ \.]T*1`ttPH C[[ZZZގ7զ'L {$o_׈be@4jjjP]]mx: tܾ}kUk,_?kbu%s׮]x饗ѱ!f׋^~sssϴG-opj5 d R~_W_l2PWWWl7)o/]tģA_+OQԏ!WU1{aassswK/l6zݻvn߾c#eZOQ Ax^@8C鏞(BSScnbjR[oW^yp8 D%{7Oanpafuu$oX('IBq띝f|N/I&&&pu\zuStrfh49`0Cann<##ƍd&$^{www N2TUUaeepAX,\rT uuuرcN>eaoXYYݯE}۷Z!VUKg^DQ ~?.بk׮-䀢;K"*FGG87M?D[[[MrbA y&x k{* Áղ~DQ|gX\\!  Ϟ}v1<<1J>8xG$!mmmuD܌>,..~b&+++q.^d2 łl6d2 ADbݻcLMMܧ d2p8$IǪԼ???p!^5"cIΖ5 gu7⩤~d2X]]E:(fx"~ӟҥK7^vc˖-`0LNNV>sazzZJ4 FǃC w$42 fTϡP$I/\m_bE===|B.Ul&&&. ={h/P-gT*QAA$sp咗Bp84)8w&Cd2v~WTr6#ZTUUي( mmmC?h&sܹZ8p۷o,& 0 J:t\q1:: χb@Po?߸qI+ EQh-011Q`B͆r~6E<G&$1o^uLNNn}ruV vfk Q]]d2tvvVc8CuuX$eZhQ}zâr022عs', l6Յ!X0Dmm->#!`pp4MkvR<ǝ;wp f-BT.iΝhnnƽ{J5Jauu6P%Iұ`0___OsssV$ }}}C;Tr~<ӚYA@"?wk 8ñcǰea\vM3T}v|Ghii<X-;/"'hʝ0]]]@$ t;v@4fzݎ:DQ,vjQBvڅy,,,t/^lFww7.]d;;;5)!N#`Ϟ=wv;vlq:Ɲ;wpu,`0y_$I2A4v'|DBaT[M܅[Axtm¯<quuu zMR-+ڵ iLJ_}$DQD*©Sꫯbzzz]wݰ횾ݍ7obttTS)"jjjdp-8D"aOOS˘X񫫫k.beeEM&jjj055`0>jjՊmܹ| ROF5,<2 p= 2HDx\ ;> FqE Z[[N55!Ja׮]$IzhkkÕ+W p84 !ΝhFOOVVV6MH&u&&&P]] ժL a޽+Q2 %БEu;WZ|`@mS#즦ʊ---+T |$a||/2n޼n ǃÇ޽{z4v܉nLOOƍc"ݻw!e\6x( N&Ө+ZjrD099RgEaeeEyGSSVWWH$ގ\.W"fwuuBo>LLLh1h}444hֽDQmA4,DQ<;@d2a׮]X,%`IyY\\<?}(~Qϼ<# ZLPIg<Ν;L&5;Ⱥ:444tI9)“O> ݎnR?,p 8-R ު$ 455w1H$auu4MkHJ;p846 }eruC~Kmd20˲w'NķY_imֿ~:~_Uϣw=<Պ@ aZi466|fYӳS˞fȥEaۋntvv7oD( {q9$ 4)kRCss3~?N:9 j+E|ØE}}=L&rDQJ˅]v R pev&6h4W*!I, TLxggزe˺Ừ<O\.\.\<9>gA6 d߇ASSf3_aWSdɓ'o$ u( 7 ىZ|d“O>;w T.3+ڵ ===Ņ @q=H\byy/_.AEOOV;V`D~#U?vnD"6VlٲtZ5 1LOOc˖-`YVJ!p2[-VtcqOW#-}%$Η(i۷o=,[qWv0;`tttCd"X /^{M;ǓO>_`Y---Zv, r?r\z=p:]p\2aD+ PlVcx#,// 8sa?~fݻ7hL&ގ@ e!"$%TsmCdVU_vBQY/bvvn8j#`ڊx<1ttto6$IBWW8feÚe_P3??%R"˲hll,W$I޽H&=_B4=! %}E[v]q'h r Ξ=[i4ᰦ$/Ԅy\z\ D(;wIYQrn~=j(j ^/"S$H+M  t˘۷7pq k:ttt<y< OHQ#y^}ӏ^0Vרדdw]j]q;2fUWWcx7+C155W{AMu6;(b @Dd3Yply>\6 E4ԷQ՞3,#/ʋ}8NΝWA_]4/! O/i{)\@$;q3O=fD^ x/\UuaFFFC#]]]5 \$i~D"%ᥗ^*?xܼysM}_McUޝN'N8QVI- ҂ .9kX~tuue٢욡NRfIguxA4t;~>(>VMpf IDATVV4E%QbOPQF1tg7nXǐWUUg2ټJ<^y啒K,jux;Zj2",,hjjZ%Iq֭޺u+::: pmMt?~8b.^hd2g) +:rPzVN!L"ΔaXu[B]}հX,X,09m2dT$ul6d*;5-G%)l_ 2c6 a|mEԶ4Fh"I꽨7Hp8`YPRz?:󘙙 qx駵ql^ :Pl\6ͨ/ˌ$IFGG})7R hmm@rfْ|{ /s͙cO:W{O S4M L"NkF!5NEkco_/R_>hb1EŨG"j$z5wFKK+Z[*vm(eKM 1(f;Q Issss}6Vu>͆hUlv&Ͱ0Ν;h (I&A./~vƞK2M{ݻgt%|;$IG;!<{NyZɓw܌v4 pyLMMib"3O<<#*̮G ")yV)nJnt:ՅZ|>%E,貍}*1K onk@ Dl5ܻ7e{ժ%Ai^A1;33g"[+EQ8rZZZrK g?Yx4uB77ٍn"Ny;!#H L\lt8ۋz}> =*\مEJ)B4rM$tLH\@TTA!MEPbJW (fsY,.,`x.BjIJp8*@JT:& jFzx@@O77W1m6<3_}r~.20&yt:SOWU#jÇܬfSgMщֶ64X =EF7  34 |D_r$T8n>σ|^k?o "Icvn?-ꘈi8YQBI&ݸyׯ pAСChiiўk{d0Bjjj`X%>>>Z+ϗ}A$I:IWVVJto}˰n9N(,viƁսpع{P=+EEAm}Q( ĂeXe?%OYDQ$)EkEGG&&&p[r{8q.&f~ox \`|D0DL @Xdkb(i ,3(~vb6l6dba2q0X͇w$늋Ki_Ly ElllLT T |,(0l&L6L:&.ՙi${hҼCd^O>Wʪ|0Px^:EQ7uA͛%i%u4˲{=Kݢ(1;;kx19=5_$|%*|>8p@_v;022728ǁ(||\(IJ5jn rWx|A։USYZ`Xp&8  b+Uc8J.?˲0s&LhB:E*B&/@BL04qo^b2d@Rஂ"Obyl \.|Id2LOOPķ08z(@$E\PDWSBo}6**$I5̪ſi@uWLz8x .\1SS"ȺJOʶZmV8UUi/~OR+O,āmmZ-rRjdgd*yftrH&SH$륕j fb˙ VX@3 ˳(seBH&Ɛ!*"fPj-Jbe%H8T& a`6aʫSi dj ȑK@dTp:$AD+Tn7;UN ѣx7*g2O" xWJ$ oS (ɿxM/@a[^^.)w^CZo%uV DQN<9\|6PttvX y- fV\N~yHZd/˲Yd]Ed2Yd3YHtxv;!V㫈 ur`0 UyxXőʹ'gY0FQb3˳ G^fBD$ $I1cyiYvY:z=ݰ;XZZ"R8 vf |.X O?nyd2_Bjkk55M!d  yM/ lI=N뚚o~sYɒ݊ڂ۷o|PZ+J5J^B`S@|xT$I0hnnB*0}Ι8Xfp&٬Ye>g`w ҩ R4$QD߇łOaX _U%z S/4F"M Sq"u5y,/-cdt S`Y5AՀeM_b8V hB,G,(|,3DAM n D6޹}׮_ƃbzzZ ( j|Y݋۷o[8p@J+W䗸\.nX^lK) p8!dmiiDꩧxJS&ӧ7d2 ( D:*eg*_v5Wl6d"ʢ&93"rQA!Jcy%l6 χ-[qf/`(WX.tv#Q|z*߼9bq9O@(|PM+nF6SE4PMmSX\\18twmEww\N&p0"(NZv9O$|.B|.Iw ? H=Qk."*eATy2YVAt0LK={`bblX,!~@e3I6qkP9~~muuum.!7 A,+c kQq銁>br\8rrNq%Ȳ446GTw)~J)dsy /V!J+FMѲ_qHg Br{k'b8CE*6kJ %JA@@Shll@ӖF)OL! Zm-P(e,ͥF AaA0 Iffg4FaC3>M${gl\1800Vyܿߐny !8x jjj4Wg9H$կ~ebՆu&\:An*93gy^4χb W\))Rgg'~?>䓊?N'9y|ڋZ{o^P\G",MS8Y%9:P-%t3i"Ommm4VVI0ڐ|h̼TR2dJ@YZT,JM8Ɔnajr7>!ϣH$t8Fu.S( 3}lZ nN' |X}PT9"ȑ#H&%bJ,رcCvA Vb=Tf Kra8qBj9111&H---- f###=0?vݠhZ!LW,f3xA0fNL,eXLMMavn5ص{~_Crffd^oˉ1VqZ`Z`5g2r H$Յ&;CDWIeRV1L7-&lT:QT@C@PU 讧V-bvv*֊h4z0 0|&|6Lr"x$|4E Y{4E 1<_zۋP~˸qF}e| h{XXX@OOΟ?~) @OooAWL&XVSSQ, qf)YTyFV1?*lևd:;A1(& rKCPB!Q f:b7b]* (2P=h6tJRAۃV"}>TUyqfXlVyf nAʄ#\4-" X^Y.#]#lD_YYACC,KYZ1\SXV=vL"n6Hv_J~/cqB(OdYGĄ8lFuuN_:Ȉ2΁ؾmhr @TZeQ]]I4z(B)4d2&R4zPU8B虀(]nxnz C hBF :3m_0uMJ(sPF.R)Z0Jz0~OX,E.!Larr & 55jEfA$A$X"T(2]BUJߕJ={`iiODVNj\15ed<}P]u avH@G݇菅71/X\ZF:FKs3.B R)T0s"D^fExZ[HyYTl_&x'D⽊{Tp:A2@ YFK:[]]R2D(Bcc#v؁X,1cuzd6CQp&O(kQF@VY[ɤaM85J_/_|ـ*9NtwwceeEO'ZXfkk+(oۅcǎAUsٜ3?,x1, 3gF.C6j2r<::Aѱ1jc!嗀ti7^ 5H ⿪8 IDATE@Q4ҙV*fz;wYF- @)xb@.>^ -dslʋd*T:#,)DcrS(PD+ `X@"e~Pi\v o,VWW QɧQ"f6 pؐ!SMd2NE6 ث(buu0 ;>>^QڱcڊL&+++XXXmPSSɊ?gSOd`>זѢiZ禌2,!BDx<,XuS:Eu0PPPIO1\ex2Exd(a~{`=ƿs~.a<$O LI\BC6C*k'y8(/o"/uNr6U.Y^Z\F6 azzhl. 0ʂ@$ACCTs !k8^[,5_6jjjHK&a۷ϐԙf~zEk,C}}=ۋzae45C&D>` LLrAN5M m8a LLv \ K0q&~L`ff\yL^eWdG>/唶|\N-ϯ'VWcuy#Z,v` 5~U~sϯ}KyAw^wMo6ALx #Lbiq v SH&Sp9Hg@91K%f >Ӟ `rN %PGx'`p80==]vEduJ׫XE&1͕0 pd"l$Ig=BlkVfDN{0ػwkяvebR* A R)2rtR2E$a{?0( x\[8v;q5LOO===~Eѿ1x>_^&NgPjO:a 'zas0?/2EhD\D~h?DkuI )%Jy 5U.f'_ yBg} Ҥ 퐢kTK,>"(2Rs_B:MX蝐ޤYJ<3\*TX$kMR2kLz&-d͓Lɴ/h!3-ک-n HVE w93&w-|;ԭ)\d~Q7\ /}sf?Ol4UQbzبj[ 8Yyc2bueQCI%E5˾<_ ={A.yU>1{H >&9u)3x Ip,U~+4d() 1`651M0[u|y7V`Bxb>7Дx&H۷ocss?cܼy/~tRt5/3z?~\0Vo29j12NdZ+Ia?Xy<VR?7X,wycƗ_~$*'=|+[[,T(_{"O^e }\"{0-81smaS3 BqCeMQ#)̼`(^n~qW\˽=Fct;mP ċQ=BH9n!If30Y!906#"{jIHn ྀ# 7x;;;K8wWA~+<Oe{}K&RawwwÇ1ŋyݒZ~!jg(a" IGyDE1&Ӂh G_WRP@ 'OSPyf49u5ΗcƢY2G5~M7 QC<+d|]Tҳ\b@ed:ASZMP%ߕ+#Q!Mb`!#f {_>3&Ec0\ri&l,칚4C@時'I d:1.P"N,o;%З o Eq(^d.7.g|/}X ]`7ULnݺUjbyòXܹ~}r>}]ܺu?mlo߲d~G~}/c{ !ab4i 4[MRlCC}ɜ66Ăcz@ibq*x)!@U C<׭ݨnXd'ѤE3 &s<%NFNPq*~cyR9.7qeY>h4FA`8Bh*1ot=F\DՕwKy|f>8>>Re h6ܴΛ(~`0Xjj.$֮3vrSHH#齽=_5_w_|iⷿ~(_%`BI+#+++3d&Y~ ɸl41]rafhs86ܳ4왶K-2mEC4uqjϏ19z^0 lrp*V ϴّS፫[j5 's|˗/&Fbqx2A͗hj9jj ݮL]#׮]?~ڲW."S2SJ3&IEc07_ǃ7@Xk`8]`8?1ݔ RC.7 -ks'P^q#%<,dc s|]'>{Y?Ǔ'Oog?Ҝ'''Nh6(ڵkh4` *й?gJ`իWe1Ƙ3 A%?/KfwExvvvcaXԄ{}"ϕy !WAL~Ь, fb85ċ߅KG0D@,0<9B&;0&T 7c@(LAL9!G&oTPʣ!>}j>>4x\@i0Ii\U9,M+kPǖ`xeee0? _[[SC|&I3L?VK ɓ'o~:Ƴ/0q|T  恄@xCܸq;1Ǻr7by X I8_3! G)&}<9)˨"1cJi.wチGy2_Z{*Pի@ fiql-M?`U QTaAPuդbqttO?THӴ4'!a*$l0Di̢*SZ-DQQ6ߥeHs/1~^ ӉGX9$Hw[`b(ᖶV 7oOݟ9)PGf_"*gꨌdCl6d2:PsnTjj-ܾs}+0$Y]8F wb2Lf3LSx>Ab7s?0P'Z6f s6RD֭[]FJۗa}}kJ:YgN?5lV=_Bu߰הj#Wu,\A^#$:L1[~NMny˗`bED0)1..Bui10Na*vG@iʳS#BD M\0'o?`9:lm?#°=/9 afyvƝ;xBnmސLwQI`c ` -}G*wn8&vΚ{:9(9- 9(*JQ w81d2) : pӓ,I8ɤ4ҝ;wD" GWL88\;t,q-anA|Id=pzZ'ǘNNfrM#|%ibeu 鮡q|烉ã# #|h<:G1 O7LNl!OߡlΝ[x+ C%4H~|_#w<{)3Io~rqM!Q+] d"kvm82N(?SFEXA[yͲI[~)_F_Yz]WYSeɅzh|Ղ/Sqٶ#cD@cED>6V8<:|@ݶu}m A`0ٷny!a-{389HRQd~7Ee!kcy{!%ѱ|?@7P&tꤼbqלb/׮[0~Nǵ5]ڡ,w'CJ)If*yyysM2kȿ^g]*fzgEG-xլWzׯi4M2>qv')3Beg7[?Glm`0bv:f,Zz=l6S0 v|v[eX&z4 rD@\C Iu=?A%?M w}(*`y9?wƚfc+сqB (P $I~8AnxOv%qFU%/LDMZr,cL%:ۂ|3NFE&U 9nثW'ɅzY9+յPwm\SbhO\c:`:jK':|l6,xnBSa6 9$XY)cRPs"n;{峟<}{{fZJKml&xb<<rM7}C%c<dzgq2oG A y̿*- )5"cFL%RPa"WȊIW]uFN^piFLIi.ġ~Vm G#3_Ǿ)dj\xP/l5"O2> G1{rQ s-67&L3'Fh8m!ja`,lVM.E%ڿuJDžp xe(PHhy7{2Te>>67FM*$DG)*XTޗBx&U~!Ѿ*<{*dveUd,߅2{O'PPb z]nUN^[aSYc_:cVVV]LU@2`*n;N<\f9ndE/B<|UJ+PS'qs\@^ U>h#2IE|+>ljSz܁*хv"q;q㺕i}fO <_̯˓D]?@L<(Br>f{/_b[)3Ѧ ]AKw he<] )M|,V%$;!XK yBϛ ogN%| Ьu5<67ޒ/)NZf3i g'M>75|OjvO2''C(TM"L!{DA+3w eNsef0slͮANsJ^$zDDKK+AJH3km̀ba? +e`1  =}5?4&\'H;-(,EG5L&qg0 I!>&Mjz*aL.l6Dk=D|~ɟgB~fW0sL'iL,q|2=؟LhF;UZw` p(hBt_֗T"{.DʯQg2?!<뢝Z_T3 KW>ͷ sgq 0| ߡx*1$NpttٔD['b7(Zt:S$f3'HNAW cPxNH$7ߡvS_j, kukrL?KB"j '|*~>@)}J$.#CJU1o\ Z$;&/ @j!fӉ# ⩴ f9(͑g9lLL1s7@v?-1&ͭh<++b`σVTyhO34M'ր$edJS{]EP$|]2 [e([`@@Ra(lj sLbWVݕ7YL;Cċ9k'%2e(L9 PήZ-,a rrm~,k4GczaX)<_P; J3#V(CeǪzj6} A<9R-M&^@r=ΪDSeFtx).0Y6;::`pOs3}y)Ac0:3a9VR$I"#A %R\ +ʱDXZ<'@̳2yj7d >E|6@i.n[)mWoafd88B,x~6?(ͼ:,~M֧JhںU /wyLEq(or3@ع4; ݸUBHk8KO_Ẁe@Red(TReJ<ɜ|?!Ojux NaA}0za"Ly&L6<84>9򥤅 Y,ȝH`8i;"c!* 1( 0G<\BTn98<_)($^vif2:v!Fj]uyc5y^V@Ex\Eg_ C+Zx34{bVZ@/@ >Sb%T⾁$IѨAj)^f:`/baDe9 & Q9#YA* U/9өa4/Tྐྵ,EP~ m"W2d9!8L$X, ăwF5 {r[T }2 `0eGǘLv |_!z}Ƙ@B3p ʳ `2"S##(B)8L ^2>%&JX_Ld!7&P4YJ,y@ZX54 EP#S_ƅˮy!"f#Iz.FkjnxJIEQ<Ed͵?N<~++Czo&Z&?y?|ΰ/S9N J@(G>zg 'A0k)@5ː${8|\5΀NCLr"X%8.J*g,u.Neg?7 0p8x2AH)"x 1M(gЩ%x;fsbgo߼XY#Ey6zkk|[|9ZB:oMs!{!rB!3XӔ9 ; US-'ə8 wQ@Y F,3.[2> Y@0(KW k5Lg3J1 ,G_2 aV p0/8Fְ;r7lƓL!_%(cL3D67^0/3VdJ m]+ ̃?r@ă* C/Oą3/_qY@>K9Qm xJa:L'lHF[//1EnR#O& @ q~Z"Y; /ab8#)Y$ƍĭ|dee|e\@&iOi>UsB@ϏT"e40J!e3y?` QFk"ס' &%~MBW @d濔C Gs&РD <"^fЪu a`<q< YbǘN'qep2!D;Pߠ?+{@TC- eG{mz|4Ŝw=8[hT)J.S@/X"8T slWRِ|uPnPeytTDЇj27R\˪r{= ,4%?U(Q\)a=q8LPnX8K/87K̯Mm !ۆYL"{DUOG  ϸ^d0=n&s\;L!j ϣW 3ЙL3!ׁ”}"VL &8deDΣ`|xuNiS1L;}f#EZ$IfβRU ,ϑ ZÃ2:& V]O03o[ї56b>N# 9?pFpzKM2^X=wd&1VE.7Oh1*M9tioJ`3X"-JZިUG&~!ZLS@!C׫jXojYdqقf*o).3}K,ޮKH]1Dp(s gI,(a{B8/&`DpK&Ay?!R,)H=ߌQ!q@Xa/:O~u vF"ґ}rW5e,\y`yx/$]&GҌ Mc>χ<#%M)g0 h`+XEece$1-WN1uJ$} )%T5Z@0ʘ9{]Ύeero_QƘN)H>ry)螾HY%G,`2ɶ|2nR0 4Z  /9Z0~ f*p"H_¼ 7I Ua{IʈL5iٷ P!Dܩ#-M??e6(8 ,M[(1@JX^^#(tIYf㩿=?'4d|P xB3T08Df%0>1`JX8#]6 a@0 rlO9ŕL*9@KeyٷwMv#s8!L$I`,-@Y B,LAMP yF`U(q [~16.u0"3Q<P*Bp x'AH˜Ƚ_YTE/v.[R6qU!b>E)G(㋌!ro%f毻]VMr2EῪ} 7O3!<QȌNSA,A 4Ms0 .Y$KE`)CQGC#1N$"UfD,հv5/O$Uғ\i6&ƯY_kzQ . eJlw**ۇ8Ҍ̄{ e?\j^V+}))}BK.*2%=!vI6NReh>BDJ'ci.V|F03f1.Gu[vq䮡{V*SDɖATt6S ^WyG@Yx ϦPB,b;iSnv@u-)\O. Ĝ3 XJaTL!'yxVM]9G_#HMe<pI F(x1r\GY-': ȲuÈ1ςe/mR3 -1^@KGBJzU$9(< 5'KU9>><{ x8 > 9g[P엂 Z1(YFu$*,N, ZW_C!KReM@i񁉘NrB$[7M[ò~|VW8מԼ2id9<*}+1~lUMFTf,3JZا0'Z-ݯzuWf(kO#j_`Hr}Az(霻oM z_q|ZښXԔwˍqAIaJ~D B(TSo3Ϭ3s9is yXܤ}>KsR RDz{!ljc3"N3ql$Xp`D"(*MyiX%9T)-`1dnfB,߭"M$Y['>0e2=+}EB6YW(c1S 'y ?PP=ڿw*WV1b+f;,=T!S ? A79zݮҡ`0AU9U<Y;Sթu pr2 =8"y7Q؈XoTfk1P O\W Jyx`rާSȩZ`*SaU(JNJq,#5tkfEHdK]]o%&"1YNz$LU p(%@] pn\ 0vW2d_fTA;C8 $(d@Xj"vcI Gx^*߅m|"D^A!UW޲|oCp21熽.Nj9W *A#Cw7M/Agip8:e7Fzv@I!9.Dp B/0y!Zֵ` Iz = ̲yNQ ''~횂EرPҾ0gUL3(T v^/^ !Z$KB߃_JakJ weſSVUGL%cԪ5K7_ZKe'N^!PL4qrr@DLh%s^:LC$awvWC-|_9Hr'~" C0xk׮ \_A:r}d "bށv͍ ~- %@^GBt0gv&cP K#m \_vAj8:OPgS)+BRONA z?37 qȯ2ۃ ND.;#)8NQ! o6Wvu2Msb2=":a|XǓOBFYPi aSO5y_& ,3ٲ}FVM$`尞_sp.i Q6?$Ũ@5\ۚ6KpS צA/ fA`0oD*Z3Q ) q'L1~?͹oHO-$ |Ӆ3&دjeYLF"Mg*M9ѨnevN y,a8і궗sW _n5A0 r<'Y4S \P<`77v8"Eˠkr8fqp 3,Z!)- ^.>jpQ~e :.qJTr~g㛻r8_(`#/C{!r0 /yT`1=VUJ G96?)!]N |/^֭muMZ|_r޴ |Õ+΃xL@^!Bm-tzk8>B4PINQMK2%`[^@@@i42c.c\?/W\d`|`*^;hmE/ "[O_K2 A":bc0quG)=f7op!x.?|%} 7ow׮^AX#LKAww1OZ +}\d(^".V6qx4mk627d%Hc~Gi|kOg W3i"r`ګb~> *Wrz|>W`_|+W>9&EQMJ .&0s L4r9Ҝx[$D<{{n\lx,1ŗ_qayx1 F#|ml6q۸}I2h<}LSܹn\nq|O<~nzz2lll#"5(=VWVhDy0,?L-sp5L)` *A8LE֖0 ,umdN0@Tse \TB .:?gZZ̀gϾ6660Nh4Jb|)P8DIJ 44JLxNf}q>-|[1Fe#-<|8hb,=F*4ac2V3kjA292NQ=["@-O]A|q{K8A>l^AzO6YEէ {mw*9NN!fm?:.`'  |qnÇr9^_Gp"e+>#+RGcxjDj^ BZC']$w:)W{*24<({_;D?v~>7n011&b޽oeٽxw@噢( `,{.@U:,P3tX9gwqzQ6Kk[c]@v{-3מ}Z^:wPP(т]`K{?.YCAfff7,,۳_/1;.0O1)glaL9&0d'ʶwwˤ0HH8e{[&#wbbr,Ef^Zpf'g_n  f@ECbD"INH$BX,//cYjUӲ,n,?]/sG2]c t~i.!U-ף\z)|3Sv[ע(x}'_Ѡ@4LϜ`m#s^B9-'"Zߊlg{]׹tgΜx|q:$~w `kA8M2 JUnӼd2]޻9f2 kaZxי תkVr `uEoHtG=C_z]2B27|d _ kW9qℝ;Y}XU1GٹE_pi2y~Bް}YKgo.γ\ e99lnM m)rkk9tӧdllL*wSWVeU!8@Eyw+@ժ kc|>`2;;\xE8V FV+? ~PA}12E:G#BW/J[ݚO֊"\~x:/">`PPIr :vv\ή؟йgH$98R-ݷLz&8>&gS)[Kv-U6|8VW@ fؗ IDAThR.JŒqZ&3 8DH|HZ`bw+8jukxƇahty.T*~|>?fss]HXՃ0JQ(teɨbUQ0-R5nw<3/bTYCV~9_]mOZ "!/ H$=1KXXQ,D"oiDD"A"'56Adq1NRN<+'q_w,TӦ{fڈ3)]?+e!ݩ FCP`j2-˩k5]@ZP(:={%,"pEVWW%R)$=l[T 3uaC0N&W_*9|2j%{OK&yw(WcqPsӼKX8t~tTi.$4`yJjH 5/ZEޤVd%)q!ZFաP(͑nSo<ݮ( ##LMNNNck3CTP,2Y391} ր=kII~.-M\4MK%,¯0 ygp-6-( 3mtzV-Q7! ԣp5#h>UUQmqlP,ͳ]䳯V]RIGe[nz_/#wto]wߑιK&ev]`t:²^*xؔ?ou]NuLNNl6)z<m!w ttLEZeeeDP(hGia_0>1E,!I a4Ό X(ȗkwBH@ %01v}=FDaMR 2E*_-Vi6 ,0כuRU7!^eYY(x$yв Qz_{hVte)0;=)cF 7Ėfmmmq)'9y{{gl=!vaKBl6e|~z!<E᭷޽loowYpG}]@(g X>X"ϧ4uպ#JRwie MU=*+ܢ4TlQ/Tl)nZ rv@et `+m.)3O\zͣlNfd,N1u;}Ď{_0eG283;#(X %_Tu?XZxk~`0H$hxlNm^y00d88aP@H0KtzwaY| ρP(p}n\rC*tL hPQ>Ѧިz\q)zZZ.Xj-Ξ6AsQjE’w[[:h-Z.{lY~ݦيt WQZ.,7TR|e痖W0 c_UU.^$a:\'OH:,..m);Dos`dO|kŸд EdM0 jJ$a||͞"( H&q&DB츼0- MS}EPTM.Wi_E^PGJp;6ݶq]t9@٦ն'I=ͮ"O Vdj  \sӑ&~9 SSS\rYLdvvl6;bsc)?VH8"vsPc|nnCA'切|{=kZmmew J;i Fw[PVjYшO|7w=.Óv_E!*KKri64[rn~}>gOk(BQFť\t~|o:\M8}j6664a&^0K'ӱjI~uCkq$!LRT|mn;BX`td>*<$`v`(_Q)U*4 ҩ4JzAW% DM}ox뙙m1kT/3iY+ MRI=t-W۹2cMs0ޝqm(DTǃFFm0 o;Ru 'O@kpXfeLNNǥfQ}`@{0K[ peYT*ZEbQ,'ܺ*2z@S(X2ML IĢo022j hmt=P;G2]A$0FL4`vzdTAfu-sK>#T\,Z]!40޻TQ=qzdq eE7=Y͝"n˂/7+F{r(sj{Lj8i̛<2B 90cx:.\Z1ywh YIBL R4f]k R\z,L,NZX.aY&S-͖Kwv7\/<3=. Z12^{&).^3ؖq)ӭ}]xqi~'pG,JѤѨ]#GH3G6-~;2JRz_U؛iB!fggI 0??ǏeY i|pt B8uv{ߺ˲fܺu @V vɥd?\322"ݓHt^6ɅДGRl13= c轇LGz[Rty1Ξ!T KVShK-'e#Efet1[DO{ p,V'O1^ *OA~'o1es.W=/S!HpYb1.\ښgq~ζ,, pT Dҿp΃qtOC$P,%1jF<jχlI$D#Ït^D&i J)"r2qQ%arkh~}{՞-bv.ffEqҩW{igt3׵wUGw\YH2 g)X\\" F(W+4M Ôdٙ)yb&RBaG,vQ,gϞerrOR!Sי`ffX, wcccr,JO?6+~pccc2 b׉bJȧNbjj;w>i߹y7iLLNjc}"B^# ZAUUNΟhFKJb ںqr-aV*Z瑗cqp;2?1!;\]V0cAe?G_U{=Hoi:m}9t=e/v)?~}}1FCf RlM}4efaEyڭmà1DmgN&Q,dh/`S|Wǀ{PŠݬW (wInfKٯt.%MxD.cWjܻw4Kbt:v 2I\Bg(T]?hw>{,4 LEv&rq$-Ks|~ş Dޡq _ȑ}j zp N<ɝ;wXXXs P *Qz]&/Q2MaP0:i(D(P,eb|'i6`Luū4JCٰM/2͠sob~ν^Gabv7]ʵ:ufXёjBѾ74Ob$D4SSSmIn&H$r9y ( Ú?3ς0 VAq~Q*0 "f ##:Zh$O*>YoPg)9x0X][2-.\8H2IagDq$^M~rϞg}|/tk̪g==wshZ|ˏ)Lу:,ZYS)}yR#TBAi\fqqntgnnM)-8Ǔ$>Of3MEQֽl<Gg@('y)H0KH$۷1 ;whLb-!jCFMӨT*`M͖ej0:x` @6#8.]DUł2t/&1%CPzڻt/?"J"mC=⻇ɦNţ7^gfz{ŃD!4 w2=s)8nAr<+^O?'ATU àRJV'E9Ν;': dH@82<+B&ZQl6u]XEQxwT*|,K0MH$T+"GQfزLm%;67zd2N0X^YX,siΜ9C)nnrK~w:Uq^W'F!^{7_cm-~DQcjbH8D6gP NCiXXt:kkT5,dyy4/^f\.GP%@EIqz0x NgO})0 ³dosȘQ[]]Ḇ'O(߫wnAQ.\aj(JJ%i3M%YoHuAY2=v'}JvᕻK#TTV'TU%a*+.NZM,%RQ4G&׏i׮] JTc Zxga & 0t=Wwr-Eazjz|Z|_h" ek;'x=~k2DNYQimm)f݅G 4%E<իH$0 Ο?/EAhssSs\17ݑpj!|!}@Pt?eWLLLP7kiX4n\4~  fյ R <G = 9$Q.W)6OK—i;րLe-QCǽĹuq^HODh@9C /^򥋄BA<~JTf$ 5:Xl ELs'17;X:%wֲL6Ȭe$|ٳgIܽ{Ní[XXX  1??OBQ(}n/J,.. Y>@-!$9ܾ}d\"3r~D28>uP)Y,-v`d"A4ZQ(VkR?wa&Oy1;=^9iq UQL`(39,slnnq}<˴I2:IJD`V~gǞ){Rg2s|]FښLXz_UU\B4O?ettq;L277G8>S义l_tT*Ú@Gd/S'"ŗ_W_/EHFEc4 E!ti5 85wd2Si lV$),n~z}\UU^J8O?;}fS99o=lJ~'^%F4%Gn߾MPrߕ+Wh >|8MĎy4d"!H0f, MI"ҰT*R*Ws>}ӧOfmm ;EyЌ( gjjY813M `qqGzal6K$&IS}ETYZtYfOyRvmryv luy +^wg%Re|bɉ FGFh4,//KKK,-/cbdd" u6MRv6K6ecc:u(Eb)a;""- 7IDAT!"*LN333%SLˎ_a ‘ I  駟z҃²,ޖ!R{;XnC% /^PqY(«J(Vq ~_Ȍo@?4Mܹ:yMMHҢc @8,mm O5'Lt8hX,߯jqjbk<'  GEDUE3-ˢn+-* JJBXjbaa¡pȖ,* rbLT[˖nzUX4ccLO뺜A0LffIc܍ʾ vw,Mvlkk ma/^C0fUUIh6R@4RW\G?MBwܑ 9Qpe)\rq|J*w BRfksR[u%‹dŽF C!ӡhNm`YD64 445`=GQZSTTT*ɥ')({ BLMM01>.;.¯TR٦E6eeemIw'fzzM?E弜T* [hxQ 3|>鴨`w}>Bŋߧn366k{キ;pbDs211f2S}X FM>֦(Ng2MUу:`@@'54͏S|$ Ga$TivVMݢhҨ7DVKGwzhfOetdɉ Ʉ?\.I"ܣ;;,x|v݊D",//oC677\}ߝ}^ x ;H/1'c2Du}*} B{aϔ722sHShF2`llply٬'{ܥ:Xmu#7H&Ni>pƴ, "?ϳFVZ=p- ;11+W.M{{{|^&-w:iƋf hF:aׯ_gmmMژ\B8棏>mn߾֖m*|ctIpNqbfO<#J$Z) T*>/[9v#to@w{]w0HQF#h~_ׯsZ&llVe6[>x]MӜ9sw =w޵/ ;;;ꞅ?2Q˧5|x@|k؁7~??xIa,g?pOTe HDCL!֚(- Rrd3{eͧ {" IgӲg[[Eloo1:{P(ĥKdl6ˣGjƛo:KKKD(D?G1apEǀ;0A6%b3 DE"._JOR?dIh>t:rjFZZR5On9w-DDeH$L<# ^02\\N\u*g}?h .w(}{ZT ;$h~ = j4ي,!ˢlh6dv[(m/oۉŴ淟bkh~?B ꄂ!%̚Va¥yggmp{{d=g˗F(S{j&.yPg/L D?@DUUIR3Ћm;y&_/_L"Ν;{cV_Ƹm5uFbD1WRE[LW zDZn5Ejv}5AQlHXnKLj;'_>q{^N%1;::˗ |lmm)"~4MVWWe֦b'U G 28,^%?eo;㖙L& BC/οʥKd%ӧO{OxF({-hƆXFIRH[:pD8ÄB\$= @6h:zjJ*k*-{n4E v9F9 PN1gz*> p9R>Du.^H>K O>˒ dNX<Gdy/Q ń݅a(D/W_ɓfffxWV&߷E/Pq! Dal聀mGA+OU2yM° mqol60\l4Tr]\,f]~|G*":y$ܹs3Ɖ',//e믿N\ÇV)at:b2d.?7dja2$9D"OݍaӲs/|>/KQU .NeAeC8"@0"h~{Mo3΀P.[ojcz]d5lw7mr/3^H fggf2ѷܻw\.7pg~~EVWWMBAn%!Hy˶pEQ VViیJ_a+ $I:t2M/RF1*pPu|>J]K>]Ә~R*H8fzzxDNe1>>$R#;(2lA3ß?d2I<gnnN&tap9dT*ҙ4}1S719"6#a05в,je 8v+Wp*0d2Z-N: ~:׮]#Ztfgg9w_~?11kd4'Ly;2 EDb?Frɓ'QE:ߜ‚ij5y1KC`0ȥKؠT*I>;Yf{[DmGsSƳYex~\{@(T*4 `صbP?֭[v=wSd?uHēX쨒S9z i\~PH|1˞|7t:J])Ȓ;!cz&3уo!Lr`0rB!ێSC766 Dr |._:^D>tXYYt1rb(i^T*= q_ul7HDd-'mc-4;~N3^CO%8J|)oo?*mڽ1 Yw:Zv77Y՞VM/9P~ma a%xnO$7~UAa0 ZFV{ޱ /i QH}q1%CD!|>|>߮³[thv0}!/ M2^("}[&0%qD _"=|-*"u}-GW=1[_/>VI:QZ8 _U^~yO?1?DD۾a=iCᱞm;p>1^ Scɋ&_ŭ c _ /p'>1Q"֧P7ect'~}Xi|E$ 7!B//r8f/W75 m˥c"=cD8f<$<DhO<ߴ(l= 9cI#D)" @a*ԾeeÜʵ@) Tdd,/"~!!!!!!!nC|px'J4MQU4*)&(BxEq[(4%` @4Y10{qWt+!|LqѮjn-BB.O^Rʼ0-iJ)eee~$ >ćE0 !z}P\/F> `R^uo+߇p>b8tZ)#BBGuB~%RX ńw<-49~xhōy  Wv !BRBY=a7 ]I$H)niRJTUEQ4Mpp8~I Zե'Md|GEyxFJ~;ňFDQ{=HH$H&@t:m2px<^EQ/))KBHbY<$?xy"`®:Pp8L8faa`0ArQRR磬 ׋obRB0ĉ?;!|@سgt>nEyB/$g~~X,6BQJKK2ۤX )9$(?8~[^?xH4B})~[trvj;@ii-`d2 @ @J@R^^NUUUUUdp+0sio'  'B bB:@9fgg뺪@[[j*멮[ Bu RJWufffevvi޽{ܻwa!>J)++jS8/zc@0C }I) <+pY ~yCkˡuC__===tuut:󄺐nL@!B(mzzk׮o/nݺopPSSC}}d{O%%%s}]!ؽ{!EQrλffflz%plذm۶m6nJUUU@/'Kge TRHWJs Ξ=9w+~.bJKK6ZY_0??sȊ/ CX!PJGBuB `jj \>N4l¾}صk}}}8_Z K@P drrgrIN8􊞿㡾zrwoݿmN+ tZۋD"Ǵ׳w^˞={(--}_2۟9`]nX_[oɓ'xP!SQQuSJiw޹ C(eΝ*g(G"Ƙ^VůСC>|M6(/%K i%x؟1z+кLUU~)`@ 'ӧ" DmmR&ON>}>~O@ڵYπn(,`Qqm6M_\O&Z]Lܾ};_Yv튄=S}Lo||+WtLR֮]ٰa^7\4˜XfDO~~aNfjjj~ˊ$>D͛QIUٱ[j$Ewz{{׿Ά ˾vo߸q .pKcyEݻYSUUgrriO&E͛7e***F<^!DQ(D/2?|QV^/g_?i+'8s!8 [ [^gVOoRJx"ϟ B_VVFyy9%%%!׳~ud2I2ҥK@JKyy9CCCA}}=dzާV4Mcnnk׮-;zhhh`ΝرR[0m"˒^|E^~>ga%E5).^xG yv [MdllUUU|k_cEjr?N$ɲ8B^μ ,wy7 rfXHu…?'Pku]>ؼyksUyܹSqrg>7MV^7y DBa`/_?1/7nx_=(--e~~\BZ[[Rgww7W^-xΝ;~-aZ֯_ϱcBV^m_իW377GGG6l`jj}3ҥK9sp8Lee%xаc kI۶mMZwCv{166VѶsN]vCGN< X,0ڵkttt:.\`ppKkkkV7mDCC8N!300ɓ'k$I"M~ȏldTWWs [C:GRʯ766\Q?&Жㅾd2b6hhhMX^^k.elLD"9N<ٳg^bBPZZjk!]]]YZN_^Pojj(3rnݺ֭[xa<6a l_XXW_^`|||E/?ΪU\pvAJشi/줲q*++q8sMӤ~Wv044d'$e|> 8֬YÝ;wQ!۷ogbbI̙3nz{{ywn%DQصk7o޴Ͻe*++WT8r5.^ᠾ}RfA6)c=F(I؂PUp:0>Ǻ6ǞV^jnn["Sx#w)o߾??̊L|^p8iD"=ʏ~#FFF |>mreܹcGY4MFbׯr177˄[şfLLLFYf Gww7DO(baa͛7~z֮]K{{;,,,@}}=]]]b1^/7o3g`pe1 ,رcׯ_2'}}} q<#D"nJee%x| X7no(LqFzW,O$|,sR~6}\&*S೹^~P^P,.WRH$D"躎i;Z[[mZ^[ZZPUx_WW'/?X :Ж)388Xko>~~i⽾5 y9~|Yظq#oMBBˉ$ w%dhX>'&&H$LOO4gVRwDf V; [n%mmlld˖-vDdss3n?K8EK$)r||K.QZZJ]] ioڵkYyTUpg[n?3!DMA,c`` O@_Wx駳Է^0 Ѩ_xEzuK]]^GykիJ[[[zii)n۶#Ǚȥ.y[[ ~zz{{˗/388FFFF8y$PAmm-Ge||;w+,d2I"4MZ!(//g,efr[𖒒ҪGPާK)=]wxx8'wwYjղtyy9ׯD"ӧRO=xq_[9N>s1*++ioovs\aV F9K/IX(|3"nx7V|mc=֭[Td(^Mu^~ r ^YXXȃR>/tgށ@}!6m͛H\SUU gϞ à>v~ ~225kPQQ\AGWWV"=͛6ap]FFFXj&bڀkRQQAVkVE{\rHm 늢-%`vv`MW([arܹ̽sgEٹs'Nӎ$s8<쳼 9kkkyO~B2pى… x<}UUVAKKIᒠ )R"%R DDXH@igFʂv.Auu5CCCܾ}p89r+Wv0 9qĊ.sr8|0֭cr .7ov^PT`n/^|,> | `ݺuL-h۷gyf,gA^}%K~PWWg~~Ñ#G/~ 6l'Nd(7nXaOt]U4I)œ8#aH?2} ;+%"MJHs̫* :0[Qs'GWW`wvZxWRӃAe_؞`4#Gp\L۶mSN1==K,;jjyU(P X}Zn!ul( W ҟR*-qNi.x3q&2^[&xז}vÇYz5RJ4MCӴ%i2 GG֭[iLߚY7 ˪o ~ӟͯPRRO?͏9Y|mm-~}#B/Wְ=jn%q- @1 4M Ӛ0LLn&i)PHTECQ55 X9HXCB08c$(7e| ũNz~[neϞ=vGbMvRhI$|;Td9;J=tG֫Uۚ>N|+^zI ꫯ.P;vK/GÇcG߮eg/7N`[UUU@obhMMM^bMT\ok_{}!DքN+zuֱvZz>|Μ9䩧Scu <ztE,٭^?LEFcq2;BS\nz<}p]Z^ZV@QQnPTx)Md,L&8f81 Iz(vf_uiTu%m,d 6$47HW3cKih^9Buu5PT/`=7ofODt:immM"Rߺ}w?) W~,:BP*_җXfMAbikٞB'NĞׯ_իc޽XqljUiz-Hd8ш5SP֒VUWW'=puvhn܈V^RVr *0]ft!!v p=D%9>ArxK._fZ/nr /$EU_S\JD|8Drx'd\"́\}Ӛ@LJ;w~G/r|{EQxg^燔""y?nͫJee%ph4Jmm/^c|}]J8ºͩ;$hH$VT.'%;wݵgg;ζV5)^ʋgk)ޢ[#a%1WfDJ34! #1$GF_E؛S٘ÎNWtT#`WT0dl q@UU LIDy5}> -rFU]7oJ+'իW]Jwg!R*Hz-g-%dW^yeE[9r7o2>>NGGk֬AUUEԩS ۓb C94sly~Lb8tq9ZJۃsU+Z]MJDZ!ݳg b[ O ʹH04!2i&R4DN!m* !pxN5@&DI @Q!>h A+>kkk)++[lmwݯ}` _2hiir8 !:3Emܸz[lH$xWȤia'x([}d25qH<:p:=)ϨWyEZT^,8|e]7Ƿ[) Rk(=GWaI=XfD`Dq80Bo$7IOf=JUQxd 6Kf0MLS)%SSS|{JB؈kx/Lvv6/@/| Y~08zhQpx]UUٻw/333v?gQy;xf㨯OFx00ba^B{{ !;Wxkkk򗿌*;vhpHMM {W^Y,֮]/gy+J:O"҂"Hap8äIǵqLUZe1d7S5#5ӁjD7 t#PQ^TBqPnò?l)gBG #È01h =F0IM4Mp*R$TpR5 (i*aOF(h*+pVUz=z0Dr!H2G@Jۅ q# B8]މRD3H cx3@Fc$Y0%ˁQ^Hsħg̢ht D4F4!O8 SB(7Ɂ!G&GF^UMդMDGpT$ _***xɐ:ď9N|I`?|FQ[[U4|޽,zV;-333ywJ-F^ZP#G0>>ٳg__}E> ? I .dM4L0aG|j? 3r@sh\N&x_ j gC=ZM5Zy:I<\}O]éX gy)j\Uh> ѱ bNMME:Y"L\4 C(h7ƭDoڲ+D*N唖1-T"\1z$p8x駹uVVLe:.)x'-/U{BASSݤ5b%/U\h4o|3KBݻ7[.8p=!ŦMx 74D(8zF@]7bdӉwN`8ǮOut8I$uIQ^w},ѩiyMI:\pTWOu8q聅y+E޶^Sq57lj܈}vc/^!>2t੯P<#Cr)Ɗάj:7cq2>> /9N{ 6R/--sWhP0k@`|K_v/OOOovee%ѣ*>(ns @8ih(ҐvH ~ϋihCM xJJ'G-+÷c+U55賳\#vg0B9|o *#TWk3Ux{YۉO!t+P.͍xۚQ5'qBwqܔ'D„L3h>JE>ݘ o /317nF&iB2;(;_d*%>E$xe ׿>p BN#Ė-[عsg^ߊf3MǏ Ɍ Eyy9d``r(udckj7qP(-?^J"h@XGk .PNp@U^o nX<5VH0IZ(QHNphM̈EcƖ=|bO oYpG;n=@OFmiĈ 65—vB!bh@Ϫ(8+HK۝t{#ɟFg! )m"LrL }/`߾}466QH  0+HH4 "/V^]Ů!Zm311%555|s+csJ0Y~h2<N z8@I":+ir5 %^Fa^ G oK3z(Lh`D`)۳ЅD\%>2f+Iy-?S )'\6 Et&r"Wox2oK` x< B i &ybNӉL N9E~N*rک-ER=P޽7߀҂m\Q>P?BJre i<Ӷ5VВΘP(DWW$ͭ[2&,.çX_Zu]'/ZVs۶T]I"SZpGb(Ytp6!<=Ts]鿁"K!BUQ. !aEfK.,uc^&1:'Ox`tM'jI H8ádҸIp`a`&mH<Qbr'/ uƥv؁fnnǘ̌=!M1- ÉDd2ν?0 uio !l7m8+cgbiH˖RU;vPWWǛoeflܸǧ öd"I<ӚP;;szI<7x"I$E+QՁp%:5we) s:c_zPjvTUr|}'C|uՔ܊{M[w-0M|pV%2߿R~.ϭطo_nD7M~o244dOvihhȪFuV/3hK㋞~ 9P^EQSAE&<0QZ . R @9p6jnFq)EdE3NSVZ󽒳oʏ=۬g{aϺv'I3ҝ[oEq94a&"p{.Z2 >4 ۲Gm5[ަzX`i\nRC})%̇jtކe[ E*;Rg4o8 8Yv-ׯbÖ`bxx8+6 L%P(󢍻VRJz:>O[,8??ɓ'Tݻejj7,VK!6++2dxAtuuuY0 UlB{{;]]]b1[ݟerr7Ƚ{.?T$EN'uH5>)w:$It]_p:#nv"13K{i|ӅgMκDi_4)ο,:#1*[ 0 uXۉ'N&&&ƍS9??P(*|av!z `bbh+kΝ;WU !D&~zܜx>(JcXxl)Gdo"p,/ᣏBY8.C(4!h8NSwV;FI:oM윖^<^ǎ8u"r߀ۥ0ۇ;sA €tøC04N@Z~)ȏB[ObU'ӿqQ[]  gg`URJ!@fr !q>izz:uo|LLLpő%>|8KB333(mffP} #B!C0 G>R0DJ74w4moo399ɱcxWq<O>$/^ Y\ӈq28IP@cq iT }k׿vwd}4N`pgӦ6;c{oKMٱJD]w!s-5g/.UO:yr Ǐĉ{^Q Cʕ+C&q \jW<{fZ8v?">gO(BJT;v^̨@qZvL$e_@= ûvCݗhZ?yę3g8x`' !#cnBJə3gX^^//xwY[[s˫aTXy3&S/0 LZM6C^-Tuѡ4җTGU)e! FPn6vch#?翬;VRRZoVT:ıc@k3իW),J^t-B7MRϥvbי45E7ˆ&hL0}jq8% 轂w{˼ն_+@ȏ^ c.?D IwvhH&ߣ}:\FٟeaRZxi׋B@ۈF8?K|w!l5Z<@/bz osqjRVæOG廾 63555LLLwAin[c/VAêy)&>^vJ'auuu8ӧOcog3OrIo^yzzY.%+l"Ώ4L0D!I"Y;+%7<ln d1U=:o5Ef]` ,i{ԏ.Q;rڡ9zH iE@!H$"M!nlTT_wm_ *ltcܻ!:p@4[$6E hZ8r} Q3/A~OEY fO|Z-^}U_^?;;yW+˗YRjqyn7sZz~vvC ]` 5iaa*8]8>ZNJ91^{%=z .Pxwxbe1FQ6sEv@D ?h1{oŒV'MbLm3B4Μ&ε9¦z3 k5q5ro&`[03EQ¹۷HWIn@,t;DJ A$ @iHZkH _@>Lx(jk@+t޹N̩'9Z*_ \\@w{$[:II{=@~Az lngא2@KzTasKKK(7|L:'7-ߘ{(xFQ"EwږBJ"+E^fBh#$BӦ֢P_:Dq@i) DDIA"ȁRI"fxmMr2()ѓDG2pu[/AˈXo$msdNS59DF4(D%5Sco AJ~K4=]=)xw+03<LNN{ﱽlƗLOODLOOB~]B#G7o6UFFf UsM8aN_d%xcˡBN8WI&?8Sg^cz-˨na shJ5faJ3ЊDim̥DkPH=+ *[uZ0}(/eɴ*^3gxEd$/ NR4%@4AZh4B:bK٩;qI$/0U"rt >OrA{=^z%YXX _ؚ^rPdf4MItF$oz=;Gk}ݐb(qgW\s=Nj/ Vř3g\t˗/secYF4 ӧɲ ),PI) NR輹;c᳌ giOI^|3iuSwSΜ?̘BP?qdeǕmQoO16];w--R{,wW/ll.U` : &0qh ,L#aQ+7&%.ҤqB$8!ĤI[q>G/(+q7S`Ȗ7"k5dF0$@N4{ X|zzE$F!"0':шQm(W'U%p{'XZZ+_ f(uׇeCH }eJ&o/Ѿx gנ" aBN,'n])bMT $>NxM$$VMǴ/]֟'~駈v p C =QU,m8AT!XҩBD(Pf@8aS&C+`Μ9<Ǐgрl2$pT&@Y<0z^[+^o/eWk:th3vڋo y瘙!MS^{D5\Q > >g  1>*g#Br^~n糚^;m)2Aep~KH48zhr$l*Q>IKӦ{&;.3BimP(̌J@)m[ʍjYYk?#W^ʾe4oР٨:]TCll f 禍_&sFcSS" %<ϋ/W8{,ϟd~>\ 3j~ajr_y“fggWkc$I q0 >ZH)y7+wsq7L8pgy| |o]/Hwol6QZO<7RCsO 56Ou0C6kAWF4~85A۟;$*R /eLm:Ԛ,'1y8AVe7ooov.:QIJ09i>Lq‰M־|A롔픙PEMn3>:lL? ;]$4<޵t,~j\E_`@ֶ(|!"€EfMFwY :~>*>J0sI_[o548Z\\f*NgHt:be𩩩Z_w֞}SSSmN^#dyyyEKKKlnn:SǎyɒثШS;r8›129LzY@!{˟sҝF%T Q#l6YзqS>;/[ܼE4l]aнi8daZlnT%nwؼt/|KɝO,>4(6_.GyxuJ6OQnEOķi@6yx$7@i ^AaF+\tr8rq<4@ܽ{|]U쾅Y?q8}P_VaHg ~vsF S1O<a@>n|8^pR ӟׯWA3? $B QGcйu%6yQ\Z-iD{^C0 h4 &Zi:H}ahzQ)i@_}Y^U_z7rlQB@Rk_΋/sOv_}t / 톿y.0t$ķVILК#@TMD؀M9(Ci{!5fF1$IaR 8R`0ko`gB  ; Ð(hۅ0QO||gorq_'v V/0k(F\hx3_Qg/_:wޥy`}\Zv$aqϲy*%:+wJ0$65HwWi߼ŝJwݻk76ol2bEoc;t^`c4Ih<@45Jw{h,/sOW+ǣ''Y >։*~_c1Agz'_,](Dk-W|S4;Y$H /{ь^U`ju+qDk=˚|zzJ݆ MFiۈBّ,N,2p;K&oS3NtcUo" CȚxԒ P ;e B(-#A( -^_w6‚=01~3xFͽ r8 @56y=:|LZsY2ڽBuG;;;n̂Gn[qU4,e4JA N/YHS?.FB05%jus&Towa{޻>SX\|TN $C WWr6FgH{\fE&$L^B,TFڧ2d H]*o5R) ! \+wC~y PAR/9314ok*x㮱R~GQdiI-7a\M{!)%BYOEdIV˺pRd08Gw0]h^;v;N1ǎfŔ-q<лu AƁ3]zojpSǏgx _*M`kq >PS{ jG'C&}M^_fW'$ F1|mR\;l[͑|BG{./SSS[.: |1|*>}hbbrfw4<) mMDI*5rds"񎁳,4`"#Ȳ|9fs'ȩ<G 3Z X ԚwCXm 0>;hyC-2A2w5NO0yS TJ[, C߷ B9T&ܦwא f+Ɂl8%JfunWK+O|(? -> .XSQ+柨,܅7*OqtR)G港$gSdL@һ}?Pp&/0wYJr7gN_ y"D1Η`̮3Ia~h8q8n[]#<|xAaWL6yV|q' tAr{S?~efu-aAgt!3PHBl91lxHc ~t@ngT[LQ>F1$ 49&/m%RBF^T *]Q=Yu **8ai99FbUql~|vL`+be Shm*̖=$:roA5'} j{#t ТEbM,.]'87eZ1q}Rga}V(&k(ajW73P, *} Ap g'b*G"|`vc*z@S+6\ :8p@k}h@vU"A#7E):L9G s 2mm9 9Mqw@)jǖظt'I6^{ݶ ]#ˆ`rxm=?3w[YR07^XzLih#F0h8!h֍@h12oE>bwO-0\=dQf0EF L#w4P Xv酪zC /TyIKitY:t0G׋FQJ6ҋ( Q@ _"ՊT+w׸—i MƒWhBzy.PG`z݋KҍMw  ˋ;}?, |-D&}[K;Ú3:I|4 V+iݶw<ߜ; ?q}߭JW e)Sizyl *I3vZᴗN*'&Dt`k׈SaGiԌ|=:Wf Nyݢ{AKu/O|g-ofATGd {BbF-‹L>lVTf> )i_Fr+~o}C+AɆ 8)@}84BpBH#3poῌs1d@Q9Y޽&׮*e+[OSÇDiz|z*?;C>Ci]DR/H^^iZ9&AzQ;>KҔR;yxU&H3b^sU<_zH pJ)|BGf" oAK,e?ν:X4Rvh=_QD{s72jiEs$5|/~]O֡ӗL޵k í9wp_}9e4dIA7i>%OmګphAQ<- Q{f@&Q-XL/Z5 2N@j6c@9((샡M{I {3 o!b0d&'Xq U&4#)~<oE76y"Gt\!2xhcP/2 e`-c\{ξ2Gg24:SIor AЊhv@| R 1xFqTaWoq{S0u{g Z2G`W!q( TFi._w8f+_]Hm ȷ2v ΄ /4Zٿ]wڦfFҔTkS`RJ:w׈]&JlSft%ILKV?; -DwΎg&8?BiE6}ƎO͗Bt_IZӿ~f _{'9ퟒ#_\_kz&Z)P.QNh= e崛_!TElQMe'kbnAi[6 G2q$8F adjD2U~\k%}63#"JQb寢(D &NI;*a*[.^&^Y~:;`|.k;ξl?"ݷ3aM8:͢vvtKl XD@ MUsQ˭Rqb$mc2Ǧn[FP `RnR.;* i 1nB퓊I_Q5i h3IRh)M%]!I`k ߭Hz=XB+u\#B0y[1 ០xߖo󻸿1}F aKa/0 .FmmN`#6 `MTlK=ĐhhAČ*r^K|P~JZ/Zf7;٣F`}aդЉ6 )4t+al4 &H;w2 CA BJIFhE$"p> 1U`L睋,$h6=J{9WN;Df6މ}0(#G]Y(`޿X ܿ!Z.'C 6v=9ֵfwIw:Jh!:x$i )4HefQ ջ>Vr[qD}_b7z=E p߾)Ɉm޷-~$a0 &vW0ELQmLrhz F0lA ]S)_pW s.vZjF|DFNFzn/Ӝk%jS 6I{]ISS0BI(R-(3 w#AgPޥ+ۉ OpԎ,(O\d~8͝6(~|hЀtN@4~V')AޑLbAIQ@(.U!^g 5ܯ/=P!knjX3gh~c%OfMNp;[hLsazw"єQImzi(ifIo&Ԑ ]_j9Է?O@kdV ll]C649IK#х)a=9{7ۅٗӸgZj)b.0Ր0 HJ@IADU` M|'[K sțg[*(յ>x&@H;fKx3Ds ˤ@1t@4$bIgĄNݦ{[hzEsvIbB'6elԩ-"^^AmD֦:5Sl=h'pB 7 ;i"Iˆ M /$ $2[a?t2:Bi0Hg[[6~wo?P0? `*Svnen ;4bB0z)8!`L&iE A1 ^ Wtl62vdcݵ0G3юLnݙBDfRHR I͙! *"ƴ-ڭZBkLSV]]x@$Ud+d4S\!LRK @e{`:q&GygF 4 ]'#0 85[Fd)cNtkdcNV]ysoYrp.tlIff":H=2sbv_ rAmQ_ =< J[]}@iz= hE`GV !8'3Fa)iE IDAT盫շJwHwZ͔FF^\^5/2gi6D^)  K!6xI(J 3_S?(~ a`:4Ux~(T00i L@ LMm%MQRYk4 s4RĚR@^Iw@{zKAΡ;1~ Dͼv'DauQ\5*ao܃׫}oc/@y.EH%*ꠑw; v?G$Bjv$hMhM!afRz2uK hGPwO]L.7AXW0*e˴sj +EZ!$ R@$AAc2@ -)`lp6s'lo_;oHۢ MiZ/ITCw:ݞRoڕo$Qj׭W ڧ(=#DH4vǏ+s_!ec)RQJ"҄ZFUHZg%525~̓\CMgcSt6g½mi5À6)9dXynF% KI5QM Bf? !1Wbmw=!%}G12 '2#ZXy DHĢFj N5t TJbm֘dV3Wj8 E(iJ~x +Z.LHVX)+35Rh̹K,b Hl^n`/PUy2P@[JChǨb`_i2+Cmd#՜f3%%Ԛ TqB03M" 2Fh 2d]Ȯ(Ƿh! uPw; fx4%MN`WǏ&[|k$ {L;ߨ<4MGV%_ږ2ӎF`^xC!#MtO4[3nN F4!Ibꓓ-A%L* u .lUg`ņD6~#eBjDLfCvͺ=!mD!)"G`ۣتL܀%5% {>4mv:H[+4AvWD%1"t50Z^ ej 1(ږQ2"I~jd &&H:Lij&ZFTU_f(^W$j[|:!=u͎qpPd_;Ks%^XM=hB"oX@c{8sa|[5ȨF&MՆRj 0A)hڍF̭gYOY5"Pim>;q,hg Amnޕ+a M~ȧc+ݨ[ , v3k!X g_Y)]L TخTvXTPa[&NHIJId Eͅn%J ƶzYv+?*;5;^h7כh?rݾ\Ik3h3R8I׭iP:Ӹ}QyF Y჌|$Lݓ } @yݑc,f4؞QU,,$ \er>Aǧu)i!,`@hi,,SAJyh:nXJ#Н]bߓ:YЉ9#q ,F)=Lp7Pľ^ݾJhsjYQvREИ4I 3n=B4E"Ү$MN!2U0!T<$@Jv@m4o߱fETy%_{A6!k홌 讬'W4 ѕPJN #v1,IRاV,Hqf{!p[Jtew+Qk|mpg*@nl:tsY³= -7"?)Y)랺fy>EzwMurJhm&LQ"Z\ͨo[K 琇 hM_Gmw? (Аsص0\YɇAwJ?P bWj4R})61R$VBH B^u-!ROI.ރc~k8@p݇Ce,C³7?:W:W"Y[;($N XAUJ*BO$8_vo1Gk +}0?Q&dB( e1>2nbvFiC#R I%i \H)!a@81̀ym\YeO4I܃(0#5m"G f#}*NԠaY[_Fˀ`= kS)w H/?OpaPTHQ 1/#_*5/v=;_n[Xd<߿j6)4 ֖^Թ0Ȏs@3FnL9s@alx.g"+2D YllfJ=3,E)M9KC>\)F>~#Pmj + FCb7rfT0|? 3W% ="sޮ+llRVI$$ !AA8=Mjl~\Y>][e>>ӋB[ \O>;e-+0s** 7o4h؊ţOo^|6`˜{=#{!'/Rҳ/(_`P%4%ټM&:$e$O };*ݝ<&I,2a'C4o"T°,:Hl&LQiCm;-y\C* 67b~X]D/%=4 ^tEY@(NjsdWwUXbzz-lj&yRu ;"󪠚خ/ol v(ǩ(Z~J qص=+49H10eaZ6mNRDJV޿Oz\ޟ"Ͱ/=!ͣ"`+t'݌=VDp!8yG^Z:eydzHJ>ONn\Ѧ_rӠ}]{nJ`+sHẻw_%]\\S'J*,,hnV ]ecܚ@E5 <~&|5@F8p'=W ]oa[+*'e )CKRCgNﻒ}7-7m88b.j%Gnqs=WS):F P\j@|7"Hz=Z>A ܝ&+czkò1-p1:nF/phëGSLmt1ݿ&D%]UA>";ﰣBv2JyMi!@fq}1j@N| }*:Ecq=*|V6 !vC zlItJMny>ZFO~$_q!qZ-w?J4-;Q\-,vl7őkkܤR2w}9l6h",# o|j@`a_xj7Tnx߽Ok-Y)䐆JK|!޳|d|#.`}.xM4|{"<q`}T2@lV(g`}w/ApU#N\ ;d؊i;ޟe;W0h/-ݻn &ж;]xؠmD|(@"ҍ(2#%y.~'n߸~ b]wν^:|'xoT@se%@:S ?<:BJTo -GrچH 7Ah{q.;`sBҍ{y6wUEh&; ]|2:9Ę%m(2m; \x/TeO8E*)@7 ⡃EnLEA3~/{T3:ݘ"2q'E.*9qƒ>غϿ /o*ZNW)@Z#nlHD& ϕzC*z۶qlڭdW*)Y|k9ՋH@v&"pS`q~<ſBv PHON`!@ H1Vbozb$$#V'|;<\Wѻ9(QHX٘8Hwc#`M)Qou _ 0 -%X6(]1RF8R`¡78uuJ7%H۴Yc%0[<˿^.^? =ag^`?7!r(#)FB-5S^%.C'1p}ɿUvӽҧ#m/N= gF շzlpGp_Qހ!К-D$V< IR@dI5%iD*wN[5Mַqߋ| +W8Nzxd 78+L 8( -J T6áKw,?~ ^\vS{"SB՘<\E't7#ڎ_fҁ˯#HW*IA'Gb[,cG(NM!`o3§8^Ck \ޗms?OYy]fHA4,`-ro;:sZH"z2B7VJΩk\ | ]flcI)FZYrqDIhI{9>_4-Z߼sϢnm`[M0E6J{y'R9y{zTC\ Hr=ǿCC7\)};0.B9/O`ѹ;=\NHTnfv儍ςvAgs_Mpi]T-g>YQ2.{f #[n ]ݭ%i"܎H{B`Wkn.;E&"ß?}N[z KlI]2CC =$A=.^fWuVdV.c9?4{Y7t]CSN>FVHv7;Etc'q`x}sA$<6"|O?&r){t:^V*y7]gzܿZb6jo6f8ǡP(!0֪RNSqTBDϓZzy!>#d iKU>۷]Kr~Wm/`7L~p:%{,.qcz*%JZg_`_EfI<^@4o ݶ|" 4_{XѵrG2>"e=|puRP@Ow]Ev;t ,0էK)d2`- g,1-BAlrlAKz&"qi!]k; }3ܽl&+;ijk7~4#O?IkvX^wߤ53?@M󈎁@qFѾqg4>Rx,~2ͫp};T 8~oU{,"I/PR1m!fFA<߬:8 x l5kk[APT**SߪUQ\.O>G\snU I P6֭Ղȯde c_>PDx ޽L~Kg8kU2 c_´h{h}r3w_mLs|̑4? On ҧO{W>fw- [= rAKoƖ=~Ep$+7)Math--jMF2{9}\[pCJI8vqW__Vdǵl TUell,8""ﯮrvP0Bon aiݸ10ZG0b{iH$q#H /ѷ7$Ȥf8s}|*IBTd'xir;0p-V {I䲨ZDzj?I\kuޣӺqn8~3y6˯NZu]e ;+ 4Y 7Њy]W26^`@.Avv@aZZ,rHZbW:wh߹]S^x/^faHKYEw\bzm{WsX+!Gp=i7j`PVX( ߩ[q"RJnݺeY,--mp8Qt[ފpvP!!H]֋ຒО"kDX \Z#Y!=) Y={#ok7?p)I.J D!aB;}o1k\9WPO]õhB0Sy11Lӹ?M57V.޿}r^me?qCVG,_"bCB '=@>e)Melx c56R:![{PVi4%<|oB[%cEK_׮]ciiӧOs}޽-'V9l[0s=+$qoWkhH dh}.iO[O EL"u w1J?ӯ4 ҍդOGtrj]ԏ́IgL?d^6QddOxUdF[>K*ݓE:DT?49CZs=3 1jZo/--a,.nEg?Ln'[2t:-m255EX;ywK-ܝQ<=X;^FfkN51*a)JO _0eTa[S'dTo3/f2 x!R+F!{1gM{ȟޑ1 ?fTb#?~EAt##d;*BvOJ>ŕw4'NÛqm¿lDOmN7.QVZ2118t3gΰ,=\;}cT""pQ(P$iqFr]cVu"ܿ{]4|4IEо~態 :¡JEڳv+y@ W-JB/9|?|?Z?qb*@ز"r/Bs`[Hr3)\m[4[ᯣ~{Ǚի #%',DzI:zf$v>S'* TZku5TGԐN!=L$D~}l7nS۔Np~ϼ9TvI}94}_ED:+о %& j:u78cDoػCVf)McxACou.e9z>| togsDq :ґtR#0cnQ{6ȿ]x K>1F/Pz)ڳ.O5#4sO"/I I;!CX):'n)) bkiݡYQm'6o2:<t:4My]&KU%?,//3;;ǏFJ` QTq׭ﯜuRJkSMv lŐ1::oD P.'Oh4{nd*pܲojH"bؚJmOST5PE|iABC/*pjokcDP<Kqu)>NQx ܣs\{8VXA92HG~GR*HG' q\铸: qΘ} "5:lhc!_ATW%B \-T*N8;94AU4T %A(  1֭{tNӾ; QMGvڋU@}@Tصjy, R]50L\(cy .qVVVo>r sʕWsMAi$G[ew^ Sh`߿ nl6Cg`ɿxܴg[222V/ĪR)Z;wb􂮁ϻvQ%ܸ K8t?ޖWE#Ǧ} K ]VH:+ jܯ~sMHP\䗆!˱zB0<8ܽq X~$ RbϽ|A7~< nqq1Pv7Zi'tmm&NM`qqT?iFpCK@86 Hg.\03XPEzF@Z6 0REwbe"}O'I=f@6c ;d!H&j?݋ѱDȦ''@R`V"P֑,!xI P.M[(¶mt]GӶzWRA4,ˢha~> GlH0M|/1DUUTUjAEwLø-D"wˇn R~a=scGDs>|)='L% NOIR֍;AuL u_#">˱:A.^IZ4ͷT*ʼn'tRm%4MСCl쉛jnwqt ;1_NE0hd2H?%No}+ZT*uL3yyj jJ@qʚb`Ji؍'h]榯qc5]ċqBĸo^\'WBp=ףxZp+&œ~~?hDB=v\(I&up uB062HJ@^s˱n>J0M3ݻwqJҺ+vW6HlYtOw׶[9XbIpVu\ ZD;i1VF D˻ceNr9_/2Vϱ_/L%,{-h2KWQL41ELzժH9=f'@ppW. #:Ά`;[:![qPU]0Z=F#!JK/aVo:ewT MIˢjN]6l.^g3%: njv 0扸W1 , D ["I{ jܼktLs{ w:Q-IzSO z*itžP`Lfm"2 f1}_%tl6$I@u/ ?l2 Ne47m R3<5xna*ŐpvhdYTUE =+[jZV ߗSO@O@O\J.[_52V0+\>ܰC2<Fv:{Yڝv *"堫z'[J#zяD# r#GܾX,c1==aD#zEq3l_wqϜp \t:Eץ JFFy%ܵ>ho{tdί*O<t9,b߾}ݻ%: LOO3;;w876K;ޱ#cplE2LMMQTVn[zŐmn#BA􎁑">~]3=i(B!&;wO?= <ѓ$B3 z֭ JG K%=E SI 7((BV\}= S. l$VZ 3*H3+OLLfi6TUT*LNN%wb&Vk'n?p-;2`7.v*;RuC~i)BaQ-:\T!jIp$]X#м{^ܳFG0<% qg !! `K÷ .Cu9zܼ?Y5^~cP.Z$5*  Z-kn6{Ե0w|fggi$_^^fϞ=JV UݎRJc`?NjS7CI=f!*lV]俗Q=Ec܋[كiea]Gݽ{7|' Q*>v |g)JTU, ܴ#)Z6#I$ϣ*w 105 GPR)n}5i EӠ'Hۡc"'7iHTn#ll$xϜ`k_iYo?d`LJOQhXE=Eu]}#k 4Rj"[S ߯T*z?qsatv/2shqr௪ڎ*9R)1MՐ')-žrjuZtx7u~EQx'E=z!&&&nУGonn+;;i [KЇN%B^z%:o&W$KHdal4`T ! kӤv g{t:MIWb.<{\\"Цޱŗ6zݭa?>\+gi}|ǰ`L:ZFp\&Ǿ E)aeu덢od/G'Ni,--Q*8z(R!>̃z^}vWq 콫v||ؿ~$X[[ȑ#A!HS)քqӤhR^Vcv F1.2KIƶmJ|"k])|>So`.- gkS#ɫHJz!߄Td˟gg{WQTJ;+080(trAFb/?~:KKK( eqΝW,۷ocQ yNIGI$nm-:rq=j133([pӱ75{YlԃR]Բ!(iYf UUͻw3p a`,< I}1!z\eAHHl` R ~iwX?)#X^Yhm|{1P)Mdq!iPt$˫4 ~ߛ4 c= àZRV1M0pwj,k'9|'FQ%\v0MUU={s4mYڦU7',BM&ˡ BQ(iMoVE&2PF 7~]?F34BA"=e$g:Mk&D1 aW,P} 2j5A׏B.Ǿ}{ 4upٔo8%XbRR*8}4e,cǎ"|4}ii7 uk03nW`A6cPäRQV;Jq㌔FL]U%4 fgh41]OQ.d3npI% O=I/޻WX{ ZH C(9`#c\ 'A-)Qye[^XPS*WϯIt{mR׾Vhzv^wsZϏ90/^Ķmyn޼I6ejj 0BP(|rO jʝ;w>Im& s%UUAQ]'Ro\(4?mǍE))ʌ #Bq Z9C.A:S.;.i:ORzyJ_@:6_wh;'K!AėFnA.:Kȝ>A]^}ޮk4IDATE1,J"b)aZV5nPB!cبgtX\Z²,Z4"F(:uBKdttW|r!/Nׯ-gvz"&;i (+ NK/' чȈ_[w)gX]]MЈÞJ+% Pש8J^|S簛Mx2K#9G?q 2(<s9zczlv+0L֪e?h$(|>T NJ7G2jI^}?|_QN>M.ҥK<\|N|,7nta x]_~ %|( J%`wY]]:t)Lڵkɕ<?g&Y$r!+_0cqdR((KNӋo4Zȟ}S(>aw~LkA.$1$#Ἇ1RI9D rgN;~ %q w/R"diPUשVk+af{D?ctX\\2M E?(8Jod_Rt_r ͛7 ֭[4 lÇ;ͲM> ?Hp7Pv'eowx ^~堝sq^{5ϲAM0j;'6O6-ؘo/(VsW˕#d2T]hzdlSO$w܇nӾutOcab-`!87aGCCi6i|p#W";r|B!OJh4[jT M(C+6++4nō exC$"N~NW/ﴑEx[a sQ^Kqxxg}~h:LjO6c :A`@B>cttċk t:h#<*g94Aբl\MZH!G9zkr}( H5Z i^#!xME-%Liۘܛs[i]IM:fRfesrYrHwkZN^Dzcܰ@k4$>uӷ-{RWsZB#~$fte>I)Ç?M v(p wQ#!π[%B|I,f}]E^`ee?NC݃id T* j,--zAQ/pp)"||.0MNM&Ymw LpC 0@@T@LK֪tW1W߇q4M#v b&!I{DXlu<)ArkM2P0<4OoOdqi01nNwzl^}߿666Jmi֗laa!c,pma=`r'(0H4!N:+\?wl . djjHTZB+O[6)P)WBUna_Z\bVEJ'Pt]'͑dd3dYR,4 L²mlƶl c*C5 P5 AKinFd="|xᥳvnAI2t*w'dlZ^Ɲ6QJ){Ru'4Mĉݻ7N~#ďAT>V;]ï m~X\1 vhPUK2 gywX]]ٳ^{ 4ᩧ_O|C/\BVTTP!5h+׿d5iZs8pƧ;~eXiZXE01 ϾTSAXdp`YGUn*"|W.y'X^^޽{m:ûY1_UUXZZ 8pI.\ic߾}d2ݻ>,Zk׮m'j` -n5SQ%H+)ˑk;lUUW{,c1<<=6>CO7 yTJZR2#)HI^BInCB!$ ք`Us LLLp&&&X^^NXMG^RE^Q;ƙ3gh6EhtZ2==͹sHRܻw/]wIa>DN˲d2I෵B2ׯ_TM"zK$$}}xxJ­[mV{b$bH f}k3mw#GVpw;w@x~,+!8{,s ;I)e||j~pI;h }ap1* R 8}}ܲ,=Y---Q׃`q6Eshv0xw0 H5$Txj52 DIǀa8NNI5f den޼5%U.cצo8lmmtl:|l6뾧 s_C+x<{P ɯ=""5eith:G2 ˗/s=;Q'R;6zl~'ͺmdY/tw |_/0N]>|x+T*199I"Lz155E"`||˲d2d2C111f25Q.LEq?+y,!ð:;qX7ӏJQjdNe666>zgrCY^*D"^V=1t?j+~Xty7G@}EEXsJOʇELdbb72hgiilf}}3Q0^A *L*!hUß: Arēnv?anOFrm}f\h>?8xR+hd2qң&:*_TZLzU-=~3&^2  ^,1m5.'wjm-#)J~z۩/aHC|0*| ,a&dҍ4sZป^0j5k |{( HK"9Hx\wZ9oЏy_qqZ^մe?h |00S#HFQ@,/!y9J Yzyq7:@"}+FUOu!m8e 8bv:Et:o+Anz=Oyn'C̨ Y7ibY;ֻ=мяh2bҘV~/!#nj!ݯK,s4Mwa?(qׂP6Qu퉨VȬׁ/o|0tL,4MW(hN͐4|q5<?j4 [k iZx"H_tZ[ +f<T&>-!s `p$[ o#%AoӸj^bƀO q}:R֭ q@7Ӱۈe︦odL %oB&k*b`Og4,O#Bpi0jO%b*Q' OcH=#s Vk>"/ۍo< Z)D.!b0x3ϴV^ۍ- AJy]OʙO|p>!>|p8u< x`mRIRi4R-(~BP@ `!]G?SN<7/n-w%]+?B ~6%HXBgvI$rYj;ﷄ1RVHRH)I&R[:C0$D,߃U?iӧO'nMsOV}o;+ }V#JL&\.몪J[[===twwˆ hmmX,F4p!hR"DZ3;;(###ܼy7o2::J6XA#B h e߾aX7noGR~xT4/'ZD",DbIhm-en[ne``ñḒI`9BX,.\ŋvՏlv(kA ^ߟ8q".v c@:t>EQZλ9,z=p\ر{w^C$Y"k jCx[NKZ˗/osy}N~k&g4߿c `rBJWB- 155 vfc=z244|5$(Կ)!H)ԩS+ήnZ[[immnePJ_`&5w^?fI sZZ[[9rG[.+@ZG!ҥKꫜ:ugϮBBkk+Piu)7|ʚ? `e(xTQ lgBkXcǎsNE|5u2MӚOQ،ћwh$!sSU7 VOR+׿MvA,[-@BӧO~/n28xayfL&WP(ıcǸ;lLlZiMc]qf2ln!$ y~_qܹUnikkpX7Q߯H)f}^Y_w ڪ\!7昘XUW!z!z!\.o4NZR|+5;$ܕJ|>O.#͒f1jr EӉ|>Y(VʰS͛<3\pE!ѱ=H/~Nj6hR B)B8&F㴴FWW.kt:q:s󣒀r̫3<Ù3gV b1:;;W$!ćz7ߨo|Cq_B󍂟d]U;;;׾qAWTMӚuactt@I!EWW===  N˅4W㩧◿AQ(]]]l6"23g/,۷o;a![l}hho~k cWUK^Iܷo=7o^7%>2>k~ ~͛7}vv؁i2L4'֫ ~ӟtŴdAgg'hto[?>([o߉Pk׮;E;.;yS͞frrrm۶ccǎdߗekoT/_̻?v.ZVяWׯ7H$@"`zzO#E]v{nBPSvxBH3<O<KLߜ,Dww߽bAZFPh3 gΜW_Bs#djj !6m VKK wuO>߶m8q)񦥥y=wޡP(ݻ9y$sssۿ?Vj`~Ç}v+Gʙ"]י?!/q:X&-{w=FBԵ_Ů]ؼXO\vmYǑ+_ wٸq[瘨T*d2riΝg?O>$/_X=ebR)>fffB͍7c˾\tvf1}vy,Zd27Z׉lܸd2I__;v`ffc737 qݔeSm,Ȳ6a0OO8xgr_(u!dSNo/s!~BشiXSNYВqk.BpzT_{&DRfx 5&ZZZXsEaƍ\~]v*/^dttt߅ p9r;wZ~ÁnKJٵk?я,jʵk׈Fttt, J)s_iB;رaEQ~ ,gggYR`"ȑ#|ߥr~\&R*Ygxꩧyw`-[pBPlDj1>>NTbppp>͛q8N,R244ĥKU;::V􃘱FǢy> rUك֭[xR^z%_N2j֋l6˅ xp\q+40\"Ҟ={ʕ+KkP NzZ]]]]<99]!cǎB-orx4//x-s{i\L&s=ǓO>^N￟ 6H$xw-gZΝ;y饗'399I8n/[;:[n_ҕq\nȈEfubݻIRL$Jaٴi׮]£By7p\l۶zI$ZZBX$LrA\b}ݻ z\,pgϞnjE_MDXoq]w,6a:TU].\EӴvttjjjsݛsO7ntvvv/(Bk׮-۷˿lLoϲWmƹsv횕g^Su)Jl߾I2lr5VeƬ?,bjjbȦMehh!T*$ rL]v}v6oLoo/d22 P*x<ڵ oiz9whFggg߿K.5J!)˸\.N {!S.P*|2/^Sf'w/^lD_T|M$R>1}^&n^D"ȒMb_JIRP(Pu7|5}100e;;v}vKxm۶Q(V,㷜ǭLH3yk%mm۶q…&CJI:FUU+~^_n>we˖_ !9!R7n,η-~&mibњϞ=O~㍟zjx<8lܸ;cUU)q\](\rgw5iK[fx^oζms1<< Q1N:E.Ɓ58q8pZ_a5TU.]]]]8* [-Ж#`0Ⱦ}M2u^TjuU3U*mB66v뻲b|n4GOH)]w*b|||M800FsU~o:>x5׮]!عs'W\AJsM$ann~{̭iH!wb_G-n {mڴP(D2\100 (֌k.E끦i\~16l؀n%m@͛ B\xif5swt: ~H$>jB|LQ/[sNocWʹ]0s=׮][x82v;>(O>XTUv;x<}]n7J#iD"~|/Qu$ZU/V(Kge̦HLKBX---QUǏrB44ʺ.tr1lb ,nHxr $EˬWzٳ~[<lٲQǀe痵Ϗ=#>˗9ӯq8lÆm=[uFJ*B{@UP:MMӌ:MP|LtX+L\a~r}{|>dY_͛gEJ֭[q:ܪ*۷s=X:嶩)nhYRc/_L} `pp'B+`/`zzzIf(|_g+B;ꫯRRU]vD²AAJc9l6Koo/SSS9sJBww7`xx .QO( =lGZ¸N4EAlE،cU!M( "4 R3^7뺎p\'#u4Yɍ%2L>r+MǎҥKo͛7$-aկZ˯loL_̙fk"[Jyt>xܹڅYؼyBYT755*bÎƴ`=qĚ*Yx%Fǹv|`{^{YTU]%w`CE?. )@jئ"bb3fSBAUPUTEE*ƽtV-% Z X|D#Ts$.M0U\Z^KU_ *&:::W՚pp16n܈͆f[c2TIl6(]A!X ׫yf^ګgJm&UUKo%x,'j$PxǗ9c"^r IDATo^/>D|`֭횦Rnh^c=$߸k !8wܪYj~%ifˬV9}2u]s+CwniTC W Ř%H&K&e>$W(`ʬ!2RuV/+dq#XL24ϛ PPHHHmnޤnX Dߘf=|Ǣ|---|_*|9VO,dۭ>дO묈 tttx<ϋB9!&v|;)RJ^{5e6o0ϟTcǎdx7^p8x衇 XuĹ!:7{!>c3TzCPAQL293| Y %uPm6|?xh{Ͷ:_xI**ZMZQT)*F!L&,Tb4A} ʼnEDZP;4qР0OMmާG^Ǐ`-GsJBEDRJ?Ծ q'AsbUOUUoiӦeXZu~_Y088Hgg'[\ҥK?]|9rZM]wh )˜񥪀PHLNM253KRBz]% EDh$ .^a2\OPK5suMGʺ`]- etL*sJ:CP "cmT{]ԌBq5J¥8N|Alq6_L9عrZ\N\RڵSY ?hTz{EQxG\u#'ĉVJxG8yWUp8L>X,,E-Cy&ܑ B5OP6B(s&\Xj^WJ[{#a ^iPE bD  E=V$uԥ!R"uuХ!9YWd݆Vk3Y2s Fn uba6b-qPu@jd/_gŗ(W DN7* ?p|5\9UU^r9)N7n|LJ/. BQ gbuAWE50T~S3K$ݘ-RMO"\@2f||ɉt]T,߂@%5J0g֯\c(gW|D( {/14' DP*-sab;W,OLLJ}}}; !<9]_y=pСߌ¿V6=fcjj/2==;v੧Z+ߺX<—m%ڦ#,_~Rf|bY4) l@4# +Z1ČH GT(@jn!T#'a+ JؾTk:FSUV5*JZU[ :)^rI@ PJHdzjY7)dn?mmD"Q:!^|/:!җd~ҬAd}b1h "׿ |+bB7\1wwC(&V*}ُdfY@ uxffGM`Bq݋ÃƍJӾ`J3Rʇ/N_z i8qbEn{]UU9rsssV{+߮w p`Ql| &fIS{{ îRzݎVT6";x>/nEMӍJRB\ըju@`Ql66(ծ'U5VըUk.v˅ta9u2|($4]1R,cjrCR ;Xh@IAPdsRUn~2D`@__U1˓O̳>* Yl޼Νkl [nի +ߢ}8pGE]*5337 ,hObzvJM#%H#pذp4%/065y|MLruR >D7)˴[hoB=иnQy IR 2N E΀jd@ aIxhmmebb@ ѣGq8Kdddd"L&X,LLNN6I\tZ BO\5Bºd2%]}v́k]9y䊉>y {q`b[oestA\JWЄdv.ARl}׃$W* .Ucw9imo# HNk[m+&f' 10G$!W,226R+<hC]P8gy`o5Xo04E(D#-tJPatlkFv:ډ㔊FoN0H GlD:c7H&S)bVR`STL]_ p .r(BRS̿r ޽{Z6U6b۶mV;BpQۗ-2\.O~$!F[[[:Gڑu]D `ƍRo@;_@U4dΗBVe6izݡ :Zܣ#hj}TbljJJ8W,1:9E: }=nnN01=M25Z >l񛯁@( &P_uw@<'2$j@kEWpm099+74:;hK'-G/9IX1>SW,?TP.W17H758 )2gB5\_TUؕ {%/QMv~|>X_dפccc׊ ^30Zr5 f) @3??ĘH#G4?s m6l6Dª?|>݆ W^p=C]h)/E=g?ɐ-m)Z͘Q I{G.iR X6,ãc(696drF' ݆0 9}(U6J!bW,h V  5|}Z>J 9 B 1|þk0JlD"E0d)ã bpt:NxֶVJ Ө6;]]6;drzjgf~+FHgs4( VOc;a /}MBM(! `Eԅ(ꂰ.Qp7>6E&LaJ\x sjuI[3Lキ|gww76l^~޽V^:fzz!^{/%;;8*Ѱݫ I*\B(^5maG|%B d.$Nǣ$3&g lO\̧2F*z A afr ,.( VV4iF.jnT.h d ?/5YBR٨ G3JU(a ʋ[[C͟۽ltJ4rzj55M4Orw:q k)g]t|>G](u]__v7νȈ8墭O_Xի R?/+z]a3Kҙ,Z)yC7oM%bs8ip9!_,׷P()Ƨf9ڬ6;~`ȏ7£AQUJ.TY~e[k$ fR,D|Hb>㣻b,vx4F3;@D-h5I\Wb4"QTZ@'y IDAT\EQM@ QQ[&c]DFgn;88;p8ƍ\x_̾gl;gd*#NL2VN Vl!G;w w/vQv5K=^I,\!Y%u(f48Zi) >p*~hOlCנ A{deΜ:!,/[Ў\YY\pPI$cTnYYi4"YyuF74 a+0_t^{7xӧO3qF$'ZkFΝzPpyyRZk>^.`kk)7waá YYRM}!w*(eeeW^y7*2\rΘ*l1rh!YZ 8vYY[a0s4N,Q32R5c]Hnl\14Gyeoa+Q56*_2j̠݆қ./&}Y{Gbueh`0bק2O]=i(OQ#ET܇=Ө|v$W~WRr}]~s]*.n-` ~.}vww+#d,np8g 歿֚ʃrsi믿ΥKr ɤ;;;ܻw/˜;w[nͥv2kLl:MIsKE$mW3Yo>iQc_xxgX:*/lm_#BqUP+`P475\"P-PVkMpJ^k0pXK\sec~4I9E$/}K\p])ׯnH/o<$4^aNǎ?^^pD21vhw>}}v]tEu(| "_uPXv NB=T2Pgu_!C`J`6 g;eҜ'rV3ѐNCa: 0.aȸd%&Dϑ0Bz)_|ot:]0Y-,[|%8E #s&iP[t'_mkG:geme {GG,ʃ=c{+fuuŖ6syxE* ~ҺJov]F(b*X(Һ/]Hw$:ΫP7 pnVpLame},\YYJGZm:4+"2mP82+uL-}t?|'sKԁ͉9w\VWWYDc3?Hq|q`h|9::bii .r}Y^}U>䓹uAݧf4&JZ (VBc8byu\k#VVm٭;{.YH,b Wڻ~z> +.Rdiw\L4xP (}[ 4B3YQ`pB6ht_h F$Mf.^G)Z"0?6$ onAx1N}҄=#Y@tTZ?\x'CxgEOHVWW_e( /1}ׯ_AzVDdjaO&E^8.)dJKKdZs4Bf{od>wIEg_*)@P̸( ]j$/Z @1M5% 1 `]Ԗa<"E$c0Mh$ql&?t"n"[T AtH:[h.]|ƍ<\x}{ ylnn&W`uu7n̸5Z&Msy,8sLi d<W|$Ix7PoooM|7pɄ ^~e^~e1\x>@T+URWx K/NZ Ji t&iF5~a8L$kSkv]6֑q",xeT 1Q@`oG5nva)c&F<[+(Œ7PvߜBŷo!L\,b?F0$+ ҩ6)VH1BDvSW@+"BAi1@Oxǽ=n޼Iկ~wyբkƕ+W,- hq3I^cͰ߿>Xʕ+>}XۄE!{1LARJ\½{?S>#vww}LA:|:ߔGۆ)֮-ؤ v>[35v{h`4HZ->PRU(} [Zb0.P%9C xT CO||"TK~2 - @+K cLtQJ3f6Uq>)H5RZWcTWmcgϞŋjF Ct:ܽ{[{2Z&XYYƍRVc^FG(8zPC덾$DQLܻw۷ose^{51}YCeYwˊQDqlu_!\!3Z6BFS:qpH,,RRwG)NRv#-(e#]}6@p2""&ʹA7S"P({E)=n~Q Pw d4͐"3MPiw2JF R vc l!\~s%:;s5߿yW}6Ӄ,) tyb+nx8{A~9u…ȿl279s .1v7o2 *Zދ}=ƣ1YV(F&q@ 3V Gq+0ɵ-Zi ~`Ki~M8`/-(,|zR2N!wB(< _Xy߃E/`K0 W.ӯ*ڭ6i![nPbH< !%H8B@(ϟWW 7h:آ)c{P#p~<O / bOeLgyc s\|^zW3߿W_}gy&H7WyEts['Ϝ8m;\Yf'ǔ6\ʘvpĨn׿uzLJ/n~:sKz=N>]:o<@'o\`<lnn7 OB{?^{_~?O>,v_'c+G|2gLq<,4G&PӔGC!7VVye6G ߿ 7 AS7a}{k`V 6T2.+Zl6(ΐ[ǧ!8<_׸r w)\s]4M9uX@3S`[~q}}?:::Y| d_vy^L׮]k lllܺur΃>4֭[Q^g$bBre3Z2i+I xX][iU?EQ?! 3E/tLp\szD>sV,v0TW`F0Z#B9b!2!L!d9$`0rxe-넃a,WjZJ@բ`"pFC'*~Ӷ#awuD1{$FKDL~ز]xo}[>}?lmٓxaMdfQ9xًRDmyd29q0]_7\7$8w\q^|/Y/7?A^+W0Nvׯ_ B<% E[ޟU\;lsZBVW-8-r_Vy awDaow[;Hf\=nJ(BDk$h[GidAAICW} B)5"[g!s &>V+k|㭧scʏ?'M*71`h BDBKI\I@>H_8 c m4CHէr6`vE:7EH?`Cf~=- >Nywl*gΜ9V|+`X$1"֘?\ WR*ЩS,LRQv_Ȧ9|t๋OKG Gvp}#QSj8@ŴVIZĮxٕ]+M"Fb?Om7pS$EFy=Nc;;Hhiio}[dYt:E)ٳgݭLDyꩧ, pٙa>17C_7Ƭn3g(`7Mvz7|URE1x56.0)tYt*xVY*xۥiQZR}X"("嗂W%W?\Ɖuo"cJcpWSR `MJ4u9i%tvJ"2eiCSW|eYe\VdZ?[|K?4wI+@;?S{Yҩt[-ZqRvcÿ??|[eva-Cjxx B;wK,zJt:LM]p'.nSdqz#ECCTd·m^xvvv^N"˲Bj#ss RTaWM)6nMB nWB]}?7:+|{풁횔e+cqVBivڜZ^2~1*gOSۡ -"b`]D'_n%<ŗϞo ~?v˖L@(޽o&]|i1Py\ D8NkMRFteR;{f\A y`T|c [<3ܾ}?? wQ_Or %ˮ͗vs$0 IDATe$dʇWo2IS$feotف+It=svO٧w`$hI64%NѓXkB6H!S HI'SO_|ĵ|j~u=}pQ*jA[f`r͛wPِ/swG3_J4P C2Fi3NbI"NPt/}i]w{E?OvZ0EkH)ɲlf.8٩>!V鱩'v_E՟,f~\|yOq$ݟ\+C"wԀR"Un~] dD EEPh.b?[+߿/gs$⥋osӻ)9ӛ,{.OSr_jr,/?`?=) w^iNS:Z~"c6^tXk -&SYbow*Ia(n20(X6 \H$c0!| T_"Em@x-c:,Nϴكƴߓ1n[zo_Z{jZcLxPG1UK$Ip8\?NKPj](DqhE Dn,m[J]QU:xe/B^ʧ>Ξ8`.Ie4Ϟ?dz`onpXğ")&-L)g G Gc1hp8f0s88ph498fۥǠ4Ԏ4:8𳏯s |/sGÒxd4}o"powZU 1",P׷ sz y!2p!S (e,pj;e׵:$IRL 'gs"XZZz{uӚ7N/ʘ;q#zh?DiEQv[WzZ|޷=@<@#CH(r`Bnż|ynw:֋l?~EK8Υs9<[w8O w6c:-ۘih4Evsw{+1J۔"d s4c\Z&``4Q "gكqU\`cmqy' SN͸`*"vww+ Fu7i/Ή I@~Yk]7B.]H0;w</Fzx& jh[9~<@h= n/ EJZW. 9{ve|f}ΒJG&)C8&b F)6 SDkcrh߽ט<'fFciF;ĉh܃c"!_&G;;{>*⣫EV7R+.`VjLp jY3MA/Yp($Gb)ZOxt: ݀d2cT}*4oP]NVB[,qϽ]?5[l¼׮3#HM8Xܲ5?6^*@6x57K,[\:7Ë-L)2aOZ#~ve`(dWW̥fv),Pf|ߵT.>`\W+VuG9I-N,.,-wpt:-B6O>EEL'SF1Z)i R1HZn~N۔PDÏ*+U[|*,AMS&1Fib!@1?woA(F`S3~b (JR}* >aWlر{W}qR`PL ' ?KKKEX 8E8@/P|_2wjsx+I+qq^gX<@\s[hjpvKW~kL `eOWЊ"nmҊbNƦJrNCd`/?2v*?!>c0Be 0Zw~¿l P,WZQ(t&]>WY>,+@4kj;{ Gbv9eT(y :h.8Kg?/-Ν=E`4"IBWXg$z3h0`wo(BCey#ԼKAM?˕o@Ϗbx3G$Bk?NKB'q<>j*p8Z~KKl&^z&vU(w;˞#G?Sg2?T|ڹ+QJd: ږQ$ 5?6ih:Țk@_ OXg^o(o+FʋmIbg.{Pg4"\|l?Ξj<>y3*{]se{_ ڙӛ\8wڂ((zݯW._}*)?L}7Y=Ň՛߿˗(i{\60C@hz' utw ?ӅPz lǑdBe>6: x]@z8o-BݲEYs UB?EOTvOq'S6׉hb~! L@QGfyPӾ(\S\yq$?;c'Cʂt}4#/vZ?{\2: n̍8 ,7|B~0Y^y'-x. fJY1@JEX;x|G8x>~@D `Nw% N;%3wu;("qqx4,Csi4s%_~v ~1wKLӇS֫(8& gy/#ZFg?)?(5_h(y+*4+օ,zu<)h&~jVּs/c/'pOZ6%?EB 7e ,܆"Nu!= {h7[rOOo˔y"AhIZSngnv<nܼ?hڋ}@L{C%};|`_|T"~8|ι>ee[z?5aFr?UF"S+& 70ky8>"g)o蓦B^оoޗ ^FϩhDe슕Ue/;ܔA6G;Fc XY^h0DyPvxorҩ46+ +/pss{TSxg@ /I)%| p?eG7s\I,}W`Sf1f.G@`-QcG&I%R7M)a1F&IHM/'G<℥ h/ٞ& Ik~yRϰ+?]4״k'u{q*|3y L&Z=ߦ>^+EїλNc,bM74!HEe4qɍ;\xd XY lԭ~}}m9TFZ Pm>#kP 4/! c g}ΟE*i\ ʘB'?/vjЗk1y}+ W`ރm[#h}燕/NbEDgwZ'bps,_{iuG1D;QrN*-w"(3IiB(׌b7ĜT9%{3m :ԕ>Sh mT|zBPNe oH>@O~|T|ock TY㭿S[|~m ɔ;ë1M ha?_ֆo_wSG3x ~ Ӛ@$Z:cE@KWTU#X_;cT恂=6WdAnd`71E, RgJ.bg +)SQ0B.hJ[p~Fm{Ttp(1Bhb wrT΍[r%,)r2n`0b{:4M{ #!/WUq2`2-  ,Al vl*pm}k9PP2-xV[:yn@a>l(xiW<+\%i6k5 JKRJp|Jߕnv@!9 N*G(o5WJ?}/XWغdy (Xt{]DWd@T\/| ֺ1u0k!*n@dЦ+ <Qʮ(yO*Bh ʆve= @_ID uKns0NGjQZ"֨1,/=8h\jC\q>%UybݓO@DPS. .J p, 7Y=L.Jŏidn`vњ9nϬ.O!E!7ɕ>\Wj?ZzA&|@ǧQ/ ?'"CjpE[yV*w첝ae8ɔ*{k(ֶCOO?{ReA*֟3BJs6Dq 2b0  qL֦Y\hp=SXE6S,REn"{j<7WjF ?i_R5+`}WC8ض4W&'Abƭ~{LT[INɝiƵwg{Nܸ)_pI_@ %6-$\ZNeuh+)\~: QR8?8`ԃ~V}M6p' ET( * Q's sSzW!B βmn~#_kLh] z]c7Vkv0>x*! ӊ#ZL}U؀E[vs&(Ġs$)D\~cDM`[BԀ=ܯa%m`ճ,rx/b~]f}FRn.:& 9o݀]nMq^@ئFƔvN 7ݧ FYv4 ML !p^~JQ?BCI$4(%6FSLvSPm$"-@?AMg~9&yF!Od2!z`f晩+}- txe((N28?\ߛ7/7뚞n]#h7Ë֕/EuiW"jX7j4qiJQ: )2Dqd>&Hg,Om0nܾK_7D$|~o{:iɌN",z;RzQJK(ypsEt){/p ?Bc32n/q`_%:C+?O{ {+e-R__0?Bf~Us6‹Ż"gL,( ޴K<䜔ѨLs>N-yocP$GG2`ȿΏZBȌnnyWq>vwmb,5+ɔì=G<͏4Td),1 $L[ŏD`\曨nYBgL?Py}]{p CB o~4ʇZƠ|5ט<hK,״ݞR-eCn GC:wvxO;YNS<'cڭfe @^{vf 1<&IAկ}uV aiO7vj8(WTY)@RyFEɎ<8Q@bô+7`0;%7̍<H<hY. ~QOots9l޾)(2LRtxp4Hɔhu #IB턪0>ĽU+1[[[\r(% d2 Z-NKEK椺axk@a~^N5!J,A, 2 D B츶Nj1Ұؿ]/rϝTt{笟E4%*"ܽDQ1 IDAT\kL~Osъiz.$t$VnȜH46vuuCUC@}7qיg)O<8';H Ӑ$[(a oՔlÛLO@ +PZpP`{BYIVXg\O\ Ӳ>D?s1:sGRu~t3HfbW6 RH" k.t[h b4^x&D N.j.)4?+ZIBL@ؚ wSSa#QTumcԙDmH_=EOb~dДE>΂JrTچr>X"HP8W|d <ȅA:V%Q``]Ppr}ʿXL~~rWܧ?Sp1UfHgwֿ{Z7@N_ 6%4Vgw@NƐbk DyJ v 2gIѿ|`}+)10U1k3LU\YnNWE F5eWiQ]g8qQXXR }8NД(0^,$!ƕMQ&eOG Кĭ) L] rЦ ߋ7A0QcF0NyS95sP3Uf0eϥvi#J1cܘ!rXap8@'b '[qe % ×om~Bbt~<֤2"ӊv#acEw#= b'= %L10̃@ "ZR)U~{^ݩDL6y*n@;P'RJ.щ \Vt{.:")7qà <ca UG)H5Z hETݬJQ\~[{\9\bAѐGyO:'?7 {ML ߎ"\v ry FVAHԷQ>y03ݙ9N Kq_#b'b ݞ;RM=iB'%5d9/A_7|:([R4%"_Ηt !0z%  %f(#cxf"ye6WiC?Ix0Q^riNBT_"(wx|$%ˆ  !cNSs<$h$>δ*c0sr [W>Pk1R)8!i+_oBiH v{a)kud^FeDd_(RX?n1=Q SܖvȯA 1NMtN$3khta  Ty",;c+7; kﮬ5!u{@=8=C9 ǖiX)pz5izu%w8l}` kK"X(I0T9{㱥X!pQ&9Yʉ$Lu2'1?BEqρc,XgJ 0vH$3{{ϧDxgPGUN~ >^OJ} F'7EȶgtmV;d%eML_9̐e͟F8ˁ{>OPiЊT+Ib+7En @}@uX~@:mG*v<&Js A-&m5͉}yތa ƍB&ȣ*B6y4߾qCo+q 9R$FCnbaOP7 X%{ˑ.lVG \u,wȟ ֒0D1z3Mrcn*?6("e>O c 8ܟsܙhF&Il P>$r:ԭy}]_+ֿ4Iˆ!(ñVc0֘UEJrz\N^b`CY`8ZL}n:W+Rao=k f>MC3S,n HzN1rbl@rqddxPQ5qe`[;5r`eals 2xF:|=sjᑋt_O E;ُ̽$׽9gUVVU־/]nI$Rw;: dž_ {l{R#{u)(.kʪ=3NDfuҔ|X'@:ɤ7UMWLK4 D6$8vyIoc]@?+@1&P AˋB1u}y9tW16-0M# aP>i!@TےT7`%w<Bm#g%Pv%ݎ]Q` P> @DNE˗p?2هJ箒Q4̀iA/ceq@16z%z#~t?b3m}H+,hI(G4´B\Qֲl%(h~ӫzPiP/a,ϓ &ZN]Q 4 #>N{~rL&!P3 qHTנo||y|۞g'0L`( b D؇ҕ||8- J;wVsG,Jbq$JARTV@ 0uE0җ}/H0_gz9ı;0"24F?Ǥ|FU)9^iogq|/@&2x4_?0}۞d. T A*Y<'=2󬮆iF?bpfPl_7L5hz- ir2\ `a:wH>ȅ$؜ Ovq<*6gďmN$ZpJҍ%\s{#={ˤP<4N^ !^ՠ/*Wzj@Fv^AfN}eYnq lqň$qk{Cz} R%w? D-J?Ý_`m~ӳf T2mZo{+PL u@H?_.K{F`^<4W꼬*p^z *ėzbyn`]e?Ѩqf8aE#r'hR1k:$,; %Ʊm.c4B:#^G*#`)טg nOIM&ȦRl$5az^9 3tpGDT˞7v, nf.\\߿0X0ҫ߆'n'@)O Yei2zQ)qlNL·BזeaLLӤJ#E7^N1 (f11:8?G eӴ[mLF15JiUnAT62rJ},pom@\+S;@vI|XA_]jTPTt#t:=64q:%׍HNzGLZlj.]g()97?=; ` -%UEV$c\ZW_?t'E02l!`GߑG89C˿O1Q Δnz/% iw #}m`Pf0A?AS@OUX|.x\0W0F!ꗋnW Ox\Q Հ/pN B /[<8g X_Cԑ?Xô8޵YLe5 ٮ=`Ze2#M$s|“C>| `I@Mq[b s7WY_}nK(vv|_n J'(6\?BjJq`s;e4&}5=0G]~v^Eҝ[e{`k=s$\5CI5 ޡ1"?|\,x9}TSEBz,d:HQwmVN-RȤAJʧs~x.3^߉:DL$we2_b6LO3 ZvHOͱw-)+FA_=1}ݏ30AWzS41Xn.CZmr  >) \n/j|IT*r'@ rGgYqJ zS@`̈́3HĈ1MeGF(AB n. 4^o )sF};GMg~fVMP$BZ^#?| 0rC{(F?:;X3 `$tOwU$-kWl c<Ŷ\]r'q$7wg)7K?=Jqgc>bg v$FYa{f'jBW`jD6;nO؃@O% Z?s>>.ݗhq='\'BK$znD^èe^te^q^!0VKޑ&RkuF=1B(е omz zI09IXm["{peC ۡܜYwC\ռ =P7[t{wW1mfeZݧlwbmyZV~~ A^d+~$l{jTuTkγ;Xşxbleԣܨ #+6XT/\FdG)Ef)-fRC *?躿`ߏ'5VSwc\;tQ0~٫K)d2raa ulg z=븂OȚP <~E!{=_`#:M MH0JYw-(x^)%n) pVo3LZ6oQ=w{I$TXx |wݷ(U>Ǒhk+ lp\;<$f'\f{nclj\=?ec}Ȥ|q1P> %3?;;oAopSXZ\dxto^Tg>q6>]9F*j.Om{&tظQ7i.g\DU%~[/xl%fGw29]ayqv[ ?)[o]Ggavѡh[4mp[̎p^R>>>Zn9;;*eo*wQ*HR("aE[Ql6V[f IDATq?[s~wбuJAX%~w!54<826UwyyˤHol'<}ay6J$VajbhL&M:!I ;]GNt~?KXcX[afr?|" rs~bOZt_E[e0zz@'cn` rvpxxjR6^U21.{'155P*s\MR=aW6&\)`| j&g~5!u*@DU:^/uU"Jz!u_UÕ$~;W2|+@z K$ccEXYgbbjI'*v^Ϡ3hu:$S)2 鴫G FeXv$/g{];/EO&ܽ}T~_siɨ/Ede@{yx '}ҕ2 +h8 YF>sl6+SiZj+ {/^Urp^G&si"ǏT*ܽ{%B$I" cE@PkcW&MJ~Q~ߌkQk굄r K`N퍻;]}]rqg&x GrdsYsM١hnw9qxt'[WN1 Q_oA ܺls }HGW_jowOm}ݨ|'jZO 8t1 qjovj.6E ^?^VL&sa)@u~m(j5={w(྾ 6eqzzJij(V: 1)D;Jh3W-AszA X ??<}%m|"Q*tTk yUˮ0>VwޥW{vJdANPoq :Ŀw4M ㉄R`0DGnht!M?!OMMh4e?N Bj5juj,]͞/zޥ YZZbxx??\.x۶='RSR"t*>RPv] ɨTe<l6Bwbe:{qLj]([;q/w|r/s(tb~?e#c*@+\4V^ʅ[! . z)t6ۧin߾څqZE~?^U"tR|zALEz:sssLOONy7999\LnXMX@jT6CB"H06 DB xԨi5_XhD?PB%)MwdRu ÌHn[~ʀ X\Oiz_?2^竢PX|E%|jop;;H,IB=DNM0ZaLΪg Ahs߸qVP?ZH){ -|x(YIe/$[%LƣG\.tddFz $)4 =/7 C`?t/0M:=#$|%vb w;OMҡ|N)~7X_pvVǖm8{?>Zf_>:'ewxU1:αUd3 )!b+I0 ۛ4F`#òt<9`YBjLf~A6qeڕ 6t&iuڌfhw;XJ!''XA.u[ʀ ˢFD$(wD~*~2 P$q(uxyafaa?X]^$3M/w T}->^a?|{=9(rs#7&=4={G!(`R1Gm:ɄN[Q떎"6*.;b y*?_  [,FĶm`m(Ͳ4?  KIX`O4O> ?==M;W,+0 /e+\>?\jjZ0::Ml]g.$ldKdg{۷FGGi]}Rd*cYA*s' UU, 16Aׇғ "zq0+ @ --9:mP>m @#zB'p}ĥre߯U#]=>2ôZ 0 fffx].iz%s;SC? bmJ.LNNR,I$XE"399A[E`Nwf8̤[sY $IBH%iu:w@O `y^s$"` 8=4 ҟd?NYPq.!UkGDW\ȹJ*[aP LQIIC4mw>Urwn@ S& ^0?? ?L石C\X,FV0 Ldooap]* 5p*m&]J u,..dΝ;,,,8 Iv6Vh]P&gӱI3INe[iz=0BJTI\]B C B)@"'b9'H "E%HU P#.o]^!Ҭ?಑2H ~)IgTHh FF 4mzfߚH$w6d F!ΌghZT4wL&}6_|E`ѷmJB"`uu5p,tvF^:]7R0#p1i(Z)4MLs mGOYHf.sK`vv͝mFÒҡn-4O+,'3RQ+0rA#/Y.A25cׅ!k}p $­j D(w S?&A؎% KB1[T@Bg8B&\EtƊ#0,D8וwgli^eWG5M}.?;\4#~o{{i KGu+h}yŤ/Kc}T6MRR@>P((1n2 z1^M-G+ ifE*!;G#^D] aYrY4{C3:Vgpt_oW9 @|- TS-zup@}_Q 2A ~CDDsUjxU^^ 2(JG 1U'JS>:cc4[mv7n31>R8moR Q#*G諫T*,+J4N乳a `窕_l۸bȥ8N*t:nݺE:wd2䣏>4X:)!)lC! ZB'?q#$eHNΐ'Ʊli 𘻈 th%LDx qPĨLA8B*~3@Hj= R Qγ~'y/R5fIh IRL&h6MME][Zdav;VcogN &!}_fYXX4" R)x@ַkD_]2>x|\e)%'''6O\{/ 7 k c$Id0g 5$ R4Z 4Dhvw's _&"6rsI) }WuŸy@p>VǏy}j)f,#r,%gujj8?tpP~V4xĨ|:GUUFFFX__ܺu~o{{;s]`: U*^zjH$( 叫uDB:ifS1^*'$I!jP>ܹd2:mJ%2 v۝g "+rw\# Mؤc"*o~;1 pgQ'"DyԺcs(߃CC,h888bX46)F;Dsܾ?|LưܥdcMXXX`}}i$fS:SSS1<<W?^: {U*!p,+6y7YXX`{;:I7p/ڀGhi$&3'Jhtl>OQg8-%G2 cc4Z-NkgGnc9vCWmU_tÀeFs|ܭ*9UW#% Q܊q ?7 3SS135Eax#ZNb,w6n"O+Sf|wu]޽{i,yfggT*4M\}ޏqjz ?{,W(q1^,] Svvv쌥%"r9ZI`3IHj팉 4=A:fPwp٤j'LNM`c 3XMԣ?}`#>C t}NH"jUGF^:G+JGD(AރdEO |G"5Zwyq ﶧ'h`e|_ evF4EJƊX]6dեEzfX*7i]}ʴ+4eM?k*D~zz h4\T?UPpW"U*A6tU~e>={͛7``In7dA]NϪ&' R4#5W~]ӠlRgtlJq唱1ff1,v>@2>'A\_U >@}P%%G)Dsa#*5&$LOӓrrrah; @-/V'l}Thyff4"WA@uFFFT*ޣE^ <uPӷu U*>P(z]{/TN;A)k9=N*'LLM'$cq&e"4F,ǢzV'07?iYla;E7ؾmE"mAhqxO2p?e"`WB *@pصU"<9P{/G3SܾuӴxars}|/9=uBГ=ggY9똉oе?nO&,..R.b$ 㰹^]g/S_Í-p U* y>C,O>ye08RH䄱q3>1Nۡg=NPk4s sF 8<:bltյezq!wo) np <ޮ WvR."{2PT/ I*9yA076(xt=&'Kjux+L|}ƃe!i\{دi 4MǙ N7n;赘0 䚢?uo K%^w!B>#&?/0@J%a\4q4=IV);WTN)8<ڢj0`{ bԘE:p9~P Eb"=U1V?L&u ]/O7WWxZͶR_Bc:6D=NT?Ƕm}={Na!/gP3%ӿ'HmwqՁ+K311 } P(O99 .$?63" KK֛h ˑoɧĩk%hkLp|RON(YY]aumۑlbkcAJ*DSOkD_x[cgDb H3,-, 'g6HtX9m ~}_4w/x/z^`OeSz^`4fx /KMHF ^z-5 Q(~!ggg|;w`?\ɓ46"oEO%] u>Sww%E . hH N:2/wCJ]cqq OO9(AF-U](#8}wL*M077ӓ$i.,/-H*>HG ]޺s!H["i匯OF7 |>_V59iZضu~pgٺ"WMMx%N#### ݻ?O sH2iʂаCnhShkln~i%Ey\ 4Ybeu%R,{lGդ8>,s-1>>Fϲ8:ptRVzVӳN;". D"I08"EJc%&'J=};`wm9Mϰ0\ݽ2ϞmPW %wI˂^7j9&ar?@:KWV>'''$㺍\$YUA`zz7n'⮥X*?@KRd2sm!`bfvF$%Ӽ#D ~ϨTi^*ÓӬ,(H"095$S&J2>>ET:].mV9v)DC:T2ir92,D ۶[TTN9*pP>BhLM3;338xb/Ub#RInߺ;o#!-law{_?0,tfCY{-?Hf|_x xKwxFπ. B}],vy~_3j}*?xICᎮMiU oΛwIf2.psg|;;yR>}v #E[gnѝB䄣 'T5,MchhPl.9of=$IHE3LӢjhh: ]Hs(?LilI&&,M09QBJ~^!* R4o{o]#ql(՗i[[;9&W)2}SSST <H$wvtt,,!6t Wu4RD"*H$Ν;?ο;dY>,--y#)tV23o3TtIo[ofiM̯~O>t6e]D`P,GerrIgg. vZN^th;Ca#Hhz\6G>'͑e0]1eʇ_vF5ੳ. Ɗy.7nԓ8Rz1 OkIXK ?N$ܾ}`9W~EpWE^~?-o.~W3iuRƫA&G?}ggglll033OSLdbb|;| &EI2捻wy{d֓4 :^O/~Q !TAwgc2:6F8!HR 4i#0 ^ϠPoh4Z4M*3NOTNϨ5ȼǽ*(q =o޹܂;8s!Y1 @pG }###ܻwSwǏ)/Uc!5?e5t6<E4?OiZ$ ]Mq Et';,wȍ:$˫y-Enq7q}NN@j Џ @;}n@;kxD8k""w6B:V->_}uS* r ]>zyyA;55ŝ;wO8˸i0lѸ~z|oIW.xkD"AT Q ݻ''b+w!ge>C9&8Ÿf}6_{FgƱǎعtҤM6iK[u^hǑ/?\U ! !?B $=Gў쭻&isuu|~|8cGxb~^$Zu{^í[O}bϦ(?I(t8&Zr188h5mU koc~W_}>/888ȍ7xwhjxC3!\`jzKs1d I@=.B577ٌD؈ M35M>\(e''w$SSS,//J__nΝ;GC/" d2}~?%J(]nQ [ȮyXq"} 믿_|a|4yiksJ= 8wϟgl,rO0$]. $$Pfv*E,g;$˒˩3jbB\Ʌ Hx< >2 ?k1 IH"%dYfppЊhU@(ZP۷o&.T@rg=hYWadt1'rztEQЫ!,D$@d$ȒXRȒxT 5CT̿k"S1:kk`HH+^+>\!Kv``9z{{NAw]Y[[#bT~|r`N wNdea|>я֘:?w}w)'}h?04(Ff+% -cx CCfxx3g|}x)-QS d TkK;Gv^bDbOD$Cͩ`Hy y'Yƕ(!%il9fT +Wx%&iyp%Ğp9PMY]]P(Xi5|J>ATCY& |îɂ `}}VPK.石yܸq\.(.^@)d WOeL_(DxpOOYaHOTi:FP\рJ.#βLNW# YՑ2rFGTH%W`Y3q$ܾ}n1^/+++͛d2sKѣGVaRښU,i#)ǀ.]h<\c2Kكw ut$a.^{:ӧfӭ+A\O4%͒嬿J%fgg>LMM1<44D8fii/ 322A DѺft]'Z{ DŽBl#R{IP u [vMZTv)ׯsYTU%2;;˽{a >}t:]gI!IR&Fy!ٳgzR0==mYq٬̣zS |l0LBO7հLK}ծ @UU 8R/Fԗ/_fjjl6k{{{y7ؠT*qܹk׮qU~Ջd||i߿oիV4ER&m42%IAn7BL&$$Y7J%,$* #:xX,T*&+t(i9i DPqmuKR ݲ5`YrVy1EaaaX,Jk#x<%^_~:'UT"311A8f``bhgjK'V@4LTJ-Ţ|)\.G$ld2ie.$*+ظikU \p}>J*2irMuuUU`iiO?k7;z%N-~7?rTnofGH;&opO`Ή,BP((uJյb2daa^zJB:?'JYנ\.J(ӽoς#i *}}}d6C(( xx\+Nv( dzg7ZA?b\T*Q,)JP@ uD$sv bu\,zzz,ev1[5 LQ4ͪh.^ 2Gq^h|0===nK!\.\.׾B;uiF\RP.??K*Hd)YݞZa:逺0ݣVO"= NmgE}x|OKXpqXErp)/;~|8>"S_}|DCOp|cDhCW01", qޡ?3s=wi' /b}@ I"|,O}q/DA7%y6g=Gt+E&]D8O X.9#zk+q.Q'B $;~> 9U~\ٵ);8 BA3 7b_}~D HW8"1^31YyL|PIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/32/000077500000000000000000000000001470053624300220025ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/32/simplescreenrecorder-error.png000066400000000000000000000054331470053624300300630ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org< IDATX}[Uyk}9931`CښXMD(JZȩ-Jyi_VVXMiUI*Ů4 JH(2̜ssgZ}8gC~zYK{}}[6AGk?9sfmSCsA۷>ݿ90 偁3Fёp!&jF֚~䭷bŭROAťˀ5_*zT*WF+ cx5(}67Sn3cBBz5g ku'IRSZMGN##U@E(֒dJ UQn.,qc~ﲰD3gxbzeH)"׆ 'NN~jn6N|yjV&Z T\u]ĴV;bIiefm SuI!uyo|ąf/_)Bx :#!Lp\!RH6)YGqy+^::&BBZJnF35JW h)#<711U064f@b9R)0rw0ksTEcyx7U d* S @Jry1vZSc @@J]Ik#|cnxZ! CXskx3Q!|J2$J@&IdXd}}8) =Q,R)\}Li [*a RLrFLN3<R_[wK/]ǎ=^~No;)p'ZY|AXk~o80c'JJ~|:'SU?:_"TzIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/32/simplescreenrecorder-idle.png000066400000000000000000000046071470053624300276510ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org< IDATXK]GUu^{˝uNlKaLXLD,Ht?3gp9n޼ITUEQcpΡB)aHZ-yVWWyWV+Wۧ8q&{O_?GՐh!v eG p8DJSO= qڵk/ߋXZZMӋgΜiXky7H,9vl_xG|]-9qEn0Oq"d5kkkXk9}4c8~ȑ#=oMբWq2AKp!ƞo5 *A *fQ0\hN7h9~8._[ޞܧ@sO</_&If1:©^K jl0,#-IpPQZ yd ø*7^Vɓ'fYFoyی:Pn1Y$9h*6uةE~`boɄ%Ο?_FQtdkkk]toxCX[[ps]P.iZu|ȇGUWfU i"ib瑦<R֒)y^B!E\#?mp4w 76na}[qă%LTKX9zZ_xZl2 (˒e,AXC'udtkٕ\C#u GL3WHskS Xkj2 e vQ%` Ry $N*S!fFQTUžVR j8˨TFXj ,4+8 syR c)󰔒Z;΁ 9°ZY{WةJl⼐b8µZ" Isc ڶkRtY\#kDܜlnnP h'!! ! YAy(}5UUcȲ(KyYCU:"@򒑎"w ig;)aylG`$}[p2()pynͰyFl@p8nkS% #QNA@Q8ȲaE `WFk ;#,#y_!TQZ0mF\ %0~| ps9<sҁXAZʭY*[0?h| ΆXSl ]At:%MS[oKʀ4 ޙ iKc\> HZ fdR@jCD cT)%-ڴ;j5ܭ/ejK'dGz1S\JiYLوB LCQBY!Ju`8@|zh4beekے|@)--=!* `frJJ˩[es(5hC(<?%M%zy4eeepOU}eYA,%i'>wA1m +5LAR hu:x`6D}[zEիWqv?9<ϣb3 !8 ^ٴ"KRl]='"s;`wE࿅EqcE)Rt,:J5S{<-%/Oɧ|j*VOWu 1fC#`,{mNj IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/32/simplescreenrecorder-paused.png000066400000000000000000000045711470053624300302150ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org<IDATXKy޽{lnnn)˒(p]c Bxt:\tj'|?O pԩF={vK/ĕ+WXZZ<ʲ$26XkQJu]|'MS(^ѣG;eE?}|-h8~ꫯzw}u'N|x׮](r\ojn7~ȑ#|DQVx ݷv6 XK*AX67{lc{=(8vZknܸѳmll T{?<7nܠ^3Ǽ7Kg^eC?d4`{8b0Q *DQцnw/eYxޓRɓ'+++uzM*qH%/4IVPqeyQ2 'c|u=͈yѣG8x١Cx13.O&"Bs16]f`˘"٦QQģzݙ.H% BbLR!ci eOjeI͙~z!jFttZ'%:mƣKKK$E6D1$oX[U~4͈UZy@(F-<'"=(%X|ϡQ5X+k=ܽzG?|G)EǬW)W32 CRH)BL;i>s__kG-p(|ռ i?F 8|h2)>|l|a [bà`rIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/32/simplescreenrecorder-recording.png000066400000000000000000000045731470053624300307120ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org<IDATXIu^qDR#RJA-MY ![%A%6%$8( )[i*L2Ehș}zZr1#?{ >>q'Ν;ϲhtVZElnnzZR|9W^yG}~O<gϞe<vFch44 6/27oތ__=NZN(s=GZW_ƍz=ZZk}!XZZӴmΟ?VR9_w <4L?s .]> dY1J)OiJ^gauu .zk׮}c?=_{jK/$巚|<!&)("_ľb0 СC /޸q{$y}˗/l6'AƳ2TΜ&:yU$&}7]? w֘,//ċ/+nݺQbaaͣG>toPmW:'OxrXE̶ p"b}/Ocwٙ('N`ʕ+]@Y_XXx9'<W\ZG| VkR]8Rk;3bS?Dy$jב3 ńqq{xZxÇY__)-QDB2^,Up*&UBO369ZЏD)" iƏE1XiizȲ;h |1N#Ox"A sw(iS>@YJ6Ʉ9r4t1.|E%1k@ 0 _d%Y\$ɍA&FTS<1#d$"|ߣ( ְ84Ɛ$)9A[OR}9iaXl6$ay %>@fls^x{L76xT2OC)j À Ȳ =p.c&l HrM!zAVGR2(3fQ7K  ýW=1s!%< /qY 6'WH%J*$Z>SZY& =& q?s€jUa4b,CSq (C3I9%` psox4)JlooT (H 677q+{7NBP׉!A0kT0 QJ1[[[c{Io=/~!:is_㏱R h~e>j)pNC2̊,w>G[%ݠIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/32/simplescreenrecorder.png000066400000000000000000000046071470053624300267360ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org< IDATXK]GUu^{˝uNlKaLXLD,Ht?3gp9n޼ITUEQcpΡB)aHZ-yVWWyWV+Wۧ8q&{O_?GՐh!v eG p8DJSO= qڵk/ߋXZZMӋgΜiXky7H,9vl_xG|]-9qEn0Oq"d5kkkXk9}4c8~ȑ#=oMբWq2AKp!ƞo5 *A *fQ0\hN7h9~8._[ޞܧ@sO</_&If1:©^K jl0,#-IpPQZ yd ø*7^Vɓ'fYFoyی:Pn1Y$9h*6uةE~`boɄ%Ο?_FQtdkkk]toxCX[[ps]P.iZu|ȇGUWfU i"ib瑦<R֒)y^B!E\#?mp4w 76na}[qă%LTKX9zZ_xZl2 (˒e,AXC'udtkٕ\C#u GL3WHskS Xkj2 e vQ%` Ry $N*S!fFQTUžVR j8˨TFXj ,4+8 syR c)󰔒Z;΁ 9°ZY{WةJl⼐b8µZ" Isc ڶkRtY\#kDܜlnnP h'!! ! YAy(}5UUcȲ(KyYCU:"@򒑎"w ig;)aylG`$}[p2()pynͰyFl@p8nkS% #QNA@Q8ȲaE `WFk ;#,#y_!TQZ0mF\ %0~| ps9<sҁXAZʭY*[0?h| ΆXSl ]At:%MS[oKʀ4 ޙ iKc\> HZ fdR@jCD cT)%-ڴ;j5ܭ/ejK'dGz1S\JiYLوB LCQBY!Ju`8@|zh4beekے|@)--=!* `frJJ˩[es(5hC(<?%M%zy4eeepOU}eYA,%i'>wA1m +5LAR hu:x`6D}[zEիWqv?9<ϣb3 !8 ^ٴ"KRl]='"s;`wE࿅EqcE)Rt,:J5S{<-%/Oɧ|j*VOWu 1fC#`,{mNj IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/48/000077500000000000000000000000001470053624300220115ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/48/simplescreenrecorder-error.png000066400000000000000000000123731470053624300300730ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs a aJ%tEXtSoftwarewww.inkscape.org<xIDAThi\יsR˭Zf75)pYzDY4m8$ˢMzDA21 痝 2? dAI,+^hYeI C3ֈFdR&)Qɮǭ*5%Rk~﷼+%7#M)LTk~~;01`ПfmW,756g{txۀؿ.! x0 ݞ̈́ah+Zcat:}#JM!8ϱuV(z mr9L^kq9uX|lܙ[Eߴ۩Wk 877  婻gv}uOk;?{V^,6??k.|ImFwٳ\|z>2\)E!T*LOO}v&''Y\\ȑ#Wb\f Vrw'̼R]8yOjA LMMmI-:(w~p<;{ڵk=z۶1Mfɞ={n8Ni4 6mľ}سg._?iWC{nfi8v'̼Z]xtXmt/]AOVS D۷ËMr:bqt:T*^J*4M00 8Ʋ,rdt:DQDڲ4yjWtu-Tٹ~0үji=!q-S~kgY^﷿ BH-K9)eyl&dX^^B>O*lb6[ne:r<>@²,H)qi OmLenuQJ@3Z۷'L6?R*3g={'{?ݛ}nI%kmyLӤEq˗9X <3g04Ml;(aiRq,ΤyjfUF)%\!v5Eh zD\qen+T{ #;vm!0tqi&9xjfj5ٱĦCFVVXYYX\Ҥ4qkqhz5KK++q\0R!eJ@jq7mRy;gۇj7ǎF,CZˮ?aÚJ<80 (fVJ%V֭㧵Y~,54h4ZkP.μsNNRo4Z$D)E6E4kٯN ;1pl۟fwmL:{=+R "0 DĊkrq3`PZ4@҂9.2&'Y@u)A$~l0N;6q#^vnN2[bFNOiTd1 5l۶1LsPtXl䆍|-m0R Sh}TMe0L˲0 㦾.MM+W`Vm[m(N?d2;mZ%&(" DJ!%ZaH@Ny$s-R3Zx6֭h$^ 1L"!Dd5}NM!KKK ¯՛R?t]֭[mW8eLNRS iLB` @!Rlwbf_>Ievɵ`DAP51^eP`0 * .]4͜?8qa,..ƴmZlZGA@ט/21@.N1f_ g&&(,.7 V+Vzj=V`ZZ=<fJBu;<3Ǚ{JEJ%fgg<p=={.0f y BH0L(PC2qbCaa!D)d fh0ccԗX\\5|>E03Ja qF?uef},˘QȇmZ&e4 t(.-_X hw(08A$V~r_tE`WVVHhhjz@keYEΦ,l#bR#H)1$\Nl ivn)RQgN^!P!1yZ Gj5mwMt]RR)8~h C8BJgBrf5^XXdq,ϣxz#fR&\>#rPFu=g\sx,RdҙAR ˲PJmR6MdmJgyqqpQ#'cBVi vvMDJDXE!Xw ?e9F0R0CӨX $uR k4>AI!r9ڥz :QHzD:4:4oxERkZ}Ӳn*i/BŬCl[Ah!6e3cTE}ab+=B ofn @D s.CԀRA@!0H RREJ5F`DH 8}&o{hM8W$8>>\JOsW0#w0A )QZ V;=8>*sKEnK 4Gyq(Ԫ(ϣqRaLC`})%~d\87Ks 0L<2LQ*_!L$a[L^! S; K+l&Er`]!o?^ȗ.]Ls\.1{(CF;#d4EEp]V* Ih4nuW!8:H>h'I٫W^A %DPHPg*r&-e@h>|:>H) T8XP}b@H]"2iZ/{JB"kv ,91>e#1 lp\t>Z뷁u+LMMv,=:=~\(JeC(¶m4P*0Lm4-^lhoۍ5:@ǠZzi`P/cJ1JL0ԑOEQteh8rQǎ_)!qF?hdLB A4yeP-t0jZVhW ?{IyzFƫu)J2/Lk>|89rX:yrc~yRIٽ^yaW5iǡ +[ r(ɼ@>| z# ";ZH6|~'~ ijܵk4]rTj&Lf0ј(u]K9~=L@4aYax Ky[->}jqZb˗3ͦ3 pG9Ɍj֐\ץRI?iSw0>J%ƻn]|;9t߁͡u{𼴰,1ur _kM)$8iu:fNCp<\ץ7nܠh4@)Wz ++5²[zoimZ9~}}𓧟ު^ x 7$<:>'O?;{~羻o_9~D >]'_p?m7Hƽ[ {ͽO?OӧKf|!y+9"j OKQ1*pߏIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/48/simplescreenrecorder-idle.png000066400000000000000000000107241470053624300276550ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs a aJ%tEXtSoftwarewww.inkscape.org<QIDAThŚYdyܽꞞf{`&;OPl$Rl#"'%J"1"!yL%(D"NO<333\xǏsY\Ŷmָi6t]"x$mVWWٻw/{/wy'aSO[ocǎso}=]W.RĚaUDIA&,0(ZyWpdA;m%VrXר|,\.i6 4rʗ76,Z^._L٤\.>++->3;žVUxrf5Ir|"cI+cKzN;–>[^ )oEONgc8@ۥn^׿GfR?t:?1OZb6Kܽ J(خG?LQƦ1RWRNW ^ )SXNҢP*NmrLZ-8|0q P,5`ff#\.MgjT \Sx5  ȠD;wQNcA]ѹ$4BXU\Y%eb8ϳk.,jV/ K[x(033\t Z:rL'ۦm \ / "ncn_c pM,s8mpr!wb=<>Bnm4 mEѿm1!vlEGjb;.6J`Hr|vtLJ i[Ǩ]?E{JjBƊ$Sh@Jz4Mj5X,NiF֚U=PJZ A"wL%h%+A,ۧޡ/ϣUt<V2rH.jlɡ0Ѻmض=-1i؀4Ƙ-Bk `Cf5Yver noF!Y_HW9` m\D!@WP^/6^qFO6ZIe{kd>B\r@-Ɔ0D " Ξ=B"?\E%6u1 cc6[4Ef+6D)JF[UU'k;b́4<8wmAl5S qXK0F]3M<,)qd4l)]{f])#c|O A-F  F-ȧ !I B%wM1FmI|g}L@JyG~azd]QLqϜ'7 m\Gj n;Q` m!!&$ff30W<5Ғ+09iNgѭUr qgh8B8d`209h=bDZyøoӆ\{}$:a9T62!1t]Q\;8@\87!(G qr= a$wr ޶m(ڌ;7,(?!y382QO6րc;$+eմm[1M'NȤDaM n'u( P5 #h [8Chۛ8\-P`zzoǠ1H!t*A>@!OݎJ W`Ikrk0)w;SIjEt &Gl G59+++cC[kbxqq G߿\Gv+@JIZݶmS(ÐbHLLLl{[uV8R !ou2q_ڥ1lU~~6dοʛE =?I,k(]_IOt_ 8?R8Ζ 3,A }/H* K#KGg | 8o4w80<^|Zg!0YXٸ~򿧬i)LIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/48/simplescreenrecorder-paused.png000066400000000000000000000106661470053624300302260ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs a aJ%tEXtSoftwarewww.inkscape.org<3IDAThŚ[uU]>3;e,A;k $Rl$"K~H#Ey# [Q`6Rr[fݝݞ[ϥo_;^ۘ`QwUT7?8%cc\ZZnرc !>t4M=c ZR(LZ*xQk/8zh/k <СCϳsNq5y9wkkkTUzo !zW{Ǎ1E_6$=gC1nKExGTqyWx8}4Vge_;qo 8rȟqW\yǘnorG7Ɛ$ B:zu]ܹUoK/111JLk'O~lJ x'… ⋜9spuNVb0~8Cd߾}|򓟤Oos?]XX8pZzcǎw]Ν;GVX,n\xq4MQJu]Jq!v5}yz-yA|ԩSzwh_X[[377ǫkF\} `}}8N󘛛cf R)^%~pM\ץR)#\.syn6?NEcccTk5\ף 'mHB E]6(!Rq4e߾}{x*jyMJjZtQ<7ޠ^#Xȑ#RJOԏc4ݍG*8NE$&I4&IqLk֠{h!p]EvEV/z@EQ4~!(lRTz$I=Q*WAHzqr-!A<ڤ AlXGuQL5kpOVxCXfϞ=\xvMTBk;wu@clFAKEq *Z`ll9ĎI&wL25=Xٙ)|GD=[$Qg(\ܠtH{n{n`,Y"z.6hfRJ݃1BbFI!-a!{mɍxÓ 1#@.>B\w)%錬'1B䜖Jp4\*hJ8 -y+Ev h Zu~ ̐CB! pZњ,K֢d{SJ"lOj>JJn@IQ(*@g)\9lXőw,X]]v y#:EQ Ns4KZ(%F]%pkc(7`q* ,TPnHjR \sUH,R~eNLMMykkkq 0 k[wazzzfs QLLL>q?7>kAz@^OMUQ67K*9EZmv오RE孷ޢ]'|EBj!llluwE'IM45ˀvC@Q͝M 2\z\fP~6u:=6y4pnƍq_.VJ0 P.:s:Mӧ'4}'H_\s5eqNZzm8pC1;;KT4M*Z_gRV) ۶BOJJ?J%3557G?QB!m>iʓO>#<ӧb?$ʳ> Çg>áChۼ :usα:f\)EE!(޽/βC=?dq^zk~>.瘟ܹs8qW_}۶1MZƑ#GT*Z-,X~4`޽p \uUt]>^|Efgg~_~ +؟;vO~򓬯sY"lvM\8iqaieYO<6i8r ɓLLL\+ /2MG;yz)~ir.Z~?" C\wLs8W`04ԓnX XE>CJI.caa}C?~0 {Bӧ:th_EO7wy'SSS<#,//>AX&w1i3hi.(Z@0Mn!˼i vq$r]3gp|$W~;|emm;w)n|E exN:k.D~;@+pǚI0,6Hk~sh-/[Ds޿R+L%yj%>Li~qMyΜ9\.:G䋍&zfs4"aOO#,iF:Fk(yc6!:U"qoԙ:LL,EvETչBxp0 ?=??mۜ?PJissF#"M@ 6&)!Pb2^3R,Ls`FQm(J4T@O:€8^kК4 Ij;3GIdH\B UBC3õ۴ `ՑX5q# L]L@UfZ7k(+A*KX50 aCa Zi=^ h3җRP,l'sB ڝm@Z&4T-$řKH m;H$ d@:&۶X6 tGwLCz őZC?IH."[QCZ6ٝh!F/N-\á}zX@7eaNqy"88bi=H(2dx`i $EG1"d/N(bA@RCˣn%UVRj^~m~*$kx;&&6AE[Fy2TE"*uWdbqpZ7|sğmB@X~!5ɠ=[QՉfζWd :&Fݻw$&~eb teǎ)'cZ6$w3y[.VG CIR.\N j{LcBAop…Qcˁ&aИc\&DQDSv<ʩƎ0hY>'6HBBסX,88^4M9s Jeļ.#I 5m۔Jm)ݻzDQD0$iP[ɶ-<8δmzgϞzjqkGRJ W&faoB`6BQ.^5׉;osK9 =83!F=ϣl^r!0A)$ CVWW/ aObiJ*P6qNd!vD4>^:3WkRnVOai5wǗ; äA?cPX8?^x?!k+8n@xT,J lbbM_3aIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/48/simplescreenrecorder.png000066400000000000000000000107241470053624300267420ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs a aJ%tEXtSoftwarewww.inkscape.org<QIDAThŚYdyܽꞞf{`&;OPl$Rl#"'%J"1"!yL%(D"NO<333\xǏsY\Ŷmָi6t]"x$mVWWٻw/{/wy'aSO[ocǎso}=]W.RĚaUDIA&,0(ZyWpdA;m%VrXר|,\.i6 4rʗ76,Z^._L٤\.>++->3;žVUxrf5Ir|"cI+cKzN;–>[^ )oEONgc8@ۥn^׿GfR?t:?1OZb6Kܽ J(خG?LQƦ1RWRNW ^ )SXNҢP*NmrLZ-8|0q P,5`ff#\.MgjT \Sx5  ȠD;wQNcA]ѹ$4BXU\Y%eb8ϳk.,jV/ K[x(033\t Z:rL'ۦm \ / "ncn_c pM,s8mpr!wb=<>Bnm4 mEѿm1!vlEGjb;.6J`Hr|vtLJ i[Ǩ]?E{JjBƊ$Sh@Jz4Mj5X,NiF֚U=PJZ A"wL%h%+A,ۧޡ/ϣUt<V2rH.jlɡ0Ѻmض=-1i؀4Ƙ-Bk `Cf5Yver noF!Y_HW9` m\D!@WP^/6^qFO6ZIe{kd>B\r@-Ɔ0D " Ξ=B"?\E%6u1 cc6[4Ef+6D)JF[UU'k;b́4<8wmAl5S qXK0F]3M<,)qd4l)]{f])#c|O A-F  F-ȧ !I B%wM1FmI|g}L@JyG~azd]QLqϜ'7 m\Gj n;Q` m!!&$ff30W<5Ғ+09iNgѭUr qgh8B8d`209h=bDZyøoӆ\{}$:a9T62!1t]Q\;8@\87!(G qr= a$wr ޶m(ڌ;7,(?!y382QO6րc;$+eմm[1M'NȤDaM n'u( P5 #h [8Chۛ8\-P`zzoǠ1H!t*A>@!OݎJ W`Ikrk0)w;SIjEt &Gl G59+++cC[kbxqq G߿\Gv+@JIZݶmS(ÐbHLLLl{[uV8R !ou2q_ڥ1lU~~6dοʛE =?I,k(]_IOt_ 8?R8Ζ 3,A }/H* K#KGg | 8o4w80<^|Zg!0YXٸ~򿧬i)LIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/64/000077500000000000000000000000001470053624300220075ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/64/simplescreenrecorder-error.png000066400000000000000000000200761470053624300300700ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATx͛ydWu2^/UKFݒF '0Fش6aa! `$131[]Uݭ޻*2++mwx/_eU/vĜY*s{99v@*xUBk&Sy2ևOR).`rJR&RZSJU]=y޼yo|iľt۶W/k>Moz.BqzvM٤y㠔B44M#lj$IbXU)}G8;4tiq?:t|H4 ݓݽ充hu?}te;f\M!/7͑RDRu]Ν;ٹs'r90 5jm@x{r@Xu쥕/s \w'dt́fӪO?hj"~_(Z\.77')8ti9j?no!ݱ#gWz*u BdeeoctrK3~\u@"۳쳙E KKvmu]LOOSO=ő#G8{,Dz!:lzN*niRJ*J)axx{r!v؁i4 |Io2L-l \Pl?hvgu̫˗/_v-Ӻo<ݚ!@QT- nv~~l6<_x9s ffIt]'LRHR044ƶm('N?=m3::c$PYKFWchI!|$C'O\{M&\w6뺇ر;iW@z^gqh6<$>==1#Gr)o`jj vXVqIRDQNC¶ HMcT嗏077Ǡ0veg΄k7b&F#!y}.s2>}zi75oyl~OB7B57gx^\)E:F4lۦj뷗ͩ) t8;~B n"+s]ǡR\Xŋ\pZ1X9uʉs Di=f1 x<`Y{y.JY]֖-OB,&KK@Ph lzZƋOܾ]%MSUֲmx8Nȳ>[ x8,yއ޻{L-/ ^A A)@ 591Awt/93~^ SOa g9w0e,*<BܛN=FAd|Z6cTKkh胇+.\h6l *w X^6u7 Z-L)ٿmp5iH}`9{fgIa/-|@5MCulF.1x8 :+`Oe+,뫅5}tj.wrS J::qMK/@$FzdQ H-0^b;6'}fX]}^`p)J"ԧ7RJ̩)!x\eYyqfF?nߐJصoҫoR8ݵ5$MKb/Nx&ؿ}9d8J)!tONb sj|ŋxr]Rz$vS"`xǩS8iP(pdޣ2Xan m3~yDmdQn%V[(c=uU)HnV"f)N,Cht[U0Mv=233C2P(j`5[[ Hj+&%^I |hZmJSxirf aXH{D<6?YFrTapyt]2M:NaL`Y;w yx˲(5LNrONFq qh!タFUi웜^y۳DTq\F$>>"='N laaH0 :mc&B')lwl0m~s6%vY ^ x䶟͌<&&0 ǢeED&1‚988J"Jy )@u/ f<]nXYY^MH$(XYY0eYRͮH$^_A@)u_]p/C$-%t)%:2L'/NĞ#S(*!dMOj*{Rjw8{em_,al1qGt!$O*&KM/"(/R^oR>b+m(!U/Ki9޻};fAR `%JO*gmnWe9hi-!tFϟCnz#hoZTNy޻};N^_n-nYomG+A*WIcoq M۶}PYAqPT㔟:MA5hRP!]_?NB^3/̣/Gإ}hL_mo"^ޮ2DT=̻m#ЭT@ij_H6-RB[m ѵk­% 'OXՈy x>PX[Ӽ{6R6nDOir'xB D%dgW.k2f,+<*os@x5* ˗/cYY{=ST$ XΊ\?)Si)$~XT>woJbbw:qwMMŋΘT*5> <'灳'뽑Rd,j2!cxfHZ@Q_H^l4"zGk5ϲ|7 ڄl9v,nVPnee~ ?v4} 癚b*ex8) ܹS8H#AK&:u<)Z[T@D k6woBri!ejj*^l)f7ba0{ӮGGhޖUWDxi%7q? TS08DDjx]*on$ˮΖJ8Rl̥K)4u]F%4:Jƶq \YmDٍQ- 4vȍSZkVmӉ$ ٝbje} jM\t0\KKX@GӘhWL!0M {#>2BʶX(Ds6̞ӌwOym#m85١wj&xcs9yd0z޲,On f{pK9(ty|c@;AD}5)F"xeRӴa.vIܲx[>m?׿n*6'?{sxe5^'<0 .i48z|Xyu+`qB?jh4*YZ b)4̸dDWRRT(Kt]E&M,Qbn 3+y9.%P5 B!'3ʧ?PJ{҇7::yvqәzy=ž~$BCJTbq0ogݤ3ZZFAT'q,LaclvyRv:ѽ1<>/@MbRvE4*4-H2ԩS>vD-jJi;)[_ -b.h˗/"Ͱ{oN&&u~p ۶i[\x9+E4M#Ͳ͌Ųa2Ĝk0,&u=<-*qyۍOp}'C{ZC<²Bd+_tJm薆v 2ǻ]J(F#\.nFb14c;8M`D$b0::J$axxTI~u_&麨;/CH!뵾DEӌtoԧ>7;;[ cs2}N{VK)cә̷5oqNM0M0( bz˲H)1 -[l8ALX!@#W_b1rϣtU6dse{jC 8NL à)Wqel6f4Mlfll%FFFX]Oz-Jt:A" j tR%F}u7TkE)%ҟ~;wݗϗB (DѲmIVy\*L&BrZ\.Zw:onf}pKH*H4Ӵv#⠑Jɻ7 |*78ສˎ}X|\GWWb1Ć~¿Dz= .jVT*\7 \#Y 5*Aߛϗ! #Ҡc)kM)e"H.A)gW\I+ ={7{KRڸh4hO<فf @?AM?U6 CQ~?˿>R÷eտT2L?w=_Sr9;zATzsQI7w`Կ>0s=R鏼-έz?k,3>| xxWS@*{7P5:?;r,IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/64/simplescreenrecorder-idle.png000066400000000000000000000160141470053624300276510ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATxi\ו޷ի7&EJ(Ŷ#PKq&ha@0YɇL0|ʇ|`x8py@6g<%DQIsk]]]{ի{UU7--'@r~sgs?Ng}RE)ekmZZ7Ƽn9;77wK_~;y{[8q#57OgYvEh1,˰֢B)yaHJ% Xku!O:E ੧:B Fv֚~GrQ:,333KբrMΟ?ŋ9}4vZ333 5?| "xOCƘt:Z}C>яr!w˙3g|2Zlaa^GVQ.RJ:Z,c߾}?~'|#GsI/yR !?ap nQڵkrffO}S?~k-.]^ܹsxrM~ar vBP`ccbH^Gk2O=O<tSX]]X,ZW^y?^Mׯ_gqyp333F#>իWٿ?J)^{50yB@$AR 5bl277lll)J~Ƚ^E9lz4{]:VI4,h-pebXZWq[/s}8Gy__ɓ'9}47.\~ԑ#G|`vv+W /5!^NB|5F0 K׻czS:t"˲!yVVV:u uY]]%MS$1[)[lZ}9gg eT!`dͭm׷h4jhYoQo` R`A?Qoʐڑw4D,//eJ)%8ٳg1+ v[ȑ#~es{{{رcd4q)<#24X,8X=9OuEp=R4.o\Zc}CcէRoRrFNw%]D!: 1~BaQRE'? AP՘_&+ʷz@E߷O#_$2fgg)A7y`ʯ+[,RVO&$L#ZJR`UV <.IވV{U@ OizU\%Džj:j+WHR!I =G+_l6OQ7:Vb<50c8Hº@ %pY4eaaa mooϟZ0 ॻg(@V$2^~eR>|VE$ofb8XdfaY H?I*`U[gl X)Az(aRI4@!{W"*rFAay\zBŸ9qkw!.]r1$amm c,?$dD""G(ziFe~ npF%)`N`"<(b"IȀw]F1}"qSYYYP(6ep;P,Y.w}cx"a""OFhG9.~ sX-|Z.UFkPPEQO.xЉ-s`I:QH^KH *fq!oE~"~7c:֚uL_Ie,+R微p<򐎏r=EKh mcMgD27A!RE * Ae SJ-U(鐥 qŘ“ /1;; @T0???f?Xm?jXe4Pհֲ5ݥiJj !Hb zhtE((F\gA\$ii4$(30ƍ\\a`020JadzU8G6{4 tnqy6} oi,z޴TX,bj] Cj p]"@(,!*^JS \r 3tTiumV"j+ [Y"icA$34=TT6. ZG Ƙ' m=JnJ9@X$EBQ44Ͱre*e[MNzCa0ƎRKoanz(w, @J$%·hDeak)>'4MɲIܗ%)Ǧ/BaX 2[*{8MIQssDkH $26N\M1XSy4C۔EZk )Bc\ם4k-3fbXrkJ 4jl3 kbF#d{=s# S U &Q9B8ۣ\ZkRce1c J),MKZ~ykka#769}uZF?bվY8#kqet ̎d6vo:q}RfZU`qҔޱv7o4Br@K ifbtJG9/33؇]U&(iHsH( U"i9c-,L[nRJ͘)Ƙƛ.CcPBO-` c!ՖQ1 m?V(KF;QD;ܔBwCC!W.Js1 Cyx3k/v"hBrPJzj cӱ\kRBఋ2e9hiB5 M[@$WZ-f %J'׮σ(}L$vl !8RJ{Zk4qfȠ!woYR`rw5;4\Kc/^'Nrl9qu3mܙߨT6hƷwmS]6?#Zp, ˿$5 !tĸ;N)$l#1v@a8ᥔq-п 8)Jv{ZF#HcHS0tnGJT+vOd$b1;Ak3A0I0:1 M;ƀMr3fl v79SqGvzćWa^(0!K㠭;b"9XYHRH @6\ (cY-YOj1 eMtՌFB\BRk ÀuUHwhILB5lЕF@\G=+xc;%Bp^[5yQkģ s3ݨp4M[|/M7(cl8aӵX]5S(xh)/8 $׀drN0XJ&aOFĠSQwiwHR*k/*w/GJ3᧐ULM6=,ǖrX@muRJ^~y7C{wя"\e'Xbt &3JiG:*l )mZLT$Ft|nhnh xq-APA}[M$M4a{Ôݤ+kN£Včf+j!$ҾycRH, ep0ZbC4Lb|ed>I< I2dtqwUn xa[/%N<.`M:AGqDP)Fsʈyg?2FM?`vnY 9n?[ILDA&G(nY:^v[b,JhF_5<Π l^|+A t[-zrCI% g >`$#Г*0y8/< B0ֲi(j\RZGMs3J䙟ѤNH0{ #Ƹ` a8ʈ##>İLN:mMJs1vh;W=c^>͸>Vc'NAP.RD:A1lGfƀ8BP# tt"x$&V+ӳшhą -OGw |ߟ ,.R*6zG>DyO_8 ahX\`;F(7@7Gek@f!KKKhvq̵k׸tc-t,,,ܲI}J3Oteש,/Tp+e4'FbmJ\mG*ĕ>ۍ>.!?nTw)n:배@R h48s^ocF?ؿIm! !{nR8ԯ=Da^=bٕEV)j8A@&%:}}:[fkG~cOY­ } Ȥ{W(ބ^&߽7D.z/?<u] E[*lP(XMk\!3ӻȗ.?]uSK!{;0Sc$o0y>~ 8@.tȗ7;U%_y?];~3_4IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/64/simplescreenrecorder-paused.png000066400000000000000000000156501470053624300302220ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<%IDATx{\}?y{/1mɱŒn@nMv }?H-P;u-`ٰ c4ZDrE!RK.ϙy߹sN3%˖h,ݝ99yc>$_J_R5lcJZZ7SNB_~Cq:M#v0 v}֤i1˲, u ( 4{Bgϝ;W{pɃJ$nwVh4PJCqQ=j1a{{V W^_W^hPTgll, ɏ~{'xWAR̙38w?Xc0&4Mk׮}{\xj \ezŻ]C}J[[[s766>3<É'p^K.z=0xqqG ݾ}go۶ٷo?kGfs2Gg?z^xoTem* vbHXf14M<9r˲t:={o~󛸮|jYֿB~}ԩSv$Iŭ[_җ8~8nܸ?ϥKp])%bc t] Ν;LLLp-80: \5<(ɓ<㸮K_:Νc~~|>17/^x}ĉ8k=um~iylfuu{k׮166Fܼy}aYhq]nK.#c|Dz,R8֚-q5$T*O|{ !|+_qfff1ꫯx`Zkfh<<Ip9^|ERit8&IޙڶMTPA dii >O199Ik_+++߿?R ÇO9z~|_fvvFٳg`ll u}nZ6PZo>{1Wq۶AR(nSո|2v4Mp\.7SO=IӔg}!/˗8vXB/RTX^^/X4M6LMMg GqD* "!}zI /,_`qq0xGEoZ#<“O>֚g+0?? !~ڵk`9rv^*KKK<(F&nlӌW!ؿwY*chHӔF:ܺuvCX cΟ?/[#Gp)p5fggJ'.R:t_FQ[[[|scrr;wB@BJ +++ضSO?}䂀G033y|l@k`H)E%bO^gi&[[[r9:?>Bsi4[Z(_YYsJSiښ|ꩧcccs8$IBǴZm' R8I74 ĩZMj-6k[Էշh4[H!ӔnK, qhh9c,ibYa gqqT.l4g1G4z}رc<}Ν;뺤iJ$yZ6'ϠRm;+Ak0^suVجYYbV}cZ&mi˲Afvfx[7o! C}J.\Rvw6pi'?!MS*|sgc8ǟ8qS* be;ll{}lǥP,e H v^ Nf-<|!%XMݦ\*35=+WC\fuurӔJ%8fuu >|ѯ`~~~R٭-\.s-)T*x Z6sWAv'SR,!-(ae Hk Fviw m#ıza\.Xʍ7Hɑ1>>իW1LvZ}m<0xR ir,loo1ApR FH67mx( (e0@ElwxǤFi*JQJ$)f ̀4\ݐB!eIVWC2bZAܼy\.7q۶j?0Ap Zǎ#cp's>B cT)ju8""O8>XX6J(Ҕ4IӄTŤ*!M4'!n58<jB۷cbbbfRPA>c Xıcύ!Am~hc*2V"(" A^/a01t}zrxR!O.`f~~7oޤ̺^355EZessq&O1RylooW #m;60d<&|۱ 4i IJ1(fkA391Av ,UrN~۫,߹{Ӻ[Zkr0>Z?" G# d867=±-V+VC("Odza ˒bo$tBn-juƎͥ`ƘS#Rʷm!Id!B21>8X" K01(30Q@JV c!Z#E7bm y>9l쑯t:G Ce#=B6 LLLi0D)C 8q bAkR@k0ڐN^K$JiLݡlc`6Ғ#2og1VV,vF T*{6/Tes $1˲F^T*D;LK+y{1lo7I6U|'MRw,;U4Y.%0^H̐DFd}Z<æ1枵Rc݉-R(R(ѩ!M2XEHfFPDXz^= 5~2밐RNw-FY%:TZzqƘZ[.=Ee&U a?B')aG?PJdyi"5Z~9s?1iaPmu`0Dq2., ȵtC (^{삸9kY jZa(0i ]8]FW`txK"_Nw7F)M*=qqǙ{- %ffTfR*86Zi4q<<48vzs59vNUno๝ny.^둪DEmaJEXFkC%I,+LshaYt{!IP(pxNMmFckk }ccc1 0IFA$Y*ॗ^baailB`6 ے!0234 ظ4N0A%F#Bѧ&`LvYנhLmziL>|K^MLLLPשj:^ce9:C61,'J%*%>O H[9+kkA_`X"7ư&mRPf& .;~7M]F)š},/[N)F/L^ߩ?[Y 🀿EQf5 X^^&˱ /8yG8M' @3Ct@'-C3jmI N3 o[˷R~ hZllle;o9 |pSSS@V/.|#&Uq[Y;€ß 0JԐhAe01poFQWq<&Qpv^ ̿hā[`$j7FC+HӔyI;w$ uPb!͏,V0Zee/G3%BFi .^֚r4E}]Nvo5!cව 0j:iܤt.,y.[ 8P(0Q:ngdw}Н]a0B!ǾY*[u& ܾhmfffPJj[nqƍEf>E/Edrrr4Y `zzNKӡ:^jUڝP(P$)vcvP[Xb|d1BǬoH!8K .^*m86JZw_&ySbg k0Y}Gt2Gq]i6X&lJLe0W84 67kDQD=r KK,^\$MS嬅$ Nu{'/C"ltqv5,WVM؎y)W*=v}0&mJB\",ݼŋ} C1ϓaK r67nܠ^-v (ρ֚^71v0JӔ\.70Bֲ}6kkk,@u#EE&mE4Mz޽ 5*=N )?dwq oA;; `["|ojq yS(L&-~:;?*?__B+7:0Ycg=sqgKR#)-;sg{~s.?Nbzh1)RʽBPJ1m6ԕRg֯kOz{{cl!~Yq|Ç3??OZR84 9{,ϟ祗^VQ.R3|0 wիW52x뮻qM,RJ1bAAq̹sxg~+BZezz ^Z{ۼC ~{Rꮭq#pw8J):gΜaaaUt:t]1yT*fffѣLMM AZ\\ĉ<ض͞={}y;󗤔_{\‡?a>O2??&?9u/^D̗MLLl6)4M n˲RR1Ɛ)=zCaYV'N.ssseY ^x!y8v]~+I߾|T*| _ѣcpO=gΜu] 1\z[ovM.ciiq._̾}aJ)fffx{q]zη-N<a>oW^y{w];v"=<m“O>ɹsT*z=ϥKسgek166uiA@eY(p5[[[8$IBX(wuBN>Wazzz˯zۦ߫w6 >rm$ 'O:B@VX,m=cimE<R<?0j5޽{#)寽꫏kz 85GR(QB&(#`, `IL[5dcxv]^kn :?h9q/ssso;w ֡Cl6t:|sZSO=RjԄ4M |<6iK/ùCw݁wV#D6Q W7/.9*/c/[7Uo,cn:ıcw˹s瘙+[XXx/eEŧ>)&&&XZZ駟&h4R2==2eI~!mwI!x#; Cbh25&MP#"U/y<9rm1LNNǏ'MSo(]^^po&4M|emm'O8sss$IB4M潀?cNג݃{cc)tW]^̮+īkVW]]#!I{zmh U.m5)<G<333ieYvf޽>}dZրCyq8r!~$?!m1aX^^0ϿJRdĚu\rkfJkTM Ȧ}])`>ULAMĕ%SSt.6HCG L-@fGE$W7WבcedL5Ғ|dξHqWPD{ J%VVV(J,,,055EX$cVVVrpᯮ7`nnnRѭ-{T*qe\BP\.sE&w!qv#HjMZwngr2sz@ aA DvZUobM#lt!DZL~N$LLL &qY1v{n?xnr>4_Ʋ,6qS'Q8pTB0OU(:̅ie#=̦}!ҤF!5"Ez$u)J 666}\.\t >811Z-!Ba̶7 ; qG!c\ֆS.ZAz>I6kA.² i#,݋ѱ)1r׾н.f%9'᪕ aEkkkAo]PJ-v:n@80h9<\˲vZm>n [un[H֚Rms<;vh >E(XYYAAR|FK k\T/"8xTVݲFH !C-_H)tRUP*"h%H.䐞h@)Х:Nj @>gqqJ@\VRtWMkM\w$ Q $[#AG s!HwpK'˳}u!(UL\_H{QSVϜa)J8tMt(kcrXa;iͣaccR4,jm3>>NVu/0q$rLZ%"h۴mliOŚG]` mb!TgJ<ҶI{]:4.ϰp*u:xFkTE־-aD@j4+呖F#0G_"O۶qfWJkapj)aanJ)k .FdJ6n}"Oz0ux;.,݃, FhgɄ7Ϡ7M A }$Iі"R <* dm,@3Ԁ!)G`ǣs 2$ }%ieIO;Ik5DQq!,I*hg9^]nL9qJ3JP >z l۶m{z8;!MI5 @k6nj!Z,%1RHnW*ʅD(&N VW\+<-%3q.{: ,1 (J(Y@Ɣh6k(B n5;(7!,?6;=D'eF_w3t]5: A׀xˍRa0uЭ&ɵhiI fnpj5"F \SS$ut/!B¦&>ϟԅbSA|f< @(mQz$)V1tF%8KXO^'nZY7 Aaz Նv&ĤI#@pMX䅜& Vi{0tAh 3mU"c$5DpE:3V#()>H 9in1I]d @@oS( ƈ#&0a. R)''}l>`E?B_t:;0PǁOj2e0ZNN:{^L%F i! vd.ׯC4ɥ˸kL2ϔWBn:=iR=^`=p,khc7zM6?!s Ga@ZX,Xn&摭-|)ZdF[YItuhq_8һNӾ; BH+Ξ\.Rd}mm]'$ PH!BW}7LnmVl zi_Hx7:u.,5~䳰l,Au{ \J,v||M666h62cH0&Ma31c `⮻mu&[5LOs5yסR1APYYYʕ+g(& V8dRut:Nb˝JC89p7I m@])WJuLMSS1?-05fv͙3gz}~{3jdUu,&IR,d/$E{.%\cA tCf׀>zQ :f'P1_uXyJY^Gܹsgٕ,+y4Phy>A0)QGVf\½d. ]gn3])9HRB-\ 'tiד󘝝v=>˲h69sz>K7~pc&@*OLLjܤ\ (%"ȓuh َ6!$lJx9T}نup]0 W{llWyoW''L{Vx Yd=j:ho~ ~(njt:ó o 4M `̛Ѡe,..z# zm^E}0K A EzNs 2s;2l DoT;6܄^&^"}Sv4B8}r9&˙ yFJy灿tY闽kC,Nݔ~ xl/+tD&`;dV`/Yɝ\27`^~Ly?W4 IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/64/simplescreenrecorder.png000066400000000000000000000160141470053624300267360ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<IDATxi\ו޷ի7&EJ(Ŷ#PKq&ha@0YɇL0|ʇ|`x8py@6g<%DQIsk]]]{ի{UU7--'@r~sgs?Ng}RE)ekmZZ7Ƽn9;77wK_~;y{[8q#57OgYvEh1,˰֢B)yaHJ% Xku!O:E ੧:B Fv֚~GrQ:,333KբrMΟ?ŋ9}4vZ333 5?| "xOCƘt:Z}C>яr!w˙3g|2Zlaa^GVQ.RJ:Z,c߾}?~'|#GsI/yR !?ap nQڵkrffO}S?~k-.]^ܹsxrM~ar vBP`ccbH^Gk2O=O<tSX]]X,ZW^y?^Mׯ_gqyp333F#>իWٿ?J)^{50yB@$AR 5bl277lll)J~Ƚ^E9lz4{]:VI4,h-pebXZWq[/s}8Gy__ɓ'9}47.\~ԑ#G|`vv+W /5!^NB|5F0 K׻czS:t"˲!yVVV:u uY]]%MS$1[)[lZ}9gg eT!`dͭm׷h4jhYoQo` R`A?Qoʐڑw4D,//eJ)%8ٳg1+ v[ȑ#~es{{{رcd4q)<#24X,8X=9OuEp=R4.o\Zc}CcէRoRrFNw%]D!: 1~BaQRE'? AP՘_&+ʷz@E߷O#_$2fgg)A7y`ʯ+[,RVO&$L#ZJR`UV <.IވV{U@ OizU\%Džj:j+WHR!I =G+_l6OQ7:Vb<50c8Hº@ %pY4eaaa mooϟZ0 ॻg(@V$2^~eR>|VE$ofb8XdfaY H?I*`U[gl X)Az(aRI4@!{W"*rFAay\zBŸ9qkw!.]r1$amm c,?$dD""G(ziFe~ npF%)`N`"<(b"IȀw]F1}"qSYYYP(6ep;P,Y.w}cx"a""OFhG9.~ sX-|Z.UFkPPEQO.xЉ-s`I:QH^KH *fq!oE~"~7c:֚uL_Ie,+R微p<򐎏r=EKh mcMgD27A!RE * Ae SJ-U(鐥 qŘ“ /1;; @T0???f?Xm?jXe4Pհֲ5ݥiJj !Hb zhtE((F\gA\$ii4$(30ƍ\\a`020JadzU8G6{4 tnqy6} oi,z޴TX,bj] Cj p]"@(,!*^JS \r 3tTiumV"j+ [Y"icA$34=TT6. ZG Ƙ' m=JnJ9@X$EBQ44Ͱre*e[MNzCa0ƎRKoanz(w, @J$%·hDeak)>'4MɲIܗ%)Ǧ/BaX 2[*{8MIQssDkH $26N\M1XSy4C۔EZk )Bc\ם4k-3fbXrkJ 4jl3 kbF#d{=s# S U &Q9B8ۣ\ZkRce1c J),MKZ~ykka#769}uZF?bվY8#kqet ̎d6vo:q}RfZU`qҔޱv7o4Br@K ifbtJG9/33؇]U&(iHsH( U"i9c-,L[nRJ͘)Ƙƛ.CcPBO-` c!ՖQ1 m?V(KF;QD;ܔBwCC!W.Js1 Cyx3k/v"hBrPJzj cӱ\kRBఋ2e9hiB5 M[@$WZ-f %J'׮σ(}L$vl !8RJ{Zk4qfȠ!woYR`rw5;4\Kc/^'Nrl9qu3mܙߨT6hƷwmS]6?#Zp, ˿$5 !tĸ;N)$l#1v@a8ᥔq-п 8)Jv{ZF#HcHS0tnGJT+vOd$b1;Ak3A0I0:1 M;ƀMr3fl v79SqGvzćWa^(0!K㠭;b"9XYHRH @6\ (cY-YOj1 eMtՌFB\BRk ÀuUHwhILB5lЕF@\G=+xc;%Bp^[5yQkģ s3ݨp4M[|/M7(cl8aӵX]5S(xh)/8 $׀drN0XJ&aOFĠSQwiwHR*k/*w/GJ3᧐ULM6=,ǖrX@muRJ^~y7C{wя"\e'Xbt &3JiG:*l )mZLT$Ft|nhnh xq-APA}[M$M4a{Ôݤ+kN£Včf+j!$ҾycRH, ep0ZbC4Lb|ed>I< I2dtqwUn xa[/%N<.`M:AGqDP)Fsʈyg?2FM?`vnY 9n?[ILDA&G(nY:^v[b,JhF_5<Π l^|+A t[-zrCI% g >`$#Г*0y8/< B0ֲi(j\RZGMs3J䙟ѤNH0{ #Ƹ` a8ʈ##>İLN:mMJs1vh;W=c^>͸>Vc'NAP.RD:A1lGfƀ8BP# tt"x$&V+ӳшhą -OGw |ߟ ,.R*6zG>DyO_8 ahX\`;F(7@7Gek@f!KKKhvq̵k׸tc-t,,,ܲI}J3Oteש,/Tp+e4'FbmJ\mG*ĕ>ۍ>.!?nTw)n:배@R h48s^ocF?ؿIm! !{nR8ԯ=Da^=bٕEV)j8A@&%:}}:[fkG~cOY­ } Ȥ{W(ބ^&߽7D.z/?<u] E[*lP(XMk\!3ӻȗ.?]uSK!{;0Sc$o0y>~ 8@.tȗ7;U%_y?];~3_4IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/96/000077500000000000000000000000001470053624300220145ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/96/simplescreenrecorder-error.png000066400000000000000000000361321470053624300300750ustar00rootroot00000000000000PNG  IHDR``w8sBIT|d pHYspMBtEXtSoftwarewww.inkscape.org< IDATxսyeW}9no}_k˪Z$RYFW,,63p;3ӃI ahABRT*վ徿ֻ3%_fe"D8U|.oιgtO{ l`(Wt|$[TZy)eIJi)aVpW )i\+:x Xcp!RʽbL1,qhYZNiO+{gk#&juXI&o+ ;NG{kouݷ{[RGk}"/@)EVZRV}0 ZcF:M$ɐf8Z_)0 s]v8uCO/e-OR' ,R^[ ֈ=Bs`UޜGqVWW ]w5hYB !E +++uryhkkCJIZRJT" R"Dk BǏt---vy^絶4ooA!hiinLDkzT? nmmmZwik`iِ>7Ϝ1?L{g&Շz0چZLs##7,K^y !vg~~Zm>|Ço>LL0Zv@)R R0_YYᥗ^⥗^?.]BAGGݴCdTO^~ӛ_Js+Ǐ[{l Rr \+)w>"] wx ڹs~? ?{1cccwH)\u]fggC)ZGt aRY\\ZRq]d2dTwuu`I)1Msaș3ggz) L>zzzBZGtaaA}4lPsQKO=UlfWO6jz}X,^wt-mmmClp>+/X8]@w2N5 C˲~% CXZZ0 >z!vܙ\Jy'N… 2;;"J]ݻf`²,e!Lu]//8tttZ/ }}[1p񩧪^1#Νk̴= Wjy!:rGw8)Ӄ9\lwyIk8̬2=={ \z'NpN: mp9{zzr`&,--Q*hmmT*]8޽s!d2d?̙3yv ~=)8Z}Nؘr]Nٳ]_Wl>zz'w,ܱ_zzeӑɘ} l?!Zsjq;xIu]yWxWY\\\wVWW8tsioΥK}1z-aǎ[o 7333!x0 ˡC8x |4 ÐU^|Eؖ1 wbTzgr]vtj9|8bzxZ"/Փ'0E*MNٞI|{.zH={o|?gΜZՅA<]]](봷Htuu199IGGDss3ssszr92AѣGyVtttnd_3$%.T@JѺ"Ѝ,/+U]7,z./|6ͧr\?7ܹs`p}^S6Eb%W z8;vpύ7r%N=˱7MzT2<ԫNHo5ΊUJ~k75! >6qN;koZk<###,..R.P(PVSy벺J 4Hs]RDXLb2AB6ez{{) 377 ORaefCt}:+/D0Pa+Ilk]5 lE, z/ 7š&)ߩ-ۅr2F_ti0:1 ?}{}++gƹjU)´,۱l>=΁aOL$J !˩X*V,,,O㭯R2|JBss3TUZى e/DvV~L!%硴FKbB"hIP((U7,;Z,eaaatΞo^c>+3 -ˢ5ՋZkjRt}7/<<@by2vpp1"0`&m*V j4'O ~JxW()eCQ"4MM)[*FfdjkNY\ءazMީg}6$D[~zFFo˯Z;)WKٖIm܌Q< C*JlλB9ZjYadKf56H)R&T!8**N!+i677ϱc8uT Fb3zyZ}!!Ç /֜$7uWp҂+ ;2b5:y9s:B!N:P›o9a#G7\*=XGYΚ ǡ9+" К$^n$nPv]Jn?p??s׋mTUX,fBf8 | /^P,RT}ˌT#|{HI ħZe R$H)muQJ!MT$U ]ŋNzCk+on ð?d_ I޽Z-,5iQuUބ,ڔmYV*ia~lG_~I]]|=477aD/W~;پ}t֖&қ:㗖Y\\drr /DS>O.C kL:҉ 'YE\M$˥`:2Tj<A:hlkR~n޲n믿^LСCRJCG sOѮXӮ=!2B +EA4ܵ(I ,:G."EaddC5|iDEC"U9JkTz((K\t'OZ]eQLuU 0{)J!0M*JM ޛU=vI)Ǘs_cǎ q80<_nrFmzZ-k=!FL|)%ATD|!0- 5y RcNETy`[wSSts;|S0Dkbq3TOP]׭>^|!7=tyzF T*:mbx1xJJ[ڶ^r_=\۸RN(V6 3JΝ.dX?655ARZH!0 #ҏըe455! .nři]ϓkoC-0T\#ZNRZQT]j*z QAVBRLPd[{-ߺdF1v5MĊR1UbD~LDföiά烿yq@_~b#7:t5v斗?d_ FڢRa.`2u>m۩;4L&R:܂d/r[hiiŲl23T5J'%Jn{}Jʹ#G8,>{]Z#G=P)d:'%F`ar㤠T][ohn拯'ҡa|u4}uud0=?K oE5^eY;f| re&|3d?/Eއٍ4 ,ێUPjбo>Ԛ},Co}7R7?R7I!8?ϡxxK!m{/I*ԄeY鱣T X\$-۷snz3gA|sa %k5 ?Xҏtwc7AuVA"e>;a~0dhVD&cʤR\V)j=\՞xWmE-FRBL.,vuQpl;JCF HeEw#})/d4@ Ng'v /|GRx~1 (aLM߼pEs|ЈK)+11J):h4 ZD|嗙PK*,Oay88H2+ێC&˘Ru4Z/.;9?3Ùȷwu iX *H%! B deS?!Cg\JބX:P0X{57$ZGfYk7iYq8wJپF&ae%޳,HR  }}}޽;msqz)q` E|ϋ*V@#UFDJ8_i"L9ttD LOsI;V0ɚ*q\2q!3w25.!{ Ls{WҚIߧj!MMM\p5mrWK@GG!_j5y$b&,JxD>ynnk]\D+E>W ӌ9 .R1ܲcG'=Eӊ9? T!sL?(½#&~ o^5 )H$ĽNtd2Zz{{SuuubH&A1XVf\.HꞝqS@R}]_j>O…$^EBU\N P.ɓhYQ9!Q^{ ,qD|$c6~?wL)a@[N|>s,&^~K/vNP9Zn%5eyZV *?|8$̆ } טCa1&''1M3ͼ,,,PGGG ZookkwK hoooUJߞAk.,¶mffftrZP(B\`-t 0;3ytvIx{-X72 dlD^"vk+MJ>~-ຬyBeEQk P P8CT=J Hˤi^ؑ#Lͣ4 2??O\q)VJܜh* r9\M,_<˲F(, !9|.mdZ[<tg˧IzBSJ)CGNPE*q0$uֿ LʶG}zoޖҤ)7|>---%*:zwoii:n>۶mKow(]Lyh;u?bɈj+:b-Ϟ!k5'O@\:#Ri SzL^~Y6{-/:A@9B&7-6m6VVV@3 S 8#c+q=L65lJU_dcư,P{0E=grjӌ3d```Kay) lj M?H4T*-OdYVRf1Hut;ʹE %ّ0C !#5D+RvTP XgS\Ƕ"G3TDA=(`2`tu2;?{OO7vk\.||>0nZˤ$~ՅeY) 0Lzva@xu kAtQ) j/&2QJ20 akA~nmcǘ{jӀ2~"#x+ ;n 0 CSƪ?:ؖ~9ϔG ]ˊBVjv5O%RAH%暝IN&B?RkInn71;""F 5lIM3 Rcـ$ H[] mq8(߹k?!|G7LvaX*n µUO7,GL |D׍Vz-D MA~d8|1&cd JkQqb5]ΖSxma1uh&D+m54团hq@)sAQF L{.}_J;bΌsSףKQ'BU֮(=!BD:?cʱy7==(K:އ*a7םa-f?)6vV9Ԏ_"ECJI.EL{S{qvE5)7^Ǘ]QFk|?43HI-^zUFlv6O?Ƅ/QtdIDAT!C(-׈A%Wq.Z(6%ja1uh[9+AJqK-N+_6"1 ?CE_buG In5*ڈ\_ƪȰ-;wLz.r6D5A7Dv !] 'o|#ıVFқAD WI*rû~wM(J̴ ^ {3lM$J7ؽE!%-ۆi>pӾNB:62=JMTI4 u=\u!idwp2<[L>wMĿW 9pģֈ}EqAdؒk):!@h"OHkVZ6qtObyI0ozkh ܈\0nO0$3aF2מDWw] a卣 6݋|Ij4 _dJ!@)qn̝:I\Ɛ] :Jj 64?i?Չd8&b}%l C) ۻFЮ ^ w|y|t~;?sηG]]ryAmJ)Fu $!h x4 />àeJMOljL7ޛ8ע(u'0<cmS<2,L`H0:֣BB\6d&D_ EWJ)*J%F3(!V<Aca+E87ePu !/c)*ĝ@cض_@0D, z(5&^olLh^ t;J^\.5>͠e^ RWH%@Ķ`+J1onwj |dtö _) )$A_~[:Z砕ƭ h7pZ4w\XjZmDtVΟa0d<4: BSItki7Zqً3ũnxH#{f3Gaq:]cFP5jeK#Hm2N&nQo_9|!c{c! : cbFy{3!0Hm ZbA G,bH)eQֱUd'k*Q jU &batdvß L{ 6ۇx33iu>Q">{H!77pugM$Pj}]0›E>62ʰm#Ͽ\0eTkC⼭(a&go^Fޫ! ~f.cو YP*M)H0N-l:c%aFz@:Y;>Jap 8azC/Q*$^oE֍'7~Z sp=7Z\9&l CheaZGQ igm[!;nk!8P^kBsAFm`iÈ$!t !m6n7~uݫIʥ Ð?b*E7pMZ$YA)-^߾.0z{z{B r!%*İi|]~ڮ] 6Ӱ4a47ybk#'GkMTJWn C$S8wgeq"!bSQ_uc$ maeEAX²qGeXSa!l'&~r-ZZ+ bI0/,,ќKK[Bje{p!y1?olpd֪Y˯¶LFG"XZZ6|q{V:I)8Y,LSyfNR=Z ﮢ7c RWV&.J.C&C &"I׀on@xäQkm}ߧyqa={_Zt'/~oƑ&)(پ>i3fn?\#:TKѢ\Ϻk콆^dL%'4!yi[ʆG@^l2~0 ffxxBp>ϐo|dN X^F+%N6z81VӲ0h$=ˤ]/܂8Z 2h5cmMm] Jj52*J(d`d$ú6b\ywhl3G%4A𺽽lFwBaJ̓"Hu!*e;d{zL>(A&Kyir]]dZZ~$A\vTPN-aY2[U(ۦZ86 |4toA'[$ bZ3hBLBBrkιDm&XPKN $_;Iϣvwt7@]X-m^<*[ڐMXʮx=dt*Inmۙ))-/#2Z"[^ bOoPhL&CvXJWVA@fó.8N'A- с_tIEB)f / yLW֎ɢZR'=9! },:;.)., 2"~]t)]3ann`Ǻ/t{cr?Y!E D|MB]Fu@ v|c"S =mQ$F 0d S]aTb+u $~AO4(4)E` mmT*,,ZEcrrrf&JA\%N$dY7b!**7%yԅlбVx˦@ 6B)8SՏ'O@",9eZ03*Q AeZ2?`: $Tv1 yX ̔˸K2$[4ypB|::3M~7WVq]҈t[ {,LL+M)jmCߘgPz$!wQzYk5sYK LUmmfu7NL|0 ,\WRv$Y.ҲmmeRҚJPO^.f2O$?Eanl_]]kHs%hH4HZϣ $fk+iLyjd,&? HO*1 $hD.d--0U0ڂ8kI-[V8-"{HR:_y4ٕ2[њP F^~-W駟;޻bى & HfP\oYf)ljIA54?'=zy!b_y\wWfg0::,#&> ɹ+-TlRRD!yBiͩI&];s>Ϟ_eX ҝgll6Lyf,_КTі4kkd#s`an&+ljBIwL|Wk'_f^]:i[V*FÚxn5,ˊdql'ڂ_`uѻFn۞uw`YъrTDiюaP BH#ϝ߶YBUU$%y!D BgZ/1W'}!hߧ^,R- gRďGez:;LOMx^&)Jβ'07_-T_ߜ .JQ ܌mvҍ@H3ˡ[KKb(ol6r MQm dͻځaH|?+s]nݍ, v젯\.ש~>Xj5v#quhUÂ-e}q&Z47@Jtd?)cw TjT;KY]Ef<9hjjfǎڹNauu5}@[Z%X `Nơ)6,+";b@[+L_&kYjRIVėBC3nIt-#/|/vmUgϞڿ7L3ഥRB_{^o)Z7+#ixJe|iԩ Apt=U,]@mυZ0l$ !q>OL>]\&''\xK/"HEؔo`&2''ժ@Zaeuzmm]ٚ=rӚm3=7S |L.Gr ,WZ/-@:e,pR.mX;cu7 B -%Ϭ?^#$a&lTOK?/^taDOOm`Bemm Oui^LS'+KKHyΏ~x_|q%!e\O ۞soORr_ssB+#eqf-)/E cU[F~:җ>|=/'dxZ[ JW!p~BZr~ r5zFO|JJ֚L&C.cGSSaf,i.ZD!mVV <m}빅m F%7l{s:Jc#']ܕJezv XWLaD` )ʼnJ%DvnC͆BiWO<—_[^ | kvl[ }lqu[njTyiiL'K^7 w?MMzry==5Ƕ '+W ^)CH|$Q^W^M"V l[Z]7baavt_#t@\ɤ=qso|t]Kn7ߺ馛>o{f4OtudTxRMS 0Nz,|;Yo,1io۟˕ţKKz6 $n| k]ȍ\~ /LLLd 2Mxuk '&oWhnn !6;):_qx7ȶϏWAWa~O~rz Ot _R.2;m[ )7~WG[[[WWzønYG@KoqD-0C1A|M4AX4ܧ>S Z=կn~VWW;֛&%#BK-~n]ZgWWW;}K۵mZk*fx[p1h!JU.  Uh DܝI`_B֙ X󷸠 ?> IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/96/simplescreenrecorder-idle.png000066400000000000000000000321121470053624300276530ustar00rootroot00000000000000PNG  IHDR``w8sBIT|d pHYspMBtEXtSoftwarewww.inkscape.org< IDATxyd}xGYUYgw]}MⴆRvx"WZk͵d]lI6 xZeE@@3䐜L_2|館ᐔ eVx_"}OI|;Vhi8i+Rq)eUJi)qC`گ^Qo%/_ƻJ) !.!6u6Ok}zxsMCk>CRh'!PJ.8a1QEZk țiE* RBZz1ϼK{g޲B'3B<ϣ鰷GS.T*TUǑR8^G'R!Dk{{{z{ }/???R_!;;;4M|`rr{ .py{1.\iiIa͛7u7nʕ+\r8BPיfzz4Z7R+_}W]|L?! Ðvvvp]۶yy{?ŋ1M3gW0Z~@)R(r0F_./2/2Y]]EA`zz 8+r =܇Zq}F)Ņ G>‡?a0m~xGq<}0X,R*r?55%4Z17oSb9fffBDZꫯ6wa `ccVa|g~g8}tp)%A!曬f]J%昛c~~>7aR ˲R>/"ϝ;w( 8qi=O:rٷE)JJ@uwwu }{??B.Qyo&_׹~:;:rP.4MiZ}U(8wO</_fjjbkƗe7nP.YZZR,_W_p)ǁ}}˗/}'O>_**Amzw Ð7n*arx Nbgg'NoOjD <3T*ĹxWmZSSSY=o|7I~=S?Mh4/"?S?EVqnݺşɟO~7o8SSS!tQ 0;;i055R 󘘘l1SSSh4ÐjJVc{{g}r`0 "vwwyҗ84 !?qs&RɴmsssO-,,N駟!a8vm:/+B01ԧr266"(m4`wwz7|%(BJ!,+ vakkEqL֭[+de]x'|W֚JD`nnϷ;. $|_~u0eY?|΀pE^z饜qJSN/rFJ%j;;;z=," GTZe0pIؠhv90;:# A2m* 0qB0==믳m#gT*o~~OZJ)fjjKfs2ŋ=8s ǨT*̗%LOO8H)Ų6ommbtҽiXbSTkkeJc" !oأy{om 2,ĉN}7XqB1:{eyR7oo .fVŋٟY"B:prnn.`6a扶(K}Op]^0S@lb% BDZjhi1 PW4XΗ_gڝ i2>>Nyu }/>l)ݹs},/xk^}||[?ׁ鰳3nJeZe)&Ƨ)jDWp:@z]x^x˲M~w~ XZZ0}ׯ_Vx8{?BVf2Ν;OO#M>Ys=lfvvuLgv/-v"2$;6;z]N1ub4c8Ҵ8D&z=ڭ;[m678ÀL5(UB_1~' s3x-//}˲PJy ø <֭G[ٳ_<ɉ'u>H/--1\reyGP6ѬnflzL" 6&v].c-06$Qq b<'d0p8Ag{fKfWDfMV_+  ??GHoeqI֟YYY1f9 pB-xvssq>яR(Z3~~uQJv)O?G>8=7 v=۠X| cإröE, ? ?q " q1[Ҧ8} Q2999<~^hju]RRtn||t:/TCOzNg?Y0d||JX[;2f9E! nam&1l XP)'=Pk0d;1; axEð~w@bHv~o]Q;Kmz-H$Mloo311`0`0GkM`8 "_{>t L?~vyta!8qzmN%&.Z!eB cUBJLe >ww!J b!QB@I% Z&D4'%#FF vľ@GNQ)x$@8uj^On9qa܉ҽ{9OՀ3gΌEQ'AZgϞ˳_t:H)9}4{{{4Mfffh6x^b[?.xfsmhS269nt4BK3=6a 4&찳T+cHa"P1 ,b8=s &r:iTUfu5k399LLLP.Z377q]J2.^ﯾ-Bjaf!qw4MΜ9CضMӡ#ǟ阡u1"))41 {8ADRRSV mX)> qL 4{raXh-^ ZIeEG8l8.33l6ڢh.[[[y+޽M,@Z`08vR8qb8w;} HL*sss`YvRp/qax:pQ-P@˴AġeLYB=t=!V+Oa>FՍF5zRɐR^[TՏ+199˗zW_})Jt:yVWW䙱'>PˤX ,t J$F6L0La!-aQBK 2R >H2BA"0"*, w YBV/`&AĩS!czq8N>enoP(j/[o wk`@T*!d}} !6 nݺEVK3u>0ᩈa$7Mj9|$V6M&&Ц0ߴ0Qٱiahsv0iA+|7T(I2MjT,)KET NC"c.]Kfaab^GբT*[T*Bennsɔ+WPJ1;;KףP(`fLLl>:# QЖr 2 |V4V VjRL35;d!ع/2sfÈ&(X!8$Z"q5 I*yH) (Z۶ܗ5=FqIo\AX4sN21== ~wƐ|HMq,LM,4qLR13-}[־0, ˦Hba}i>@Pf0$f(%JU@GIX+e $Fywq!ea6O=pHgnnit:Y`q(JJkFٳgڵkhP(JVKAwӦb")i*!G|)֪O6PJ$]fi!2$ "ՙ2rT8n1>V^(XI4o*i0+I(P&ZFzWCʤ&J8 aH\ݻ!0Ml\#uDDpԩA}q2ap=.Jv,//IF-TUhPb4rKPh *`ěǴ Xciv'4_+ؘ(UضfȦ+2LWJEϾٗ~#5GIӱ$ 5Bi@ɱ@kiLA`YsssLNNR`qc&{{{ "Zy@P b&ϟ,nݺR*={6MEH NLOlHb) (T*TL - aXT6V%hAC C ȁ1t}ӧfiT ;1N^ק'4_h%ѱ81E:H,PcIbtqLMM8R+J^,J)ad!u: X?ͧNMM1mi64M:kkk=Kcꘀ`ɫiZ+eHqx]t܀bR-*vqN(5[6&hZ#bVuO,<  N!c,Q-1{@$sep:a2 1V+3C?  @E? XQQ((JhQJAʦ,i@fN $Bo!ZS 傅)`|.? (J1BI|-(i.ޢ;/}T T2 0t}eJaҊXFzTfbzz:7M00~OQJ_kMu8FkM}I 8ӕiJ& mD#"mȼ!$ۤl8T&J4(lЊz 8C-z}7^wkhxӈXxK\ZGFY"/kyL|v[{ BIcxn7ȯT*BT?3ǑV?JOaI?"yo$vM@xRR# ( ؖiI&U! f 0bEEHzxIs}pCv6OvK_ɧ9J 6$ݡKub lhspY 4T4(ZBd 9Yu0w@"Z?!3)Pef&P)(7T.k_h{[^Wp<@I,hR \O}_⹟AtzXl D*KeV4 PJBԚyw-Hq@ CO$ / ~rwoYKׇ)v[K_ Lm®WT$0l1YK}Kļ|і/3C#@Lk=6 9 '1M$Rf+(a24ZiB# 4hv{( Jw=rX0fK %u 0 0 b*;E ! :~5NN`(#O i:33AZWJ\~'W)M) "KP44ǥ`()@kRڼUig˘q(\}{]~XM\)hA;EPs1]bGl6P2[ B&<! *)%Nie⪐vZcZINO\5%ǘ'ҚEk'._42El.WGs0C4#@r?pP9 `BA7&.} *PAz}X݇)R}!{I&C k,iBpš I·p6@<#ǁBN&k,x8LoT'#TKǡ8(!g2ITLdR; A5 ̍OMDJXmCKS4(0j/t)}iA\(a'B ut@Q)8P}1?k2;άHad:^AHaA; @=@C'.^p1C)ir%4J L+fMQI l 7ȅG8FW?)K)?g4:>t}@ʣHp[IDAT{Ј4<QV%X-aLU%RIJL# alDžtp/Suȇ c?ш _F2aL(pHþGtb02i9g|z.c0Dv\8[ *YII8(0 @اH1C#+qLIHG:F[2}m6s uYMhxxX`[Xtģjk##`JE+ Q*n[ >D}}6Hs0#ΨQ"( #7lgEH+RRRdhK?B |BXN&Ze~jG&ppiMT$ BJtwpQmp4ՀyX: Xy_g8BD((l8I,DzL٤KLTbH</4`$~d<+"QQ)}9<;9G8Kq(K1!4Ր0?*Ƈ!8.BVWJ^a$|CЅ0ˆ5G8Fi:Q[9TCLiR-@,.3^cwcy` 4a{{˲( Z-v\_  HEpd'N^kMTbuufdBW%͖4'TQxr 'zD@3 8h:B\%=c8|'FMmU6]T 'Yg}u !$&&&M87_YY8D(&?0 ) yD!^ᐭ [̟3' І\>L"?@exlP9ܑXNPDbG'p,21"Ul;;;T^9vRlnn~\헁oH& ᓙ)(+>}jl2s\6Can~3RBMO=jR'-NtQnM_#юa(0ɉٙKFٖnu{Af&8s,ᐝvRW^шhBEY#em/4MO3QGkA4{H xӋܺvnû]]V)z96nbgg(ӣX?z π m,[s %K5;gAXvˤP)RiL5/ GP# M8s'F dÁKX}?un| W(^^n2>>p̷,rdwemm8[$( |?e;R^4- V6;&{.{JXR0=3I^ UfOh@@Ls)''1@dg3g^ÝLc s/-- SSSt:t.+++Dž;?Lyw,=, F?SNq=J>p3M%ͻ/7ĉC"`vaJc< g4nV7>呌'd>x H#Ah٤@Lz7,7 uZa<0q|֬quQG1Yĉ!84MT*ܼyIS[)\\zs?}4N7+W>"󧖨-P@Ia$8Ɣ6R&V8"5lm*+h~ng,o^׮P߼0 l p Է$rT*6iyyZ*jVϴ 1ͅ$'Ϝbecׯ_ꍛB @SLLMRT(V*v!ٮ\vwZt:N:MǹE.r믽ݯ\po.K黻Ң,y7oͅhpgsoݽ5vw{nGbeP ) &&93,-f6ɝ5n}&\`*g|`H\fee49wwɟU&u<9nvx7iϣ@xzԇR)ߗ0zEVWW9q7o<fX~ 疙[Z`|j/p? 2fBdee{6mv'rYd1+ >̞=NuԇQO{tRoSMii4& { l6qeJRv wܹ 63dBS4<{|'`뺔J% 頵΋4=FK|OjN|Py;4Bc4g@EQ >wQϹs}6;켥')e4jϳ3eah9p88 T ܙCNg+$)Q_-, ӲhG7{Yvl6ަ<(I27G;#q"{w?%;^.sxu 2=Q1TvErۥ={ Bv]*=}<{ I ;E8}Ȯh#*۟=l${fÖA&J ;ˊ-F"`;|.A8{ |oo;$ɬK|;>I_+wn8C$?IJ!#D{dom E o i+0{0I&GL/RIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/96/simplescreenrecorder-paused.png000066400000000000000000000314421470053624300302240ustar00rootroot00000000000000PNG  IHDR``w8sBIT|d pHYspMBtEXtSoftwarewww.inkscape.org< IDATxy$}y/3csp9pHgHHֵ+l KzߖBa  -)[҂"̵@rH#32̸/"*g8$eC? {^}, ֺ$RR@1zίگ>Kz~RʇBU!I!:{M pYk}E):◿?:#ȓZI)?$@)p8<<# C8&"(BkeYf6|RDP pi_PJ_|Žmp~pqBBFv=:o"RrRJ'~055SO=*ϟ硇buuZmوOgI:+0d}}Wr5\++B!V033mhJO !W;?Pf%~BB0dgg!O~~ .`vïeR Qet:|_WX__GA^gffIq7 SO= )?B\}-QJ?'> 0t~njF#1}0  LOOOg’RbY1W^ _|yfggBDZ/7 'X,B }6f˲G?rRJ Cvwwy7X[[ckk-J߻, 3??R  f 7n ˱ +++g?>}G|'C)]666,/'NFiEF#x ose677^9;;KXdss۶ljz8wy{tylf|_r bSNQ*!ַn3]KRzܺu tԧX\\Fܹso}[4g{ZK\re ÐU^ur+++찰믿#IXE|;ᥗ^<:RJs1 FB`GO8񧛛wM=د!~/ i<ʯ 'N@A\~?? _BBj'Od0Eb6Zop)(BJSSS!p' rV+3籵ɓ'LNN1^k׮o~ʼ .#kqm֔Jq^n #*~[n8??O?1`0}_|1c\Tbee5\p+WW(T*vq0 2~Ejoߦ^t8<ׯ_lCE^O|{ |3_RkKkkW `=Mn`~~__!^{ʕ+!8uloos 8jecǶm\%V Q3vr|Mj!loo3jrlڧcēO0U5F#!3}lbqqvX" CFN.c E\Eku&_K۩\… <8Clnn;;AS,!})?m={ !~+UΝg~gsss/~yyܹ133={^>@?+OllF}r KIimfC2F nܠlR.(Kb_86-e>8J)6662ueYׁ]|[8{%WGQnssjgcc/SN1SSS+N{{9|mF,Z3,-0xe۲3_ݐDr֨$ 0 QJX__ڵk}* |n+c1???C~:gqE_\[[Q2rV(8677$\5;;;󗖖(t:y|ߧB#1jXXXQ]s=3̙3(>AlrYzLk_n#h4h0@#>LE }uit=|9|*1>3؎9"\RDRLVT*+E+x0bL@f_E6ijBZ),)y}r9S'O2u P>=e;r9ǥS(7 8s7]7BX!|FB`āYD -sea[^x5 ;zfA#sss&H4ܺunKPzgޑgRgҥKY=/ 6?8I}Ξ=LM`Y9qlۡPsgk6Qcff7PK"maF~R}z\+RJJ*9,eM]YYavv8vqyY:4Enz-rӕJ~m'y RJ666}6`"zεkרT*z=@p~q#bjj˶ (!5S:R 4R*&4QF)(!Jke'2GQL'?W (AHiafy^qY\\͛D&qsE|g8j8qJ)я$bre;XEFNฎqAF+VZCbe|vY+E(DcW* TV=~EףP1a`I uq.25UO '&&Flnn"̒DQD\fmm 5,/xwjzE!?" m7nd8 z=n޼R)% E(Z0-V1Ok%K01<dnIm0"@:i4[8ܼ);:N/%`BtϥATʌQΝ;kLZ&Hz_qN#yd3%XRm!-ImҲ0%"B$)$QJgOfVh `d~n)Jlon0U+~ BV&T8JivMnXǶ iIؿ)c x+5R@ZkAАz9G8YIaPHV"!`J\B%  MzT|`qu6jCHJ!i%B)nD6z! 7Ch䳕avvqAY_%ew G25IChzz:2@(M&4q0;= <<J -67CZ,N{Nr VLbci%1!&cG n3cw UL8^hǞ 插=ݟ^*d4*eЊhp4²,02@\(fy>LXqlsiLgF:cTc[w,0sEZz,w0rj/Y2h @GSSF׻:j L롒"nM(N-z"Nb(uJf!4BMRhI( jΟ2O9WZ?ݴ̀[ @ATF  u6tj0uLql8{$5~?X_`:Wz=)N%3k 4sccq8:A$ "AcarH=iUG ХZƶPJfô8R]cGڣqHhDaDբj aĭ[(aW>aT^I?]5eig <T=,xg9sB { d T>Fih86"Ih~OբZEanPp) |{$4)IimK@g,L. ,B:r@@y|ͅTr"u0PC2,)&Mbd E1"i+u\0RpOJ3ϝ;*fyk͵ E3;2]F!m&cDL cӷLx ejgmg?0{HRU5BXXC@#?b8f% bvv&a me!2SVRs*Ncl`f~kwvOD  „2{sTlZeTmNmJ):2GoVȉd -8I -,3,bӸ z.Lde ?@L#˲;̌Q\"i&Ƶc$DI|Ɣz~C3m{C2øQf 49=Du ?$. @ ',x8 |f?$$A-qEZ+8J&x:{Df"0klARce{>Uta4x,0F=8h{!yF#(6wYO%̨s\ XDr&F#I%1(OnZ7u{5JktHd%B0|.4.ph e)unIDATnaiB\1*VıI*\ql}$Ud'4B'am; &d8ad`Y] I09{Ai@@V˞ ~8@CkӥxGȌ?20R"2DqAW Ii.Ԇրc[DQ_}+Tq2>xݖ& 4FQB0QpLky^NcW{D}NE)#MńAh\>_(d' : .aIl)I=RI ^&2LY{i0bBIfOpxP >Dp##!2ciWG6,h 5 L:1"0QEθ#0@LHZ@,Ga99H*SUL>z&9m ? WBR6.¹ $]0j( H`E"FHEđI9S8:JJS*e ĔMJ 'Jqv0 F/7 ,k In. ďN]BhK)8Bk&|~Gn6 [`br8gRVn2f3L ]@Ҕ n 3^W-vy氓#}߿!AC8rzl$0S=DbLӛZ3$bI0HJ̖E&'mRN}hA t @T"BO5^8WidnXg4MP~FTl"^karJ9ӴԲ,GZ^SV2L0uSfpd(!M2]!06kmr1O4c]O!5OZ/$OU2)|rs,/FJR]q+NMGz]%SAJ)Ο;C,WAFxހZF>jwF&i.J M\[tꮎ L#z ,n-;GЁUUπcu)ar]U筷b8܈Lq#sZkP €ad1YN̚7f*3SzbOsr.BN^kT+U t}7S%n(1滶E\)n3pQu <7# !Mɓeܹ,*˧To"qYp5ܳhB:MZcMEt:]v]d=cC| S*R.Cw0$ D}-Ol!E.+,!u anfhDa0,ɩShZc5`jfӚC|i|QHg~= ̂Z.gk#W^}%1>iI|,!#@ilKX&;f)Kv(LM/2=4B((V233K}zPY#ڤm qprLAY62eƱ-HJHﱵiF>ozfffbb&Vu\=n}_0(;F+Eiz!Z)benfP%}n?8R$}$6ݡd_R*ˍqEgggqLDkn:1:_ߡ211MQMfOMMlq61Fc Gry,OKW O=)ZŠ/CitҴhvD~ֱQAkdsj6( #L'bmm-)2OeYvc||/Us8pr|gr#RJeHZeooy;h3Ӹ1J).a055peufKZek3fM pYUq2[<(&'s  U98o]+i}czz:KѮRM666~x LޙRO\f~~pސm,bjN>'V1?I,ۦ'iE^ߟdT˧[ "TjJR|i:BS-z^V@PN5}_~\ҷ̑\ߙ,)K_v#5f42u^YdipH%ə1/Kg{Lpe`u$A=OXbfzVh8NX`nf[\r^nekW˦Yf׮]cgg(G8dً wA`Z RUrmإ3UbjB.ba~GW0TbCx2/9̉ {=z.v?pՇPY[[x0+7H;%R͛ܺu8[z/>/|?+SZŶ ͛e֪0FQdr%&j5b|F`8k4?|daN^MLyoW^% Ì Ӵ]֎kv»c>TIO"QOZGR;w(KY.O<^}Q:lmmq1%߮癙5z)4Mv Ȧ|3ԒL׍]Qj5cϤT*^>+iuYZӂn˗i4G0c `6p|>>ٶRW255v+LNL̙Evvw&BX/6+gnrBZ!E l(Q j 'TllsnD)̹CjfjZb._uA#OoG`/q*J2iyyJ:rfeڄ4Lxߥeu]Z-lݦֲ$ah |o8u”Lՙ!sټܼy7],www֢Sv#>y"} W_;tУpq8q,--k8 d+`vvSr9nQ}Ab8(j5&'&( tu߼|;w2ɲ,r9'OR,Y[[ömΝ;Ǎ7e 9jOR N 0eP?,jɓ'Y__gaaWyn$3gNDZ\s]Yr)JT7thZX;H6jVy|gww@^Ǎ7v<|2 {0GPQy[NO8%ZO`qq h69s8GR}o7N:1.+++'v~vv6[!vvvv<R~\ICG8d({ݽȲ,fffH; 05$BR/)Mˋ)63I#<ָqƽ?;d>|ϐ 1h x|']樼rOT=Y@LOOslMC㥂S33315|ndccFq>/hލXi"&sEQ̈́tIxϹs~:`iFҸ>OΔYƿO|m_!z7% ?ꀔRlqJ ij~^kWMk7Svá1۴%9zލ?9D+Aeexۡ4:\ߧ}zwfwS~ wnH/٬#3=Qek>O\FJIۥl6iZ(ɹJ%c<)Gj;w>}Dķ~~OO !dfkk<`rr~eN !'!A^uyGyxG9}4m'[1&Zkքa1o4<<|scee!Ji&&&|R /\z0?~)B< 677Z>3555t J)>;;;t]>tL&,)%m/^SJV#N377 BR^Zk >xȲMﳶFZŲ,;ᮻJ.} `{{_+W;;;h}{2077 d282-O{{\zT*<5=>m c)/mVWW,=8Nzi}^u/qyG:dr̐fY__ǶmVZ-JV&R'N{ٳLMMNs\pl6ˑ#Gr !~_|f;ٳg>j~:qY>qС{Ǎ7xy饗bf_ pKKK˼kR)=k{TUB$eY:ugm;^~뷨VLMMmc/˿Nz'/v}}1)cC\~JC?C ..]ⓟ$'x"n)9s0 0;;mlmm155֚~;;;(buuJBy R_~2;|g׽wg !~/˗<<FA\||SJDHTbqqNCmLLL֚b믿Α#G)ebONN"q7HR&ʼ"J RZ-.] /yLNN&Vӧ{9wkkkcr(ܟnnnֿZ"H{ソb8Ï؏裏& t:<<3 rGʕ+!8}4.D_&P(Eq Fy6T*FRhpI._LZfccF@R?ˉtG>K/DP׆1ΝWüvXsoc~^??ɓ'B!ΝpB9¡C(MVEe{{;q~6J9V]rN'R.B0==^W_}-p])%<LW^yVE>K)djj ;;;+o_N>{]رc|C"%J _v,--!d{{qټ1y&xgyH!#l)񤤣B6J67yZV~^gss, |}!PJꫯя~#G8N_J_kvX^^uvvvV>}%N#^O:1'8 Ah 0iNuţMqRBb8 EGAT4ʹkhS}T70Dmۦ\.~bmeY<yKKK8B<oV8y/^<8Bvvv?6b!lnn(JRd/l:]RyxdI>d¶1n \Ŷm\MHil9ZYR31QӧOO8J)]|ȑ#X#|Ͽ^e?'N?ÉΟٟ%Q̓'Or \evv^D:?ʲh!zx@yAҧI>37pl2(Q j_w /}A"m;(axյ?|hii}8֚D-,,`YeK.m)?Ǐ?k|O3???"vVWWg?;>rJ)&''yGnDʲ꠵C2%c=LF6 *D!QAQ]W="lnfq sW7VbbnnN,˗/} q8tƘ?rw4#wVr! (f)|$Jaakk+a^5Ft:yZm@syAOף =} +dkG{>&О辏{l!-D{^F.ӯ7nlaI@M F}rrrZf-0P((hd2'r^?#J)~~b@^穧"2\Rnw|P;iNڄGžmVby"0RcY(aOnvh4ۨtzD=#|t5hv{}T3&~EFZUΗyD&m6V1T*:ׯ_'JӍFSw$9F'x3g`YAtSNl6q]U0I?99OJ>R4B(T(MXoy tl7B>r %7ўuL0BBZNZZ8KbP~|ׯc̙39s5O|],jgnܸǘ}#رc0 ?~Zrq~$:RבRr]wQaff?^o`N\:840ec\9(~u/ѻ&&bC&+߼Jh]E`+4Z9]!lvP.9aHe T>F\.Jke&''jloo311A6ϟRJl6 jmۉ … \}ǎ^s]w.zf_*@ XOA>884LTrvsoZ-rgf ^՘ٳI>K/yd2:<+++M>*$t]e:.lkb&,cw!3YeEBJ뢚Jӭ=PF!4(- ƀP!ҀN!q4L=z(333(h6(vth}71ƐJ* ϷKo G1n>?8L)%XTtBV&Fk!OD)ۅ\˂ EqY Q lLpHabm٩H"R.Hj4pݤx&zNZE)ř3g<^.FkM:lRVd2;%2ocsss8q//2Zkfggi6R)l s!ʀunD:eېIC: B*V@)AX!b=6l!,z;bc#!vD~XD 8F՛ؕ F)0i & 4}4QP\.Y__GJLOLL!|+W`ul6t۽)j/Jz}? ڶիWףyifffhZ\v 2=BG2RZE{ƮL=uہ kO!-d*EXkD>~"AxbRMjJEPzY>Fm^>nqp]NCӡj177۶i68Rv; 1]vJ7,T ujFLNaZ@HnhqgȜ a` Å5c:%aمcx8199:Zk6sss(mZF&NciV}yR.;FբӉb(? D<|,+z0 z8S " LIaE;V]kn4Б}? 1xAH֠r35zc"aIk#S.V6]aQr,0ȩITˁ; OeGիAZb)βKXP&?&Z#dwww0ivYΟ?ϡCAŲfc4XBNL2U,Ύ"VH-ؼBlELlep)JxHKb<Im9k< "/hQ!x&0FHEd)?$m .!uω(ظơCl6hT*XKrA%!ǎKzt]j|h:v Fɐ:02qHro*O"U*[%G t6ZM6ޤqm: W!2JO=@uнJGL?8vD]=j`cylaG,}}Zp\GJqz=Z')8:vu1<Ծ# o0 IRd21h(xV bˏpaY83kD#byD ?bSFXw`kn$lh hw"m €Am\YP3أ&;/|*+G{_9^cB3U!uP4/1;50}a[OeVUxgg'C>odfCk1b-)En 䈈%@`4\`6+ڬ|QΒ ?;N*@qq*Tu,(?.nhM5w6V}q;h/tt:"B.JS[F2ƘL&/Ƙ'U'i AJ  fϾ!BJ m.q3lA5H0d8#%\+,/l2JCͿkf#q/".MQgͻF+0,BD}:u OR0$+zO@XcA!reyQ'*#20[6z~!8jqf,D527aG(a>iI)MFi2&4 YiAAO£g1t5^Ğbѽ][K]ځ[vdqA1F*p]72}F oF&JGnjhmn^:T b|a8TN%h E %#022"[^>{8JJW/Ӟ1x<Ę-6"Wk@po1nGHJ${`)Je#^czx~6S'(ZaI\ІMd6j>(Βt.3)R&$y! I @1ૐKc`yQ F(VQ*KJ9;A$ٶr|a/B$)~ F|>?_1V?`0L.ݍ>Sn'hn} m6&PB(@c|O[tID0b| t!D0Yg^x\F0Ɯ8sK̆ٽFUGG`h5I>V[/?IRt_?OoռQbD+e uv&c[t;A{71}L!MWXض<hlj?k"JEc Ea4aVvgs~.\B5l}Q ه|c0Q ڤ%R ݡq1oC81󇷘ZL~ 1cL!QhA (Ȁ4"P@!lA@d._lkZL'*s@ !N.#qmҶSffAb@ Y>{,DSqXjQ\~`NF惕Qr D]y68.R --D" xϐgFm3.?W{ـ>k1x%XĒDZp!cd]6##C?>8`F(` =~O # 1r{! y礌G5]k7P>e.]\ o^[xar߃ Crߋ$A C fτ0Œ8k r;62c堑>>F@,~G=2CJ?:[ )) }\t&M mz7H+M~ckW1h"y~vth)X֊sl܎io"C6zhĽxew"~B^>'"iYLD9QZ ka}86m+X]BDbr۲–gIhPDL8{dXS="}eaeY=H!DdcfЎrl:2ӴFk2+M*&ט~ϟ2 D&i@Q Жe; @ &`P=a=8icP  hښX=^9`nݺYo#`7"_RF7\nju#l6 :>fqȧ\imc[V-ȱe:$#t|+!=?(_/ k[ÖEx? e]`òF# Аg! ^G{JYKR|0XFD ?;$]"13c&7@0F]`-noL7ϰ#kC_iL"Ffhã h% cjNH>^0Tt'OJM­B`S!KǏZ[DZQ*B'B*!r]o 8…pS-ۅ!Ro8a = QTNjugh{^ë7I"BDNӽIl96ť#x; $G+RȡS1RnL(u@? ޸w?NNwP:@f⇍@0on=AiM{}y3ɕ4\ې-1` }gPBJL)%ғNdtN3j\2-QoW<@Uau}zLns]J!<dPу~aTHZ \Bz(Jgz[)w躷&Aaiw=H5t 5^<`fC")辙t0 \;`ZA"<-qd|>y8Ɛj|\*Mכ> _jةkשswl̤ɿ[(<.l1r jb=Wvhb-I:!:Bny:Jx#Sz=ɔ4m8qK _DL Hۂު2e`yV~9Uo ;]* WD% #]F"icz}\!py*N`_cLwcQ,AXx!qɝ}hx <5|0N'u9qקjqmp1B(A\ߋ̌dnt˧|i:G7k~@ lg/]f2b B9wBi,!(,PF]XYbXŌ&mI&J.Js#2ѿbE> <9|$B"|7n033C}f2zxNM W11_E'N,;qW_EZf^Ky}qoz@"f:±!ʥJFʨ.fy󷀧7r 9"=4k6| VQ N!1|,b!ap0ZC Mdm 7jL-| rXNT bg"sqC]5zo^3⟘ALwOɔyZc0d3i ,n0vHӤi"n7I=SbcOo?~|`%'.@4VWW2ilx!UśXL=f L">+w M3إ|Cū1b0r`]86q#4˒T)\,)d2TUcgg!f݃8ȾVjqu]gX[[c,MMӞFra!^dݯ`o;wxAj= IR y*m`U 1ax(P@#ڝ؛Ix8L~ YcV; Q-Fcv{rΌ.C0 wuj#=1K%&'ka?3pRn'KS/_|rC=Wc%$K ˾rP@:vvPA"2ރjJÊad3>i縇u {2Cp2١#`fBTbVwLdޭvu>'l6\tD+` I63?`,3}\qIQ%+M8yf)v;A<]cw9tF|+#vn\XBR>2N ol:v qCQܕZ;˅8#)BdY#vׯ_?H& i&?~m2~ā?Ep2T4A? $ӥҗуy#fhMa4~:Az3@)뗠Ѡ!(JLtx724SN󗖖MMM%e1loos6m^q ) /RJeIѣGql.qr~=evw2gJk\7F 9<jzo 4gv# "GM+u̥+t:]6evv˲h bb?~<)'6MΟ??X'}1>y;y.!_ǿHKpFȶmr\xI67RrcPit&nu.J@r8n**]<9PEtYۈ6Cק 2=5E&VwJ.Aw]' / P*NIi}0s\.% +++ydMx,P|Js>O!_iSi4 QA*"Jce#EJi;a 6ksRDyǵ^gg܄l&,-c~/Sh<撽U~qÇ^)<~@Z<0Y,ẩ2nG8R"y )4B|rlv:EŧSoY"l+W`6'Nիɻʤz9 ovxAϝ@iL&X, \xqf<|G&nTzPn QZEZ-,&hm{}:.K6-w%,r7r;v Y'-jq[.[e! C"&b^\\dss~dR24PQe8 GrE }g4Yx*5,V2$Faz333Bĕ+WF&ZvwwY]]=(:#2T] Zi;2~}iСCOZرc(WR}u&3P.G}1K^?33xWC{￝&~_aVdY!rlnnJe"IIn|mhd2&m#n+Wp[1~|!N<~[ݦd}?'LMMQיbqq񠙦[p`\xjzzZFOj: ***= rxMDa&#!Cx}<<ĉ\|қbۙb337k2ׁ>3Fܞ^& aﻢ(f2?=Lq\"h ݺv4x:3뱳&zvIx}E(oueYIdNh|RHg0qn{Dd Di%؋=@~;O(l6{[a2> C:έMX <~''OR+ r[=ϣ57h;N_k{}1}s~$DR1v)㋢ɒ[r}h*qߪ_2.޺1?I)eYƶm8mH)⫺`{}GD)y'Nl?-_-F0π~K7DwV oN)pd~;4K4L4z[8 eNꯁ8}7+C䕀2lY"fUɑ[ s{IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/96/simplescreenrecorder.png000066400000000000000000000321121470053624300267400ustar00rootroot00000000000000PNG  IHDR``w8sBIT|d pHYspMBtEXtSoftwarewww.inkscape.org< IDATxyd}xGYUYgw]}MⴆRvx"WZk͵d]lI6 xZeE@@3䐜L_2|館ᐔ eVx_"}OI|;Vhi8i+Rq)eUJi)qC`گ^Qo%/_ƻJ) !.!6u6Ok}zxsMCk>CRh'!PJ.8a1QEZk țiE* RBZz1ϼK{g޲B'3B<ϣ鰷GS.T*TUǑR8^G'R!Dk{{{z{ }/???R_!;;;4M|`rr{ .py{1.\iiIa͛7u7nʕ+\r8BPיfzz4Z7R+_}W]|L?! Ðvvvp]۶yy{?ŋ1M3gW0Z~@)R(r0F_./2/2Y]]EA`zz 8+r =܇Zq}F)Ņ G>‡?a0m~xGq<}0X,R*r?55%4Z17oSb9fffBDZꫯ6wa `ccVa|g~g8}tp)%A!曬f]J%昛c~~>7aR ˲R>/"ϝ;w( 8qi=O:rٷE)JJ@uwwu }{??B.Qyo&_׹~:;:rP.4MiZ}U(8wO</_fjjbkƗe7nP.YZZR,_W_p)ǁ}}˗/}'O>_**Amzw Ð7n*arx Nbgg'NoOjD <3T*ĹxWmZSSSY=o|7I~=S?Mh4/"?S?EVqnݺşɟO~7o8SSS!tQ 0;;i055R 󘘘l1SSSh4ÐjJVc{{g}r`0 "vwwyҗ84 !?qs&RɴmsssO-,,N駟!a8vm:/+B01ԧr266"(m4`wwz7|%(BJ!,+ vakkEqL֭[+de]x'|W֚JD`nnϷ;. $|_~u0eY?|΀pE^z饜qJSN/rFJ%j;;;z=," GTZe0pIؠhv90;:# A2m* 0qB0==믳m#gT*o~~OZJ)fjjKfs2ŋ=8s ǨT*̗%LOO8H)Ų6ommbtҽiXbSTkkeJc" !oأy{om 2,ĉN}7XqB1:{eyR7oo .fVŋٟY"B:prnn.`6a扶(K}Op]^0S@lb% BDZjhi1 PW4XΗ_gڝ i2>>Nyu }/>l)ݹs},/xk^}||[?ׁ鰳3nJeZe)&Ƨ)jDWp:@z]x^x˲M~w~ XZZ0}ׯ_Vx8{?BVf2Ν;OO#M>Ys=lfvvuLgv/-v"2$;6;z]N1ub4c8Ҵ8D&z=ڭ;[m678ÀL5(UB_1~' s3x-//}˲PJy ø <֭G[ٳ_<ɉ'u>H/--1\reyGP6ѬnflzL" 6&v].c-06$Qq b<'d0p8Ag{fKfWDfMV_+  ??GHoeqI֟YYY1f9 pB-xvssq>яR(Z3~~uQJv)O?G>8=7 v=۠X| cإröE, ? ?q " q1[Ҧ8} Q2999<~^hju]RRtn||t:/TCOzNg?Y0d||JX[;2f9E! nam&1l XP)'=Pk0d;1; axEð~w@bHv~o]Q;Kmz-H$Mloo311`0`0GkM`8 "_{>t L?~vyta!8qzmN%&.Z!eB cUBJLe >ww!J b!QB@I% Z&D4'%#FF vľ@GNQ)x$@8uj^On9qa܉ҽ{9OՀ3gΌEQ'AZgϞ˳_t:H)9}4{{{4Mfffh6x^b[?.xfsmhS269nt4BK3=6a 4&찳T+cHa"P1 ,b8=s &r:iTUfu5k399LLLP.Z377q]J2.^ﯾ-Bjaf!qw4MΜ9CضMӡ#ǟ阡u1"))41 {8ADRRSV mX)> qL 4{raXh-^ ZIeEG8l8.33l6ڢh.[[[y+޽M,@Z`08vR8qb8w;} HL*sss`YvRp/qax:pQ-P@˴AġeLYB=t=!V+Oa>FՍF5zRɐR^[TՏ+199˗zW_})Jt:yVWW䙱'>PˤX ,t J$F6L0La!-aQBK 2R >H2BA"0"*, w YBV/`&AĩS!czq8N>enoP(j/[o wk`@T*!d}} !6 nݺEVK3u>0ᩈa$7Mj9|$V6M&&Ц0ߴ0Qٱiahsv0iA+|7T(I2MjT,)KET NC"c.]Kfaab^GբT*[T*Bennsɔ+WPJ1;;KףP(`fLLl>:# QЖr 2 |V4V VjRL35;d!ع/2sfÈ&(X!8$Z"q5 I*yH) (Z۶ܗ5=FqIo\AX4sN21== ~wƐ|HMq,LM,4qLR13-}[־0, ˦Hba}i>@Pf0$f(%JU@GIX+e $Fywq!ea6O=pHgnnit:Y`q(JJkFٳgڵkhP(JVKAwӦb")i*!G|)֪O6PJ$]fi!2$ "ՙ2rT8n1>V^(XI4o*i0+I(P&ZFzWCʤ&J8 aH\ݻ!0Ml\#uDDpԩA}q2ap=.Jv,//IF-TUhPb4rKPh *`ěǴ Xciv'4_+ؘ(UضfȦ+2LWJEϾٗ~#5GIӱ$ 5Bi@ɱ@kiLA`YsssLNNR`qc&{{{ "Zy@P b&ϟ,nݺR*={6MEH NLOlHb) (T*TL - aXT6V%hAC C ȁ1t}ӧfiT ;1N^ק'4_h%ѱ81E:H,PcIbtqLMM8R+J^,J)ad!u: X?ͧNMM1mi64M:kkk=Kcꘀ`ɫiZ+eHqx]t܀bR-*vqN(5[6&hZ#bVuO,<  N!c,Q-1{@$sep:a2 1V+3C?  @E? XQQ((JhQJAʦ,i@fN $Bo!ZS 傅)`|.? (J1BI|-(i.ޢ;/}T T2 0t}eJaҊXFzTfbzz:7M00~OQJ_kMu8FkM}I 8ӕiJ& mD#"mȼ!$ۤl8T&J4(lЊz 8C-z}7^wkhxӈXxK\ZGFY"/kyL|v[{ BIcxn7ȯT*BT?3ǑV?JOaI?"yo$vM@xRR# ( ؖiI&U! f 0bEEHzxIs}pCv6OvK_ɧ9J 6$ݡKub lhspY 4T4(ZBd 9Yu0w@"Z?!3)Pef&P)(7T.k_h{[^Wp<@I,hR \O}_⹟AtzXl D*KeV4 PJBԚyw-Hq@ CO$ / ~rwoYKׇ)v[K_ Lm®WT$0l1YK}Kļ|і/3C#@Lk=6 9 '1M$Rf+(a24ZiB# 4hv{( Jw=rX0fK %u 0 0 b*;E ! :~5NN`(#O i:33AZWJ\~'W)M) "KP44ǥ`()@kRڼUig˘q(\}{]~XM\)hA;EPs1]bGl6P2[ B&<! *)%Nie⪐vZcZINO\5%ǘ'ҚEk'._42El.WGs0C4#@r?pP9 `BA7&.} *PAz}X݇)R}!{I&C k,iBpš I·p6@<#ǁBN&k,x8LoT'#TKǡ8(!g2ITLdR; A5 ̍OMDJXmCKS4(0j/t)}iA\(a'B ut@Q)8P}1?k2;άHad:^AHaA; @=@C'.^p1C)ir%4J L+fMQI l 7ȅG8FW?)K)?g4:>t}@ʣHp[IDAT{Ј4<QV%X-aLU%RIJL# alDžtp/Suȇ c?ш _F2aL(pHþGtb02i9g|z.c0Dv\8[ *YII8(0 @اH1C#+qLIHG:F[2}m6s uYMhxxX`[Xtģjk##`JE+ Q*n[ >D}}6Hs0#ΨQ"( #7lgEH+RRRdhK?B |BXN&Ze~jG&ppiMT$ BJtwpQmp4ՀyX: Xy_g8BD((l8I,DzL٤KLTbH</4`$~d<+"QQ)}9<;9G8Kq(K1!4Ր0?*Ƈ!8.BVWJ^a$|CЅ0ˆ5G8Fi:Q[9TCLiR-@,.3^cwcy` 4a{{˲( Z-v\_  HEpd'N^kMTbuufdBW%͖4'TQxr 'zD@3 8h:B\%=c8|'FMmU6]T 'Yg}u !$&&&M87_YY8D(&?0 ) yD!^ᐭ [̟3' І\>L"?@exlP9ܑXNPDbG'p,21"Ul;;;T^9vRlnn~\헁oH& ᓙ)(+>}jl2s\6Can~3RBMO=jR'-NtQnM_#юa(0ɉٙKFٖnu{Af&8s,ᐝvRW^шhBEY#em/4MO3QGkA4{H xӋܺvnû]]V)z96nbgg(ӣX?z π m,[s %K5;gAXvˤP)RiL5/ GP# M8s'F dÁKX}?un| W(^^n2>>p̷,rdwemm8[$( |?e;R^4- V6;&{.{JXR0=3I^ UfOh@@Ls)''1@dg3g^ÝLc s/-- SSSt:t.+++Dž;?Lyw,=, F?SNq=J>p3M%ͻ/7ĉC"`vaJc< g4nV7>呌'd>x H#Ah٤@Lz7,7 uZa<0q|֬quQG1Yĉ!84MT*ܼyIS[)\\zs?}4N7+W>"󧖨-P@Ia$8Ɣ6R&V8"5lm*+h~ng,o^׮P߼0 l p Է$rT*6iyyZ*jVϴ 1ͅ$'Ϝbecׯ_ꍛB @SLLMRT(V*v!ٮ\vwZt:N:MǹE.r믽ݯ\po.K黻Ң,y7oͅhpgsoݽ5vw{nGbeP ) &&93,-f6ɝ5n}&\`*g|`H\fee49wwɟU&u<9nvx7iϣ@xzԇR)ߗ0zEVWW9q7o<fX~ 疙[Z`|j/p? 2fBdee{6mv'rYd1+ >̞=NuԇQO{tRoSMii4& { l6qeJRv wܹ 63dBS4<{|'`뺔J% 頵΋4=FK|OjN|Py;4Bc4g@EQ >wQϹs}6;켥')e4jϳ3eah9p88 T ܙCNg+$)Q_-, ӲhG7{Yvl6ަ<(I27G;#q"{w?%;^.sxu 2=Q1TvErۥ={ Bv]*=}<{ I ;E8}Ȯh#*۟=l${fÖA&J ;ˊ-F"`;|.A8{ |oo;$ɬK|;>I_+wn8C$?IJ!#D{dom E o i+0{0I&GL/RIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/scalable/000077500000000000000000000000001470053624300233245ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/scalable/simplescreenrecorder-error.svg000066400000000000000000001550611470053624300314230ustar00rootroot00000000000000 image/svg+xml ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/scalable/simplescreenrecorder-idle.svg000066400000000000000000001420051470053624300312010ustar00rootroot00000000000000 image/svg+xml ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/scalable/simplescreenrecorder-paused.svg000066400000000000000000001575241470053624300315610ustar00rootroot00000000000000 image/svg+xml ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/scalable/simplescreenrecorder-recording.svg000066400000000000000000001437771470053624300322610ustar00rootroot00000000000000 image/svg+xml ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/icons/scalable/simplescreenrecorder.svg000066400000000000000000001420051470053624300302660ustar00rootroot00000000000000 image/svg+xml ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/man/000077500000000000000000000000001470053624300212165ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/man/simplescreenrecorder.1000066400000000000000000000052431470053624300255230ustar00rootroot00000000000000.TH SIMPLESCREENRECORDER "1" "May 2020" "SimpleScreenRecorder" "SimpleScreenRecorder Manual" .SH NAME simplescreenrecorder \- A feature-rich screen recorder that supports X11 and OpenGL. .SH SYNOPSIS .B simplescreenrecorder [\fI\,OPTIONS\/\fR] .SH DESCRIPTION SimpleScreenRecorder is a feature-rich screen recorder that supports X11 and OpenGL. It has a Qt-based graphical user interface. It can record the entire screen or part of it, or record OpenGL applications directly. The recording can be paused and resumed at any time. Many different file formats and codecs are supported. .SH OPTIONS .TP \fB\-\-help\fR Show this help message. .TP \fB\-\-version\fR Show version information. .TP \fB\-\-settingsfile\fR=\fI\,FILE\/\fR Load and save program settings to \fI\,FILE\/\fR. If omitted, \fI\,~/.ssr/settings.conf\/\fP is used. .TP \fB\-\-logfile\fR[=\fI\,FILE\/\fR] Write log messages to \fI\,FILE\/\fR. If \fI\,FILE\/\fR is omitted, \fI\,~/.ssr/log-DATE_TIME.txt\/\fP is used. .TP \fB\-\-statsfile\fR[=\fI\,FILE\/\fR] Write recording statistics to \fI\,FILE\/\fR. If \fI\,FILE\/\fR is omitted, \fI\,/dev/shm/simplescreenrecorder\-stats\-PID\/\fP is used. It will be updated continuously and deleted when the recording page is closed. .TP \fB\-\-no\-systray\fR Don't show the system tray icon. .TP \fB\-\-start\-hidden\fR Start the application in hidden form. .TP \fB\-\-start\-recording\fR Start the recording immediately. .TP \fB\-\-activate\-schedule\fR Activate the recording schedule immediately. .TP \fB\-\-syncdiagram\fR Show synchronization diagram (for debugging). .TP \fB\-\-benchmark\fR Run the internal benchmark. .SH "COMMANDS ACCEPTED THROUGH STDIN" .TP \fBrecord-start\fR Start the recording. .TP \fBrecord-pause\fR Pause the recording. .TP \fBrecord-cancel\fR Cancel the recording and delete the output file. .TP \fBrecord-save\fR Finish the recording and save the output file. .TP \fBschedule-activate\fR Activate the recording schedule. .TP \fBschedule-deactivate\fR Deactivate the recording schedule. .TP \fBwindow-show\fR Show the application window. .TP \fBwindow-hide\fR Hide the application window. .TP \fBquit\fR Quit the application. .SH "FILE LOCATIONS" The files used by SimpleScreenRecorder can be found in the following locations: .PP Settings: .RS ~/.ssr/settings.conf .RE .PP Logs: .RS ~/.ssr/logs/ .RE .PP Input profiles: .RS /usr/share/simplescreenrecorder/input-profiles/ .br ~/.ssr/input-profiles/ .RE .PP Output profiles: .RS /usr/share/simplescreenrecorder/output-profiles/ .br ~/.ssr/output-profiles/ .RE .PP Translations: .RS /usr/share/simplescreenrecorder/translations/ .RE .SH "SEE ALSO" More documentation can be found at: .br https://www.maartenbaert.be/simplescreenrecorder/ ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/man/ssr-glinject.1000066400000000000000000000024521470053624300237070ustar00rootroot00000000000000.TH SSR-GLINJECT "1" "March 2015" "SimpleScreenRecorder" "SimpleScreenRecorder Manual" .SH NAME ssr-glinject \- Run a command while injecting the SimpleScreenRecorder GLInject library. .SH SYNOPSIS .B ssr-glinject [\fI\,OPTIONS\/\fR] [\fI\,--\/\fR] \fI\,COMMAND\/\fR .SH DESCRIPTION This script uses LD_PRELOAD to inject the GLInject library into the given command, so that SimpleScreenRecorder can record it. It should be safe to use this on all applications (including command-line programs and shell scripts). If the program doesn't use OpenGL, it should have no effect. If you find a program that crashes or behaves incorrectly when GLInject is used, please submit a bug report. .SH OPTIONS .TP \fB\-\-help\fR Show help message. .TP \fB\-\-glx\-debug\fR Enables GLX debugging. This may reduce the performance and print lots of error messages, but it is useful to track down bugs. .TP \fB\-\-relax\-permissions\fR Uses mode 666 instead of 600 for shared memory, so that other users can record the stream. This is insecure and should not be used on a computer that can be accessed by other users that you don't trust. .TP \fB\-\-channel\fR=\fI\,CHANNEL\/\fR Channel name to use. The default is 'channel\-USERNAME'. .SH "SEE ALSO" More documentation can be found at: .br https://www.maartenbaert.be/simplescreenrecorder/ ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/output-profiles/000077500000000000000000000000001470053624300236245ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/output-profiles/High%20Quality%20Intermediate.conf000066400000000000000000000003261470053624300316750ustar00rootroot00000000000000[output] file= separate_files=true container=mkv video_codec=other video_codec_av=libx264 video_kbit_rate=0 video_options="preset=superfast,crf=15,keyint=1" video_allow_frame_skipping=true audio_codec=uncompressed Live%20Stream%20%281000kbps%29.conf000066400000000000000000000004361470053624300307300ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/output-profiles[output] file=rtmp:// separate_files=false container=other container_av=flv video_codec=other video_codec_av=libx264 video_kbit_rate=1000 video_options="preset=faster,minrate=1000,maxrate=1000,bufsize=1000,keyint=60" video_allow_frame_skipping=false audio_codec=mp3 audio_kbit_rate=128 Live%20Stream%20%282000kbps%29.conf000066400000000000000000000004361470053624300307310ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/output-profiles[output] file=rtmp:// separate_files=false container=other container_av=flv video_codec=other video_codec_av=libx264 video_kbit_rate=2000 video_options="preset=faster,minrate=2000,maxrate=2000,bufsize=2000,keyint=60" video_allow_frame_skipping=false audio_codec=mp3 audio_kbit_rate=128 Live%20Stream%20%283000kbps%29.conf000066400000000000000000000004361470053624300307320ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/output-profiles[output] file=rtmp:// separate_files=false container=other container_av=flv video_codec=other video_codec_av=libx264 video_kbit_rate=3000 video_options="preset=faster,minrate=3000,maxrate=3000,bufsize=3000,keyint=60" video_allow_frame_skipping=false audio_codec=mp3 audio_kbit_rate=128 ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/output-profiles/YouTube.conf000066400000000000000000000002571470053624300260730ustar00rootroot00000000000000[output] file= separate_files=true container=mkv video_codec=h264 video_h264_crf=23 video_h264_preset=1 video_allow_frame_skipping=true audio_codec=vorbis audio_kbit_rate=128 ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/resources/000077500000000000000000000000001470053624300224555ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/resources/about.htm000066400000000000000000001116201470053624300243020ustar00rootroot00000000000000

SimpleScreenRecorder %VERSION%

Copyright © 2012-2020 Maarten Baert <maarten-baert@hotmail.com>

SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

%SOURCECODE%
https://github.com/MaartenBaert/ssr

%USES%

  • %USES_QT%
  • %USES_FFMPEG%
  • %USES_PLTHOOK%

%VERSIONINFO%


GNU GENERAL PUBLIC LICENSE

Version 3, 29 June 2007

Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>

Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

Preamble

The GNU General Public License is a free, copyleft license for software and other kinds of works.

The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.

To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.

For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.

Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.

Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS

0. Definitions.

“This License” refers to version 3 of the GNU General Public License.

“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.

“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.

To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.

A “covered work” means either the unmodified Program or a work based on the Program.

To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.

To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.

An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.

1. Source Code.

The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.

A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.

The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.

The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.

The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.

The Corresponding Source for a work in source code form is that same work.

2. Basic Permissions.

All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.

You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.

Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.

3. Protecting Users' Legal Rights From Anti-Circumvention Law.

No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.

When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.

4. Conveying Verbatim Copies.

You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.

You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.

5. Conveying Modified Source Versions.

You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:

  • a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
  • b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
  • c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
  • d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.

A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.

6. Conveying Non-Source Forms.

You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:

  • a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
  • b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
  • c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
  • d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
  • e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.

A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.

A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.

“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.

If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).

The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.

Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.

7. Additional Terms.

“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.

When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.

Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:

  • a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
  • b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
  • c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
  • d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
  • e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
  • f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.

All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.

If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.

Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.

8. Termination.

You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).

However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.

Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.

Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.

9. Acceptance Not Required for Having Copies.

You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.

10. Automatic Licensing of Downstream Recipients.

Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.

An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.

You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.

11. Patents.

A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.

A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.

Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.

In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.

If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.

If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.

A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.

Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.

12. No Surrender of Others' Freedom.

If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.

13. Use with the GNU Affero General Public License.

Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.

14. Revised Versions of this License.

The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.

If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.

Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.

15. Disclaimer of Warranty.

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

16. Limitation of Liability.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

17. Interpretation of Sections 15 and 16.

If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.

You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <http://www.gnu.org/licenses/>.

The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.

ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/resources/header.png000066400000000000000000006341151470053624300244250ustar00rootroot00000000000000PNG  IHDR"7ǖbKGD pHYs  tIME8B+ IDATxIdY~;Ýo圕U]U&ERÖ(RW20d̍mX0` C -`,ololxAhPb"dw9+ogDSɬ&xƽ#8#8#8#8#8 GqGI~<#8# ;#8Ql,E';C#8#=HK|׎DwGq$Oz~(AFiHrGq:{ۧ!OErd>}8#""Er`xϑ8#tۋ}|j?/I*M~ >HpGq:T{g럆4h'=OdמIHrGq=GdמIȍ|P=>ɮ<Gqď&rǧOMprS~$w$#8-~f#6 g>Y{y =g L?{^^>9Gqďz{ɞ|JfOsjE$wTqGq?y[j!7I"!ͯH=%@pOgܳjN>?#wG#EpOgUS= |8y^DtϏ3>x'PJp~v|1?O#*LoR md>.ϯ蚎v)v8Hkuv=m7=<*i͛ .6EFGi1xX,&dyFyy=Qɿo9n߽C% 'g,6׉!%8JlJ3]0fw Z|pzhe h:fܑ&RiqIVԻ-uU\k7N-f%5O8cv`23*s>z׹co˟(֘4B@xk5yE k-mQ9B =;0Ib6+u;֍qDf0b}4kzƭ9'3 )Q%JE'$m]WMNN;y7b R9iqƣbIY8/y*W6{2tJ v!i(Kxu z[fB@UH!IBǚͦ9(RLsiƿ Y1.y_\%'7%fP:qu~RG$iBZ0]5-8N|1o΍'{׋Rk ]Hy Xk @!Dt] yN^f!cJ ~XcPJP y#NbV NO'L%mӲ6l;Xh-"b1e4* C?< uXiX+4IDs\Q7IrN& ]KxD(Pˋ5{N#&頻4M4(R(Iٮ7h%隖t0X'Py%وnIP./W)@)E$7}ZĚɜbqD(Uj=A=U}ώ|W)ٟܾujt>kۊѨ@*軞,Y/dETvOVX/<%DG`mh|LD9RÇWdEB'8u1 m4ѱ:X묯8q!yY04-8IɋjqprmVeS q)(*.ϗXcΧıo;-:hcAjI&$If[f)fHdEKA>*DQ5׾ί~/x2NHbO^mq륻Lf3+CkŇ?yGQdDDXKgw_ 8z`]W;"' R(<%}s5}h-u|3rd>f\fjWk+KzcXGTMX4cg qrHR$" pu:O?#`XGi=%$ZIx>ߧ;xUnݼAR vUGDJմ=!>QC*J+NN&{-IQ]7ҁЬ8gCQD_1.CH+S?ܻ{g-,ߒ2mꆶɲ(IuUBY|`=y&1ZIxJJw8deB~\!x労HL'%`o ^}eNO$;s"I""*@쇶eٳ^o)ʂwn1vY]\{v3tb͛'y~u=]E%`uUZZ:/tT{ɳop yqry1™fk[;i WWK '1YAv8"8i3MP:f,}KVћ&O& E(g-0rR5zvβ߷Xg@JblBo{TS9Qq֓ ;R "x5g-}ףbGX+ͦ^?%KRT+ZAuxsE߷pSƱ"S<ɵ$.Uq,k8#`O\>~Boˋ%i1eҴ-i3BCi$ͱCOUuη׿"!RNSn߽t:!QJ4-ycZp8TZq1 u$F۞ݾfm`Ŋ4ָ"6$.iXa:3 iFa HkdEt>y~W%7_3#M3O2d0mP' ]懮iZP)MӰ]CPCK<0[̉cYcf@G;ޒ)W$IBǴM 4,CQjFEăgֿ*US{/&C#XKۅ""ۊn4y5e'?:uқm 蚞U!ٓSv ??6Q)OWp/ܾ{(q'P,釞 E;|/O0PQDi6 bM6b1f:(u!  EH` yf,y3,˂7BZجh8vrEu9]HcT4OȲz˵w~+|2]__EQ䁼 c19F(Egu&,*z<J)h늶iG4q~_#)&% +  ._#3ti $I̮jxx>͎Q93!h-MQJ34ul-JiݘwRׁDiجk&z`+5/OFu782ghǨH(}HBK Quqwf=H3!aX)0PY3OX-w8k8=Ҷ=ҘϿlT՟`S~_S¢^}g\d_|ߘL q NN'T]dHfitm~!^%NPJӵ=Ũ7^joy=ԕw!4]a1a臠7ϳ(GH4ZKƣ@و$OCZlP!':.@iV$ix:[x峯g/Qҽ;4{aEs!uӐŜ"OƱ^B8'O(ps&RuOӆd\Q,\\œ$]ՖQR$1i>(HhaeȐ4""3cyA/tQY MzC"*\/YvitDqdµk3;OympG)!~-dv2 ȈЫmQNJ~{.%:ka(;./dy.ܹ =9o֛S@k ˫+!IQAuuyՓs-`]٭7Xg)G#,;Q&iWd(ܺ]o }$3hs ޽C Cg<:+R %%]oA%fY4¼Y#,!Y6(ŬtFZdTa!,B!Ip_/=#gz5G~zNE s?MSfoſYoH5وdUS=:zCOSae2!IT=X6gO)t6 Ynv x(8w"ɴd299BE8F @!m*-MӳX̐Jpu&5B'a>KCXGR>׹p]N-{)g̦=lRbTMqoV:F' وd*$L|n3ZKRQU5Qq{'sN_9` 7nġOJN8s6o|𰼸テudd6&ϒУTaXqa0t}O(f$(avl2)(aWLMՖv cCN*IE,Fv ކkjZWxSGh(jvRVA-q2qg v,ٳVyt>/X^6*pN 0P &Jeq}CH9\czC\~(J}juGxrvqP9Ɉ0P)I3z5tD1), @99zՓKvQJF`2 8 #Q}f\gc$AVѸ P]۳Z黖jKv4AIEQa6w0ы6,REa,B(n:qXcq2y>= (HCO4=Z0t '9t]c9(gp'+'RŨ U۱x}ů[L{܋7=._?WL]bzBHmp !ke;ބTdy*{9{k)<˨B)n`-QdYJZ* c23Oqְv˫5Wk8QDpʺGIh5R ( 6۴{F(%QBDIDSl;cu$4wȯڗꆻn#UhFmǻoHZdL%!!N`mhV(JBi_q`w)I[DZѵf1Ɛnowl6[,$-Z IF1RAmZ3I[TRy:N`HThMgL#f"/0Czu=Ub4QGFQƸ4FX]m̯d8h7 z|Ֆ~6ֆ0O uɒzf Yd1E#iZں!"fӒ4OɳX{Xcn+dD#as[<Ѓw֡LF9ard[7y[ܼ1wQZa~`)3!(,apq-o0NhFf IY.+S~o ܳld 0oՊ{ܣ( ?<eT ]0?]PF!q *J#ذcTaBPa`uynWEӵʯ}>,_O0m~)m݁ I Rجw\]n(F9Q1qfU\ϓ%zs$8{|I۴}GN#Ο\P=qNG8Ҙj[;#*Ҝ;{?˟/{}B{Bi4dBvB~{96-Ɉk7OR+$,j%N"'0(=Ya<`e}$qJ[6 o6|>b~ ?ƍ맴MOtD&>LwMv߄3"IUu!Їi:$bugxW2BOii$yQZnHӈbRRo!1fp㬥( Ւa0$l]myag<?C  bBẪ{\=mSgt1h[PBQvxc7'`9Mۓׯp"dޡutpi$Mӵ׎<늾(%ZmYojn_ñ}&ll?Ezp[?qvp('S gm&1ZKB?r!bз-!mۑa Db;ܻ{7dI|> 1IJj?=*R4U8x{_OƇ2>TB Y.rzg>-GTI.dyfV-/YL3?{H+[[oɵSFɘ,CR,NO 8_AHr&#ʲ 1fR-;Xn5ɸ7\\3Qo]g<3i@0'l7[~ӌzWSK.,bJk tw? IcNOE B#0X~0وӓ Yakv[mvŻ@gkص4 IK1 :0K# /3a=vmKoy=\7lFaA$A5| 5(>!AjPK *RdVVq=nsE"Y%P d+{>ks qPs]\-3l/.tMBCߓ7i&75PW++0%QUyiZxC5e^2&1C$rPmYqmР;,Sg6 Y.'.MX*aے%;nV[^zucq8h!洪aϩGR*4\ϓPxӊ gS('!eD,'Lc0 -jH 4p[~.^"Ӷ1M8Y$T5A2 I4 qNt ӴG]1:#SWMj1smPeb;.u-:G,ݞo}>Irf44ÐeWN'M3lb]O~/CI?`O?s":9ee^u=:NMD=_quM85i ;ٹ+%C/]?i/?]OP XaWm;[6) X.4u ZvnPܹN4bY&fG4yc[4xpp0l,0t65>c^>l:a:NN\\1 A[M@ri]MYW| ۶F 1A? GBOضI tح˩`uڎ۴zFQq s69ԵeM8a2^^1&a_?Ƶ-c=z͛kYo(b: }b ]+ڶ2-wҔlʲ_rxY|8hjCāiN ,S3- 6ݗrXٖ7(T/5|g,1M.cюLS0iFCOYVyvV 2_LEQT-~0kkK>baM:ѽ Ӡz[1G bчCRY'O_˯bp9GhJ}؆i)т8ϳuݘƶm !O؆N Bl{+Y~J>mz^P34e٨qMFn T$l%9+߇zf(sϦLO c\oIyw%˶Ȳ4#lmC[-Qh@SulL(#.LY8)aY;C Eq,d͐0QH4UEUV^A iiHdb֨˪Va&ژW)\Aw[Mc;#Ĵǜ0ecYA䳹i?v0e/Q\&g-Y'Әw}0| oSW%yݬ> \ץ, }eUc'+ekA#MSab"1%:x؎:ꦥk4eڢИ-fa@y!Cgemd&i K@q] G|FOx5m'YN'h(@nwc$GCa:A衔FQ 2n3Ex=M64/[7qe#=2P blE^]Eu~jEӏӜ_wՔ~**iY&}'׵F'4yf$=E-e^2Oџ|rAd</_$)n&c>a4E3 ,m9\?,<ʢ(*`>4~c?yAx$ p=sV=MBQNY.MЫ2qɘ 0j#zn`ϩE) np}ṟMC',DWiƠ4?ǟ=]+kq]o4hXcdI>Z- Z?$9gKׯ.5rJ] 254nzf8i,fESh $Xa, N+=||Q }tC嗿|̫oIӴhdöl\ϧJ8:$4LV aatiVxhz]N Ll4y){ԛLK&[/Mhi4`11Z0TM[l6^1_Lx=R<>7Wk^vh3[$b$I)X. =T4DjzXFKU\^n˖r_My9o_rzv  =Y52$\ৃbN,-ج7؎!]'(*ױh/ ,d:ȗˆ.vdAoz-pV꺑adBwMt6QT-_)^#cģA`KȲ4Dڶe$i4M%mL!˃9cxBPJѷ۫ IDATvH}Vq,g/.l|tbIɊw1M<+X>L9;?xՊ_wyNr)?-Yᘖ s (<;P7 m]'\6-C9\4:!M ȳeMX`XdM]32]:ؖ5zӇ8we޲q-ǶbH5u M'ym҂mv"/ ˴9wٝs&QD~7^ Pcmz|}?ph܋SX.uiZ<+&(63tTh:go|'"?G~շ<9mbYԵ/.6UmxhLae3F8n#2L*m,PlKU .ܮgI@]֢ių۶yVzX.,瘺bE!\k\> `6bJtR1\\,#K3~+Fzܧ5]j2sR _) !c1Dcp|%VpRd%]?M+ص|Bdw+Ӭ؁ 8Kse m\4 ka.ŜO>~cޯgl+ECs}|mRnJK]uMg$ڶrieOc,ˤHsmJ|G{n6sx4nУ{,Ǒ2bMcP'-P+i*:iD#O3=tLBL!->)S--m%[.5s瘣iP7-m zp~E7emt=a3^H/U˳m]mQ(0 lS@.]+񑢨],"xmݑ5X6-zy'Ơ0k:@S9eY%e88Lgs'_?0 ܇~~]*d՞m1f.$fQjCG ]Ǣj,`ssumIR=JiXCG1u>~kʲ4 \OU-<9] >>p0qln9:=d1x,ə6X~umvkXrPu.86ɾ_pu Ç: Щ6Ƕ[7=j=g5yN]~ ..V C?0ݒd9b&K})X.'qt3xܽs9XLmR8AnrV5m84yVOq/ZIS>kf='./*9\ߑZQ肂7/4)c݊:TEE׵@[7 44Pf<ne&J]qې\Oat:MՈkm^hhw?7OYwlCrHiJӺ)4/1u<˨bdqb:z]zq,&Zf Kz\c:W hA4eMۏZhU]KUVm`N Xҡ+8 ޝS>N'/ySnnnmI [weQ3t-5e[&uuS慘t߅niƍt>!Btv}tÐqil{ʲE0u}\l7{,]:(99^yjn}Е$i͎pf\S`WKMYU;u. sA$r4MS4侣+ay$LSiV2eat)5-U;^ 4]Lm+u3Vf!P%]FՔГܠi:RBmc:M] |i:cI]:x5MBOx8I;>[tbĢ&K&/9UMQAq,,}95֫z w4 㿽^3(0x撪I|ƣPdmɒ<-(4"=ַ q-[np//(c$;Cߴ{IRvAnr.-$%Im8 M}04\$1M[ͷϹYOpql#< [4)' rzGY4G亦)`> P]fg66F\oQ84Mz% XT0O9Z11MeWss}IWywK 6]RFI 2)i٦V뺁i \ ]Vv<7Mhc&yQk0mKS>GTeoyiېIUGVU݂nǧDOՊfЙӉ\@p2AӥupsahfXD4\ı]͐rQsPFtEhL ,@ Pi7$ UgGܹ{̃|O>-hrݥܬV;ʺe0O0uY @zTUtEB0 .GK(W JôjZ&C>PSZmۢ^s~%ږ4))Z^SB޾mc\OYwnTL-kwMiw4p=q|ڶ' ,+1.y~(7/'0H #-/%o[Qj9;;vK,:dE-cwӱ qeɒO>oV4je^]0[C2aIvdXAIfKt<\]f u(ˆ(UeKӜXAZ覎k[đnDi m#EG6M5Ra9>GNy [4BaIeH&. ޱM]{Ǒz=rCG]TUw+4C'*޾߲7;֛J)nn-]lrFKòeۨAFUUq{8 N?!j}ˑoKƦjpYMKx͸X #)+lMӎA&V N]Zddڬ6)UU&hv iZa]'i׵1-i1,t1c WQe^RM[FE.fL]/vMWRSx͝{'0FivOQJz0Hw{4Sm`!( a6֓2(.PxB<}Kʦf6 '!n.}O`MTʊƠGUtÀ{X>q|QI, })mZ &N¢(1L( 2B(@rl GRG!8|ɓy{& =}/>xuE^(41r^ŋ e;! VA8&!#C FhmA_$te!c:E.[(F8>^пkY(h蚆<+H MNQ"@ 9iZ ۔xo{adE~%WWk֛.cImS$H'?zIqs5jl"~}c:͖OѶoߠMrq&pliF[S6= \4Z@l>:v<{ejX+Of\o" CrI7ZxR[8p9o 64Ҭ{{^jedk J)r-sַQڠ|[oxh ]M|󇨡?ٷ/8<^rt|8fkm9R]a;6Ȓ4)&dYz$ VW .S~FBU 1tT>R9HsuWqaH&ɴј-"&=޽!mQ-S CaقrGAIYw\]oK/*eZѶ-C3H}/-݇3c1A5RL&>SH2-U]càagKV+~߲l qוϱ"IȲ ?tƯTEMxGP!ea1rLa\o&xx6u̗ YΈxۮ9&`Yc6k^_i,x~?o]?K[WEھCq5٣/С**\ :3[LY.X#%h2isG谆iO G̀ \fZRy'e[<{ǏR%c!|#>~xidnnrfJI .쪑j |<Ƿz O pmR5CQW5.B qbs1u@0(4MH6اt bl>jH1v5]L]# D&kyR?;o;S#e;%Noz&M e9 y⒃)q~<-1Tk wm>n0Fdi.+dE€q4H;$0L>> 7Y1ӥ(LVTEEdl{LC!4X,'cM76"N6j>ke5 qp\,pG-MiQsqnnx㘔U|9FmEMws| ا9}A1 :Cp2L"^x7b|\䩐QtMKQUd9EQc;w`:?/8<9`uEiG'K)\o1t%qYYdYI;c]5DK۶y%g: /߰ݮA˲q<CƞϙW-o^aS4],ǖ-Kl(%eIE<>zmG ؏U׍\, quj;ʏk$IL&1Cߍ/B?(o~Óg/AHYLJSkbW]V vRCO]5Ҧg%isub1mjui`.ax.}۲ߥԿi-'nsv`hjں2t|)'GTeŻ+>}ǶFXDQ4!CtÔu!wK7PHQ\uH%^ ēQ `An3ҙ-Kk:,CavlA M60h{^Y4mOx O<[OO`_fض%QF i~4eMlw yQR֝t}RVΨ:EȤV m`QZ/+$eE9JE^a&qU 6 vIM*n[`\HeݲKJV\aS׿z}?pJuttԀmܽsD}NNWWA&Gи01M0N'ja:YoJkl "]_^U5D)ws8x=d 41uslj( G{+4]gdD?$/ 4L <KUQ"O+\Ǐ"xh±L$ac6]q}e: # YyEulw)(E]{BY?0LcdMUֿ:6-e2 |Ώ~e^񫯾rL}! (9??hN]T{+t`)c JnƮc!qh80-aw~hGY]74m$Pi?ΨEExQ(:a/ 'Q0z\wa %dt泐+zg>hϳɲ`ܹsJ#* IDAT(K Ƶ ޾fy0&)Uӱ\ƬnhR<"c:.n..pqyˏ13Mgy0c6b{ gEEL&>WW[ڮ4+=oq(@Ңzf \ P8Ah DK,}i7j;Q`f60maCfȧoZ,ɷopttğ8??&O2_X9<\P乄]#+A`~ZGd)Ѷg::5͎,LbMt,Zߋ3>eQ:6{Iah2-}&*CǴ|ߥs~_[NLg30¶]*67[a:GI88U5n5A7LiN Ҽt\Ǔ|fQ%>-yPcuh H\4LB<Ųtݞ\\\xe:rr|d"NڦnbjBڑܝT4m/m?k]Թ@Uw Sg:|L'B4Ӓ|Ue=7(]ô]q^ӈֶW3ga(f00 1V:.,S5"UwhʊWy8OUVpp CV>ǰ4lOz'4Bh҉k:I4i[86!uLng7[&KtdypǸ"KTvhvж= y\%l\ЧJA4&ca[ȵ1P.o0P>%u`SVt=ѕwBY۞A)b6V7٦Ym?/E=ߡ('?{K4jܮ`:=M?H(͙ͧww$9y^Q%oSMa',h늬8< ix+iy86 ,˙N$2C~ʂ7ē8mL$C6|;xZX1ل&vL=yr 6I s;BO{,x1=/^\+al>a2ܔ=y.&( <6uۑ b UI1ȣbCYʈ56US%ždٳVcI¾ږQį}6'>|시xmzmhNNVTb~? /zYCt.[֌)qh{Mxf, V:h Nv4EA]MxxP4Jqi2:^_^WXot٬rھ[c+ u0qf|Jm#E-^]N۴WalFݠUBQ0q$)0bEq\6MK O&ɘ|/>9sGP5Mpa|HzDe^)Bx[M239=A>0#c\ǡo[+~bW/\/GklƎfCv\0lU @\Q~gMˇM]$iBWlz1b*ldDv 14OLבֿW_{22]@tHhBETej2$j\&"{ #ߕ%'QiX\K]UTz*]ӰYnK6y%C3Dq)qR E,5? 1bҌ]g ڦZv=]hL6Ͼo^櫧ӳc\lhuTuGDqHfYuUp]$~a0Jhʚ¶N&%RA@LЗ6}ׄaP%UQd]@kR}A^Vܮlh#xh̏f86 Ze^ұe8 ]k$-DyϟEg^k6=9 ?S7Km[.Y($УkELD͟2^ E]7cse,8LG"2wO>0 uAIJkQ}ݾ`)@q*"/Kse4f>} 2 Dc!bp lq#0ujPB2kūK|E7x{vq\dg 4u(~`RPhPx5J8A[9oeG1yN3vFi*'GݠOc|?S5Aƣrl;snnė(x2xrє`_Z bں6}3OLFTuf{f'G$iBS,o8,71K;nl8mˀ"f60J,PeO]*0/#zsE6HnGhMWmbWw50R d0xt( y~XC|S~?g_٬QHNY*nn'Mv1gL1GgGh҆ FӑoKָmZ1n; (* UwE188+ ȒŚ(HrA= <=}M~_a4_|eMR4%f+k8n$%lK(. M"N> [ڢ`%e^qP: Nހ(K aH8v7ͦtyW<9Ϟ_{C )qKjI]MxxG9,k ]S+k$d8N98ރS$?zl󚪱jU0L}$" CXN}Q dY?n(i4w{yqp un]d<``d:ee]SV1Jy;%/^/ IDNS7}oxx,y<>G'l.]G.S~w1|yw8;=p/_z.gǧ L|\[zp@pUw)cReVثy8(#KжC"RwO\URX_mw~d]|+C{^3?{u#E\ڦT-|Jˑ˖D늽q Qڶ;BߥP=;t9Hb}'cxmXw" V u]E1pt:hx1?8un`WfDe. q繘^'!ž,rʲF4# dy}aك6Dmױݖh/OF(߷7x|l67b |2bXc L']X_YܻwD)0?b[8ErWp<Z4E8:дZ|,>їR8wEe)cg2gՄ>U^nVerzr#>|_0 (U EIjKw싮ywWCF(_Ӷ3o\0m t8;lJ"@8e uۗ|)./_|N~_W+@|gGtmrjMDD [qe)qCS6tmֆn8> /ZvamPId1 2ںvS0%#^_-y2\\G(Z9mk fX?%__2L#..1<nv =VQp5\zQ"atZOxP.E~g%aNY9,&Dla QG4VFYB2*b]S\('I5G ,5'+ǡ j'#NO 9;s.Χ\?a&HaFQ<`}打gU~{ʃL!~ͱ5ЅK92PsYCӷ]1GqƘ~ݜԳUsӵ M;ooOhۖG 2/Xׄqx2!\v5~c44&d480 nI8# =Vl r: $I_b0LVGNEqucaaAj@NMTU^9.̦S/zξPU5^08 cbk;vUM^a p,#Ē"/ cr'hv^4Zf[4m#Ok]K(ʢ!/jھWOqX0+〺(5M+"J $lX.w.7e`4{  qUYBT:C~j" P}o_YڕM IDATRd>]we!jl(}NY5L3<ཷ.?'|yc}> f<}n((Q=0xLgCڶGѴL>oz6yG^GX^}/lL;1jUh(ʺ#Nc\_F؞+xD~0Mw ~ѐd1a@OEb 1:\\2MH݋Rm;ECi=eB(eq{uիkڮ## h;ݶ K?z'e.2a65uɲfO6MTE!cc8zs$sh2莎F8tFQt2Jۜw>c+XDs}un_Hllf# q\Goݣo>  g<d-c/Ss>f:F4:!}`$49eLj1=cb"C8;4PӶ7C:)S)b7)iYOzx霮)v[MGl(Cáb|_14vP-GGS<^ A.x[^|s#z!"{=>3ʢ`qƵX@ګՖݮd<}B0t>䘺x%~=a3hspy;Xvwhd<]EMtit,ts~- \8$=qb(M(zqfvP5U] _k$;< D+Z:$}q$G1OѷEIOb] vɑּ(*$qLf,o/d>1"^\9=sϽs]ǎ=[-HlG(*9 lA쭺y๶;TplQԖl7V:H#RE)+ﲅQ)G/r{fM6ʸvnCo4>[(1i?F% ںjNGϹq=ƓrJq(7˯^*tHݴJ$烡 nj` Ś:g>3$t}F"X0G;LN( =*S ~ΧKL6-yQ3 )˒풦{'m%I(*fG$q$QJ_> CƣzjGP}-# f4仂l("B][ZH$K"8$x8??MΧa(XtADi@w= [ ɍ aB *Kں%HReQ+nn]0g<8'È gkR Tw8B z'/Z7m]0pٗ^;p|Ot Hىgi9uH7oǺxG lW+Ә0Mp1žd^ԅM:ɘ: HXܰhjM\r,N|ȗ0iz7/q=30;(yy=B$ՋW(!Cb~${ST1 YmF\^-Hr܁l=+|>;ݷ?`< ߊ=PIicK_k<[|4خEγqk!wkv ލm!:G:<; m2&(Eg)-r-x=u@65JF⛧!S^]0eqݘRF!ʲ ;c)xxNlknaxn5K6}n Lh m|(&p\?@L`R_ꮀcPZaR-X k!Th:~\`0wchǞq(!_t644߬4&;҄ v+>QsaJk#:4 16e[ \Ehh\Gn8xd-%(K}ǣ3^YQXNE"DI"8BY  a S )NbHvWP./ eWW wEMt]њdfE! pX)E,\CQDq{^:n%/__nO-NN {]RkQxiĦ]Oc PGI~)JؕrOn4y;.ʵV}qן?(}AU7wCT7'3;<GW@՞t fӛɀW=XŎ#y.'Z#8` }fދ$s%g[|3,#|ǡ!Zk:Da_CG<;m!mgvPG$ali oWԚjv{|[;avZہosSJa\>V0K8$ڞl$ygNGAQn[p~D]4md,127-i(ۖs)ˆc|n{kLxOQk"bvl(7' uw ЮSF>R~̷-~sκ+F>lAt79T_-ɿa5Y11FTeU%:F~p\)YBzwt+oy@Ɉ$ie=R 뺢.+<ϧ.k YG`S.jw$[䘽 S%Z+LɵvJIza[[9lZЄzt]⛗|9Jf<~!DJ~<_52 Ppdead2o͆ۛ5y^ug,k%h'IBPJWh;|,F]+8ZKWFlv\]/($ Gg;sϿyI{II^}^J)םrqTv8NzP~M/?bw>4OFk''SDC J'0S(N %GkNnaKf%.I!xW;z ^x 5˵(%GH Mʲ9KPe#E(0 hB$UutM+mn+l|r`ܰ] 8U%&H`8yU!v#rg ZQ(gf ])~%Œ,Pge1hH/٬wGjXm!GGLGd#ℾKxCkJ Ⱦrx6;azDiBEv0HR9TH Z%U]-,&#SNppr\٭mz/# ̡beHn".qB(GzE;򻭢o/Ґ1NOdts0`:جv;ƣ1bʺe6зux2ن/nˆф^xMStH^ be ]n;4\,m5 o B[8{2]n?$L{CbVy Zݓ;~9$ _خn|G G( |eX.8S4&MeM޻,?}'k ņd5P%xr HF=2"7۟wd]Rw1]wMxy[)޽JZ1Syﺻ^>NSF2#'tV-ݢ ]d'QQP5M'] E5m 9mGU:Ɠh1n6{ 8iZVh̓{'V~ ʪb6#Exl V!MB$H$"Mb8:p{vMdYBuS,KQ &]+s)XvR]2pyz#f}c}mIx@Ոw3'arg?_kM6HN讥) 9>1L}N[ׄOl9x&? A2є$%$sjP 㐦Ү)'MCCٿBZ :A|>xNfW7tmE]|u74a<q|vLmv,>KJq=(*?/>Bd>̓y'cJ (ja)\ixk-Ʋxg0L%n)Tҵ1\_.i$ ZcNOtmG4H9Zx~'h2%NdvFC73vTZ%cw^X1]a`op7o\?r9z<>vqx vWh,=~=NɘƟOqpX_?#LC\V=a2^sy8wB:}-M餔+GcK`+Vʨx0)`vy(FQ|,̊"k%$3e'z2#eNEq{Z e#y6s$1rVגl^Vt(B}0)jC#<ϞGvG'Ėıe78B)E-j:ɫQ[I U+*CJ01لtnٷQ$bETezGӶ6-=(z^/ȷGHضuC M- ău%:*NC|ɗ|xg Ǔ7gMqf&C nm[Np CUvh4I3MiC8)V1n!4Uz2<^8bIuO5fsin%u[fϋTMك{\<gKlgi`{wo\toO'?|1ۮ)?jv-|߼|?{VyY5Y$g 󓉌rh1f] [=yoxƽo3='^߽ +:]VjE%V9tdw͘B'jU;לoS"dQ=kՕv(Hq;H5}N[5nWڲ3/cdeF)н%Bc;*iQ"R}km_\^^r|{>h6AmA G[6e"+{㊥AhLW|+{PdInWD9bxq$|C|{?+ز7ӓ)*:PŲ*J~Qʖ$r]]~\K,ZeA2I  `0=s{Ҏk[{}|}=}tBň |6z_g JD.s($}'u]9ɜ0-x/Y.Rn/JxkO>υWnDtj(Ԫ"C*K \,ٜ,V&+Pit@khh,[WQ FUخ\'b~F1FOf-ҌOSR&?z_ 8!TFY@cE;8+DemvKc \%Ix‡rN̵˗r~G˒BnV~_9I7inYwbE+_i(&>W/occ缢* 4c9.Yf/\eww(XC)HBGOwe9KQg9UU`JCD2ӾG09:['t;O^?C#v0P91̦K1.Q8ʲpJp `}@'cS |!s<`1.2P+CA-$#J"!q-S DyhC_k}?0I"B4Eq<: kW!XMK9yZp:_|be<;<[Hm%}q ;]{:@A.`>-S&9Jk6?f|ȵkqUtBPnXuѫq`qӐDr7gIC ]Ou볖% $ a`Z c^WIY IcXVd5)-ҭG8]N};{>MƜ#9*SRw>jC,+P=hŔ8,J“P{,s<k [\| v@)nG"F)KYg;%l&G'tc1ӓS2?bJ\U~x=(TaRVD/"=?8}<~dܳ7xh#OO.5DyVtzqGsX2_đ{XeI e͛0 tK2'_A0A|9exTƢQIK3Cɞ,)08@  ~#8]ۿ˟l{^KeTE&=NAo:/nUMNN<)K<8$vxS!}e c*[qCN޸CLO9VQ]{иJt_~@(ٝԂ;0yܬJ+9H;`.ū\|KbRZU"m9ȪqY,ZP;mjbJ{WUk95_/w 9_J):LY:t~'9gg^"];7G]HQtED2s9>cy{CG<εKx'-܃:Q EvW c{`'fE`q[X+ҕ^jYz6Z_SW;nL|^~nT2KXn)Rb6K!i-Z ,L5ET3l9SSNYΏ9_V)NN9<<%K<' {;b~t:yNzDIBYTTUIDXeSd9F-~p'|/HSYLS?xes/<˹MiY$˥BqFSGczIDҍA+OD %yQ|G{HE$.e$/ň;/;.F3=vb5Vt2X,RBŒT"@SU9SA)k]z.'_ OY@G~09h?`{g{hX<~tLpxM3-E0whO]%:M5mֺcLUYy>W]^yןyV̸͌6dksv]gMZ3z.gJ5S,˒Gi]QiWU-IRO|n'!ʼ |8+*M>~ķ^~/#{$`X00t@Q=+Mc[fPjڀb>2C+3籦lW-/6ډfD=kǬthhv6l_i\۳FT-+^]y~eMFAy)mWfq6Rho%*CQU՟SPar=PVsDe!2LĖ,cvR (d trHXPPM1eJ[nHU?>lY+Nfa@(OIvo;lc+N&2_G!SVMEZ!O3V%p67| ܹE!.]SOtsqOheThO& n8 Yb "E r:eIsasH9=2EP9"MSyx2Xơh , L}7vR=/lSΥ ׈;}ͿpZ&r?ݪL 􊈢h6ਜ ynΆ(Xi\{9ʸq DbiVRViM+ېc4ZŪ}~"GbhBkY_F1wNf$ n[|%`Ur7O[^W Wy͜M%Y,QKUo{JeYrQv,к{< <̧§Yeu-k0,[[/:{U mTj Wgҡ0 BcIҏ!QSeE֓^}7(Jdߺˣc8Ï}SDQ v/_cV]/}1`sjAt$ o(hFkCp82a=UP82Nq~;kLz*I+(U3)Zo%AjL۳]dc^Kq]E+^n{JvRs<jO.S'RJ<7w[k #Fá|VpZҀONc~K#J*gYx+8TBȪZ,*CU,)Qu) t@wk[Cv7ܾsG|w+|;1$ūn1̶^V5ΜvlJ]3\}{kTJ`5 ϱʢ|u¬=/r6k=_;AYr?Ã$B$vq‘ y_1ݪkk55%p;2Q{ l+daMAq sW]_iFG4?ey&w{7]77J,hJڷ{n/_ Y,2P WO;^a[KB}(Qqֈ=̝;b!g |<UQ11 9q5ƜK\~;O:p<>bX`6[`,XV1-ea  ?F0/$(ؑO<1J{OIȲ({.٢ 0% 4Aoc8?/ !z1%_zOQVӼJX"&S޾t=NM"8K<_3;]9BߺKƻ{X +J=*.+՟.od2WJ\%mW;cfJV̬T^Kwdka`Ws9)r Vflrkɑeհ# s|W!̋$?DTJ7[w^ך/I}҉tq-S F"Ȕhqfܹ5>xȅKۏ腱ܓ"K6FJQ/Dt8a}4_{.LLY', h~7$H":ހNKtm ˆEZ(!d#k/b9s{*,uCJQ:ۆ iuҁ_ڧ3Vu;YּCiO(&Ŭ9uXI3y)uwtњ؟),tVQWs+bɪV XdGSI{]5d{9݀1Q{6tCZ߳]$=>g|m\eiZ3GӪ$AQ` n o0$ܤ\[ FJU8Wf'E'B/20׍9-Оbgwl|kwo1u9Å󻬍^$])SUiSYt@hOg*Dq~C۷yVH38 #yŸW/x>󲠋$4K])JJ#>]fgPk˔oοB>SʅK|0 "iDxZaeXrNlUP5-IH!i U) CF6m@A@9JaG+#떬>Cv4M5XlS6߻n6t"oO>";cwp|4Va yYg439SaBEݓ5_O=믿LJuyy._z׀rJBqQVI(1IEolQPj+Ubٌf8KFo3.>~8I&~1|'S<=.} 5s4 lo!׮'cX,g"g>)NLMJ0XJ_|э P"ԎK5ljuv\ߊv,|TH"ݳB^ScS90`hg^8KwV} lezяd%dYQ^LKYI,1E- 4xn%t^]tP(!"ƒjs ?]&LS@bOR<:Iy NxKW3O_ЕBGq}!+i]u;b*'NiRKPL;[@* *T޾hWp [v/ +pk'T3h7Okϴ4q1>k&//|~އ <(mO|,ϗcVYii#:(gf]<q|ӞR[-PΊ ɤfU+>yL4RZu|63văWj՛9JƍiW6^g<S2EqMM%[fq2-Y̗/iTUUILU" q3Oٌ0z .?h8"J@cJ! . }}._;'=V CNOSNO a9ׯ]2Rs&vQIv#N/}@ #p(<,CPn5E筒mUMUtu90* F*KqS=s3j\׵J#s@݋"k{֮*{rs=f+/p`X*۴^kW\r^ʒW%NUñ,;g\'0jIV`5Ri9iZ։€dᄓn7&_z /rp0&fӔ{szD.|en6y9Nj}(!\OyVߞ%WםSwyyʘ5sW lmI3˳Ȓ.БFBfSp;*0e}]K\ /]$FTZ*эaz3j*I'h̬n6rޓ2ܻ V޷)J"wL_6hcbscaW2˒l^%qBvQN?KR|>'˪-=vLt?E)KƒE(E p<2I&K$& <3Ҵ BNK@E3tb`ۉxHɺe:. *n5.(\+H z.׈c2:־90@Qܴr3:r5CX{6&zUQӿkT;u_Vr[cnUk֙m+X ^nKCZ6JJP$gןc]AM( :z]4*+F,qs-=d$#no}Od.c=baKޯϖ2_euxh҈ŽJ]uUiW~Ou9_Ui'قܾ|5}{<.yFwĬvE-?I7ӕI=YwՄ:Uz*ʬ_t%٩ƶȟ) 9ͪYyZV],~CaD 2U TVo!mPZ󮆥ZR>֮k`R-g4mM[wJ6E g*MSi M ,t{@Cĝ?^) Ē.Ss6?[!w􄲐unEtQDžqv <|W `|s\|IznbVVR%YZ0tb0b 1c%yJ{ Fb~1@6n%X(M̑}%qN q @.@[Ej'Ҫ ^4n.8KRFʁd\tcyjbo]Hjv}Ieedv5TXл?23/ɳ]VآG\1X#qdaZ0)KT[zO6,eZ81b2Aqp4&OKNzcƏ)ebWgݍs}⮫V A2Y >-0d *w7T_~:rV6ʣG`=Pg̏U &ԕ3Y`roK|_0[LI@Zi'W5tZ(^皫e5au E_U=DOڴ"+O( )ɌR$>Za2--3+ڞy^4rUP-AOo}iکRy׭|{ոԠW5ZnַPZsAzٯIz?Go}zZʼ #8 B8Vbʊ$ Z1 Gxׯdcf)UyV+iIAV-\ eeD Z5I'$ 9'1BvX.LNgX%dirkӿ^|dJ 2mL6gkw)GŬB_+}!P4|z{}hӮ:ӈXYKr{eݞ6%Z%ԁ^g**¸T+eV:S!<uZUb>S&xtϬ˲nk^k6Y9#촻5om,z_ȍ4AQB^ץ;}tYQ9WH+Fn躴"TV*`K(}JSֆhexe "b}5lϕ>ЭkTȝ 5T&ȊIn=9}&;+G6h:Dm6X[ppowe[N ^O겺u~jzVg5zz^c0҂5BF, bI8$\jRyۑyZcuv4՚jZzuu+૩cJlUtrR\WMNp6gYoiZ 2hUZ]?W56>Cg'8~*n|AEzQGDhߓw ZaH n0q> $ޣcn~pÃCu|N(YzeY};1VRRL&PbssHH{G|x83b8XEfdi! t&!JIp}H+vaɄcF>[aHe˯+O>Yys֙/3`,3a^20Ux2E!n tlYqƔDqDqX}hg|억M|M7&R6ܠZDcd?E`ɑj1lrQ@w~9+-FMzqUWtjQߣn$m.ÅB*[VwUx=!]fʠUY!8rBx*_:.L[.4E4Sq'(wsZcRKqfRevMBE{T-Txe\͡J22#*t[ZWSFR+;UZ `#WUF³\fy{ܺse:'IB.Qu;xOer$NB!ji-&8b4G'i`4䷿O6=(k3];Of Ib!̜EwY Q`8t9)Y?@^6 yc]ݣ\ƙr 3tc6NZ ,{ϥ{3@[%VU{X,yϡEhI-7z;V mF-RgZ37kM_uUYؕ BBfe)+2\|- ZJinMaZFJ&'|ұF.'3S_ɿGVgV$ӰSY\eTۑQYג\@-q:=0XPW )lhM10kE^` *O Wԫg+xoo7+oYޡ7brWm;|+UV`Z_앟`,kDQ"q$Ff)WdtյIs=YWNJ[UkdIٮ99.=wHM([55e}-ۀj[%5z,9$ t.]V xV էn(M~ l?uY0~$a|:e1[JlDAɌE'/ iVb,DQ{R,d1S%#n\&oNIe)oyQDq\dYF/Ɛ9õ>eY;'K?Re$n7ރC$&/*NƐeiMI(+C1]Q:ncJqIi]Pr3^*7!m^nƁ" 7GK,ѤN棚ā8+UW98bvs7ؔb,aUߦT˛Бcj-Ts8JUY(>Uʱjq \Ɋa0V5U֚ϕX)-) (Dѯ Y>Ɍ( c v>p<5(,kj[Tm5"VJMr;߷܁M12zzg٤" ifV\M-;]-}ڟa,۷,m)&W/}1ook?dXlX~JH1 !`Z;ΐCTy|,kv6F7?û7?`39kʽ\GAnkI35j*rlDK&t-ư)MVyi9wRV)ۗES( .9H߮UΕf5@]a}kwգkwZ=s=+@դ:*IT͕*rXcJZ8ds'(g>{ SYO)Z))X87ҪM2SrlQdY8rOks,pak?&nG}lԊzZ鶕mBcW6lȺ_WxrmK_ԫ[mڗY#^擞N{]Wȭ7a|t 뭂VVXmBBwSW|<~vwIH:]hS&M]Ww>Ɛs"1 84,K,J-&L&-V[}}mIU3X2922&x\ `@?9sf* c;V48{p;ܻ(mt$%ZI$MmTIpM ..sq' *hє8ϰ9rfj^SGJG`l na&n張pt♘ ԉ]9ycFXɤΤ jMĎ"kM`o1:A ܛh%2 ˋIE9v9Y$܆_\CFxS4Q%X7ElT2PJ.dw^K'Ar<1%jI1 IDATC{Wnݰ$*#*%0h(}!c勒*R^å"njϏf3n'L(?SNǴyY9^׿Mͯ~ ?\ѽBĹg%c0NwHtF$>NO?% F]Zqm AvYx{e@- LilyE-YGٍĸR/zl1CkFk2ezRF|9bVOe#vk`Ka*Uiz:/IHT#%O(% K6 }b>hĄ\C1,K(UsT5CvGn߾p4jaL UEYϾɋ v30݄RI&vw,K gx eQqx-1"L]XT)EZ/))vPFj<{)s+GONqM B['ƬIBSd8X93ϓ]&4eF E[6IYs91%aX#,$F S^[<OBb"dN?7Q16γٱyvٗ-7V"_pB*_m1NL$6r ".Nƀu^/״Nus_Պ u+d!~JOj#16SnxuplgCB9ps 1PAu.~t/o jM \r&W'Oo?\(" P"_䫌3仚op.)tТ&zC!NN2v$}:I(Fa39k~9m76 )ž4!f۟rc#2e3umI^z^~h_2/6Y/C4ӝ4|O>,yZʭ;Y-WՒ"/Nfz9c>[D!a׋f:^RF=Ą#:A5ɒ !l_Lh\ѵ'OODŽp7CKҼϿu4Ko>eoG)gSs/ڔENU"j29G:"/9:IKhhيedd/dj%n~. E*Jޙs$NQbiЫ~ê]R:Jxu੖|VxX0ID%ÈVrOtjìAx/J?9jLř%XQȓo}tN>ek??Sw)fnC7='گ &(/ܮ 'k1LCJKZ%ltzq6lP&8e!"Fi)!N`-ys>.dW kk Y*$VBȭU[KjBү (,]a/|~ƃ8 2|}0Sh*=azW;PVpX?nf/V5ʮPù#Q{XastfM \Εs쬆Q: l;|l-LvXKHʭW9 N0pU$RPo3Uz݄/6g\_2Oy*,f aLig#pNu}cRЎ<ŹVGz.<꜖6>ec[!9#G9#жdmyNz8}I:qPy !iB9cqv\( VX[t챷G79vV+|QW6ƒgڗt"kaޒX v (WJu~ &∽4%sehK!KsVߺ3?z .m|rK:v~?9Ͳb/YeY $7Gܺsx_ \DeXGDAE^X.qh^bfLq`(KpХ cڬmBjH1Њ*aK!aDhSĒ֞גDrB--:qNj߼&>rUXL8_ͽǛQ7^y0 y{ƚ$@IUܗ!_ݯOOZ8Aon˃ J61A Er/*q X[Q(BWgwnpM #,wos%`}9m755>ݘ oRtFz+A P9? 7Rmp+ 8\tCbSm2ٻD_DL/XV\Dvn⊒hdB NH" MzC@yq$-[<;)Wn\,xzݽ!axFU FW;ŀ ."[֨Z|s/xXPvT#cRbn1v, CndDxZ1T֦ g.o$>º=nyk7^RU5SdVd왙!&ĵfSnے9Wm9vfh0_ N\RZGtͭ[]̹s6׎vM<8 wGKN.)mPMX.  Z;Q`+Gh| *0FcLVZBT=aU^ n]3^yauQc.w޼S. LAx=i#% `СpVQg5Y.7՞[<_ R(EKmE|d윧Ncok o5,xrcAk]9j~YQus<+lY!_ʷWώуߒ>OsńVd %ĉ[acJUTf2qBKXHrtQTʟN۸'XvpLs 4 2Bj=D7mX\&j}mwOֵzKSINpiow﹔LJoh*fYZ/>)'Mw5"tz=,9{8TVvZZ^GEh_UС•j,,AhuL3&aۯq~r\yD+x~|~gh󭩫+g2U~p}QpJ: IXQjrNmgprQ|DX͒ʲSN6N ndN4F;Q,+~3*ڌ+ys~-^{t{#2eNF/b}؂8/4huv%J<8{9YQ?݉28?>vBLZXet;1^;GiŔgg?qJˋ90"V& kFQ%$Y)t&[PZ> x&nIBpEnrʳG;EIhF;]V˔CViiF,(na2`nJ@CaE}[dmaTK GG{Vf ;}n^g5#BqtEUdzj֎V/w?ENd/8V؎IЪ֛0 aujimD]ׯ/x1|r/sxuo9tI\ S3(""号oˊ(Kc:VrG|yNph`{]k{Ơ~Sk1I` qP/r屝0/fEPD \K$AJR+|$geZ =1C)ƙ8>/NvW2`!}nܺpCTZS(ʜgK&qxt~/yG&^&rCcgҊO(Eو;p,?:kW(WUceԥND|槽ؒޢKc0Bഢ m[_$;,+%Ei8MfL' :6O)戴a녺JTUBrՐI+b>Um9`ԐNf@iCv5­ړ6^J @~Ub%n%RB I mLPuMH2j˘MAl-1&_sE$^w[*950Nqpb|O*/UÈ쁈fOh ]5A^\ i:٭cM$jO"#xWlՓTCX.FS5ڷftJi9O>?78?%nݻv>~4 [90*'21ɘt5ڊ\;VX[$IK.ϧ$N! 1ɒɚ9v@X,(NEcXg;-JY.SZQ@ioY}N*KdyI ϸU6'-wNd:c6_rtzRZw; K 6Ztβ6LdrM8JեP-瞄 *x_):wz}uIBh RD6<2(շ^\Oۄ: TkȲ&T άchꃮtn/|*o2_LƄZShE%BKK$ޖU b0h*38e0bﰻ7Dq99l}v8;sa1ZMbkHrYjB餃ߤҡcj6mO>G+l%EPWѣ^]gdԎcFj<֤#ֲ/qɋO:VQ$Wm"!F wj+ȺF7L{8B00w"!ëa!BN$݄"j;vIU8JQhKDjDJZ[b`0F AK6o`YHDBRrcxt7O2:{PN+H׀y7k3߲M\.5mblUp G?kV {WIUFtE^ZPJ?" 4ɒ4KTbCY*1ZъCѤy__( / 8PZ)x᠍&Q'PEc,%֦lM_Sj[8τ+ IDAT> {8]:8]`cm-kH<3)|kߠt7{Q)V $[&L Wsk^Wǚg*'ScZVlXݣ3^0!pxF܉!BYAlS/u/?qbdbJ؉NbL%z}5be=É TcUrJssWU|T .7D[9EpbiCХ;#7,UY1c6Zm<07WR.X uVP fU!a?Keu>+B"% Ü-"? 䤛ɩUcTwr9Jmcr4"ǖHa*IQ7 Qzy͍6䕆aҼ!r65~Vs+T 7)o5q!ZX[r1p "wԖ%/eA̦ǬXa)Alist-ܿaÀabXi^,) I:t)bXRY`إK,,Kڝ"%qKPZz/ٟ}:YFQT8z+6r(ҲNs|Hfh4qtXg9b )R+aKTG~z@F;]**kY.r~NFW{?׉i6 UzuUL,\P+Z>kN7[gYIۯŻV)fcJmtÆؠ "VPkU42c@IUFZm8fAk]ݧNN.s&͙ RTn<0yA\,hT)vl ]cYmyMҷec0 gib}q:?q*'vǚ: \5'Q~>GsDa m^PC}/-ߠ|`|bZG0&I*vvq9?FwYl @ #h-z姊EM!S7/meE#pr./~tw'K)잭"rDiJ*L%(Jyt dfFIz Zkʼ`~|P)C(U3NލZꅫΤiΤjS Št[V T -7ێ1[u7D Y3ꥯ me.[(@P*Sjb"qzk/E)5O{7!&XsP9yf6d6>#]]P9FǤՔYlJ);I:1:s>)7eA胊7u:NOhg ES #{-6!=#Ms,^a4C>ш%oa"\yG-FV !~`S?r)cʄ8RYZ'(\(f(L8^VPg+CC=g\}嶘-XU☞"X)př>KjɹR:VLg,EVJmT+#Kbx"Enj\nEN7#Y-\]rQ>ϒ3ŊI[ypu=69ahWjYt]d u!lV=R8{{=}[|}3SBMԶ OL/Ȳ^{:<}~W@/QJą|i:WǾ|0Hoc#`hebSL5µ%77CMA=>ݷp's8H?WD*+lU1n[?Mܮ:wOWxM{D/"L-*T~^Ѯ5\&K,+rTYQ˱i]bK oazG{q|bcb|1e6_uهFgϙRmf-(,hp## \PrDO%njj~H\R ~.(XlMfj#hb݅tT֤@^Psl&u}6S+իgc%& }QْJSgU(Jlrf Q c :#CV, ?عKԊZq+$ {2SU(|.1E:ٚgSViN70tI"ŘNrE0&yΏ~go_|25:$B+/V*m^e:1;>J;sdYEQZqBE aȴX2@Ky玎;Df}.+k٪*Y~;Dͥk <~'0I?fu|9'Q[dY&=3Si0(fs$nyVC;6{8#Q_"lm)DI{d{LNy x785\\.x<]nG wKfA.Os>^FBL;Uwq+eDŽ#[|Ͼ_O:JzIwuJ9A⽲C(oMm^I?|lo>l`;"]M]E+IZM>S^xV9لGN8~|r|Y)Gxk$nѺYw#@#DB>zR@ lpTekkKy6dgL>bX{ܿ=~ByyF6/Y^Ty.Vړn(EhSͦ%Ѻs+Q锟7ޢPib>02ŋ3N_zEQ~K]" :~hx||d aC;Mf ϿP1$I[Y:Z+>WYOpI;ZKEar9cy1&^Q;IEɴ"Rc6 -ݘVJlB/CQ^^rg-'''̳5U 㬗8fweE00 qx"18)N +Mvp5qnI|*SHXM\ܖܽ$5PsA;VO'n'@|d6Sz.*=dyPk9Fb?TB>Ɛ'PZVOz>;8*n!?]OPxߣj{uMgD4"^|5ƀ7߻N8P,S.F>&puz1?ؽKnEbT,~ĭZNvzr #`b[t!(*Ƈo^BYzlj*'Ib\常uAfNJG܎Iz\¹ƔtT(WdΩ| 0jksjk|Z/6; DZ!ptP֬dM%-r?V>ۏe Pj jSdEY!ZRF=pw(44h<c>K*2>+^ʕ9=KhIӜG 钲DARˆ̘O( jh@wУmG!$fwlʋ af3:v{htM6R$(XL,+Ɠ,Ϟ  %A U+KZR29R|q͂Qz,jce)oq_3ͯ}Lzr[pEAZBm]DU3%kVi6Z PR<)rj42q9YK.VvLYւs)9$YٹNңux`6_X[uy9c'y~!J"^Dx8# %>]gu5"9bTnp&/n.+)]fG#z@M)vQsrk˯:N4Pк;o^=I4+q:%cÛ7xI/\))в_T%l9GYۚQ^ZeсM{)'L 5qQNҦсaXJoL3J c_j>ZڶѰ[e(X獊=#SaZaQJ8ЍVG~ge%S2!o%-hU(0 !yYy߃fZj]]dJ'%K> gU:'?9HΛ YөFRR~˿aO?b1;c2>!_W7X<|;LJL vG =V5.= QZIZ,K..fm8|z{e0ݤE̦3Z7~WYN8?F!{v $Ҏ+;=/<~Eɜ1.p wT7!oH^ΰ1ϸkGL `c2[ry1hH VZ{I"@1;*dRr&;4m,+2߿O1O), FYx{B\O|1䕲*QLQD-Zmū DY3++Ɍs.N.Bd';6 [5+SoM~9;<ɝ(zɌӳ1E\.'+&ᥢksY;fĮP:bJ'NňYHl`.7o/slYvs&Me * "YBPDĎPH@.$MW(иCcD$T+A#4 Q@U}w͹m4똬ְ5Hd2Z|}^ܲwʯ~;q*&a1swF/ ÒV:S)A<{%Ƅڒךh+?;k{;6oY5Z#4^0XDZ|/`oosmUK_F]NbP5ͶbYoJf1uYvΗwǏI&g|w|-6-4\o0f;$YJ:I}6喺n8s[.o"]%FEAL9{cʲU]S%O>F.Vn[ڒ8V uݰi R~.MF{$Ũ.D_ɞ%)~\!%Vy/!ڨa`cj#;|t5Z!OaEkXϢ mqe"vt@Vi9J:_)txm֚N]~qJqN;_d`xXV-e31 *BK! IDAT`ܡ@MejTMjWEVqr.U`qvILa/74~ۿ˽7JV ]g& `#]ЛX \y`_%>N</ϯx=c)@ sAةV"翌4֯"+_?{ojpx#V,O86ۚlYlX5Y@+PRGrA5r`$]P_[dDnO2-/r+ߪ'Aw;Oj 6PIBdgE)k=ydhLԜ8't:ޖnL&c{xF,Wغ$,.SNOfdџ}gc^{xo!@Yw)F>`'#vf9?ɧ7޺Q%UՐ&1Bm-vr(ߞt i8OtFY^C& \·.$wrxIȵC4N4b`[UR(QQa%mn18y\qKdK蹈N gw~ǫ ~b˼#%M<%ITn^T6o~6jAU[ 9k=}NFW_ǛmNE$SwO[ͯ_)[<~_|jJU4\7L:exylmƯK4q|bEm6hnhx:f2ldid)EiOגF wv_){%tYuƐND)4Ҳ|Y*}@DZ񑷽 DqH:t@dåk1`+m*PO$,rqxsTI JGJcvv&4M#O;g&͘pzrO>noGQ5< '+21c&rupMOq~~ Z)J{;dyJ!uB$FQmmoCuOY'/I F⒛%MUsr; IH&Sk$(F*n@Z+6-w7%K- QX7T Mo-Cک|5x[(rMbP&!$Ai1亵ŷI:yg!n'hd>h'E=:w|F7j1 t#ϯ]rf0qFd:I8i;q9(?y\^^+u.6LvwZY9ۺ:iޣ& ,\/\2et>Y65Ueщd=s&{>z|AH Ʋ3dr]ӏΨ}NZTUçιsz{&M? ?&B$ 383+%*Z4Fn=VVzHs6= `@J1P>3$1zoIBebd2E)J"xO5 K"ĭ8xE-h-rS7l?D&nPZ˂ʹ/1Ds/,MC:s v9F-mu<m{_zl<&؀{r{~|E<*c:e7jliAkRk \Gb`٩0VgTmDdh<}Y/W>}x\9_6ߞ:^ Odw}>&9pXT&NG&MhlȬjS=g6pG9ˍ'qB1_Z((?b螒n01~jAjsT=t?wkOF%iaW޿m\^:Hq 7(r'(L:YpP8j? +#ޅ:iaZ []TAKw08vg|>9qm혌p1{ ?'-5h,Ҍ@p6^EA%\]5aXn/~,Mؙupyq֚$P.yqvbbe | _wGIX$ {H(Fc>o<`L!Mf % [|oYy#I4Y >)yv* V#(FJ =U:0(Կty)E2{:$-&y\"}d1)aQ)ˑRhF 8e3iKk;W..Ĵcgpj٩)%qF'I)z" I0(}gJ-qօ3^1$a>)rE>GLY7<{ϸet.Zg5y4!˲x.%) Ͷ S UYQ {vfiT=.W>Z1Sol-mM3A0Fޔ ޑC'O&#֛*x4tu3vܞ{ry^WRٛto;>!LtU{ Id&80DP$Tb"m'h:y[1g7230N*>(r3]gD(m2m歀ET81UܫE`dmH)v,fCbGT*-A6>Nh_?:w=ﺥX8.8|=XP-~&vD$h0XZ3cKŒAtM!>'4W|ӝ) 6W+5o.XG_#G)K'2|)GTLn6MDZse YbǺ@Q9>:`,g//y9WFyg=l]EXm>E( wwsQ 2(kĝVX0Q-cGNJQNo}EH~tAY(3mUZA4-NWueJq j Z?' ::("EF`D BŒR20d| I{шlI,Uwi2Q:ṡB:MP_Jtp?:|oQE?vcT'QƠk$w,~~o|[Te>O8&ISW%vZqv~(M(<.^:6e],WW,Hs(gEqMkky' @XҚztdFW.®܈ڶUI&}ŧMvmD#[BF-VxԀF鳸0xhH,QFI^ -X ۧty3hs| U"/K("]W-Uc35߇v{AvJF.AP/yv nK=d<)vwfئӗ!M b3p>PFh9ZaBʈUMQ9k"AmwDo3?\&Tb+OuDI=tImQzKN'ǖ$qxb >xB޿_[ҷ.OEԘ2XⲡH gEۋ|)KA6oXT,;Di ϜibLJĀQ۪6Y)Z)Z%`wwؙX,|ϟ_Z-ֳTTewo}nW4s|rx<"I& E`. ֫ |o~9؝󓏞pqw iYytsyqI //W=yqMͦY, 1uf>}v=0mhnSDLFQF&ubHb2 qQ lBxEsaK(H3v ړ8jmC^m9Vs(Ȝ*&5EҤ4C'T`L$1|gJhM-ߓTqޓ(%@:kkªZǁZ'{f$d]W/О_PHcAJ`B I[ܠ{$T}\e׼yD^XknKV͊Ͳr7*!M lGdIJb]c=ZwInlWH)%oa>fNqfOGŊ#w9>↟Cw'|Ӹ*P|jIQܽwHl6%s~ g/:>2q,>#$QQaHgzj1 b@L@$IFI2!/񂯼7hK-.LMvV,RVSnn. nS6JZ P"  ݲwh)# . K ;7ɼ_Ʀ<FﶏPqgQ}|Τ٘ͦĺbEY#S* նn"PίS>yLӋ#dz? pCg^HI@ngNJ#86&\sa" Fs{j{Ros1%Ɵ%?t FCk ;9Վ*)jTHM^|F6Y9^%FU.`TݤOVb2}mJ zI#[Xkf7jEUmV2ҷ/Wt*ezQIkwxh C@+RiI*0y:v|`vc@ e4{Pb0PZijl>c:)Zly]ӂ{5)KWqy͊FY2b3@mD_HA%(15ZR5NQTRMMb!1$]~ܽ3ju=vgiK,oQV5Q,I 8*t`7)K1HQPP☳Kńоn42M#K"oNW[e ܲdm0\ۧbnbYncdJMc,!e=^w(K~c -mt:e23\_ް-+&)Z+6*Qv[sus˿>#ʷ}7`ے4KXnܮ+vyf#>@U;R6 lkKiEm\_\(Fu`n>BiKT%oGB'wнID˼<ሏ>p6^EBW[!*U/5lp'LRdcgD;G8Ƹ%XKX΁8 UŅ(Zi3ſ72d &i]m60Ω}OQ,EJR2,Dŧe*OO/|F T #/Q1ܹ|/x>f}͢l6Tz,vk*E` W%C{+5=it7c;~?޴:o}vM!(N=ɿ7f"Ks9)3Ɠ1sVu$KȲ41Ѭ%Q./k9D3 G*hF9䵌teY~*V`L]h;E*z?(O'(%۲! ɳr}`b9Տخov7EƶXcjsh]0 vj t"֙gm_Uw73j4H7T0(zu̦Eh>PVu'8͊uYc5h4n۪򄠙Mf9=bg>fgxyg/Y.7&wIWؠ99=$'O~.p_}އ{vw'Y72j~OԘi׈@))"8{yxiT+U&4\kV̭ ֢֔j,iP;5÷J\b^2|^.(u*vj**̀Z?]D6d:PtKJNhAGX\Ap8 *G/>zyͳS/ R75 5y| 6{> IDAT=!DJo1lmJ_Z+A `!tb`2R%Roի([g J ̙W..O0_:Cr2=̲ag,qjjaowtbo[ܸ?ɂM)zP%ʾ8(ErtTdjTD%0+q;+Y gZZɲ}ƣ,!xʍt% Z+fڰ\mHG#޻{wخK)7>i`Z>\L'hlΎ$ҎS~[lų?H7Q'oXI Ig="#MLO{\{ú@UŬO19=9S^s/yy~9 & wF 6UɄf?}G3,,.@&FL'y{_8xݝ)i"+nkVui֞E`ig_SNeL˽:Ш hy*w’d@!|sWQ F_Ҏ3RwzUbӥo{n+u%i̚9Fe盚ZB]ci% q⭣X:xнp)[t>&҅/mNtz@r/{AwORh8~@OE3>:ɐ0 w+ 6DBGs W rfKk0%J[LoZ+E3n*RYjnkedzl;ϊOT!' +qtz{Gk4p򜦮 FYt:fٰXAd2*ޮO FGs%UZ+-@W#Hog>#K$hnwlNv_H@tQ4[}J11.F,V\_sYI¨]B ZQupgl6yާPsq}Hc+k>\u[u軓)I ŕo<I=7PNxPqtq4Nc]oDOu6mi](Kɳ5:'4e46CR^>} EszI1fjUr/_'0K NQW5]%m,oOQĹ('ݬK֛-/<\.+FYLMfg̸98isYO*Yj؉Mc|,$xO)rIo?x]eb^Lw 5Oo/x~s-7b0de,YSX MúnȚ&,۠`wob1Qq"\hزUXY+tqg\FO",&0D1i".oŇgH{mq|}->4nbfƽ㭗CK숗bT_<4V<r`"I31Z `%~NQSj[JǜIEb_gM GUeY.KvgcXKlcs r$]cM5 P$Jy+>{I5챭 P׽[ ]c"1{@M$&Lߕ:t4!Va܆Y8khj:cwmʆ"ц͖}/^p~~Z39<{>tβ-i/.{EjjYfz[3ٛxl/d4{$a:|w+넙V̮oJwOM2rƶ#vZBJ.-[OCt"v˦%)m#}R9zu 4(s"qcF%ij˶iiDR9=14Ťҵ'H7$Bvsr40BvD·MV:dwbhPVx9PʹiTI;ܢ ~'5vh9x:oHC^%EҴt>0u.ݫ|2^Fv.un2f踉".Aw*.@Ɯ*Q]wȡS|yEtlgNl˒/.O LӘXqo~;35AmbR$6QTSY#m;j|w"z Z>*Άy1Td[''Q$ŸH3bzit:!1j,ⲖVӂɬmHLj,'3˒]w9=ŋ7 )Az0Ԋ(Uji;6G|o-N : CHr@* '4MDR&Eθh+r `eIV-9yqhm!E&]]YV4g3w~r]>xYt9;8.{ ҫkl˟H-'`VcRQ~޽4#V*vϡdT"x%^P*]7&4(wrӦ& nXKsQ[#D?]1f}8=}^쪋/Jڎ.hdž$ˁk$RfP\D+ISY&WbԖiHC x[[uZiwDcQ/2>ĢWVG'n1Dչ1Qr,Y,7l|o7|/}v ?zt.p}`։ږxz'ϩ˒: K<䣄';\\r/n&h5Ͷ9GEZ3LXݮY߮Y=a AZZ!(|ۉq{qɰW?`c6>SAA#c84:u_V mT4ՆK6UI4#uMmqLl:m@e]n}89&-ZC[8 UQ'eFhM1>$w> /^̎T \% __} "0'%^8zԆdtVPɃn 5!TT6g"o Z9|(+jk-43~)C(Үxt9F)*Vc4b YÇ\Mt$x/:N|>(dx#'1Jؽ؈@OxBOkC(Q5M|'S6#6XL٬k>~Jʽ|>b˳ nooyq M4qъnCYՔ-1/$k5? [b^MtR&q406FVR4C g*ƌD[Q`,1{Ƥ,Ň f6gM0xH<1JG36"DiI5?&g&|wmaîgL6~'.mXL99(PQ@ Zh5t)2M?e?;Gc*ÆMސt,9kHF 7H"&A~q'z@"zt)OBw_ocy!o ʆC M5y]Ba{ВWh*BQz$EBXSt1)(pv!m ?c>v|s7oBCP$*4lmSV n- 0`u;eUJ)1{+0Pj֪O=Jlq:˲^bcHyHӔLkjn;4<`CņTMͦ!Xe;%kno?So_".>Hvw4ńq(YByy(z ߎ/ٿc`-/Q]Ѧ "@w f/¦\ᗁK<w2 k=DAG+BL3ҹť{ s@GZlkn^ _z )eSb2Ʉ,OL M͟|(~ ؎:vlSKz^Lj72b}l=#صnV+'/((fM&z Mm^i!֦hq\AEw4X(uR/>g?+ 4M;ɻ5ZY4X9 <(bԃwcGB*m 1 u\ޗ4Ņ ]#PJ##ǨTAwDQ8RW1; >irrMs6i,Zws[*C2zuN9QUWrԾdnXkfo;|vkxOW gg?}*"=} ڇa$b޾Y!x6J mzo:HM7,l='?zYcTM->8rỸmVxo? ~O>3z)ԒTSO!`'k ̝ܿ^*\>/.Y6G#?wJռ`.R9:>f !4.)Y2~,>R E&޲N c ?fc꫇=#k8Essoc<'j[amP!os/~'~WSz!S!QY2D^VVT=SUl?4C;/էdeqdZдuU^Xy'eM9 $scj<1&ys騫UYQ7-Uհl6~m񄫍?k_=kx֚,OOFڠcܑf)YniyQ0OجV4K~k(R҄s'<Xn!2qbw(%;IaGhgu2in:鈬m[ fwQs8_,7%AP!boCگ-ظnb`. `XX|rXpvuqs>JƸTU4p/)Z\GR IDATrhfx`1_-6- if r=+ڸKu[[ я@m֧|#VwYI7>*B7tv΋.ē~yW!6|e WSIWZ}&j7J0+n&vcXpy[&7:w FyN:h%bd+/X1<⻘ø#fQƼI3աB&EQ /jB+SV '@Ukx)Y`eLF0>vO;ᩪ'_S7~sB^A(('C l_Hm*@񸗴(p-F^tԠ ;cTW4̳qe`&RrC6fYFEFYV¦첃tȱE1Zًe0?—wx|IbY]k跐Gt]Ղ{N4I6"X xӂ=4MfhSU5eM,W%m[lTɘWkFw(ųǏz'*މ:`o꓁)_E (ȅ8*%zW_,58zvƬsZs}y_?|Gjf;n9b,X n)We̠6#9FQGvܮ^3^\P5g+t1G&'a$*=${$+ BHt'Dyb''OBP55| t< ggW?OhV-l \);:&z'=لӶ ]1XEUig_lK"{OSW/o8{)_ u]65劻%MHJ@&P<;Jٻ1O&HA(&ÕaCP:I:&+)Ak##H=\?jsy}AkpƯSWK~s{[=V?nU* `Z B'EQH\Z?;Iv *tMRދEƇ@UTMEpik:-'a:)D'(HƱt h}FL;lq&~kﵝ#IrOfs~zsm[L ]ѹt68[Bxrd\pF6+Hv.SMP7-"UEb4EIjIN^g(rD/;UL(zlI]tl^SɝvAJa?/NV{?qZʗ^㮻c\[.œ_ܬ︽ZѸH2d$%9SW\^\S_|r98*]+񽒠umKI+4qcE(1!4͎^Wٹg[I>̃:wCe ~|v^KJ@rjӲ:s_qKے|oZ:$Cev|e¹8c2-GU{U JafBI<.רo*^itp ݊?k};g^xr}AndJx>[Ps}{2>?]dCJ՜5 ma&).OL1EC75W>ǬTM œˇ-VbohR!EH->va}]}v@WDJ\D(iO A_x S4e}Oל}f0m,h uxS}YI/"Q= 0YDn2xp0(6]T[횳ϸ:{IYMW-oK oy? Gԭ|W O˹w0awpq^u"]xO,R^;wa(S>߃}LU*3[no /Hjtijr@E:%PPmE%[賕xa2DX,֑XK|TGV]Ɖ7V ,緝;9cImls'כ~ 1Mƈ69⇟sQw7sFt޳?%iyلxfk'2cvtgWtsr6-(uMq=*+U!gu ,OHRE!0Hb=그U͊{fwև CNڢ wF.Kb!?ԊIidQ녣>E4f'u?Cj>(Ka &Gq>-kn<}/dS\$8v/{pO^\R}RAi~;UF݋0v :)ie[v ~Wf~S58iK&^=cBX8"^辳P1, V2Q/݋n.fGޑ$ E"+ " v |!AS(u<{$]t]˧}w7Azj +Fz )%˜crBX ԛN$7t@;pfp廿:!,MJLjnB6شqטeITJ(LZ(EnR4Jદݪ\L..z}DF1Y[1L &6~okfqnfoF&՚jS2MC+^9n:e[tcvOZ-Z=vDvqR J@mD )at0-;T=?m,'(5„4ptd)^ҮjQI1vjPWn;->-~dEkK>G\_wta|0fp2q,EtV*K餰WJ~$ ׇ[&C"&Ɛ$&Nt1ژX&tNFiǕMM:$CŢ M J<,;Ѷws/Lf3U\^rx|s֌gSi|| Iݵ5{lVK9] xL6p0ӗnj&3z Ũ O;1) ٔ=7^ Ÿ O-unMj5v4׿ɣ}}ŜćFIm@@}EUE.>=ґ Q_K!vE`SFmyZ/Uyt浖u,Y0d"8(?dDJp~vO_SWZ<ј>Z'2ٕwhդOK< ~Gv^ C&"oGW]Sn}A7NiX}fU< Ck邳->0>$U%y>EߵneGwl a_S >qWOIpFYȎȓc j>@YaS9}}^;θH}q`dw=D|.{k;$QD_R[j C2J3+LqxDOQ%$ Xd;/z OB2)m ߩ G,VWp8BTŽ NQp^iAjcP_#(IbYբԝfv*Ir_{2٬kQL n >P)iH6[p1AGߥ 51~GMMEmUD_y= yŊlyCMjUpHhH (,8|ѱnK%En(fa^$[̋$',:Gb4yuy8;_~w/?mor貜n m 3BNnmx$wIR"F'DNȫNm!]Q w' 1e0`adnj> '1;+.ϸx)MEd*Kcn%h{L@ZN0V%4w!xyHA`?'ފ3h-Kj^@wnȯzzJ)Vk>}vJUK P) WrU(uc1͓/F¸ A"ڈ0)JܣR֊kY-K)Ibq H0VlHjqΑhd\)WEӳXN'+ KY7M$8:Gy?7MZH_o i6hC ɋ17s+Idb4B'#*.[UK^^P㽣kF*?d2ΙNs1h=[i͂UњnXU ߼:շ75toFIӔ/os+d#z~7^w^^+abW'ӑ 7Ubҫ;QV >j+. gUY܁&dv^vqqdF;nn8{ Vm(E Z%F֫IP+[GKLz;!/Dd0 ڀ廊yomܜRmt8ЁQ In(HyUJg+o9!tRT֗R8#CHe$bNk!1.k:[G1'~-!b`6dY2^~Cqێw[cMvL/ Q:淗|Oh妤^}̵Pi!ރjC8=ھC ShJ_!! I72IbPu8&"Pp]"ٛfAl:O'&j"-X5tf b 1#87ĿΑf)EU͂yNp"Oh$)YbجWtgKLDR~+foMf45M49(/{qe؂7f^ƋlQ*Ipaw yW ࣇEĀ3_ԗ#YV .NO8e^zUP(7 4 >v)Jj6 &z†Xz&y 6_ cV}$D:Q >l]yӟv[eO? m윃b %UT)ڡoslaP{9>a:y^(r_sۤY20&g+q[Qfo|/{) IDAT)|Ap{vY:~"1:4B*#I5bWՔuoR u;؞3(2eN~}KäHjnF\AFQ&KIؒ.aҖqnr0u ]9>J J1h t t}U< \_OMx+o;'-:B砭S՞j(@c.>snn_ДKT.n^QvHL쓚FC%Z26 HVWGJDZJg a$rg!Pdv '$INQf3m&- .lKj X;~-:^>zb~ՏPM롼!8oĴ}7; ^tw}.>0K:1 R 88p t#FjH }dq6wߊM S@~#BWx'd;A}MB͈UJ|?W4X/=M`Ql@ l֞rr{ ϩWs<5^uЂQƩ K_,+6O_v\^IrtjؽՄ0+B+H)Չŭ J%bX A\P @IѨR_a4{c$H2g$˜m,hM4V3صY! Tj>}nuYϿ@)H\D% 5\ Zp ز>̯{I܉eC >(vx  N,U봎tP 6I8̫b11n4-9Ռ$ib$6Rʣ3M,qEqӶ_d{~:$%(M[ &4K#yウFh˒iѰZy~zæl-#B*;5M0FӶž+!56ANpzRb(&meOzT t]n);ʺfX^І |):x}^:h]˗£~ܲX/S?zp\ Bmy > JEX8T*WG*"!/bLG"e2) C=Rg UX-l~3'ۛ+>)wN$/ jKqztձ ۽|^GjIX=Cl};>z cY>b:vŧϹ8ڬ\`23蚖bZ1*2ڦC*OSn:Σ$'Į,@1I4+ t:סe2"?GkQQ_]ow|rLYdn 8OVdLMs^^rrOƐg)MYsqvCY?yŪ&lݚ8c6+LGc<\WソY m \F:gY˸oDlw ;N>LJ}_Cc3y]jxAI2l)&Αfo~'1P]^nnP۲RqwkQ1 R (E9atT</hRXZ)nviɳF!ǃ9hmZ,tN klH:a&cPS(zI#wjaye5E'f05O)r-nk֧$89BZh%V'X^{ViA0$L  U+-sDxm{qIr*{Pjj{ȪZa{0ED#l n.yGޡmbؤTl]]Ih\iW@ּɫF9SÊŪ)<ȑALGZKF%2(%qa9nAo&H&ȋ?"H8w1c 4f4zLHJeb)B/;cGv B zHa7*Gm9 O#-Z y(g2gwU52VH j%jǫC`}>''G>놦J$MgEݶ4(R)<+e/eϦjϻݯbP67bi-\\k% <\> ;M8:>cghn-ѕe'NĴ k-{{S54uMv$`pe&1ͺmh̡ho (#R?郞~ʪb`%Yn]ctB1&d#x@Xںa0lʲ&3*sdi;zrYR3XwCLt]G:I:x q[Bh!A:4ߏ%]th=t}g[yv?H#?<_-WO~n*ذMRBOFЩUؾz:o'0iy :F(<&iZj]QkhXĵb%hr&I5 lub7~c?Oو /D 3`X) lEڠIsKblwI2r2P{_E1Y)&dN N:1wa(Λr/ ;o_b#cAm͔ãT:J TFI!fE< .`k)؄ɤTC1s}<~} r眾Q%a Gܽ95^+xiUUEKlZ4^KHnk~騛%j3m[lHk]0ƒjWh۸{oZCF JM_iʋQBQ\ƹ#.h7v8֚_ssqOxp)7,wmtc|0J5JS(բXCU_>: d(%ɡLDUvb"Jw a'Qa#y/s-qHSh](F9iP-mR+yxU%/ιcSd:n FxŒjs8tm\TU5lsM ,!xDl6u4K=[Ei1׷KMIzt!9Yby/c퍹wƔUG4N9\p9Y1ۣZɘxW9{|wڈ"Rco Nk}ۢK$8g]BlzCGW_dw&xOuLa}`Ӣ$-@m8K7<|O$b&}Tr& (n`=ɶ:n/3_[^UEHHB J,Ѣ%Qalٲgx?SGnaYH ؁  (Tlo{{soArPxUy3{Y{}dJ4_A'v,ԁRPݖtxv[jabplL2{!Lm#ܿK{uc2v~4!7`xa9qoq_;{oC UĠ6l=K>u`.[/l@ʃE=@JUIhJ?)SSĪcXy7 y7_ɤ|7,.@nwHf+) sGYnVo$.d4#xH sV fR* nt˧=5;g˛*wӀ12$g}>|'fHS_9q,NΦ',sYAY8alF innn=s^<}1Ue\ݚkd`DvD,>܁4_qbt&c,А4AYZJz}HF[^YQUIjҊ i{<w. /^CST buEs||)n.4Ml1蚎EdMvcsdi4ݧN+/pY w|jamxuq(x 2>y7zl-nnɝtQQ߮{_;lr{wh p~8 uSgczFf'B8tetfgT#΁DRA?أ /1aO>Ɣ} C}u J z=Ńsmةav3clH9#L"Y;"-nt(3`z qIfM{XkO9w擄Sm 5w6?!)߾>m  r V3Wt^9AwԟUK0&w&&zO#${K/s.zE@B":&KFBۥ=ti*9p[SeDO:Cf{iS Clk7UarrfO<_ IDATf&JNK# \|l u1]>6bƓH梊c "[IQBTRK4H7&Jo"{Nǯi%y `HߗȃYHvxl1(ke9xGǟ?;ϟs^{'ǔyQU8!FXk є˗\"/r"Ș*ڦgtIE]4]W^G?#߹4mD#puzIyD7[VX9>Y0L%" ٤DBիKO9snx3B2z鼇g{L>PӅCE$B?5`r?蜎Eag7ܼ~]&@{NANaͳ;1/cUJfQƝԎ$kXu$G~4;Υq`>aM꼺>Њ2lBsz٤ ?) Eܳ`R ]a%đ8c!/Qq̏"g7 6Ra4ٞ :X}~V3V5N ~{(B46-{C"x`K]`Ի1]eGh{2T.x G")ʂrǙN(L;a& cN|o2 ^EJ'`ؔ2J&%P.#0szX7'O仵*11&&0ŘĞ40{\e*ʪL;~" U})5-A:|3-K5gJ3FʋxR WwJ-mLr|rn޵䓊ZlIJfڒe9=%rvųgY7Xg^,ΦLg)nz;GTX6nT/cϧ#‡4Ό,>?NmL%H[7\ݤ7{ds㻆W9]vb^j6OŒh|_$:G& ZMAR[&R{eZl,x4،En(Nr7rhݹ`j6GDB~`TpPHVKUJFݹՒØ/ {Cx"t$3fZ{{#̃wϘ #3:9bf MGcv>Z8G_觟̪0jƽ̸yiaa|wF_|_K_*6Նb)4"ƃz:ta4X{Ha 3~t5%BFX$uxVԴB461t:blS`C;wcSBsgV;v?{07A6`f~;qF})B,N!T"X@iA\q?&h"*,24Ǿb?@\Fs0U#,hE=L >>}p"*0BUJj0;>z[ZReQpҵmڍŤl4M]y-}OL'"PvI0bwϿɓr4Xw<{O>gZH̎fdְ]oX6d.cq|DUtmnTBPdyWU bk=gq;m03YՊ2V z]MӴĞjVu}ߑzѝ%W_O?Ϟ~:DN GB)"]=.FBH{=Q 1Fctň R Cyq^$fixQ̒F,}7E-|՟3E: wVkI' %DQy*gWhA!쵼2b9], zuUb^ci! LΆ}ۀ:3׮/ AT:L :VY~_|+죯2[.׈$\Z<`փ؃ݞQ)cΆbdԋ` .T"72b؇4mIlLzzL S|#NpdMg!$J0JvMi~2PPGȞj{MLoS0aI1)6B*2:5~ Y'_w6ۆb3O3uLjKtd%nZNOX/]dR✣lbŊ!pzvN9e `X_}B&{yI=1xL!NӼ,LTVZޓ ڇF4^PsE4TF{  @~. oןRH*b2Vd/_,4&P_[͠A6)J1 #kZ[@(rA1ѐg9}l뚛o{v"jT%Yqf !A2?+8ZI]]!Nm2 BaSfo~]*tGynhW{Nbq8tHn׏}VҀsDmts.* F-pd8)LGa ڑ6=_,dZu .#o5mnseN%z-6qoħ  ƒ:L٥1#c&/1[ @,ܐR FڏDƄ d#Ksߊǽ1T-Tc+1WJEZ"k ~ÿI_f۩ ?`rfӆ=]$@[a s)k۱쨪RUlV4-}ST9M]4Zfdΰ5l7"'yZ )1Mo׬Vi{9==]C{ȘV]gOWy삶 >gjZ2[LYFYIp2_Lx%>z|۳ul CG'| wQNUIVdi?"WYΙIB{B ߊe>AH54l:?0x}L<أ Jx{x`߇yACe\]v/S; U$ "C"[ewK0}AŤrm1zQ?XNLnp"1Ä-O*^(ʜhḧ$U1Ĵ1\q #J#f,b@CME떾GԄl,O*_}r<9gݭ^aC<ռJ}M #tB*֩WyB8B]݃cg_4"yC: M2rFW ou Goq1u4(ڬMzD " J7vG$Rqjcph4!J4cu$0M 1STIH0`)C)[ DC'M*$f{ =AF)uӁΚ!DFLe^blKΉӷmkz}Ru[Φ}>՚vcc֧!}zRN,I[OO.19;,s<;$F3uՒp"\_X-YRmm+ BLîI+,l[]C1o=h6f?ً |6,noyuy5{w9?K]ux߳m;~珿.p?w׫4>W/]s{eai[ySC5/|`4?x:*-Q"FAIݣ>;?/wycC\5\_Q[yJvLP$Zuv=8rWdڣ (:K G1'o*&Q8QI=_ss\Α( I HcMI0(xT@TRE~ٺI㈚1Diq9{xN#E昖eFdo>, *t23Y->V!-GmQY>yvze:-py]y*e+D}ǫ .KJY=^G_&#Pb*(Dbb !yY8?j8RZ?(n7 ;?c.Oz>׿Io-2 iH᠋TȬ:8E}!?"f6c4 )_al~!1Q q?N<ɍzstz0ȧ#c K 1Of4-UN{_f}sG>F ;窝Pc {E&^_ЈOtH%#=5GL(HaVBzÏc%l6v cfo010O0ɓE2P[2QD؏;(LIa_8LD < r4*d* m2Zw289 |A5F^mNT(dx1yNQs6!>!$7s 16`yuKY\aƋ4ٟ:(dih=&,s},2cyt6#-1-Mݑ#Պ̏NxCx|٤b%/_dY9UEY,S&Uf+>==8_ldzwVgRLI89 0BJenc i1BBSV-U5MS%iװmv>]Ő-S331BK.ַ8qls:"}HIF5>&o=kI c ;<r**_$f{8pڲZIghfr?$#A :LZ7LʴqeiTX#krbmu:O[&_L ǿvF<@nP]4C"g8umb(҈Iݘ=(~()R~/.M|O? _<9>xpD~>%uAbh2^*L-4:w0&XXmB-h+,)l$+-M}Aw±j)[ )m >%IIcd@Lܪ$ZSܑG$WA' ,428GtL(AKGM3zī0I-:-.MJM?sfl5Q-Bb8T C1_C_8`sfڵdꎝZ҉EbOPܽ뮈n. \n'|#p.7l &m~ln黚T-fH햾ɤ 51B5)q.,rmK;|W'g5gy{nXGV3O83iv|{=wӊ9{Mӑegg,,/N'S:r"Ob'w*..dù wus>rQ7w.^R*ԦQK$|Da2(RC@;iGJTB^=fqm,x? LɵɁ0€΋o~bxaÈAt^N; ҫʳP_cH <] E.pn>ʼn_g#;H=K(1; C?x`QUAyF;ATqI&2tN&7}$wk,z_|9'u)oPnĨ},  :n>tD0|-1mΌb?򖊜d]Ok1{(2.ZuJ~ ;.s@/nTJ!.CM+݆(ih.0}|2ĶV"|C*mLㅷJDU}RPld9="uaRۦx0ѱp7It(HmrFe j[uܹW_S]fM}1lَzGQU9KI&Xy%Ljm{HXlvۤ(٬7]HtE7,?RHm&wуSfS/.gzmEZтg\_\x]ypG' >:ØH7ltmGu8"BY:!,9cR^빸\\7IL$!J" c5uhAN}_6W},/c8RhhA@E1IpbƛO9i:thw6;bT\C WWI~>u?z ,nҌ]iQe~O&\xfOk9IN5\?gIIڤ.6yCӅ!s[M@U4mr5ysdzk{NN05Χ`pyZa6i ۞{̷KP˷Ǩ@R'c|.x>ެ]Sce^VmEEJۈDp#=?-a2JE.4soH\pfWJv16)qH?[Dӂź+ lɔoeQ-?BBAK\ؙH=NoC/H $0.ڃj}0vYrPh_@ymd:T|ϡEC0P`:3;__ji#b߃K@Gݩzl$Qw~A}P`NK(`&EY%y5e0F5=Ch.7רyhö1_T̸c20xԂ*15 #yr)%o# hW):I*L=y:)!S]7Ox${W, 5$(R w <"J y>??ʯyL&%;NSwno.Y\Q72k{2Rۜn4]KvҔ9&?V-ml>3m=}{Νͮ:ʄ]\4HutMKeI;Rh-}p΃Lyo<+ {ޛ;.3<ΝcDve:)X8k.uk%YXk(({KkS;t"-!/2|Խ܊PPCtV,OId:0B4|puw_rs Oe˴m݇gR%ƀ4y'H `A #H,<1O78<\s>0*9,QERXbdO7!f_aC놦*+&w?4u 7$Ct\=(%SI&rSH̔Q?MYw!&F`T1$C,qWT"YdKG?^=A ~\VM)3gM'̸sDE*Av^$FGz49eYqڕAЕQzb08]?ZG+z0d eX|\b~9YOo/Y\joU)S޲-ɳ@fCFέ2 3>жmYޮi"Ϩ9嚛 g _LX, ڦcKypeF>w]!FͧX݄mknn}`2s~~UA۴\]sj.p᝺n8O*ȳc|גyUUbak)*0N!F /Bl65c 4}󰸼BdDuCә0 1dxS,I#N 94𹅆iBdP]wvRﳇA9^Cp f1cx7޷I{q_{sSd\O]E AӃRr@$>"箝f LZaBc0MO.DٱcaZiJ O]],iSW20€V#YVv䢗;fܥaќ@DeI;(3&HH:h٫)w%qS=ɌNw_jZ6+7/X/WROH ^xv5Ϟ~̷MxqyA[7تbVM DYPȔKmls럿~n/R cez\b$Ritq9}i~)ܘ&MS<@zuCV.5E[טE|Nj,h1K9J ؤ>H,lCWu(x \k[77/Y^_4ۄ)Ӱ=K!MlS神j.̦%U軞]ݦ? ֥}`@UL%}S7-G'sr}}gWԻ0bXx"Z6-\fnv8QUemd7}͊nT3N9?=73||6ygni9:89bRMi떪".TΊO,s,wL 횻4]N,eb=iѬ;rNbVS$@ XXGk,5N!E21% O.zM0uK>{|_kGqU]ӆyW,K,7|G??M%5IԘ&YFߓnT]dFƷ ]L_ <9I *-9M9Y)O^4ܽ[R뿕Feb$g%mC2]U>Ї$mǛ~TY6VO U=9dL=ݹ($O]PA !h̝N I᩠Yvo&jI1{Vp:ȉ$'KG9D"kEŹqU֐)#oGO~MKγ%pu/jtL?0O:|@Mݙ r\A!i1bÌInMO.%acМg 偏1NT4t1y*nCڸxWr:K{,B%6'5QA_G_=>ȏ&67tۛ+V%mI|zbTN"Ef-*' r+Hc6I6A5ǙNق (2 5$jm-X-ss1Tӊ6nGG3”9e#Q[Ɍ?+-pw٤(ܬ\L(gsnn9^[|i[M{ڦeyѴ-.c5g3^wOk?:3ں<]p%҆H#-Q'61Ke24!,]1Im֘Wc>b8I%q=嫗^\@mn!x,#fit7зHY>+ЋIQƵ~i b[f@Udc&hjj%׀Y2c)嵿ϟbrs?| yYB`BW>g%ǹ(bN:򶠈9(|NnKzӑKuy ,<Sʈd^6L0c s( [85Crʺ쌰K+&Wڵu48WԠυ00B՟5I%yBٔCf [GXz vr3Lg!B׷*_fŜ;GfΦϰ&K1l UUprzb># ̫zl>CD{)ňpv~X8t#2'6v91kc˶GY91:t!| HAÛrI&FzNBzz 8 6B@B%es$?1bC1QQg@ I:͌htC5kMOQ1x,9ZIŠx+=)ixoI6Ay`ӎK4Zo+q4 eiFJ> q)>$j A>F=a^(S af;tUz!rC= x4*x7~ﰧ3}c $DӦ%[l ݝN`$A&an"@Fq#ݝn VۖmRCUg;b|(Ū^Zy~N%P@10*busU?(n~w:bALgL})K%vV=]7ek0zFYkNOfTrBĹ7*Xf~Sc,ZCR75'l6;^k;f1%DQ΋9,?+>gzDXƈ>JH1BJy$M߂1D&l'HpXQ`*×@ٹ|)E|(&jJT Ȧ_ě cˆAˇvoLrdBAt8PN5KrB1墢Қ]n )jEDqz(,M~\Z>`t&ex4L!+M;IX&?ZvbT,cσt4dY.% -Uww|Yb X> 㘡ʂ΀i꼿;0xvS @vW'n8فtq]/嬤@K^z jё +o]߱]v}$NpZb1i.vxQ=Gidbqd`V^utwAߍtc6*n#|m e]EEk(/35 H+eUX-8=W|/pUWa4, Y./OH1o;˃!i90Zqf6:6ۖ՜,˓%z֊3 R7~i),*@Qk杛M"#ލwpER:&tLHu#ɓU|L@d (c4UxUQȉE;IBMw)4!*قh-#>vsZ^QH}Jj6BӚr^۞ya,sʪ+ǹ}ōّto&Q="*۳}u53$ !$#hA63z.يMEŬ.wBȟÍ5UinCiAk Z%nYԳ3NNX#:vd]ڳvo 黁ݎ[uMY׬ FQ=Z(-1Fa. >f^R~gF./~u%~!(x+–(k->@t٧#)%Q IDATt)A4&3.$ltDb(b"kˢ= %BV}3yD&p03Q"d"Z)NY\#gDt4F+,*wˆT9@i<>zwꊳ3q'eeFn#}mՊ/ZƠ1E}v?QW7SabW8FsRRFrMDٝ#9x IdO)՗"aI.]#2 z !x!d ')v )"A&GPAF!~$6;A!/%tP1RĘGlQ0c2T Ȥ4$I5W)8,u5E-}ƀsM)^]W=0$ZMiKMs u]K'a4UUf~c>a KJ9hpcߏ,J0C*Q6=|~â8m0F[Z)'')L1މďQ!bd ) dXj'zGTuE>}(]G6|] A:s7y,{*}B^ >tLT 6CLRYtL`SVNf?HRl)p!2H:g;)vwkOQX]G ͋j EYbd3:SPdHt)Ƀgq n7bNJM )$|PDB$l+HQ.L_fkT1{9䔥;Mţî-=EqK11=?}~=|'!zEIVByXZL@ζMgJz1cNU1ד& " Cj奟gJQYIgk*,VG6  iPS{PN'uaT<4l՚M]2!;0>d"\}UE]}<7L)ʒ!g`sQXk.Պ'=?\۱>2_x1Yn||;?{ QE]GYɼ n(BQ8Cijzľ8;;9<yfe;VȮ(r[Wsl)!wk\<ɏrNhc`N q_d\{RhPcK@@ @ń!Jъ"l!t3Ƥ<=eIUv1%IN I *GFBYҨ)H!s95^k#hS;lyF1K'O\G8?Ö7f˧fmXon& FlG,F16V1Ϯl 1PTĔM1Q'Ey9>sJ$CG)SQ_k)|JgOnO?ϒMF\Ty#g^S|K}B2Tsv))[e8r~S;2r#2cw8 $E!ro]  ^aguaNMd5Jh-]9{9˓NNf5m'g'Vs~UCvl6y l4Fg)CȡPg%۲E2 J)3'txRP(4y9/ yJ1PDttO)m (AdvlboBh0f5˓9JC;bE( e9ӻՆ~JމZwYY/ZƠzAk|{Ox/g" *BC+A ij\#Hk7@!sH'9$KIPRU>0I9=m[6USGFW YOz|( 7Ð,R:{e"V[e%Bkcϔ f9(F٥FEvvgqݮ fh( c;Þz3xy0L,$9C%~e[gA:E`vh_dq*v8UxhWʣo/a3'BFLٿ71<) Ŧ7SI6>H[E&5 @I!Z))cٹBNii5wXkfSs/~`(*wkƠB/%A'{KX2#@Yp6  5.` #"c*$)tf"d*Nyh#ʗ T`3~m/cIHB[Z@jԙ9Ө&DRTeI]]UYqti%L°Psr=8ʢ31di3M1]]6v !$#C̾R藦Yے͆누P鬢 R0E^iJz 䤾iw=_<|qi9)5N5~|A{ڒأ^ ˗7eŢfCs½޶FCRB](G886hLTJ+ꪠk:~m{b~{Wo?{oXblJMU25,OVTMMa$i:ǫ*0*]+lAY (n`ud$?8='G|u^V]Oj cQ!ʇ9Ե&6%mrXģ'TQT9mBϖrޥ1'c¬R ]Xz-maǪY]&b6sCh^dp7JD^t׍xKD/әQ8f.U!w'!FG0v=7/샟'i["P%Zi)`ApLǼxbar/=&GP75·|.M9'$ F|x8H#Fy`NԕI钬6لn[?{,kTaY.,vF6u=?qan($J"U.Y #L3%G0P$01d06/<!/E/ʈp9x-b>|G8߶98vײtcp. ˬŌ?_~]-eflL@SDiykrTBj17~1F>}Q[n$D4-y C9M,)!I\tWw02+f-͈* 8'롡ż/v׷0Sd1pf~`o\eD%Ք%02 QT#H[l5w.,<B_F<:_5Fʲ`HߍQmv삅bJW"׹sbB\,X_S2Kz&%7GWȭx\Q̣ 6ڲSDŽ>8eWœ́6=J)+>slRհfy~AoZރ ֠!aIreN^ 褉FdHDaQYŸJ_ ~7W ,]D'^^#c&CT4#v}>Ň_\ hjy Q 4ȉE!Y8 (<$,哐"TgWT%c 'DcCl*VT?|:f22oD@e> Svq:#Z2/QxPu|nsrV FEf12<:'[QVkcKb0P +cRXCKÏ>Q*&wZb%ySϣ܍MK҃=t'E4d妒l- Z IXXR ,1X cL0H[Ք<{,QZI)O^HGea( #;)N'hF%`5g]lr0]۲^td3ZÇ_^U@⽧m;qd0WUIQU,V+%UYJaК,e5o֛O_}7x>ŎPXnf+rߤh"tifsqkO6mZd]F8MFpO*oX&cûH(uh7V8T!05IݧYćn*!rxdcwfJV+vQ@]7ԋЧH=ڮafs^Rg *)ˊؠHQƐ-f(~.8XZkӣm! QcUV_⤅T; "zwO~G~>/xuf؏D/uK;s~˿?տ_ k鞊lO`eᛵ !+A[鬥ˏ])_Cn`A`#LP3w~?^~>Hr9-n3yEYw-8f#?UOi2E4A %K' '?I)/K}E}l,WjQJ^NMI&e;o펲Xa3}O;FTJ Gʪʽ93c?ֳ~gq0*Ce!G|=" _CgyG҈ʉb"z縢,^ !FG#VǿC([T-IIB{y{\KB@Ua(4qX]eҚIqvd )#!D [F2@>ï6l9@ [HݾYkN ^rN]=͎ɒ*aT`[hM5Su~o=_|r9d9ǏW3BPxqҔYUS/^8XT8mzl*;=ealadqvq~5jfP߂,BRpWCF2C摌5I^S K B*4g2oTF6[u_c޷g:JȢ}`+xGܭ[jET-U]szyNsuuF|H7nk n`miہ,g n[nd9gh;QZbՒ7k[M (Zǯ_qޟg,|lYpvƾn~GG=a cvxƠFbzhAR_zfu񄶆lw%е݀#x^6}BR"ˊ~T1HHbc?xk>7ԩţwLKiYGO zF,qL[Y胠ɀ+on8YPUAzt VvdfШ74X)r xȪ(&8\,X&5uJҎriǓ  ZV5"ݠVe8Ƙb^R H'%`m9qˎMWCEfd3R Li9KhTH-5>ˏkUM IDAT ~]n`k;\pNV%ڼik&~GӐRE TZ.֐9L7/e5J{=PN%LU=J%TE(UoY4b  HYXE^R`n=SV=}ls/?N@j J̙̃lCNy)Fa5IvBǙk`i1\-ư^pvvJ3b۵8[vVA95^r}jEYU]@QTem1Vc K1S þǏNݲk;MͶ0lwV+ ?8zJE80tV 81GvVaUdJW˨/wZh\ G?G]u#eq͘hA;ys Η;9ݲOU.O!~GNUt۞|$i(C;RRpggUKt-?O̘!/օⴄ 0 DDLJ `Ԕܠr<\[VdEĂ1u\AG20GoP(t̉JZģU$)L (6Q~Ɇf i%]NKN*!Nn&hALI+NRt o0Xf2wl6{,NO(eY8)m$[BѤ{.@|^#ǠNFûH]&7Vg9p덎Y9YfY0jGsζ40:aD)EQbt#u]נ{(Z14,՜#"gKv?ٳ(ʈVyQ4I--1O!>ޤۊG_!M鐌FX̧ߤ+Vݜ|+#X ]ߋ)ʔw0bn{Ƴh]OY.WXm膁=]aFTb+V~#UՊjάƁͦ~vi$T/xv~#ESR|ىˢj0`zAyYMQZib! ˆx3F7R0c ICkqܙZƒH&_ŘFh2 V2ᡳS$)<5yݖc||w;:fF?f3V(AR,趛{r/. 6 Pu!$Q,H*QI@G RoɰBsBUidNjJ-uX#E`*e|FWQc~&n+6/@e8ZK ZcދAk je,D ՟ 9 c-UY,tRvr|!WY?ySRh2)5=i)j!zE1p%9nnX̅IJoG|^XŜoWkn6T$'lv>Dn ggK(wl^1 {<'KV~Ou,j- lQ<9jEdUU8#{CP hFF5(}?ж1GΕ1¶ZQMYNf\ϙ5VUX<&Q5&x{t<\|8gCgSBחD,G3 :ɣjE5pP`*rpA>m%=ݔng5uSႨTz{^90Hzr!<ԑ89ds^3'+qV<>HM!iH . x"IQ:gYx0:^oM5%+xCLAZDoSk(s4( K~=)vW_9iZdBMYX)m! . Fp/$z}ɂϏ~:AYF}9N# R=⥔$JؔSq<rw2JPHn`>3ye/!N7e46c4FkF>`e=}/Q;Ŝ/)o+H.uF$t7Ly|Jļt6VF(BY`k$SOk1lV,YnI#蟷(hn67fݾ6 |m u` k3*2bΓ^sm~y'&'s%8U JtMߪBsPgT<0u9[\3oGPdL1s5E3al_0ֲlvٜ}]Tܢ̲'|b:,RR܍ r|eS}R Rj%412(%LQR)D--Fzc \\zb谅O1Đp>PJkƾۛ5:Ӥ4󆾗OV 8̚*h;GH=F)v"E8n..p._ ?;DC@Ce-̻CA=0\44MCU cgOM{OSlw;dVn5L;Pƶc9;9&PDł's+f$)~p΋7P`VZRڌjsmR|d9 ?dai.p2 H|y??}.W^9)vw^:Z5RhDK+ z׳)qCҏХ[6 ŢŝN#ȤՙL" Gw{hCZ[}D$Ʉ;фv)xڼ`z2wu!T%}NJGF0jʍMD1gS]axRj z x3MDt)*Bɧ.HoV}7ש, ww\߲X8]rws/ubV?gbfE-*{]+'Y?ݐXN8 C5M5QD$HZNnZY |<ޗ0FSWU (`]2hü)!Ae5{^8?]ݮ?zq*r`bKL`]{tB`@5u3r] IDIph3/@|-uފsc ''sl6Um )rzI (nߡm suuK]-#?> RGԒY4!\5|nHk { [ yhCX~EFfY,֔98?\6,Vȴ൫%;g`1s>i)Jݽ2||wT]epbJΟ=L\zMh*ɤ(IcE 3*[.4;@$:J nyDl#8Kc͈ }D퀽B;#FMϫEECmvD'&h7z^޿$7}1f1y k:SOn95%ʤIŖ %Td8Ar\6Hā!ؑIAؒ("L%qnsN?}C.ַ}Սuv>Y,W\\j6F؜&0͞P&QsBuTE>PB,Pzk%dD\`%g/Ond78aB%p+f_DGf+^nbQãg1CF E-XBYrpilfJ)ܦqf}Du::]\|{8:nEDp,2ꦒu4u7IW\4b`ݳvEb1csy{O]#c2coX^m?vK"EQR%ݾUQrҶ-eKĂ,WH7N4e&eji"C\-4u%4kp2Nf週F]&Fя]7=v^2 <{vK/#Jx,َm?EYh$fЇeTrkc= Bn^1 ˛#^S ߌ58*UuVAF8+-d*V.9 ^F:BuHQزOW{?Nvuɜw#ASQK >* K:55^ N_wz/k,~ڧ.;6̑] .JMF{tkS>%EB}Mg ؒHu 8V=UknO8aqa`hk.W\V4uâL!ж3/ܿ6}K!Z hE^:-i@)ZԵEDwYF/ĨDEIP\3jɍJc\ vtsZKi w άJknuVIS2RԁMGu" R&!Cz+QA` ʦ'W! dճݬy9:n)s+JǮ*K"Zg7N12tbZ.7ENG@l>T\]'ݤ1BFYdV&sbs>RW9fXw,[~.px H"lw=EnƑ;ik+Fm:R;ϼ'0w!~Azw~<x]pٱCS挃< 94aQtxs8qA6q.)Z g")0ꡱׇ< 84_vrd̝yNiG2mrM7EPrb+M5UAzA#c"4<*r, !Z0)jridJ:e$kORaB]4"4|΍,/ /uF<{yc ͪ^CH8dZ/G&*nÈ*8qJVE[;|KI">p$^$f ጒ?F1v1%p}*O\߶* MRK :GFZ6gBv*9(bv|b!<8 Oy7MVM^y#2`3R;KK'vP*0S2^ { M2Z:GEMa}P9y"2)UGSF 2ݰ;(-喢(r+QIrIfz@fq_t(-it]).\Ӝ&p?J*Ӭ$*9((\D;ʺLV\yYSpgy;>»>{\˴"L0qw%1LLAYe2#E AQr2QiFDw8^EilTqrlzǦ؏^$.E.6^vX=^ra'v3F]r3`52LAFeJ'(ъK ְ=I#y M UMm y&6y9_t(V\C#% iuyNLnd"VGa̙,8}#gQɷ2`Fu(divǤ i9.aw.x>$NL#kJFaBjj"NDe Z)6ʼnBTPXQ+$OmEP#ABy= \ߏ>-qWfG*"+n`$:rnͽnަ.kv=/4r>\ Y:'Ĉ֓s !G!XLtbT?SSQ*I T EĘ(ҙxP<~ uY% S#0JYr{=.u ԵcDSЮcE=K-Mu4 8)j:IeLBo)DQZpfJ|dHI U|sm u3e>qz,,q ܸyB]Krmjڦ:akNN)B@쨛k5^y U^m▂U2@OXn<7~OڨVZT7M(1`ѡWesqd%'>W^0F~/f9Ͻ|Gc4_{vVi~Gfa3QiQ &0:9!1%l$ 1F󔹖 ]Ҭf0&Ni4ȫz&'*AwA|Ù ,H^Av=۞>ӬpT^OBB֕Ai ɍx2-IvQCA?x02spڡU2`G̘f7n;'uEVWd]G?Vad{uc_7Zr%i'~IM"׊uI;b mct܊#e"t!BB'I~ڦdƐ6R&Wh|B ATxӀ29~*4@@c:ASfгMFٖ17\],YmV ݖ. y>473)%LT\ru#6‡G넜t(ƨF9wcV(181%*4\&:ıhrMD̸nJ`m0ҥdFF]0NdY nQ m,O}?0M<741wT|%G؁R#kJu|Mq8p 9֫՚z)v;.W ]7_|v67hښ"m,x{wg^&'&`6IH?:vj'~b`pdc3a:1~v{]O?9LfQ!puuοgcGLpq9)||c5n~t쇑M?q\lv#ee&-Ef>0y((#Y)rC]Y: GF| Ǎe6/ fG)rEAL1T\15yQ>Lhn'Y)3"z4Z׫Y' mAт.?M_P ŌfjIԚv6c>oe<jͽ{y|>Kc&bފhrEJ<%irᗾQ'>¶ȍI݋-ӹi7nnŽo1ۭ7KqGt[pðr&ωͫ V+^^:(Įwad<{F <&ӚM⽳*D[Iz7Ȯ6pIVДVqQ2̠c$Պ,Ty&22mά-X418T4YQWR~kٳ{xs/^jFغ$fN_ohETtTf%FcO~z6Q$C -dh '1Dr\Kʷ(`] >E \(GcJ]. EZNNrp.F12 u)Dlb's*r5e˯ 2BgtZjR "A88r ,@= !Ԝ9K6)TFH]7qE%^n٬xz {UUr~(g G3 l*?X!@^d-} UcI y{a'_ﶎNA]?pj!RWyE /}+[ysUCQ>SmSQd0kg,8>;,2 %(|WH&nGdQ|H.((YU` d&b k$0nX6}pGoEF?~ZscQbbVY"3dJB_D`AɡM?9.V{SB랗d 56F:XU9YT~'B-,GtzĿe&Hp ".D(Y)M'c[ìMn`춄:@~$+ 0,+ԫB )TUJSZKuA~SebQ /WN 'יGB4#S0-iSQ]P*B^^]!]4FƺyYMs4[iEӴGs~dnp)7cb8kP}>Bd8LaZ(Uz:-Fuh ,:mI!1A,f.%r2*՜ ,s Ueq-6q>u#. (ar6j i:@- V5=&îJV/SiZ +aG%Y^1yv1O-7[.R]DKW#زdm!xYɂ+\fmK)ԓA ,iɋ#b7YdVT~3L) ̏}RPO-xJȲi$h7Pg ȶ7s^Ѵ eYg>~9%VYtߡ[43~&Y3#"[ûOU>㹷^J[f1u2/p@UF&Q$# Qyfb-QnםYdXTsYĨH[5ZV({XV n(Jk 6|5訡(3DGB1/4U.<౏@^JرB+fMyVYIU[*g6O +Iv27h Ar5g *[mȒ\]g'l.ODt-&e\} W ^qkAńS Ĭ?S'Hӎ0"!}(IxEȆ;<͐dG"2QQoڂĝk9p?qNkSr-iW26]y_dg>?BE7thqAlu=:klNG^t tU%5@ id7Eo D&dAV 1.P\*ȭP3hm) J)L:/@jҊ*D3>|ħ|:V~!l F}t!N(kR#I(6P(2NLώz<{h/>.Y0DyZUUFu.9J9nmvi-KQRܺs){͞-)+tve2MBjsn`V =Wk RՖ,"?KD.xɧyĝxgxs6]VYiU9YVpn9/ڜ& Y]4︀1EFiEѕ)ȍ&/,Z+ݫWu\Œٛ\ Hʤ698&^T1I"@X4|o{wJ,bjTa~n& ab! nS%QD$WSe^̈́VvWc' ADNNAB C ,1HwWR%;dҁXT 8 rJ+ܼ"+HYfS\H7-c@l֐eBdʑBT:UXDl7D5%V326TFɉ:KUR5Ő:CO%X*6ȄsY%A;+/WG&].ARWz~hސvCk{ӰYo4M4w{!(E4GG>cŽ}Jvef#7n⣟kr&~o./=vA4?3bջG_y/}/6-EYP5dz^{% ~ݼg;n '\-ɲ ^ɓ[1kC ˴551hɭUCDɘPfq "mk9ns&,rB27L"Zk0:Vh1&2 -#(U{bahʒyEUd8n-yU2D&9$^Eaiږ1LrК83lVU+SuS#ٜP10]y*S2]{p/y3(։ҋVaV( _pƇM1! خ'1!8EC*=f\ NI塻cAI/iadxҖ4~Dԑ9G~80"u]SX`)~ZEQ2[ٝ^RN,bJDWL =x1O[b8b]er.,w?,Jn<%8vc1KYW34Z#V+v;A3gXƑ5GoqOȇ_pg=ݞf}`svI~{{oT91Lmidxpb.~s=MF~"''5d~r‡0 }֗\Wl眴n|?nf(?3_ä!xGzG%43ҥ^XHD|DXQ(8gLf$"υLdV^<(|_AⷾFϹqtʢSV-7\~?IL!ʒhV"]>L&{X^t\^Mp[J!)&YnF~dٺbV8a3CTu-t,UGg57f\[݄f04 i~]?ʰ2k\P,idS-*TfqՓeփPO2DY^ds74ozT )z`lno|IDI,!`LB?&|xRIpI3`ϡ~ěgȸ]hsEE^~#čƳJw/="Nh#?n(O +"4?6#$Ϩ)3~G~EKo7BBWB I&ֶumױDp2o36aL4Z88˯Ώ8X >017M, Kqhf[7$Kb$u=.Qgˈ l%pVauc<|[t>phhԎ!=j5:7cȈ8#0{Ɯ&؈;&/ ^v=ʉbŊ(J&7c?2\w <;cQt;ZO 1bnd3pgoI(gZ-.va8O!!݃/Ym@בf5fTl;w󴋖y IAՊ(6hEd*KV`@r-&g3%:6FUفSzZ !q)51zb8 Iڃ Ô^cLN;S:sOZoQђeY󌳶j%R26'Ö%6RfjB$8%ݺ}1KIE(rU~ڨ$A c(P!D/0jTEJa2tkyY,f4UdYvg@SX4eN64^Ɏxfc' /< ;PG?); Nb=Rgc6͂S~?-]C3?ڇ OݸI7 _k5g*K*L' BEv rlڊE[%{\?кn IDAT[)!xV=7sϽ߼jO ȨM컎!ۮ ώw6 [h0=#?zdr#'ϥBLѳuɎKt)LIYݻ=idM) ǁIZE`wL78ytYghR$Ed~ZyxȩnI`Nlk+3EE7[>o!gB٢ōẄ˻H 1c QKz-+nrM!)N,f T@7 KA.hK}Op bV)sU\IwFD}}3lV{']p~uv$vkE 䙵 ݾc?) ]Tj2TSBKȱCE^Ryq@"/+g翔 ME )1l#I !r;P4^0&=B)ba'j$x{j-?yǭEd̨Ff5jzFj]QV963Ⱦuiڊxp1=4.=/y;%"ɥhBߖ-pȼ-{]8_WsL^Ã9wH#.0Zc;QrtK,?_`eY!LfoPyAs|ʏߠRگS^~eqbNT6p914yUb3_G {ϲU3/Jvc,1D~ zaCY eя~R+rzu:}wӞ{xh;{/b4fZ2UvGnwlst x=Zc LFw]O(9ͬ|CxңBF;1#ni7;V,'J5gggv:CFLa%%=ĈP4ks  i;P7ȸMS2AH1G~bڍL~$x|`Xe(0Nn'兄 .YJas@(T|/z l]Q%a ԧ EI>x0VBzG7"gd1C{G^e4u~#5,*<8gzPZ9'SQIQ$CeUpKO`GRz_( J砈MG >i/'0t9~y3n>M5qqzOh mے#zІLIڷ4Z{*e6 Ĕls#GڪB x@6j;p@8TSt1` %O,!8tljh$U" Bfj9F"-ćH Ϋ4%n7W,WyɧXs ''Gt=6˙-[-jNO({zI^q̍ͷW+֫-=%ycX,X^ׄ8_by87\\(r Zs%mUs6{O4U:͞=I4ν%x![{U<Ϩ >=e~t_gΈ_9֗w)łqrmQS?PF)~~a 85)e~)Dl yw.O}ߧPe//^~D.5QeT&kg,p0<~V9!*ymĖR|ν,DtcrqՒgȸex?ҫݒ{$-_@"7NOX]\l?^;Gh2"q[r_q=^|5vdVaJ9g3a3IQR!;V[ڇw CIvETh=O2,N*vv˸($282.;ʦ`TzGM45yKi)Z{!C#543NV$TQ.XrM#  ) S4tU)kt1!F㩚 *Wk0ڼ,Jj '#/z}Qt9q FCL6 @5CϦ%;ISE2^o˓'%dB:XNp;ln1%\';pd="s"rG뮁|Y*XoLqc U Kpɗ'K ޾@* ƃD˯~tt|+I@\QI2kdpCE"X*kFRH-3OrȈ*Yd($TJ dڌAaFGz>u(| ) %/[R7%&1d{2/y`sPW nÀV%Dw kH%F4K$~FbS\]NtԔ$Lx)r:^R <.2hueY 1|ٞҜ7LH1ͩʒ C mtr"{H!Idr? Sad~ }`ϯ@|M_) bl-[dM?|~pdSfӏ\|tug7{Q ~n9vIE(!^h|pA)A7҄Uk40G 1u$Q$a@KER.H9+H"tʪDoCW-a  \"Z&\ѓ~fWF!$1P5*9#)4qcʴ,(B(ʢ(11H-)D!Ȳ]\]KMY2U0y"i81\Du`j|Џ88y?Lˇ2́=['@HI{%g3 edW ГvGCynobs~.Ǒݖ@Fi=) ڶN|FXp> ⌓{no6qdZ[`bh8?!z'E&V/{Mwot`YI z8i  V*E\Tu5O<9'9UUes{YSlkBߑƎ,8((EÖaG.J2-xx|UL|)ocn#7°8=,K|H(!w~F''~˻;0{Ǜ_yNgH0`L~~폶Ac;HI:d+Zd>[p_K 9jrU*Ӏ}_}Q@F0;Ecx\/p~?d@OT Np}BILȊS@@Tmg +B e,'e " wPE>,`A +E׹4:?0RBƌ++D UtoZ|s_1!tR)AUWH[O lǶ"@YDkqsdZ\ _R@6'WpD{_ԐlĶ6It2wQ@p\-M)坐sl}?kW` }Gwe9Wθes#$Xs;[vǘu|(K"Q!C}j-)bڱ~1ΧOx1&/?3,tpwJ#i*Z~ً_-#.E#$d}Y,gbia9|]_<Δ$Z} Ed֬K%$GO?m;Qק3" H1RKp$$:% J\d!k~'<F̽"]7_%dJ\e4TsnoǞlFk_odna^ݑ"|7ySp7 vů] #zWKڬxP4!1+ʍؾzo߻7;$>"Ш6PW+"A]z{)Dx|o7) GT r"H?/a@r$$ t~(tsH@ʗ%[%n y汣 %2Ø~h5,"DH4ƀ=:S=BLB+((RDl#e_^(bL$mMe4(p6|ZLa# ړC+|=1E-9nǑSVl.Ml飯.x4܆WOoG޿|Vw Ci!PjɄ#XJ7V"sޕ N1:n҆f3d ǰhE8orm(8r{7M#10!LW[ׄaJ|ș)MZ,(-L';b̘I J=frKfҎmW 9K@s{1)}:ISh!uSʣǘǩ;4bBxRL A*I(BtOo+泬>bTvzcN-ig5EYƑatf 7wl6;fg5m+:ww[M) 9t.`5EeU0 uɬ9:T|厉Fi/ )0Z )RyKΩiZgi/yI(Ԋ0!q)<'GD>ǘ bF^z#ّ7 ,fKoy/wv/Ɠ7xoGOX63Mo ɣӇw~9Ly.ysd.Q)M~'V'*ι߮xmyW/C\䮔V|p?wi=^V/愘8Y+Oxz1*3"RSiMVvĹ;OahUm.'-ռF,*ّ"X#irE)}I5c|#=D`D1H1#ZHިRĪ@WrGjB&AٔY#HH!I1,!MH/F`t9|dZ%Gr"?PnXNiϿx1: Ԧa3nyxYQsǟL,^KaK!^8]SNùL}Fm, c\֒ ίbxpvH.1pس],4)E5ZKcaSCp))Mmrœ҇8Jat>O|Jghc^Do.1H OҊj@ė`d>b^8tDI"bng.-[NҶ23.(~嚲Y-,fs Eǁݱc۱e iӶhRQV-MSb`;}`Z`q}}GU7,WsRۻMS3_Q:?SH+˒*K΢2.ҏ5MSSUReMZKw@ a8opTF%֏ 1m: %${d  NVKfuE6TM"cDXjM~=K./LسI{th*"S8pE|Yqvfy;vl59Y&P #-}GubW !T&M>'Kۻ vtԳ8ȧ|nwKfF䏋!$'>~_{k6k!$E1*Ϯ>Dc2c{^Ozzˊ*̯֙FM'4T܍YTuۅ&StyJxISa7:ccCts=>y F%r~v$GvtQ->86Ow||wwW軑o5Ɏ3Hrxթ)rt]gu;t!&loR!wg5Îm سŊzƘԊ[#he(L(5(a)9=}?bF8l6{ w].%JIv#k-'J^Hy]bL2YkMYf̕c1Ә3EbP $aU*/ y_bE/ILI7D*ύm9UAU䨐0uypq$ (ꆋ NNWyh3 Gv='FJpc& =] V91Fv=I5EnÏ>#FucZe\͗Oޚ>;p* JMuۇ4ZKHȢmSO#rX5ٜ*QZ* bRr -vHm1) MU0UX,jo@[7h-?O9 !uOdeSzďsl?*I$u,>>n`wç7Wnw$ɬ5#ym" k%P :I#O\RsZ|~K[M9L!z{vE[mi21Hy! zǏSx{Cs=QNqC2GD=}x3|y|FuxMqqY-P Ϟ`1P-Zb xgQm!!B"ڈ4J`C#yR2]j1R/+0 J[?DN/iFj$PE}A]ewG\O{h QklgINuEX@}RiR g9?P0 ۻEe MS szձ߲^0H`{3CwMM)EZ&5SD_5'''UN8Vl6) !SEeAUtCns:RIdz;ڳOL";]"& -Ԗ)B,dM AC5ˆV(b`4IakOAHK R{a h8eqJ:ߔEN uBQU=O=C #|b`6k%{ fߣ4匢0lﶸ8wN]@ Ĕ[OX͞o) Z_֔"U(Ǯ'9u0ZG(ђa{wN%O~򍰭ˬѨk,/HAi crj I򁮷:~`lVqѶ%cqӹ)6LuѦdV7WDy3ÏD=ìi! a~sas=}E#竆T)`Y e!:vÑϨnhVyhnݞYaU%o{7/=5'ܯ<>9;9 ?xG7vpAJtEKI=F1A v=#Vp/gԥrQIŊ#h#M[a&vi "iEQS{;u0 (~-洨w٣oab:,) M_什&%S8GQ,8|MgRәmGfDi4eH)1k$O_{oEAAlEY^9;YCv C&6]U @ߍf)Ւ7)Ւ-ϯ9{fmop'o Ŝa!1w8BoLa:GKD95lܩpsF) 4Hc1]5S %!`GKp1qGݑHj* )X/g[b()-wm%:XU%EZSu0jwHҪe,jʪ(5n88GR7ڦB7gDBH+9=a9t{шBKti痿 ш$E[_!Lbq9\?a88:cF4B$tip')[ d$~[܏OGO;쏏w GÖ<|5_돘} 'hbUK-}F)E9F? }6R` ьvP4FI6"KPQbO]h >06x̻a)1[.QEݾØY3x8.]S:0,9C?cFsCVX6U6 uM1UC3*XDT%M őe9)?a9_\|@S>kg%E!ÔӊØy !Zک2xfGQ9W詷`ݎώz^S)Xg4u&]uë_kO#Jo Œ{M ]||(-Ok?zĢw3 fFHk_l{BReVHDxx%J> us$)|zȎ97[Ӭ3P6!k(^=öG&. ԸT-Ѧ" e.GK3o eUP/`2)˖V ]SW*w攖/,(H\QJQ3%,Ͳ( g ] aG6кSaG:)VSRx5BfqY4,D U"EIF .;' l.x3' ? W^9ḻ˵CGɊe{aZK"Pad6k( HY;ur[K?D2 mf,!D9} %pq>SBAa`wxFnzJIJDWdx^@ bŔ4 %? $ټȉ"rGe|1BH*qxhۖX,g,4z_pssG ~JGuTS]kDɷ`]f3泖߾#7_{締c9t#uX9ռEJvhE;)@7z4#4ɎT/veu|-]0EohRUUWF줪D&8?[roRU޷SVeI;kwk]~ ʟGђ.d-RRI]HHǾg3:DZY͹w~gK#0F>Y0_XjYXHB!%(!1يHYŮ0 ͬ#ۛKTȪe#EEEYf5Q[DQ3oJA(fF`yuA>Eh-ԚJ RKvP )#JA--291t; ;HY7-2@r ~) ںښ}^ޫ\N"fd"FER7~j@Iwϑ1!}CߡUbʌ{ >|O,"xdLKȈ?ĭ~}t?*|oI4yzś0ݧ| )[ -Я+<97^n|72I٧&uʄ dN/LYNH.xccִ,5n{c.2Wz,E6{qkeꂓo^9oSޯ. T-s=bR+>RRr foR~ٔH'O#n"XPK MO)-s!! L\6N,G^ϕηގ#C VLƻ!] 6#@̞,˂J~ JU?iDXs;JUbCЦ%)ެɲ<{ָsNUP8v#ܷw#m[vG;I2L{Z/] `QQ(2O4غII3V+)kCNAFޣJRKѻ$0C1{sp;l;v7G{Hq"Sk$[0jϦu]qykafșij89]V fc=0Ee~ѳ~c}z/WMwݯo.́bxן=oQx-]ioFUѿc|p$.V [/r^r񣁱^"䏾id?77C"]M"(&ʻ#8:UMu~ u+)/NN|B:;mHKnɔbTKh,Նjq?korշp~MTggԫ癊y$,(ݐ˄ W4L#-e Z h7cqI(|Ӟ0 3!Lj 6x7;> {YF0"fhu|Hv6kn3=a__Yb\Ymja?c\Zʋ3&H!>>+/9JYIٻ aR/x298ş= F٣WZyo9*p d*J:9D|`Vxg%{stmC򡕈cqjӶ,+} %eo2zKuZvOq!]s{tj_eLckrN&ShP9ʅvtF!3'IM8<"8⃔m+qTUsq!8Epu#j&-';v|z;k"ai(7֗(f/?G[Vu+y$frpl:N]~Uusq珿1b8O"$$L$^2}sSG~PE!w>f3kh_}{}}[Λoí^]7 L}EiL6"7)~ &J_W7TjUװɐ=a>`GKjrJ#<9R0 L 3JKRH:0y\")DǤGý.ssS5<nXng+#G nO7wBP$vOպcYs}%fiI{{,^;LJa._jΆxNƓB,l^ؑm%N1b)eYfҢH!1DJ0v sxC̉)FJxkk)cYu+8Le!^ Rf9l֬ c' 0Ō=S6dy( t~'P[Ez a)ƒ78ceq$3iIZ#eբ_y'jA b${1fgJMQI^RioǺUM QE*(CXndw#á_oX(Fۓ-mô]R&Up3rQtLTuΔHIG9Iڰ}z%PCrƆS Vlf$_G{o4W!s&3Cw'_BdVc&s'Ly a7O[sy)2ωq |8\t]iEI2B8@nLU;O Y,h٫$K1%81jb($ZnmZrLLD4U-u L$@僛Sd lCS"S8)9HH|fq4qFlHP7 1E)rv8Ev#77|{sIٜ`QU !FmԳ @h6ƛ_ WHLN٬ iPVq>ÌB( L!  dž'=}a:0\*í[Ɠ\)FuBA/6_C kO3=~G =%41$(Kyw^CB50LF.ըsCr+i ?yp/ZQ|S{Ք;@gq&nOw~ 9`Y?"'簮b3sqy!?SFͰ_PF؂%&°1~V9ae2f<9c2XQ*4 JpN2jez !{#WkV=gt8.ƼKl)\-Oo+-noOǓzǥ%2.+埓?s3F uʉ}QK+\2YM3hNvZV<'ܹuR| CO. =9/89gBiIz:I ) ɛu53Hrh'K>L<܍|z3ٶg7Qx甦rcfjo(Jh'!dm,ˮVgqbf bt,X4'gxI1TifUׅH B%#iΉi"_ rx=c _u|:Q#|:z)2=K5*6^J!Eu8>$EU5|_` S_YqPs_|]xcn7:%.١cb*_)8r"g4sa3h ok c*yXLeb /"M.`,77{NiG#?Fi mc7XkxO2kϓ'O'nݾ\'@Uc?0=uq8iTUTa&"U,WdJ`#gmb9M[+CNOXW?3?C|B)7$.P& ɯ`K<^8WLpc\Rɉ+N7y48L8n;I'"1Q<5g̓]o*Zg;N%sڶbwЏOb c %yNeb5JQ;00fEe)q4nk%ai ;K54m6]?Y$ok)g2^OU{Ro0JG (?;Ud[݁9ך#(CG|eqسWW$P[֋Ā3]ZͺZaWji}4)x`wXpPbFM1VdT& B>mٿx3lC)kfsΨ^8e ܩ%JP g(5qT2|J:\[<~ijMў_ҿAFQ#$v>Pwt]TTdF- -gPe$Eʕ$Bd(-}_-Z;;T^:u%ګp֢єRe!_y)8=YB`=`ejlVElL#p$DnV 3lwRTM֚9EE!E6<)ܻ"yh+EB f^(43zDBj"%VM]F~-_{3ͺ10M2\׵|j ~*&,9/YJҙ0㛖8pNbdfgXh1dm8'U2S#yMK#QFMI) BdyJ-jBhjn(7N-B)9FyyœV9+v*'EǁkHNBe0Zg+NOO%C qN @*C*EhEѬ]0b 2)dv/p~/n8+?="ܙ,iqhW[9e E 02=zB5ĸ;Ǟ)z+fTdlH[bp֞| 1Ç_@V`^3{a RK 7ڈ#y .ghDΚ_ q[]BHĊPnUK'5+1J:S/eb'i 3n_^ԕ8RDf׶ԵK3_8C{>pwu^\|*k k !s0д 7~$j B0r[#B()CV\_? JaPV!-J)[ ^ӌwq-PςKIjy)yh%r2QX籶™{fɞl`kk+!<uEGRP_ s3cYzo51f|ɯ5;|UZ9ihk-''ںb$RdGǞyo?(_}䂳NY-a٬WBd/0aLsd)p28oK<{T0DŽ5ZK!i[7g94lDbD8D1$\SmIV ]B&,[5 Z8 jQx-:tk1Nn*\'I\|Pd.YJƕ%4FB )j)ᜣBQVdF8^ȱP b`1%YvPBb5Y 4g" %I![|6as4w|$anwK'խwCߩ)6Ókg8r_]4FO=yHd#MVQdxPp/~i5޾Ǘ]I*aI^|s׹[p9_ɑ/t[A i8lTzF_Cuoao-kUlmČ-nq9]P3|$HhRXGNj/ޡm%OݏXqyyFU{r Ĝ^P9C!ykO)0M3@TUmV8"MqHz1k-ۛ=d5M8 LC<8'}i񾢮/2zjO+,h9עXҋ@SsB%#4c@v#.O|ª!gK" R䆂k[Q?SiDd {Vc4ðb8 i:|8=pyq2^~[)kx=bW/\ 8N3Ĕ58Ǚ3fpF9iGAƑ~xJ)HDeDGpx0 w p3ct5bH8w3(e5?_7v<2 d)Q_??'N;J=W3?͈NaS(,\`q/ۨrQE҈ U+ib4~p|Ж5eH/P . ^:;tUkcRʐfA965b.ZaK3ѕF0Z$$imXr ջhJ^O.EW#PPDFcHH1"DP; z:]ۆY.T{o_Ae6__˰P8gx7y7⽿O>xʘ+ K#)Jp!1L!^l^o/5E46()4RĎyzy m+??_x[ko)/W5ϽV/3“G[eхG?ƺnݰZ0Z%S.ㇱ1V ;c&۵PAHJbSS5Qq/acsµRi0,@ MdGFk 1:-A6<(ˆN*ނkČk*6'm UmEU-,Y48G5 DVJʣ%q4t"}0MdT U hNO7}?x9x+,3S-"=U@iqM!` ɕZ%(3hVDrdY\RJaVk6秔,qFⓚ@D㼧q9Da_q-8C#ʐ4BH14j˦vLDHM?c)1fq)#9)Ota>XRaAF-HUyy88 .-Ek'X4$0i&&=[ݚ9.VѬv+!HGvSeLTSSjO㭈st&Oe !Xc%l5+~Ħ$=nB^9Jg%vo+/ p#I03g]E΁4$ʓr+F<^9pyR"L#a+G#jDzp`x0= i$".iygTmR}ßn`oi EY#|D#:U"F xenqWTqfX')j9, Un5Dk/a (4%::M OnZUa?s~mt ڞ\MJ#8>b8|<0ekz0!My5.nQ9-^z;~w?߁.z%qLr Պ8G2_Z+BDi q _Jr0*䥤*)Qj5bJF'A.e)4TV\wl֕Bފ;%q\tUhLWBoi3~wDksmfӢSN=f114Kpvզ{ /IJV@x6]ERy hyz'0ښ<v A-)%NidUˁQ ,iU qؽ*2\J< :JL࡟3lV#A2Sh xOʡ|'f-ZUzmeݬ:crzEA(/o![#~wH  ZVr%C8yeca$L?2Pc!fݖ󻷙xq>jBjB-g } [u$Uw DeZTZ8EnjTPc=1^(d\V 9$^LT]-Ezc0{A9gQHs&A5)dS~YVm^3z D9R؞NZuT2Eg2Վ3o2To2:[}jC3M3$ɔ0 ⸧6+q/>9u9Kƙ(XÇ}Tw9žHo77b-?ØJ 8|z&gs5woqU+|\ÿ7 ov>=O'#6)O3z.:G:s^sv%m=MV4M%Ȫqf!L#w]e}cǁ"UmX%ݤmx"H~9.YOYbTo-][M\GV+Ѱ󊓓zf,uY>Rg-ha294|hCKPD1&qʪV)lwG&BJlNO/_WBk2؏W}?q<ԕJ)HP{O ۊȋN8K|V51EZ֢rday],TMC)j)55ZZxp1hk%LUc6T*3 GZrNr4kc\mGꦂ]EE6hCtTUM cP 6g+;^Rb$k/:89k/\Օ+[tDs(Bd ]U[YJɳR9Dmk3oK6[Cb0@kR$)YKZ<{ MY&?_bbޣgğΘ )rZ .i.\R°;4n76 cbZ&tgGJAKgzw_p_􋯍ǙEk^.v i E|cYJkM1F9S&feAUv}vT3s'[¦PV̞yN̥5NÈ8UTKQ e!Don4rZ,GGn+ 3fJsmq%c(ܞuqc%39 ~p}d , *o 9eHq lz1"+L" Kjx;2g>}(M+=a`䃨\sW1^s5Ew j"hVͶh(QkibqFc)[UnqIEbPw8@k鎛SIcdݢs]BMh͒sxb(QN~p'|g*y7~׋PT aPDžtȨj-qF-3g^ |$&j2bkoi bx;E:ePuA0Q(}rkbV߼x*vO(O-Ԡg[nPFwKb>N2j"S|c_NU7@-Grt|Gs 9C72jKi8{"d}KHm9Zd7)Pcuf@{ϰ.u+ R<'+(9'Yq{=$e Y st[̠(Q9M@)nnFb]He=MOґn7[m"v(#Vݹ'#AϤ X(UKa3:Q*Hzj،Ǣ>HZh@{Ns7tKPZZ\0/u=7_gym4@j34j)JSSjYhkV[[y9kEtr'u>0=)EJʌDwܹsA1 yٞn & s.Y.,Cs=pyuHZ8MD}5bZM^G[J+KUe i)t:q'\z4Zۛtb^VXZc9F 1BGYeP¯J&A)%<%€w8\]9+NNV1 Yyޢ9/sBLP)r{DTmBw6ZӢKFCA/Y}hK4Fh̥&&$3dՔEZ'5M),R9f MԤU$[c&g<aL+Rg1zOY}.Y~K)S"6L:M d^~ϯJJ~N\"Ӂ&(\6հ^S&H7Hן[V4)4Z9(Q1>6_GKj(}n =@o|![!a/Ya/OoH -+Jxq)=h8fy*'^i+pz-oFk;AW'rgHmQ@K#)jS(iMqWW9G<NIR oA 4To~ٯpṙy=γ}^Ͼy힫Gܐj3_*3._tʷK煖RECpRح@Kw3 ,!HAV4l6>8LӲZ}םpT~s8Ybf(~+/Vۚ$JYwJ װc]'ੵ1ϳ{# Uf2s~p<1Dz H(ZRhZJbc$JgvUz6*uAH FZ")Fqa.,5.LU:ηJf.I4M3qA+Gŕ*N)Ai{v7ps=7V5J?˳/537q {,YN?#~|l.W1VvgOi!89m)V8F[ZV,G~fF,xQ)Z>xg i+P{w{^I\6h-2RDI}~*^uwܯ_VbЍ+2K*iQJ6QOZZ-se|(jpOӌ:ƅ1eʊ{N>q60ڢdZc<ªFKit+6[ccn;<֔"PRRW~xzyYy8\nj3t8'І?^'ï?/Wx{fIn46=X]N:?Vp-m9|o?ꝯs{P:RLWB{3y/m_ <` ?QH7]h4Qv%eg8"wz5P¡x*cJlpw>z:lp2Kvofh-ß՛bM g|rvf#gnv{G>'~m;딜6=F0cURZ y!Ret<\_ݠ Pbto7~ք`:{(?K/hvjFpuMIuZ"[u5.8jKKUEZ]咫MH,€;ͼ7b@N\Ƙue"][̒)T! ll53&\?ୂ\H_Sa+qa>>"N#Kg 缥 bf);c*jL-~{٦[^2 K2LdW;ɺ3Vc"IP(@Z?Z*q R>.K8EXE&S\o=iYHT$k:QK#D3x;wN3A7.K3I2[k^SY͟:%ـ5b58bD{OmXuɂ'j%Kn%'*Oh^V$xoѴ"Sk Z(sZ/,k[[(%E-Ή4Mb3tAvZ 7o_9Kwzyaa$N'jKE|Ow9뷾׮oGeXHβ̵PKcUaɅ2Ԫ2rC i$aR3lwJk8ryY4 xIVvEQ̫ۧ+MߣQNe@yIU(6Z-"af9ròdfU a&ՆMv5{<\ޢe'JL51T$Uۈ ALEF7%6ap}Cb{/ӔP%B[ xlYQ|atu fc-%'_E9zo0UX5 ,I #hˣ°=eƔ&6DPj ǜ$5FFԴf(CӲ/KĬtB7Y[ nJ6SR fePHjQOr8u(g?'/dF Q%͙xUy0k'+sZ 9W*g:64cZ JOx36oYƉy<ÚҞZV~ K.ADr02 ݆62r!/2=]?p{uIJEc4h%]ǞOLw8=&&_⭜ "Vn8h,j?Ao@CZ+ֆKJ3BΞ%# =gϡc=Mq)TGhM4r~}|_]$^;pN+\[Zs{shF#6R Wθs-?KʲЪdG_,yU[N_ >ŏ|>qO'__M^og3ycJ,;_kB[V&4Fr CQD TJcAj8Z@?xB/1 _4:,BEU[mT%6MBV%n7a:*pFqZV½^IUFc9RJ+iMBkL/[0/CGzs[ =ۉB+qDlY3Oq6Xjbb?l(9s:MVka;tg<Hƪhpss<ѦH]\7TĢ)FbߓrŖ$TE)SX#*e4f5$D/Tڜqa;eTeP`Ѓ,S)bHkتqCчxOBqSΨZV IƟ{.x}6EM@{Nh6JLZ2- gJq7ѩXƙD՛^+ ل󨜮op6M2/I0e{28+\fI $Q̺4G]4s. |A[Zq7X1C%FDz[UVJ=,T!t:`0${ܴcCOwXF)=(qSp35Gyf)U洂Q if:\3j0O5a}O}W~?_/ /~9e&~_ 3K̜bϯ|鋫0_Zk_?|g?4ݿ:ח(`%̉i/wy5W\oM̅%~ߔ5G~ &oͿSG3-nT 5`CHBSĘ|lk g[LU-CV9b;IH1SjprMLQe! 4s…5oCgż,p7IH9Pw^2FFQ~x˅kU[jP!)% =/QFceh ~r;QӔ&› .\@;Q]]>`jX(ܜSfZʚ&<%%j{"0VR&tZ t1GJN%~ ve=o V2ԒІ5&Fv^ՔQzv:{/*V>m8&P6\*?2,!}'0ۜnn09Qd*JS֧[Z;c<J:b\b쑾8g}4 86 IDAT elFNGGU=y3xf/ӍiBgH7wo1 #>~:_Q$2vR\~4w臉ãɷnm_ >Ͽ3eYV2 #]oʟKWּ&~?6cjiOolI 92SN}dQC1{1JژQs{, ڋ%Tg[heJwRBƮ-tU[TK_ۯ#"f`5Z^>5PQEnY)]), qYIj%<((q9U\.e'6X6U8xeſ+Ym tmk#xgEtQ\!'TM蒰geTsyCo58E֢VK+{ѽcll+.J:锘n҉WKy˾I8NtcѮ[pPw!ĨC$Dj%D)-|X!4AuU$1GVM}{2 $T|eYAbBo3-$rHI[{DFg:9iZCJw=zi:Sj4%Mfoؖ_ 5+ 1ETJBT02)⛋%5tΑ%+8CN).“* -M\7>L"PgGeޏG^uz-\[Ûɴe+6* gi_1&=dBɥ9+cqx8kaTPuc"Z`(\ :Jx3$#?${* L*fiKBSb7-PqcϞwʒND&5@ 1%JL=N+mGȃ$M16}\w9Mjb)gzԊZёm3ؓtǑ+X| UpG؟X{2]?ЙK|`< <1gD*W-lNfYdR o-鲵vmomv&TǑ3z?Pb_WضUhk`6on:I7vRL x.1t pw{&^.WP'OE̴m,]w]˚63#-KОqձmzj:?r)iΣhǶnC[j WĸgSfhr͉}1PfV|}_=]JQXם 2 |^O?e]>z<_~y%ai Dz:hR ˒'?x冀bKK=|:r88Oɓ'8'ҼtS 2~׿m䇿=g軎Qsdۂ2JbrgRFVV!\J*BwR+UIgz#)S'c J+8'L4|W\.WT'9%D 9ey("=BJ$"g'}OJ=$Ro}!LBq} '[7"LH1Z7 =%c\Fʞ {en Y q ,>#r,0Ny#Q \~AA.`mOYDHuO5tfVA̺n,N-N(ShmQu|~6\MwXὡDg (umxI|Te1VmWϩFg>~srxw~?{?g?O#?SOK>G>3˟u̕yџxqnB~e~D@4ƁqZQ+jB _?&VZBի%x<Y⛛<1!F!lGsAt<@#ۺ_iT Ԓ$ ޢĜ))YNgP"g88Kd ?N|>f-u,ȍ>#hq%/NGOo $Z2F,)XNإKa[nGlF1F W.L̚2"y/L!,7fM(js{칹G|J(T:ѽ#l C.TbT(hq۷&Zy`;˺cp6 O붒DwhFݔ) QԒ9s +;m]gXucy*V8L}" eˠ=o[v=|:p!)QL3aX{5M̗{m=yL/ǙVԡVY]#5|L?8w=)V\lKdI%B*-ʒcȲOԯ ,NJ3v%JJ?N=y"fNHWycj G;η'Or:q_QFVNT)PqnnoD O˳Xc%utDeb#Fk?CHcm)-L<5~[n#Glvh^ VHckV.{SnE8|̭ZZR>Ǟ*ARSDEi7 (8A S1IT1ˡ+YYNR RxMXM:*|Žk ƾEwhd*j/ߡd BL}S@Z,`:/c T uaQH阊B,o==Ǐ$yOFX鈕0$c)~ pZ C QtGgcʛ ߮J^3ID+ZZ:o#/мv!1xCRZl֖p@i9tshmiG#" 5FmQh]qz|҆_*DenOk`gUZ ۷{ū87tcsxj6y[ {20o/,` %AH-*B $Q᡼` T4)a(\@Ǖ=b/{e "H9xlĺ27F.,dR2^m>PTEv{>qec2E1F̑ju j#癚3]g0bFJd9Xxxa=~m[Eh32h 81:'8 ){U*dz8~8,W=(9R)lF+%+<1˟_%>?g~//ky~_?gg9FNKuЏ4oPkDˤno[~|Qt}pI$~Va^dV!m2!k*":^,75Ld@U,ˌwL#'L"R-hE:'e|$쉇o~}ų" τ1V=lQnJ N7YtoJz0m*#9R@-d r3Rr_Ga+RhWx+_x}5ыWfv\a$wJHa#)3L-6vߋyRDn[5$[3u"u:/ uިZRǓxr.\\î4{LH;Jؒ2Hw,cصK"KUҋc-P4)#U`رjl{UiLQD3U5#LM%)I30B/rYӄ^hmۃZ^u}rDE|A*,G{¾RbBUEnv^KfoEjV3Mg0qiԑJ7N_E.;UWlV^-1K*E\Vcq:0_.wX-}LzS'LJ^ڳ7ȳϘ<{KYDY.)Ѱ(zF$"*b0V3N!׌u[BIr) $S aM.e&La"Xp,+~~~_ٟ׌^hr­MKQQS\XWQC0旿 muqOWkZi=قKSRRۭ3bȥFa#4#|(R)2l*.$F+%(4qƎ&YaM͵RxZ*ǩ7"ނ 2M@c=} YpFc)DUn?[pepHJs:B(ldq3]W1_̒ۍ)8UKR~:nx4B!oA(i<~|8( EU(CeOQ 1WTE裚0hhؖcPFaˉLװnJ(e9'wgц"\X5f'HZ1c&(JkPb>T%}ѕȼXE2q޵}ȱp2 `xs=e_{BϾLx!4rn+!EjTƲo+3t̲,zc?IfU,!AD@rD-E#37/ĭ czrRZe$-ư3Zg\'7qԄ-HD3J܍宦ĶoNrQJFNhM tJ2Zx>{9,9 !lL#j- $>̾X 6Ur([]3؍X <\xcT/0*u )#h]ۺRb Ǚ클_ٗ{~qwyWe!ia*mr:QcZ[@ʯqhk,0|W ֊MnYhUBSY˲񟳆q)7ߖfkAYGnJ#XM!m zhkJJ;.QnϗG:K~XZ{ cm)LYv+beGK'VA7%$s h>{sEA`9A9/.wrepֈ)wp_7tӵUnpN5dkRFFoYP5֔)ɘu!$/& IDATDP9Sb VY\U E7q+rAgRh-Qqm]RхpIS)szs"pBʂRj~7H)8UBJ%!:$kJ*'*pE )fu}\ Z~f]frL?{'o6>{k=7',PvnϷ !Lcm߹,/9{bE]$N$ QUГEL@ S+}MİI1ur^ET9#0pyg[#>td[¨u~|'sZJXcd/d{#ĚN7O]&=b^^/ĸph#/삩sNM&H)r]"*Ɖ(R2DVu cK,9'gR2$<\grQa[WeC "㾔p*2f+g8hŪѯbO[ Rg=e*RDkIqb:(8Ę:_+9D">8E9u;Z,|ډgPl%e=mg e ޼Cp,g:IȎ+C+ãGb`ݢ(y]) Lgz?IFEIl0oyvXWp xZkӀ7qqce( hNVX=;vy)(cge\N?x:oQmBb%PV$Dd!XeA^q,:yj&ҥC߼|m,ZJQxg8L`RdI !`uoa'R XyQ( 9]qƐZUʕoa@uc7ׅpxPZ?{i`E%LjixVXΧ ${aXWyHkqwwg=~[O$F-)s! UF֚4XvֿgZaup7toX1|Θ&°2V* c3&|epit^l!J""4@8tĬ̺'B/ۃ7G[$l29 959:g(Nn0R JڹΛt\MJE>%&#5U6'-MNmۨZ:ѱ%헌 ѯEcYn1 ."D=a$@\.\^S0k IԖxserSHrVPC[-ǒHU|hJT+C70)Уcsi>C*š!Ϛ*57!Mհ;hEI` ם8$ӏH"VJ-9 {>ՄI!2tcGe 4dP[dlG*si0NY\{}x< qpu^)IjFL-jQTd+"yn+EUh&sGQ8EY"9+Pe*T1ˊ$5!Sk=hC~E]ZtޣsDI9b'8$QcJ0k]eZ5(R(YYhNpeƐHmR4Du%EB-$(/*SƩ5uAktӶw:i`Fk `B2&^ݯc/ΎWoDT!+yEkt| Y[maZIض1㭥z&3;'͈ZQwO#]g 1NTi] RNTT?Wtp__c{u:NS5QJ9YǢL^7\;˱ k Cƨ]s&0 ؝9u-@j&FBqTM^aGϊX&j)G&uE"Z%BGZH7׋ReYLʑڔTah4+ZiN,b%yvwd:v=]/>2^NQ֌M(ed~8tJuN5Bߋ7<_0wEF ΢+%psJ-C2JLRT7kd7Z~/^q؏tŸTCL~#EXY~y ؄ &-vS$,ne5!8R-cOgܼ:w/#J0xBl)g`L>|~"o:7rX-/ ]q웶"ڛ&o\ dss<i]Kl4 @.JFq1-9Vbi[,|j6Z+v3]Sm״JTҌcG7tCEQJf&0ƈC˛*^ZjE j8p7, F]|,%fLQVveY7K*2Nr0uTYtCz /nNb(l7!kEp=zc`3fGĖZTSIUJL/^9хNH+9Rrsg좋dbeTN{|̋1ZEB?h*B"@xFyJ|wůB Rv~QHxh02WZSQR>%Ei- nћ+9L3iげ3{ID)IޝI-E*V2gG^ܾDD΍yl1;ro99VlN'F,Ja^QS5NYL0٦07uo$29~_2ϧ3{]{Om{݁Vm[ڜOZ@ZV~pn׉+&[n7lFW=pzq=۲{ w 8MZAo#Z*!Ǵٷ!4 *fBIEܞ1h'.!)JR4efh9% rM9b5 C2fb)uG%}N,M,ƺElek}O΍n7n"WqV)U9ŕeɢg NƱ.?D5"ֆݮc{!۔’sCCR[~ӌRuY'('VdVՆv0H[YeԒ9v l -+(r-Vff4,C[ݔ-2O50;+^`:3$4)o5U|4{v=BF )&aD;E͙a?r;@.TR*7l3}`M VjJ-9|z18hJ^5r0`,--Z,cRKb6EiK-Maw\rYD.n8,StJh]QZD%Ӆ~c۷"0O,7@mk*8@%UHqʜmrc)ҚX:Iܠ&R-wGҺsUwXℳm58i$=뜘D/ӘOLwA+EZWN'j) XyHZb.ttnҥʺf>ĸFr%Ta4Q̗iD9y_䧟xgDFO\pq؏xo8m,{yCp7ga"+E*rY| ˯Xݍ&x,7_ga6jT)˅IDAT'\urX&9Wȭ>7e .1#ļrZ.n{r,q[:6a]V"IJ3Rܷ%{+#:#lL"yVC8eTُL|XnMkv(~1Qcb`¨F*r 윑ωX^W1ᕨ߽7$U YQP Cȹ$oJ] 8qOU4Eyb/f5e֘(2j`79Rމ5 "M4#KGAP=֜qX3m],Fb]Fc vDK 4l&]wym1g.)J|.値ItqMYmc]央b׍/FQnhD2 H.,:4LeabiLSd = C7GvÞV*}oBqj5NN~41MTn,ehY4"dat :drRf%-E`P00Ztgneߤ5벐JgiÛJHiR)6bW8oPM=VZ+Ԛ)M3!oHVtIWvޣWjLI6(p!ԖFeQTZL^_}/1ZWُyxuwd^fN YvW/[$ ]`m=Ϥxqsd]W^~n?w~ȉ5r{>K,jءZdm1Di>__+?;}3af^%3{QI}6@5Jiy;o18In|h1Rq?2zw,dyݏ<]ĆiaM<-jӑ+Rka|Ia)} ²:A< 84zQkR+1.,k,{=:Bvk Vs:MY$R.,sPyȼfFIM6 )ZQ a'#5]FpAQԘ8'iSjc>6bznU:dGi\@E }=/%IkX\Na&ЯiPVf- 7m/7ee}F7h"n<1^GZ؝fF=CBdMlj~ٺĨDSh8I / 1/Phy%]UXXL:E֜#Ӝd*њkwq89g}WJyUm8d,$zֆ0Lߠ8aKzňnO*/^?B1I!״ZԖn`EʅTT؂kkaZ/n黑oޠMa._tB,Ze7jShc4;RZ@Z:Jp#8ϬK$H- TwԊVb, h NCJq8MӼ.+}/&(|p&֥ ǁ_鯈;yq{aXO7wo:^p( .<۞i-6MÁs|oḧD(x_Ҕji 7 +O3'iDx~ȸOˊXo(3Z+>81Tn2WZ"[31CH>[kb5E@B=nT5|39x^x8gy^Dq=ZPG6fW2L*qXW">c(YHy]fsx˲+@F̴p(8$o|Ql/JKtDbix:+!ьBD,C*hA3jlnKN3PWӄNT+y)ށX͠R+J6eq٠{l=nFJ.öoRʰw̢4Mj#| N.}K;nEN)gj*C`fH ?3Ѝ,ʺ̌wiICG\3˅X-4C/]jxkpcp]1V:~eUq$ն%=EwÞnH󤜙c䲬T8U|0Sxg3t^zx:=eijUߟp~{>F/iQ\_L"?8Η#Ӽ0bړ4Η 5/o㉾wiaO~z~ '^%/%_}}j?{#upi]Y7|T~GgSfHIaqHrVtNT1tPĔ0sA[䜙(7>'yYXX׼GR@*Llfg(ze^irw8d+yZ6(AÛb״μkX qXJiBLʺ$@\]%N,oJmE'8i%F7*Oe4P0 ol3Vq΋pZyAkM[\IW2f5@7:kA3]"6BgF3(?c?0jKm\lX3ua1J(h񳶖R30gԪhv# m(dh Jo'YP!ǚ0tXh+'ʺFaW9SD͏^ݑc#]5[W~#߼;3ʯ |g^rQ_EoaP~C1T' ~p`({Fq1(YYDƊ{zs=JCC/׹9%d]FYZrd=ϲkVZn&vSBz6@Y7]QO5f3amϣS`wK<|=(uZ}E+. Eh;#"'ρ"F6V5U(yo$)롳Y֙qa LċWÄǹ \HpaӶRkM;~gxg:ג*ۅgb\=L|7|WW-9'1X{I9oAY֕Ww}߼n[}ݼt]i2p=.3nnQykX+;uX(y4pCӄ8V2P{&W߼d4TN}0M3~n \. AlI)e]D΅y~3KV8g_Rի4Z3Ep M|8ݑu_ SQ|NpI݋pHEh}3g4wpw.}=8~*2睏aا_ctk]Z~I iG">1ǘwXFw  {qWֵu{;Qޯ#칾P41=}2zԮwk]Zƻ G>~}2#z"{S.ڽ]Z׺ӝw.{6}:uq[j&-.|svo׺ֵ}l\?Q~{߯/\g_^k]Zŵ: joO~{.ܻ`n}>nWֵu.@]r'?ٽ}PyωC n׺ֵ>k1d)V7fJ^Z׺ֵ >v7n#JV+]Z׺?Dž|Kƒ?\>J2Ԯvk]ZW-%?` '>^Z׺ֵ ?O|AC@F}sߕyk]Z r)}к۵uk]ݿ1.G5uk]Z9 h׺ֵu^絮uk]Z׺ֵuk]Z׺ֵuk]Z׺ֵ;$TIQ]_IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/resources/resources.qrc000066400000000000000000000001461470053624300251770ustar00rootroot00000000000000 header.png about.htm ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/screenshots/000077500000000000000000000000001470053624300230035ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/screenshots/screenshot01.png000066400000000000000000001574061470053624300260440ustar00rootroot00000000000000PNG  IHDRo /HsBIT|d IDATxw|elM H%)JAAPywvywN;==^=^DDQR@ {l)l$dk^٧ͳ3yYrOq#j!M i&IG}j*@8=OaX̆VhBqx52 YvfAC'tO=(|(Oџkx`Tdћmui'ןV( 03pt0 Tg^^I/=(B <]U[AOAL;vtNqL"[ٺ DF~ EDXtGčءS`j k?1bx/:Ĺ!8KL>Grh#]*10,ej-9;1huޯEoNp &]Esߛ<7ݯL3Wn%ʾxbrSR`b_B()Lo&(:StԪMCIiٜDtO\04N$? NShGѱwX";1$_B%OUI3! bGM?Ӡ6H!=?=-(ZOb ՠb u, z~"G8sNFFD3X|v"@VM"o+6ؿUݛ1?@ ,6kᶻ4l8ܽGQG`*ەy{.ZJ$DGK{yc:۞kk-O!xb}Jص |"u{sjQDR՗ؙHM++o}R@Ή@ܓճ\ySǛk+,n;G=GE୦BpGlkվ#eG\<2o8^U*{+ yjVoKѭ++_+}e#0tRtb"8_XWT_u漫iPl:Uq8膲!;US9ʼn(@o+7|-fTZYNV}}SCOn#sGf̈XG3fZ_rZPtW6 JyVz9%>}|0Kj5\D=KNp,(~X'"Zxw5$'#RJ0*nJPi$U_#*j8y5l̛/=v6zKcv;C%,P)o&r jMh~EXqjZ89S6S*g_Qf}yݝw+T9"ઑҪ !?8EYZƐ \)6CDW VFhdM;vlK>o=;V >J=B]-Mo b>>)c41`0AX*/=UJUr)jrXȶB'ޥNIt먪Kblx8Z0)/Zo]}IH'+ ?%\ |&YF9:{k&JU:5U,_5V՛)gUZ~jz4֘K2pf92\:Gחj6nň쏲.WuGͤ"/+itGjj&|G`DQ\Dڈ* 77#VU>YfvTarHdLAH |9gq#>1%)bgT9-50Rʊ;0kz/! > =P;8M!3{;|pQh:@'R15Ծ@R jI=֏bCd,5=umg?.H^z$}G8R=.@ X-Wy^ѓ]+w},<\t>ތ+o]_VU[ԲixitJlE?H:M4J`/-v[VިhݥU[k.7v3 \4 {@lPUpb];*Y(8 8y#/+9VV9a*K'(k!=ˮ^!r~x73dV}.)=#1f튍pe:5lx9P7Jm_({mj+M7Ϧԇ1z%5=ns3ơ_S6 'qeU1r5ڡj 9<w˕Z4^ U 8 cT[_}gU~]e3:[eK1EWCWѭc(sr~a#?N-NUK14Q/W&}oX VTcZ5P8^jG7X1 oƴD21!kY65ԗmpi4X:Qc;1%"?;KC1֓Ȱ2q"N%/Дpw*ezG\kP _W.{ E'1~P7ߞ%* 3pgۙ_wa};3^Z@jY7KCxx8`p) N߿N#o?*#Hv|}PCz: y"xh-'!dPj>wF`ƒ/%JPUEQH̥Au=d2.z!ΚS}OGSuT 8u.^eW5(f_}ݾVyup՘;9Z*M?6=',0+ :!rtN`4י|Mog'$ n V]wԲݝwժCI^z%(?:ZK sט8N1j~gBxoOezoT؂&!-yggٿB!@@!`!L!ƒI Bx0 B&B$B!<B!@@!`! :Y[>fӘ8y~,S5B!DNRz?ň?>#smWWeo^sWB!8@e4O/xK<}/vGМX@$=KC]c,ẹxjrH#B!D4?(7m/:K@e0Ƙ΅1w6XC1t-smE1`$6.}D V]瑕!J@0ssϲ-Z.9]Wa 4XWf!󜳛ǘG $$P| tW @ !D&0j8_o7Bvun3bܸΘ|l/?ɓ|n7.r` ƈYݚ(1W=[>nz9..a\ԔQ !(O}a!wv[BB[lˏB!&B$B!<B!@@!`!L!ƒI Bx0 B&B$B!4e>{MٯE{Ԕh>5-UDn9xpԴTH`@ Z~;{w7e>{MٯE{Ԕ+[,)(0Q%BCB-ЪGUUBCBײ_7Ocj@@"t5I,]uqkZ=]GMr:7ycE95OSZ4OKc h6mĈ#:moGi@@Qt]GӴ*k OwCHkzA>o'Vnh/F]9;xИ~TpwMzMRi:^>tZ[?ps1oM07< ]Ck4Ko?绩O1)j BmBJ]#ܳ#}1X+ΏW77k<396w3Ά*oc/+ MZoj@Ke@11Y KDlAGL `jZo02v>]ʄj+Ρ0{l1z w"3xƇgR)IDR2Y}Εu0fu5~ϓcʎ^H{;0!tT4\brNe-_2>z8:Z=W^z6۶g3ھ5 97Ma]Y.gzO||[|0ل;c"]`wp.Rltq-7Xߊ#Xچ_;0_465kW]+Ծ[H#7 !.2t&9n' {n?c\0hy_K/%^$gQ2ՓܒWG%[k ;v[S>xKŀȥo=5 ډϹ~ [zR vq',c]igFN/_򛽝ZbDé9z#=άut~<؋;=% osTutOg^{sgFfOcoT~'G<[Iy,fWܼ_UikO:K6YEfV&W,]ױJXjDDD!Ԗ9~%Njuhۙ.| W^>~~ޟ+ѦTNf쓜6u":  c0dvBYܼ&#Т `zܕ)U֗c%ٟkdmJv-;rt(0nݦuwT~9hzzV:u*WMhJb?@rQ)yWY6 S S&Q;54ao]Il s`of~2^栣1SnFP f\vTvl._>xۊ)Vm/%LAA>+V-#''C@@ MtM}JJa߱jw0SC^D0_,FCdd)Ƈ 8ION7#z)N şخdۂ5UUm}Gtt݋fggYi  Gڞyl%W\B֝%ۓNA=0gAUն_ł5alJiζy=@U>^%PMjQ ©[JL9! ץPl23CG54dKE-o^XH7VM=ShffJXd! SZg*+a4G!{صĨUľ/*jؑ;Dx$K;ЙTB><~g)zZʟoxC\rc\8Lt,}/r$֤{G7k~(N`h_Km-xENfoؔӶz P5B/[ё.Nq$ɁVqWDϕVYg'/ Zt Fo E$8/= #jF_g89ձJrDU1_j05kVQPX .I-^9UeӡWN~AzGv@3~XI6QX ;SG IDATEBAXw"-fBaz7ϴo T:_q}%Yo 8,۟~?8E,g79쏉瞠Eepңڲǣ67҅-- 4?V뫦mds%L O bРPvoOiTYTU3r]\C,JqǏq%L-*%/,| 9*җoLSՋ"a$|||Lf28 'qh:chdhTPm؛HVDDL$ٗ\JTLx35 ADk$9FfMW0m0`ni%2Ь=݇_l@/_tgX'[ĎS86Ů~ `b]D cuX_oAUZHF.qѥWTٍMLkl*XkB OsE@Ǘ.) hFb{~ J.Ay-7ZFw:OgX x&]wqƲ(O}a!wv_i^LNPk_Tyl|iFgw' zm۶1tгoX{`/C.Ro;3rGٸy3C5׃QZZBjعkgZk~}[l#Hk[mɂ Uhi#4wөΑF~EG!Ms( %V4_@;xx@9uAAxYZ9BRԱSiehTUfPU=Y}_  F4RR! JPj#4?JxX8:`0c 8'4Mrt:LKaa!-вڏAE")S^_7_cj@t6)D{EɓtY(,,$IL+SӔ)/ٯ)us@@OV1=vq~Y"*2Q02!hLgq̘|r[xH|(fǏ>cFS9kn3G!mYѣ;~YW "0ѝ0870gsg4O/x1fqo}?BV^QJz>{p(sHw.;fcZ;~UHr` )pϵvT$PP/{Ɓ(i^w k<-n5X٥yvTTI+BPFrYJv/#JR.RrqcN_fR-ʉOf;y3ⓨ<5o#я.6|<դ 1yc|+>{d[9VMc+I\:s>?RwKHĂk,X%B6͂J8aƎ[ٴWlX0f$n#|$.co&O@W0WM X%ȚՇ(1vٗDܴlXGT5G(-/'+R#M}~H:y[ :^&ȨzSBo0]5Xsj=E!sS6b ?vkF>HP@Y % gP- \GjYdiyd/G!G񪔁ф k YO^.Ub) B!ZO3P1e^~#4xÈ:ɕ@B'@p |3c"B nr]BH׹JԠ'$`"4'QZ$BQ;U) ;_<NĦ^I=7gc:oW`/resl*~v.[esy9X5q޸bɴUc11:D]Gز-- d_{ |Q@!-Iw0!U3O CpF(C7Կ3}o+μeO0+ڃbz|. s0]]¤3\>?ɓ|n7.r` ƈYݚQN-;7,Q0X3~tcz=u $k56:bvo1!mir [é= !;;y둙2-Bx2 BvΧy,BvNFB&B$B!<B!@@!`!L!ƒI Bx0y^NjZ*%nۇQ DQ)sA!D3f`vs9<|ZBz2@q8!@Z? %/K{''.r']E+LO Al'1kUDjlˏrW ob3ǎi?9vԼxh5lj!<,VlYtZ[B())h4V9T^zjF,XU%k9Ա+1FG<[kry5E).oσ岂V?@{zSV-z1f'>7>w}įN7M51ַ|4cqա=x7w0_G+H\6ԈugU&ѵo7Ͽ^-ng{t6u7?uDfC =cؗVSydzC{kY_,?\;<|V~W.59}k'Ά?Yh :ө'rWF]NgJ!)*Ә &'0&4An?̗go/#U_.؋KۥI/q,Ig璵 Boc#-={~^& #yG;/ER2ZgSӷEfV&W,]ױJXjDDDR#m&*+ۙo2l^s:FKΟyvT&^1ɳn篫/3%z#W6^X2c3\~2r3ߙ&ȏvU[rͭWgW`OWlu6z3i *sTzQ̾c2qa^,MM T'ԲvջM55&}}5F~hb1a2(J%-ω5"+PGgS96"Pkj(!Fտʌn}4gì`@o4oE`L\ 0]:SnFP f\vTvl栣k:qm `0;e}N帳irnFSz0 [OR{~-ewPvIr:6lV,ze:ⅥU*n꺁%ci@ۮ'ۿq3r<]0m4z#׬x v߬X#:k2ag#u65} .%LAA>+V-#''C@@ M???v;f]w܋;לּ`0`Z,x1&]|\+dbXY|DY$_J pD|rˆ ?!"UTSZ=Oxcr~W)lƬP􆶩v4&}=5F~hbMqkOy;zޣ޾5eZό{3p$uѻ3jم ѫP5t% ʗ\dff}3WG2٥:T)`Ĥp*j];V3a~: PFǗw<ȩ[_Ļ3#&{_j:{?oMeYZK).q3RMEռpI/^CVXLzXW*וΊ>&s|xUb7yd_t h:v]:kBk6_U?7ffJXd! SZ>/qY bbb8y$qqqicFb¸˨aii)9B׮],X15[JᯏĆ~W֏}&\AdA{H_ĔeDsJ ٤U˓NF} |IT655ihc~hbQ3 _>7p{MݮG{ EAU:JhL{wrܩe ?tZLD+;W$8/= #j6𺳬js⚛u55&M}ǵyky=JEZ'?ǜfqٟgV_tM+;l(Qt<őc͵9%%2fvzN;K3!T^SW*בľc]H"‰:+wr1Skz__?k;cPgY>Rq^G7<JBd֬YEAa~$L////UU=kjO>8qӧOb\(..bǩSHNNW^uW9|cǻX8]Ks|+_8S/ƍvٝY(-b 0a%,p8ÕE+SOƎ|J9pbGH+JU|TjjIB}FvlI.(ÙG}plvi.El[G:t%ڠ3fU'GI~3WB ?Ŋ3㿰/ht\4M<{ʿ^;R<6,3:V/g%L{Svs_~Fw` ٕ]Jib.ptl{4,7 S͌: ruCGD&=duLGC#׬Os`w0[X- Ch\m_lN:^5xYH8(&LǷδ75:#Gd֭v ])**ѣ=zѣG;lRϰICxӟ8-l17RLsc5m^gdQ A6XGݷ!>SRg,7O6RfOn+ó(J^=iyha] Z~wyOu)+LeeqAu7?h#2aq_|p$9辣9n@>?IЙwsV:OgX x&]wqF Mk87o_aC{<~GFۘ^\s ݚM=w3y~<'m: IDATS9~ f28mn !DmfDngfΜ'EaҤI,ZІ 39;EDQ\IΦ\XIFsY?1GcnƝ%xoBam&HLLdذa\\QJbb"kph^(c:t,}^YBgӥM4KVA$vB0ZUB!ڰ6һwFI@@wn\ PIwQ?f2$}!${Q uomzX^}>ji]jEZYNB$!+Y2}? @FfB+$=sL֒{%Vn9J.9goNLU;YbT'5<}~>:R<<`:ِՄ9lbo΋xay:m^{]=Bb;>#PnAiC\|,^M[ێѡ̍kedwBc^s=l2PT pRR\;D'S^.OW[Oq3Yy3vJ}Wy|GRUϲq|0qO*zjqWGYQ揼պx^>voz׼'O{\~Fش/>z\ 㚻r8dYV9?0m0Ę\ang9al[[JV}N _*g+Zk:?;8ǁ>]B1yM!_up8cB86Zu~o?GYJ_nVq}K?+#Q{V72thdn"5HO4{"|wl kI2jz/7{#Ӎ=ܷ$U?qkgN"G79[Ĕv mjr<^XAJ%*CvvѤבC "u5L>F;;,"RZ|~]B1yͭbAѸ=+J, k3jӛ9ա46ϖ@+maQ/Nʴ3SGQAvFi9{H•G E[^[o9irg :Ma`^X7^vt2|\PckG4b W  `^”e},]B1yM!IMMtJ^ÇIHHB*p}|yi M'O=-} h9I) 24R9h41*Bq~c[X[MtΙOrz*&JZX[Kpw߆զDG^{}xݜZ䞫"]t/Q;r̀`1/ccmګnTdO)=iz0w !85#..;wbX$$$Eϥ墣2w-?s뫷}q^Oˑ&;,^fhԤ?Y b '\̽!~В~G+i&wݩh];W^ÑܸM}Oඟf߯?̭e'$y0c牡MmC eOou,{o 1W2Ί\w?ro:GYvђvotpy(#!.bR{fuy:Z-uuu`@tttP]]MNNXV7- 18Ίwf6ЃBqxu//?rXV"##YlTTTz@@̟?!E~֭j#kɥĺgte?BB ƫ .J%̞=/l6(DTId cz&ϻG !&+N']]]tuyvH)eBB1B!&0)B L !bB@!B!&0)B L !bB@!B!&0aBYYuuuL&z=QQQL2G(B\3wܳt bbb<B1yխ**++Y|9vv;,_cǎQSSsgb;cB1x͈b[nq.B^ q˞a9nݣ,[]eq !yM!p̙nr 8B`9r4k/Bx)jkkINNf8x@@+b_ͥ4̴yWySaŃx% ,xY.?/C^,>3b@'vPhǐ0;9K絟`ǪWyc[y;n[-[z78H39{_< ޣHƝeGHݿ'Q&s\^fjO5!k^dBR~&ݏJcKRYV67UX;*3LEӞ]sso .\bU4yrc6ŇOl)Iߞ[Ɇ^9-~v=z)n|3+`3n;>+ۣ\VLcV֣]( B#~w>e{(r r۞۞B1漦l8Al64,E=?K{a_n&RM.w6J~DۀIN` fW{vq9OLg' ~(F_Gj608ܬ2#LW~F~CI_V72thd6tn$5t7Q7Rj'<_l y !k^sk`0`Xh4nOJłIZ:BvRGQA@!'5w'm|{4.< ,~Ng,l. v{z#!ig%b'TFjO8!b@Jefz &W{m6qb\w,ZY%'kV!uBxHII ZkvÁV`0P]]Mee%III#[z* )nbm=769g>HS=Sç'rfFe 3g/I`_'?o#յ?pn\ig+BsN־]}=sN_3J\^;#qR oY(e#PnAiC\|,W;nD|Q.o]9?[$oϭd{V UNqh]ƈ-UG35%)¸殹~=Ya־UN L1:}9'^[1kx!l'Xr-O6e䫕*`'N^{~;7=kwqӊo_3y*1UGpw!al6\AڶԒ\F[#~??RGr[ʌ0> \ %v~ǷCGn&[ ؏e/7wAZ|{cn૧s%ɨj<}n$ed S Az4N{:hYH աU>:h3x*\,}m.=}q&q)wMoZɮ{X>wK0OGesww Y0ǩr6ss3'Bɜ-bJ~3πB 0 X,4Rb?5=íjӛ9ա46ϖ@+maQ/Nʴ3SGQFi9{H•G[ϷviFހp0"j3rZzau8h88~;9>yҖwl;(>.7) ̜ +ˎN=rHs_N--9w8pfq\mൕf;.(|pM?K^n !k X?Njj*8}?ɧT*>|-DB=WW4~3n$8N;Pdh s6h0bHߜKXy.92>:a6)y9qr@Ÿ˖]gyLvjS7Է.̚템~yM||z%\A{-/zňo#յ^a)|Qbm<Ħc_Y(؞O,yPP1$^3"rعs'HBBBP\\.:::(++~G R3Yz++XĽi͢e@M񓕯/p*-w?=~fHYpOޝkI|Ea΍ <kMNu?{L 7M*~L]p'O1gLY8:D?![<;Oل_;EwFN!Ą}Y]VK]]yyyb0P*-v;TWWCDDVM@Ͻ5wojorBn/ W ϯy'1SC18GnJdd$˖-#?? ں߲ >>A1iHEVy:"G q!%*P*dff2{쳾tfa6ڠB!u餫.߮z8WWJlhlWA$D5DgcU>yb2ZU_HpU_JAq% vԆP⧧;W8)9t,N5Pb&e@5e w^Nn nL;@CPs Šv`jX%aӉ'Z(}}ZB@ IDATC|Fdh6s`.͚Ojؔ}ζ2v?%FtJ;]':VMSD c I|!sH%kTƒkgu%)lӮ?r*3fL&HG{uvR[|CǛDOD fN6s`Skƍ Y+MX=m0E4LzR؁եFOzf2:EO St4DI &nGԴaSLwf=5ZS_,}uf'_t_u<u 9+8_۩,889^X@Iu+ftMJbFZ,AƓ; h\ƾJmY5)9$PN%+euL 'uá!0:nsFrIIjŅJ;l(tFH>H뢩>HAJ7/Rj&ל3E0 ڢ- IkG;`Uue 2ًf nXD .YI.JGqz ?}ңquqd/EfGp6RےNtvN r1(-Kşv*#Vf(vdFx+ CmnˉQ6XԌ=4׽XyF9{C{8+}/M dX)fe//ٺ.WߝTU+OB֟i h9㻙y%iQj>eh[ĔX}]x$sLU)PD$'S !1- _JAc lOM}T|$&mGhTF  4 cjwPy8"yF`lx|D`0P3CIvp`M VCpUtW`0G>|z?˴6'MX.\t{z ZmJ.g)|*Nl[0^*8&NZԟ $:#Z4=nwxՊŦ#ܿڱ ;ORQ۰Z]Ы/Vlj (|џ:k:5h31(xDLrv"%KUҎUL\ R#}Q\<qbxxB`ROTR<ǿfL&Vwe:9E,r# h(.2nbECfh B(`lEUҩ#YsngʾVAuqӿ43pJme¬^!~b0vrAqd\8/#Dg:8D5ŻqӅɉHlcGit9sd_)'Mw*(M]vj+3C̺Q_SRBRjHt ZNVʋVJ羧ڸ+Z”T5 EˉB0gF#Ujqⴴr~US^TNʼnѢrlQ5Z"'a*9@i ӎKyf98kU`?C}t/Ut]6:[1 *?@2ۭ8qq߆y:qbxwD@B\lvRb®?xY{ USgYVD8iv1?m:! ,miQbLEZAl.Id*KP{"}`eƾm|ˁJHHBJSZp,¦6IHp(vdF*q<PN (P 球<|$(%|v~ѽ_#&Gd>i٤?H{Ჹs=l~d\> oaV֬rq}=?@Ǜy81"}-^pk@ ls NJ*G}1*qA c(p`K>m65SN P581ax{@&hJ x:3i'1#xq'%@n"}-Kx! Bx!ajooOv:+k1X+b<^DFFRy$NDFFV&Z P9?,d tr~qQtDFDd p 8M1r/aaDEFTz|B1ӉnfV&Z P96Mv+k!ƆB!&B1I! BL`R!B!o '* F}C=uuu&O$&NOdd$aصcXmhOOGѠV_/*h&:zd (HOžjLv:˜'ŨP2Jh0;p)w[L,!ida&J H O[d;kSi S_nˉ6 ̙D(9D@Jb-e3{ D!e>9yl[.2*%y YE2bˆ%$>382d?'g0#=EUx52;%*2RjҮ䖴]{n(;dbm۾_4|Zl6(9As<7Fιs LKxj1$*!_ZSI?VKZZ*:. -iihA1l8ryGK O|=b|Z $#-O$臷àiootbjooG8VKFZ*:]v Å+I O/*0DFDRYU%1۩"2"rDm.+#fR(F$_'|3@\.G1gbt:"#" da1)zQQrGUxxW)AJtTCjrqWz:qCUxxW)fn`$b"knntޞB1I! BL`R!B!&B1[CRl7SWWltHbDFF];ՆFKt jşSS^GQp8oi)IL'[);Vs}{̞rk`H(1F''RWW76fΤx#nSJ!0 &I*#ow5Jy?W)%_"cZ)(,lx:!ސR BtbYV,ɓb2DZW}CJ! EjRXXDٌBbRPXjthBsxS^6zf#lXIDlǻ5^*I%xw勳rapQP%p]LV3;vrx+?_J( U0~&s%iD,erOߔӉiY&_ɄNGZZ*>Z-驩,,tJg(y598¶FnqH}|LlU_\.xUpTƮ&|[ą)' Uhr T,Γ*}rT;}L`7[(Qeqi($$=YWbvڢkSW(ζP. i`Zz=Ik ,yg_ѐ533(.|uoV )fKzK3||h! NdrDfM ('8VKU(ν*rdh dRrVWmUsph*4h1̘s))C #^g#kXH@I8Ω1bsu̙5$ΚΑjhvhYWWQdeŰ@%1ȑ7x4pvRe5cR{i οrx8IXHMnkؼ|H&ߜ$ŠisB|GPXh"&1Sc6^WN_獡9i(9%XA+9kȽAYOEc0Yo83v}ʑӸ־ji5`T6Z >3~ 2Ä#oIK6v3rW:¶}uϛACA\d[t)ǻts䦢||uob(-i렦'&&*|g?cT'.ΟZQ>h "za4- 8?yq6QQ&ya a:d}2J"@MXBk8邀=LJlD+{#8h؞bjAL`k,oIb. ifWvv9L6l~gWőlhӈ+#S2S8*DLE$Bu d7fr/#^.7ԓxLow8POE[ޘyt taLâ;‰jOI tY)PS)Z||z_Dr1Ôy h۾M߆UDBr!:5>g s~Ȝ U 3{nî֠a dkgSgEe.\kLŗP}|W*;U7W`z%G~F`R®M_U:BI$4߄=tP;KIbNӠDAg [}]$30~ņ$BNtdW33\k %{D{@\N|+bz_;ϞVEG|5ubB~$GL}S)WF@pqu3=wst$ZAH "Bho#<~fQcta&+GVm10rq$4jPi0\ICFdk<P0ş}ԘMuW11N< bQLb]Զ[qtVSssHL$+dq4QAn@.'ڀ(2go/*f'}׷||M!upW"#+nMHYtڰD.HŨI׻}~5qlesz: 1Amffd7Ըm|u:jl?w f˰j=Ȧmv0 ||>_^~o5  ~z=]f3E8XTt!42b|5Un qjIOO#00t|z9_M%_^O{{P{{;z~i|Z2R>c0.Dx^IUxxW)!2"ʪ*91NeU'eeLq?EBUxxWyF`4 \.c6= t:?ۘ=(Z[[G9B#*x=MRy))BN'Bx;M|TWWhoo;oŸǤf IDATB51d2GzBA{{n1))G>1ۿw'uM}XЖ˞a9nݣ,x,a;¬Vjw}>g{ ,P{oevh=+g#߾|z™q= P!FMzo-A|wq[x{4777[Mlq=fL&Tu_|x٥(]Sa2bͳ/?u]5!>:/w~Gb|![O)ˍoIY[6};YbTy6t{6A;9 V{Leɿ?OtX*oU]~Lw+=p#8 5G:6/Y|(P;&\O/B*Mb'\; +D dr睗r߻;8񝛈J@!F>GƅWGYN^{~;7=kwqӊopkÊ56hxW2Op)[ /譧UϬ<@;xyrc6ŇF<,ז˞eJrY)l3ǧeՔŐ<0fr"w Y!nP.$ϏضԒgwUTsK: @!t1 A)."ueu?ŶX@WApW@YT ";$@z6#!N ~^sȽg93sg93eЉ1A `?f)`0QXN؅qW>+EKsm$~٢gt m(~~~7 t+oԦ>e٪(ZGJ`(|of(VHWy(.n8 (*N!DD=Yʟ#`_+gT.4P3劻ۥFv;*f"#="4KQщV)rxuEYuZΑ5ywZM₫B I+슋 !DRkZz@W-37<"z.g>q_*wiSG-._^@Qi gbMY2NGpkBUd&.u579Wb0ÒqWS3t~yfNeev~UGq5sɥܚv}?.b)۾\OM(؂KiƜyeKz[_((uH꒡ L 德-8(MMf4e]ES')F=tmUЉF?/c${a`@#,|~&]`:) #X)Z8ILNͬ=@t ) !Djw (!=iO=j?~!nսW}WC!D% 樫>>; zWdff?^"[.y1|˜44 B4 ~~LXbdffs"@#%5  _E5pqB3f7$hAx|}|4 OO/ƍ B!D=p`hٲ?OOzGk@\͍\<<<*B!ʑ[W͚5c+ttݟA =Tp`0ɩwUBKrj2}/Dp`u4BF/.RXXO% !...4jU~~NFh4DPPP}WE!DV+}nSu*H `ZW!Du=OuA!L!I B80 B&B$B!B!@@!p` Bf82220 WHJ``  B!DӠΤM6 <_j4ڵ a)9aMa )Ik0CIlX,r_6.]\|Y:9'=G^ɗ{1_;`!7Hlb64Ҵ:zגY?۟t'L=_#=%]U~] !OcīCx5m㳍GIQ7 CQ?勖Yn46g&?; [صdt{EiIKxQ/m}[ ?'Ww\7aü9,z\PFv%&eわbK">p<BB!34E6ZjѷC[G_cseI+$l'شMψ'-f4>c?{^zDMq($~٢gs6 ~d&l̢ a` r>Hha;杮z|p OOeڒغ˕QOMOuvG !h`(E2:]_7NX}+ݘ9c4!:Fvw_='SXhC@qв=Vm4(`fU уFec;h{NG!TGx ԝfT !AvNnp`M 78;(zfB2Y:sC/2w(>nNr~fg61Axe(?Vn>G~T{@ fвnjz*ijI&i#+O!磌(A4j%=='''}z01ߺO?9E[^2VDBH[ZCE[YbWs`batqY%8$Q C{o8x>L,}b{-dy?ewHOs`})o%~J4T>k?]Kɂt՟'/a *BQ)Ad[hٲeՙpאXKLCy/ tO-[FL&O=t၉=xgvE׽id঱/E,8I6ks`Uì.Mw"b <8w6>..t=G^Փjl2u1knU9ط0I٘q+-=ZˋZorIع`< 6r2q"$)ͻژ+t8U}עj#n?/_.'vo%A߃oٜڻI ŞL<"vOl9M[}GG) ;ٞm"tba]{P}h4ң{gԔeeLi&lGfh;$q삏k@Dɗ!xxDRi'T:;Qs9̉l̪fAݧ -\y='Sɶ-7n3g.`1zE+YĬ߇>z)hy q;4Q.~Sꊠٲr;gpeoi YLC|d8y5:p]KU3#ً.7w3>!}i׫I+ϓv.ӧic-@szeš 넇Uj.Yey4vYR*Le啽8.!K|aU>ϳey9m&Եc6p!ǟif sϑٺuk@@+#i?2T2lg5'ٸd4vW'8xc|ykKs!n;LuX? 91^%*Gvpv=8vUidHz!xPp&̈́!N])HO+_WtQTd"zh/md}'h~G')n;Ul8J[6[{{>Gq &"*@58];9$fP`vÿ*=<6КJYH~^&q" MPyrwܕ kh_l:f&_Olv#KH@Z/'rٹz:ME8M}sގC\ 8_[4 cy{2ѷÿx(FVm6+6\Y\?m+.Tf,ʺ]w+U;nJV5*ǍjU9-o?"Kl۞Hi&mZ{ PٛV8<& ካs>9yEٕ5-E_8/mZ$'5߆r3$vlvzAA8R uk/ ftcpN-uĘdڱfr<&4֑GpΊ3?>8ɫ 'C>ٹl|MJsܑ@J ^H֙~=ls^+G7+C_(gbp>߆-?ؘxk%ctݻmBUI&Bblf9D2]v9mF٪ڜ f$K&תZs9wͼUcyQ#24P] oCٟMW`kzXM}Z<̓.}8#+лJ๼ §sc; T<ӡ[wtz= V'![VяE(SHp; AAӼ[9u 3Ĩ-/5PW*'8WޛuX ϠDDOq i?gɵögVʡ |vœ<:#:wWE.4؇arR]Yȍ]V8A ڏ}{큢;fӵkiT/""oVƂ3͂pKj,/j5=*&_#k֨>] QGvM.ݮjܾs..DFv Z򀒲,ܳ^{j7z(ۈ|`6t4:/ƕ_ӄ1,qwsc/(h\\D-)Ş! IX[#Ţ4ڠ& C[޴T?;GthA%&LC#u7ՈOh](4AOګ@~Ҵih:99ѹsGgg'tUi;QF;7d?MGch24 DDddg9gaB4 J ɩj:[i9*999xxx\:"mih,Ugʁg  L6m;B!D l6 UUt~ ~|w!!glS`԰r仏>k a3淋̐BL ( 6 Rvf˦Ι]a{0=BCk5]KO8[N۾1QKX<>x5qxp<||[ė8S{sx^NfSaü9,z\PFv%*WT! W D.eg?{6+Oڮ g!DנUUd21p z',, `fI+S^ 0 kRYmhqrt>>ɤKY43n`Cį3t$,ۅS-;D;cKf8//5~<20mI<%aNuKa/{MxӯYg蔰Xkխj([reمC7+r#<@\ק^!D`zW@Q0[,||6L~ݲ/OO>'QULۙvȒ?u٢gt m(~~~7 tV |UQ&$rewuvD$cŽ lʨFɻx'FSVI-̌dRL޴ `_!MB  F?_|.^ IDATV|s???L|^bqTCMd'oYFɴc4#4Yt ,Dȅ\. & K(>ijw2*Jʯ"x?񿯗2mL$6#IhPnGQڴn3fܔ)G3(z܂{gˋy8~yjtsyAr ʜ/|<2HNWegjz x+@; /|er2UԩTR>:zV+PEd_5;0uvhfR-bWQB$49Jzz:1w   -- vԺApu;}2wsΟ?_D@^O˖-իfP!hŸ cN=T(::X#- 7vB!DӠMͭj!LB!č'B$B!B!@@!p`!L!I B80 B&B$B!B!@@!p`!L!I B80 B&B$B!B!@@!p`!L!5@7Gdlw])B:v2|$C7VM!B!(õ-+{sri6Cʕ0oj}!B41hCNiĹSgٿePt%tvmKbO'(,^ŻxBĉDN3VB\ ' NӑaEンp3[PQ!hjپ57:/n'nX8uiWD5L>&.j۳(We5Ld\(BYZ%˖U* V&§l9 \: ! Th);oE,l{ow-[?FL;F:g/aک]jw|^/Mo2a'߶}h*Ƕ{"Y9 ƯQ9܄r^GC#Yh>1]B!%0V1GA}ץ)@! /Ql!‘I B8:NoRB!8B!B!@@!p`!L!I B80 B&B$B!B!@@!p` f3qqqddd`0PUUEUUKf:Xli=Z!hT paڴi .4f}~ټ?Yͺc1p`yB!Jff&C fiZ4M( |wԺ7NgP .ބみ>iM BЀ͆+f @UU363ߝAhHHym5ly#ZCi3$xuEBz`EQlX,r?5]60w (܃qZiZ2O:wό"ܽxXš̶ƣADs3cؒH&'ƙߛr2'// aS亄5+W%^/ZCgɶѼp 7*_ї}\Iy夭p}B4Y XUUL&w2o’f^^2j-/5Ihцz'WLӑL5O>/>D!6ӑLoNx-⌾|X´%1#l;Յgg/S`]>7 OfS6bT׺3wuK,XᅵV1|h7w͠Uc}B4I GzE —W`-[Yp"1UUԺi,S3FJM-zFLMІw`N7Ni[hG[ebKb.WF~7>: ?=>_m'ND:1;]ܻ`;杮zmyb$=U~}>&jp {_zT5)iP!uA F?_|V|s???L|^bqTCMd'oYFɴc4#4Yt ,Dȅ\. & K(z+W.w2*Ȏ׳d* mh(ZWB$5@n( mZ3xn 壙nyٳ"vtg61Aܪh34Z /|<2HNWegjz x+@; /|er2Uԩag (ȼޡ9O(^^ojh9ٗԤG!D` V+(Bx0ޙ/<<g i>&?3pwwH̰ѦN;/6aü9,z\PFv%}uh/;i6u`6r:u@@߈#^MKwZD_".$_!u}`лpws/ISPPO7ʓOLjA=YP3ga '[e̝x /g2U .M`#=9Kvnˎ'^:S;?77t7t$f/eӸϸQxi9&}s `%~ٻ|n~ͪ6[U[ !LcACa:..k}6oą Yxzz1xА'(5G@^Ć9MFvw_='SXhC@qP DVv+ذa|ݗDFz+6Ehhz_ь(Y`4b۰hGxruc?^]ѯڛ@UUn ";66/xvxg@{>hhv;# زg|8ӅwE |%B:Un4:.~i=/XfѬY3: g'RҟK Z(,;7't9?3h7$Ъ`@Σo>7tM)ZϿ3OMΘr΀Uu+^4$%]E驤b)n/Z.6"vߣU*s;20̳(GJ:POpڪ]E~ !D]Խ3CO?@vN6 N\\\=v(EesjvgYXu]\qua8Φ+.CE[ׂo4NiZygq~-_{ԗ6#n l_s_k?qum>UUО} xtmu֒d((=G4wl\)x[0s`?X#;*VZ A>OȰ ͒ΡpyOөU;*H]/مCbέ77\8C%s 7E1n[Y{ۂ_;޴ tO-[FL&O=`\>/Ͼצ} 禱/E,8I6{p"b <8w6>}i Oc6zکyN|RB:P>hhIʖs{أ&v "ٿ?7|swrrhQzuUl^+08sB4 @!#l6BĔ,XS6A1\]]/ɹ #B!={( q&@=kѼΟg%!+4bh"Eu/Kz탪$$c6:B!ggg7 5 n'?=!E m6Vk5~I XBT!p`!L!I B80 B&w !sJrJ2]FՍ 7 B:JfV&]#Wit229xZLf!DcQdFvK~f< 4u5 !\'N%ݎ @@I,h,jzi5QNz&QUꥡ (45 !n]vѷoZ/_cۮ8EQ4 UU+Ndc0h+fqukd[96URUM4MUB5mםJxc&NLe`]~/xcV}]4 g䫅K!72[\ߕm,mql?Z=hhhJK[2~ v/?Bt/<AUYsUY|;?1;% _3) p-zם猧mmUl.g1Fpxrh[lUםbhe޷g`/)2E1|DtѝhSɵ'X8Yd,s=J۳k=9W(5X1ﰥ{ep&S4z;ZWq |e pGGT KU{){Uu-aӼIwB]ͤ?QXT ͩ?*l~z^n_㷕FY<Ƌ^!,}g&>Ӈs"zܲBύW6XH13=i!:X9i'noв|(b 5֕j&[0VݨSVj# #^Nt“]K1ŖD2%|x&?96΀uoNeSpQ=vߛr2'//<>x5qxp<Ʈ%C-pm(ݯ_TvU.U \bwH̰ѦN;=ˢIx-;N x9XRQ>R5в9mh?!XVUe%?tlͻ rRZXKM {?٫jl=_zz]}ղٺj3bBOσ#t$WN%쿳vM6 C5geUTn-[;FқDR2y81lMMup藝4:O0W9cVվ aS亄5+ٗ`:mRxy0t])((্HKK'&^|(u4G@\-wNoMR1i'o lg8EڲlysBEy+Jƾgbq;䑰ë]m o [=uIDATѡ -L\J/-⌾|X´%TF;³) 0Hj:sOG2yR]8۲I8b:d쥬|70}! 4v[uJz.5\[ww?c͊Ox=K??hM.doiBT#/Vu9f;o)!thc(yЦ-<.(oe@6iyg5f4c/AH&}o/70}q,NL2V/m8MCӬ$|9E2iV/x.Ƒ`,{?sҕ[[᧻Q/ϑ;xӥ|[ޟʧYCs>ͦs^6U|PɫTRٻ-s{Lr/kI'Xl3Rrfa IZWA1lȠ0}r0:g!n|nk bGO~|VIkEǒ[ӗ08U&  &=#uG^^QP?%--na:yGkn2HBք ) pooЬGv ?\o^<f,g:Oܥc[Ex݈9Zp/f=87}{lEےغ˕QOMoãdfBzB %`h"H,R]bO)KE@",(]e( &!)H!N¼2sws9KP4#'{6.ԖQwL@Ͽ60e[15ŔϾ20i[5aWWIE.=s՞ѓn&:،@t~?/v;#Cd9ٽ7 ohZOÙMvY>V9Yd9j2UP! g{ȴtڅc?G=ħ_8!f|rn!lNcd؝ DoͰ ؚ-Ͽ62d:14kэs3c8~^$ !au#LĮy{(;No2[O+jo|$76޲^/XNz#}Q9ΑLں G_/Z6s!)Vs8qVC(zbMdΟOg5(cx,fK~/(8uR(ǏM:2- MPRvbrEV/F'騍vN4lLVJ]/k]EwwlXFvҮM6o/ĩV7sB mˤ?HG(=TzDLwS&yn.9ONߗzÕv*GMn;#}'=Mk;'*f%w>9@//䓜$ w5-]'1ycV-;4ﺓW*K6! ;\=~8i@N"c# ɧ/M\xd‚~ټy#2p Vk_VUEꕆA'mڎɳbDǃ"um_~:T=y|vToo싻jvh,Orq1]k7s8Mgт1݆-klb1Ʀ8'q$̏7_/;zRs_ֲב|"g.i|˪WN5{쿋=;ز :_CPs;xn?Vb|zP ەk8p6߳jn"z_O}Vʗuqm8~0`PkO6 ~(!}|:%_!]ym%mqcOHJ/Bu7ʐ|6.Z92cgW(F݀قŬa;sM rR{ėCHYڟ ?kkwM}&՟h/ڬIv6-Z 62zm_\ʧ?ydv›Pru{G i_N9s׬RWOТO|0lbÂu,hC$=fca['ѻ9RZ{"͆w7p蜍sp'duN˲/xj24l7SOe+TUC vүoeLllկ`Wn["_^{X|{<9 Uv}eIķ1dY5:/2}}m^2HouT#J#R\p/7Զ/Nux:\Q[9Uʹrvy&Fda 5{[!(8aOTf]vݥ%u {20@@!DaOTv!r2 øK!W {  !L!ƒI Bx0 B&B$B!<B!@@!`!L!ƒI Bx0 B&B$B!<B!@@!`&wVJq*)degU6 !X=zӰ~C4M+k!J=ٴtHHp;U !ig9ta喕~-DP~]d:t$?NUB(?6|w0_ Q7T_ǭ@ +;rssݩFq9NBC\JKnL.LB!<[țXmB\EүRu+3_ QsHF@!!=5@C)noRJBF@u#PdCD*,\sb6Z6mW6@0xаMOn6!V6T]T?>Ln駟K/v]+d {g`ߖMaMͩfv5}Ϻdhd-[ƼyX,fR)EllleW~{)c9gqkm,6g2\>JR_,jEu|M>3y6lX_[ ?KP^w`(=tkg &X<;B8N:m֝;aMmϳ[GlsO[0Fߥ/c(\4;g8]=Fs[@eՊrfիW[oC&M\^@h9P8V4DSBپWvl̛_ʞw"yjV<_z+m=hC為uw.ɹkI4:]]HvR յ&M2e G.?pG#:5r6yģoQL3`}/(R8ijA,?6C @SLh_/@6G>~2n0+Dj PPqx{{W\Xeq!?2zg~MomֻIc41^1$ND` vW'4veY3e t57> INtojW%wkի$$$0`-[ƾ}*l(8]@3y<'@q=o/Į#8BENg3ChR= t<Η볩w$Fz)g4j__9M qEΝ7nVQ~aM= dd8CJ  !h iH Iw(|Qzght ^W(]#y8Ě{Fh&+N]~ Rj΂knd21eϚ5kxKVfh!pnF;׺6&x|Z$De ,ɧ&uDO?"s=G[RBuk@\0j(bccl+64mv !Kf4ΒD5zj g|*˹=^.ohp`)T7 SqڎɳbD[/Cj.@"''ڴi0̞=2V }1NS(F̀ŊŬ{_-YGs*2 vYm!ښ7w0b6Ϗk̝ {r@ZǍah KW_-dAV^5%뙵d=):,0%coĤdb nI-7aߩe 4u&{%O͑|27IFb%^Ma :gWzv5ՙ&6-[0dȐǰZٿn[Na2ذal6_Q Dqܿ ]kl6CbۥmD!PuCbٰl59!7r:dgW>#mrCp8$C'\P\|&*s9k!ꆪW& !dמ j. Hnp_C2Bxq2(D]!sTEέ:k& !˷~-Dj.[JVvEBb_ Q7T_ǭ@Qx#N~@i!jϧ(²ү*ӯV $$ֆ!BxX8AA%~8B q+p:OxX88o/Z25QB!l9s#@DxU[$BjwrFL!IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/screenshots/screenshot02.png000066400000000000000000001220601470053624300260310ustar00rootroot00000000000000PNG  IHDRo /HsBIT|d IDATxwxUܒ+!Ih"EPֆ]w]uWPaeU\,QĊ{~H!=7r̜2Ns);V~^MB!Z$B|PgTog0mtS0B!ynHɵ7Jޡ> 2`o36̀]α4~qvXQ"g <|1*gh34P"|b/h4՘k;: !@+]Uu(> MH2+$*iYj3<{#}.pf,Ki k[04?~D1At2^eC~+qj\56m" &(rUl^9׃jӦk!1wv8J/'%e N)Tɣ^}ޢu ٵer(TLf7<|3^s5KR{Ep zFxhgRyymcoEG-`*V2$ ,~vEL`,8Qw^A>ΐkEӦ[<1QT/ 7.fGcjň;}XnyEug5)הּ4t=Ͻ>c"QaoHK?]VG@@AxY$''s3vx 㘗NjsǤŐ;L:[FD e.n(Z0bd;_u~d(5Nu(4=|ioFNaD[}yS^ ?X˻={za?̓c!Aq5ݻH^[L\QeuAV)b?XU\oCz c컱+#&UT@zmmѻȧ♶MW=5+m yt7sl?Rf\J@ߪ('.P \B9>l5xvF $*,3s0 Tr&kw 4zHyTZD.2ߦUp_@Tw_Lƒ=`?`5ׄ9ψ8;~]LEgBZD+h'@6T:Ŷ lړѣXjU:MVyX7tjǖ_snX_NY9w*^ɣKS?WG_ʌok+X*sd=xkJ^8ecyqkvxUv>ZON%m$֝[{92fJ oycӾC!.jMe񥣾ZNXrW'֝]Ɇqr vIK1?!@QYzNM94`:xk)/?0N%g򏷖OhyWj%_MV|nB^al6tC񐝪iΉݛ)(,<, pl6*UOzj3!Я[[f`'q iʶJn(SRReB2 <1{zb.%K׋@#JPq8| 31{fL=.z!'|7GbH.ɰo&;:3q.Į1Ya)t5Tv  y*-\BJOT/0Yu'6dΌZT*nzō]{ (鉿zN>F3|S>)Y'oYY.Wś~QSHM_.sxyQUbXgq;R2}bڕS]Tߚ%H ldt}&TY4T<*&Jc5-U(_v5.rU+kMߡs[-9V52s&)y偉 dr&x<2T 7oFQ>^y`"q,!__c tl+L(i֢jJ*U6"P: ӂ^jc)Jši#ۏBt(ĢAH@юh6\[!yVNw@I3i7`a^](˭r6~WtlK/͖Ͼ?v +b~mlq`2;|?lc[@22k{X!q1717[۪'˕{ư~VW|;RPk0X+Vej[i ֭?Nv +{pץXqR嚑Bk\N]}>]{Nהw^f2C;Sh|})o )|t֬YCϫM*g&)q^ Z(=c/ᰣi:_["nR@O&uv '$>O=.LmL~`ЕiU.CcK;LVj "++Ўz8Sw\ HRf:vf֩, hѴSa=}0$ ;=CC1܅wT%tf6 7&.W j\5]| P\a'2iCLۘ*Ӌr3jMLB Sd&@NL՘)o]/~'A׳ BdζB! @@!pa!.L!…I B0 B&B$B!\B! @@!pa!.:i>[a`| h$i`C[4^KQ!APLx9˦-دf^q( 3ضx.Vnp KPG?OZ>]Lɣvso@JN p \K@ >"B43=Njnvpy؆>ɂ/x&,:<=7;omɴy|R{{ h#%PdeB ]m,7äQR0?>Dc&Om,?squPz w0z3q4L[$B!*h̘4E1` #z]A0W~EZ?K+@ C?Ǻhfh8ćw]ɇf)i)iB!9o 7#GiddHK0`$4I1 Li B!D+<ϙr0~n2Y\#U ]G]Js O]b| ^>_<tn^ɻ3A2B!*8ow Է=S|LnƆ[`G4؀Jr:/YywٮEkT#Y6SaZV9ֲ]7uC5{ hs$ո`I۵>H.Z95@™v,]F:O[ivl@$B-Ӻu4h9meMH۵>(hVn= 16w`g x.:ihVi]ם;r7vTn#CuۺE}mG I:MюTgG!Z ?:&<-tUC; wM8rhNR0F.]慱j8nhW/?xv,w^B;C%= BӴ;5%y4P FLnӾKg5BC%N T8J]t#Oh D.KxKuyf 8m\<2oJAF2'$-adۺnYVo#yjC>2^OGEa'#{o  \lh@*ylޒNߩ=0kOfްٽtxwȍǤ.'wtA $zf#^Q-#/{r2-n3=b=Ț#zP0hY]CaDt`7N0ޅ4Ѯ345:0}e)Ni]y~W=y)L[˰wfqUvs{CoݚunTV6$:^VXӲƳ ˗$13g#YIqꎓ9ӆMg:}Ƿǿf8躕ݟ棴A<|:Tz;Gcx,YW1C"OCX7OxX8i,n)Naa߭\FJJ2aU8=T⃟O6!N-=ےpԏK+;{qF֔ȩtlq5 HB- $nhƎHgIKC\Fc^=h`p%leފӨ/SО~ڛkm18fPۇ]uGx8hz=Z-0+'Oc[L PQ+]/7]sO~9сnL~D G8nlYޝ'τٯo?os&!~&3 W;.i[ח/x擯j,Vyh滕HgŪdffKGUEfxѩ_o"O\o>Hra!a셛QA1y֭id^DFy|2;vO&/=gD m;Df^l[ڄێ;窅iu tCҮYlޔ̠qHڸ,=٧3&[PUem׀c{ϲ IӐusk;:PM% jxQ#&Ձ]UQ"37n|C ;ACE餦dSt<1zn.X4l>*]fFW7.Fu|||bX,n^-L`:-ûرĐRq=]Q|Ԋ Q(xD)"hG"'@!,g) СVFGMTXّ?#y @&i5j:n=zʲXkٞĞíև&t \ҡY=gنiٚ=(OhC3:fC+ TKk M3Fbm DG# ?,B+G_g؎LU#ZRѣǰzJrrsfeW^m3ѵ=~J!W/=CNtLj0o,fJ),DΞ IDATș<8MNp'͂#g]<"()w==XKFn% 8=3 1 ]3==k;BHZKZH:IIEkB[gY'ӜgMю-i{)*;'UlT:1!+>YL,{ _LgsGw߲-1Xl._ĞOsZ>q3z}xzzո;&o~Nct4{>)GO틷j QA-ä MFQtr/{NRL ?Qm5:JjMW$sxARZ`ԧ@@CIb9zt#GYoz&cYljh7q&V/AX\ >+.-K~->zӟ*ktECw /P>c˚0"F*o\C/ݓ9'W+uhF}}-  ?톩cuMzgh\~ikf[;F_i1qմӳX,\vejKߢ A,aI>%!loR11䳏I6%B[Q:142u.?}~&HB5F}Ft @kd|X]G w} >;++=Mcc $؞CHϞ,Pv4v&yrP -^sr{'[׶HF6}z8i?(}^\} lپ^=zUzᐝ#_>KI}@Jb[ϰ8af_Q,"6oL9_~.exbkׯ_ۺO|V;vlsen8gs5g<6eTF#>;-ɂ uhZDaPgS'Nޤ#i-9 ڱG :$Dp筻>*#.|xxxPPX"QhQ{ۤ(ԲZ]P_#b\9m"p& ~[ܛ:BE"lY2jp~8;UU堣TUmaOl\hFbR"V#yaX ?@R[Fad4i@@UUBC`hq^hn׹4AZ /lݮS(<mP鱦#mt/  !.L!…I B0 B&B$B!\B! @@!pa!.L!…I B0 B&B$B!\B! @@!pa!.L!…I B 2sFMoj !ٙ9Of^=5J̢݆ްz ! { P7W1,ү2 B8 jR:b@-#z68R9&=W W/![_?ϓBj;yo<md<jUL!p l0cF+8{1zիP`ĸk۟e*dߪ8$B- +,%-4 06vB!E]#PC AH_`rlUB!\U]#`cRd9pstJޝ1 rBoDQWN%|>Z>n| 9=p)r;B!7%X}ڋOasE!Md6ylk@!pe!.L!…I B0 B&B$B!\B! @@!pa!.L!…5KDmF#HBBE殒ŹӦM""Q5ud)(,hZDG(Z-Tip& ^>6w.jq& a.LzF:q j*?F-K,!!`Cm!\\nN4 uID5l=|;%peE"BBl!^ Τ&չlAaAAX&Y룪*AAMrZEh46!!p(Fu]F[S-(X%B'.#@4_N!DKR}4$h12q2B4/gM iZԨuuIfԴ|Vn;FBX3z+jCsJ'>kwPeW:y绣[FM)/( a݆pô;+|4N ,{M6GOX{e^S&. Bu0__=p3hE f o\O'4 'w3@{/7"FqG\7qՓqXW<-D_έY7B/:ʃN$ګr*Fڦ1܍K.gck5'ikIe/k&M grwm>w=/R?X=]?s:Y4w7'3j^ŬvPzk)'OXu>z;nGWo<5 ؔ܋#xz3.%:;>v_ssAyy:s3/gƇ2&~h%'JG@ K>|K+ím^I97ru5a=5g)jԧt3&Y<{U\Vv(Ah:5Pr@? tZv]ho(oGby 3Yl>J}7^-`L4]#mFiٰ5Mؽ>tvk i6-G6סб^<||,S̫v eik70/|!??ne5qlHq7W/#1zsw;HF^Ά՛=7qjiO&.OF<9ਦ0%eiпStrnDĹג/&˝VpŵEarӥ3oH7,qʫ-h'Sz9]mڎ~ԧ(Y<FLCB1j^rbAEAUղ`'2zD.f\ۮ hret5k kֻ3D0uccp_m!i~ ,6oJfи$mFZ]fhRII 1Ê98wMAC_ m"=0{㒛&u=mYTUukcPL zUzY]U);5r2{LLEmKPtnCyfᰪg;8ݞh'.+kL*ֲ:]!ZC?fݞy3 D:O_kl2sVʦ)a [qћ.PǺ0~_ Lnhnxt2\s$3˭`2IK={Q`j7}۞׎e[8BB̹-nr`[b.~=C,?q4/LSOPpO1)aN >p3c@i.^Eѧ[Hhcg;mZ*IDJ&@R]yE-֩?!J|B6U{.r[䵯p2 gƼFcJ۾M Gy'iIlK=#|;olزtJ  jmZչլG]cmUyLCxyYtjh=o}.zY ڲ9˟ '5MCS:1}V2d*ŚXdaq1C`,^u \94t (eaga\lBҏmdћɪY_wkI}^OShs,cFcf9i^Y|pj庻1y p1qSʲ^p,fŖ^nO̝~De_o#LucɈ;xw xw/M@#e98y}'^:ԃ1yE3cuL"/ 7Dc4[s]cmu9Rc z[)qf\wۡq@@cֵSNP\uτK)= s??]_?뎹XB?"g0ҳ' 0E34~'; ֨O;ӾOtm⺿Ӥ+I{=4Ոw>ݧ ewvzy~Sۃw\Ӆ^t4HR2ૼپ|3'|2&{,7qՓq,Mkٹ|#Gr䗏?4>ky,`ͤ 6gJkgy?ufN/ͫJ9oi޵3Y=*H۴7ӹqɵwrToqV&q< KL\X22r evR:1v} ;=7o%܅IOo}:e7{:90I; !D bN @VV#F_W0ʂjYS?FhHHѵ!/I8 488ho,Of0ռYt.~=Coƒ璬rs^|?__z334 m?eHO[tm?k!RiO&.OF<9PL}"=ݰG:@~;?G8_;N{sH-Io[/dމ49Ssq.W=0a29MK$)ˎ%4ӮɉYΓ n\yeoeND+]ڋAtM9iu1;- >s5BS& #',AAA}64s\ 8rNyeO^=H=|udSviy(Ko!E<[6ֽB9r|@KNM&=#^q5wuB4t@xhx%@ H\8|B*|;%pe5떅B($%ht]G殆BzZn :M@S]4RktS\r^lGtuyb8{L1=_^xmŻ7wW !E8n6H'CA:("bG#FTPAD EI/K %AJB)$![f?@B I9̽wf73wvV㷦i~ ZLJC^['yՑ| "1x6 yBp==x{%,9eO;gyyNQecPYMYs/7]}Ƕ^>O5\Q5M?;-4i~ k+d7p ?yտb䷧i9Sc/HpQЌQAŷRUCCi25TϳrRԺߡ`UC-{h*6򾷆jnF? !x~.GnUC=Q[c)O=?I! {\amRغ42<mpe2ی)E|QH{uy֛+vobc^0AD.`ߜы1Y9pevbg TBap:dˎi\TjCw9>U8ĈaE Cڴq]یzQ׮FK- UG%>rgo`Tti5#O?]=Ft nܲؽ'0* a5s){>@Fҵ,b#t_k8rWۼK}Si]b![G$ ! ({ixg'kddd~:bc/0Ѐ.Gp}|L>9M}~k_3b6a+旙7u51j{tnظ,lIq3/aϓXa_ɕy~N-ǔ32y):9\o9]avi C9=97 j7#ijN54yV-:GÎ1}s/OFa|?o1jR0B3jJ6,}͇rC gͯB៲{ai\8c~:BfݎsyB{jvsĒVT ;l·|н ۴On^MC˩ PQ MC#}tyԈxΔݱ{ _WJ8cɣm2$LJǪ+HKKC422Yv%-}:GhΕtg\]PI6p+ݨaß'_㎭DY1ރ]Bgih̛zeq3aGޝ|d?'Zv[N{_AF׊y+exN|= wݭ{h k4Vt̢I$Nh_^9cv 0ՠ} ՁJGe^vszx-UJe3EӁ/vM:=^έ3;؋D'fb〧˩#+}7M4d8P50&S<6p J뮏7c| ,[]hv. nj4@&dS֡x{+ILL`ͺU\HRni.CΎWH1.d._z^s,I׮](k]B~94R`5uW-h( s$㎳BCYМ9{AUU쭱fx 3˚gbi]uDDr5{bŚ=r,VpwUɩKS]o/>WrbjN1o|/N5`^l䍝UEU8M Cn(89jWn_3vl ʾ&+ Sc8U9 *ak!-5tGB;}6 oאK4 WWWx8kh]%jUtmↇKQ~eޤu(Ͻ˴pG)Pq7펗w-Gy{ t`ZwjO+, ckj>/_>o19,Nthо J/75\qs':ւZ.{F)sҎI잹|UM?~"cad*8*<£e.p**a7|x2'8f}ʴK,ߖHYVY#h$D9% !D>rsFBC;~ZRRSpvv!888yҷZ604!D{I}SҢQ:+gh S1\CZfT/HQpqBt1uq40c컗 Šbk&gvDW]|={^4 M(}'Iij"Ӭ`oh`?κYK9`ɞ \6]vΤL ]ñ&2/gŏ܈*: ʹy.'\ V ]VUwczN`QhԯGzy>ƺO>aTTMCÕƝ9j.V]a"wW{!Ń<&L2tсv(]ڏжqrrwYk?ǯYa~M(C xF*4҈]z={`ؒY2',S 4pG^Ϳi b]7nHZ|KU 7}fph4ҶuenJ٠ڠ KX?+wg=-?8S GyLw1k|Zۿ7t%y޽{^ޞGX`ϑ){}-4;*= S >ri^o|EcUE!ȯG=1"A@!Jq{eXl"gc7i2:-%IPP=E!=#GGܪ[I().^CQ7G!Cr5*II}l%Ap@UUc,!xHF#eq˃4 Řjۇ2~e/F !x(TUb`6 @@1g6A!mL!†IB!l!†IB!l!†IB!l!†IB!l!†IB!l!†IB!l!†IB!l!†IB!l!†IB!l!†n0ocl.1XF!➃]쯌^O9:w~zB!D! R=kws$Ҝ fZ1< !嚇~##ؕڄ֮ Z6E^}Eg^㈠9e96L̬?NՊS}µd/ͬUMY]^']D-쵻9oUSu;BSהVHǟhhDnfw:ZOc- Ne, ~=`âV #fA\kL?OB9{ -;5Ӌ?aԜ혚`Y|¶2z,QB!kgŇf-kbO{#"IMfG~2NHS|'#tZOvk]e'Y8vt ʹSk(];'$ןjv9\%j&NHB! 䞇@Iu8@l=aaLpxM<+% RYK(O !4tΦ 5y ` w5sVb[{u6ύY5!~Ҿ{O]o6RhHt%B lPGmЄ% q祅B0m#SB!l!†IB!l!†IB!l!†IB!l!†IB!l!†IB!l>(4MR%cIH/!Drrt_JFQ)A(.]"!1ځxyzusP%$&p,~~ZQ"DDX;RnLnB*RnWHHӋ̢nB:ՊC J M4!c4ir?/1Z#Pԍ(hE!(UU똥iZ("X\6l v>ݟWUĩ_ 7>콙 F2wuLDG$|:&CyOy|4Nm`%D&ݹ*^xW^װ7c|4~/^4MCտb䷧i9Sc/HpNܘU ĝk^0eX=Ƌ|< |q'cSw~y6|+[UᵓF܆YxQ*So6#uie}ɳydG682gm~"O[>h>ݴ4WѨK )IJ#|3|3<]w޹h̝썇QKӨ~(vnHתGu`ocEZ=ߍ㤩4}xWѻ6O \/OKΌW63ۙw]B5c4|} 퀳e222Xq[ɂ8r0wx7a X;4gLQ@fϘ3~sN-ǔ32y):9Qc z~UQdkk:"m^ʆwvrR`;:ߓ>:-_b%[c 7#W' }gePQ)$_S] r-?өLR "k[C h# ScV֡]IYv%m[͛|9RhӺ-'zEc%s7>;.t~\9KTZqYzfYH"1Ňyt1jQAPU՚fhʈyGnZXH>3{ZorUFt۔N Pg;55"Xw_瞠i)XZT4;%UYvmX| 1r0mDؼ?mPp':NYGZ5.xw7<\a%bs⎗w-Gy ;≎Uɋ3EQZzN·#ט=cጦhJ-knESZ䱃l4r6B\rsFBC;~ZRRSpvv!888yҷZGh~J)=Bt74+чX9e!-*-rJ&0%c55jFu}M2X5{ ǓLlJN*}UBXX1W^Lb$.~9H3FfVqW1_sׂ#Pz@뷎zݴ`){NԊ9?#ߥWu s&h܎r6Մ),9W>u1K_ަs~zI&`t ]J#m{]60uVϜK'}x~>~uxPE_F=YkγS'3Hs,G-{8!t={`ؒY2',S 4dͯ{ 3IOG>Cyp2},X3|_|>C. -_T3ԉ7YSQoB[F#m[^[v? 3 2!՞{P[~pHQb޽;/{{{=c{n>G Y=B-!D1wzXH%b (1rbG Qgc7i B/((螏Wnխ$2pq0:usP]ͼJRre)[uI%MfffQ7G! h_<=nyЃ&A@VoA? !TUb`6l$l6?? !%ri%B0 B! B0 B! B0ր4eGzzi89:䌗F{cPG4K_z5\]`~dfURRS8q>ޥ.P~E BbM4N+͚0)GۗcQG8OU*U0 D=6C%&=ד=ƳY#JKqpp[B@nvvv @̥Bl XLJC^['yՑ| smOQ*reN4r.떅b(QW3? SujȸQ-nٳI#ҳG:ϰK8]Ɠ=zйϫ ;P տbǨ§[ sѨ8Rn[DPsѿB _NiQ- q+VQ0y!KGw-|2q1`ϊjLj6M)Q4a AzR67y^Y~1"H0;*ɺ22zsx-Tl̳Cޠ{U@%>rgo`Tti5ዧaʮþ9bPI?Ys<)V'ܾG0W^e޴=mߧ9jRp8u{Zb>p>ÙJMb;+yK3{}s4[NcĨi{owI<]Aޫ>{&ȭ4i،R 4nм[(}HĨ4X'_M4m#S,pt9_w;`̉Qߏ Y9yCeܶ&(~3-xpeǒ=Dȧ={nmUqZ9uw^Xܞ([L\ rWAљ?XoCUBɉ*`g}+r£y9.sMVg Z:WҝquSi9t5=t?O܉?l%jjtTjv ݜB9E^o!!>$w*֤Ye5mh̛zF;z~NXPNt|=ܕכǾ=Ž#{*慮lxg'-{wu`;:ߓ>:-_h^ }8=e:qՊ(w4Md6Q|VDHNMykk\'3i6k*<: >܊6(N8;^!Wr \(k]Ղ@_~ß3,It?WwH9sשK$_i[Z xp2,H775,~иv44sw3jW7o5+X_i?RRSHːG UC4t(vF{FU Wّ+i{XrO^H>_ΰ[`1j*Ffqhd>z%1,<`E*Q6Ok<7<\≎S|^Pbwq=\kt@R,F~"ry֡<.]FU]\7Xzo.Wq˻ƌg{S;≎UB[S~=rCU Ι(MP}St%DssA)GGr t0,`bwGx&9Qu򽜵J)=‚/gtF;KR浧 WEt>:f;\CZTדwun=,5z#(:}QpqB YYGS16Y>s%:h{* Po^GSk,'6.=I7cwS_|4WjBc,;'ʧ=w^1 TbggwcrK4f3{{{ f@,'W`Hg?y~%b҉Z6Vwh:?zSOe/󦞧Fy-_"!rkk?ǯYa~M(C xF*4҈]}^ed;ԛgz*%5eN&Xp-D~/ DOgcL|b5?[vW{JQ zڿKw77fo+ٽ7@a mo w7T!nϲ? |};_nxܵ&!goTt~TkО;WEA!MXbvz;:v3 +(BZz6jD%&QTEnw T!~ @l%bbٻ?5~ Ş(kHJ8r{v~e8@{ A@QbyдqHQ7CyBa$!6LBa$!6LBa$!6LBa9D4Kq&=##щ2~e(SП)A@.@`@<9BQ8u ?_BK(c [)7L&SQ7G! [)7sA BgEfffQ7E! j J%FBnǎ4iX; DPMPU!֪|0wiex !J4UU똥iCI m5(<(_+=OrEӞ[h9?9+ E,GXqFgJMgts-|{3| {4 z04g0B ԋ[ 'ĞlwkRTYȹBx@Bk{RUzn`p-K'z/P޺};WTJ#IJ#|3|3<]wVyLSyWUо N `g]z'"wREI֓ ›߼'r-voK|oL%;]&g\n&Dn? Ys<)V'y^x:u|{f̜pvrLFF7#6_xC1 /]ϱGoX!R݀S?cʙ3y85bͩpb;4AI{8bB©EیQރdX4 ]VvuP̃p J*c?-xw֙3nNy  F5e ]yx'+vi|VZ{77Iy_Ec|52Snݎ׀ d7G< ОUrߣmop+G̥V^ANprr V]ˉo;=歌%w`ݲ>/{ΰ;:AH᝝ԣ.zz $Sѷ&,ຢqhK] c}$~? f㈮h\6)@M?OЖ*4jv Et&8J~j -;߮!C>oU@7enD=t$^$:zq?Bh:Vq92׮mvy/'RmZevݤ;vD|qރY $?.ǘgIrs!;g1jQjq+.FgBW=P-'b̹=nJ+*↻JvQP]+|ej59ǎ yժZXu)GW33|.iE@16ŊP>oucޢ_j[B>f BukHNNb%h+vholpC Rŀ1Y:ZoZ&"g1E4D;k˶Rz1 `;. ^y#2Z+sgvj?mgQ׮-&ɀ.u w&@*x-iG/!~j*(,( [F_]m8yVC(y&b콘ŚI],r>f__`j(:yu'\m5X#=Y#[=F? 4l]'MCUM?,g_̖$d7 mCpI_=~统ܬ6F-eUf{#F{SqZK2}v9RȴjBᄳCM-L2`t ]J#m{]vʽF0l,f| \ѩj2?&\֥/Ⱥtob R*냆Y?dߐ=OB"s:тK,5ʿƋV*x+g޴.'_7s7фgEve ixi 粵iUjTTѦ#ڀsޤ|*Z~_4mzmۭ0R66h(ʄW{v@B! 1!㟩]!"w&88׷KrPo 7K,;§1sg8SS 缆]T#1yOE]V_c@*8ݰFZr>t%J_<{xe OnJՐx{`;*]:<;R1yUr k~ܭpš !DBX8UU$Z>NǙ6k/&] nn6_; )ϣ!-wZ߄/`,>1G1U$80CGH?v&=0v34cԢhܜW1gh|-0l<. #]Ln1cV<լ\$1PZ{>5/x1q$.g̟; `ԢqL9S!|2cܜ\k-_~]_==Y=݄oq|أ*N7g !<^a̲aJa.|:MÊ3eM]y5=:2,VBR((.bڌta#nJ͗_|2ޡDB+BJ+(Ӷ6Bރ,'ټÑt=F|;eS#窖žviDt~R9KjjTs7d]椷x^Y܌v9Ѩw'*ω=iDYg4mR{73Z4Z9[;:҃@iBWqKAMLoM:zkUxkZǐfyo]zҕw{Z/Ə0ӳ<=fDJj-حZ6O`_} 1- mϊ.DǩP>D⎻8&w%rϳ6i~`:_bx)r*ry֡<.]F=y쎗w-Gsk1h\ xy3Gaj qܰ.ꁇh_s5K*^Vܝ:Bܠ zf3qqq(|:#\\\'W4 wL#z˟5xmZ̧c˥;hʙ+9d”t3א֨FP=kǯ^unԫ%1֫[Aλy2c2088ha?ƚw/b콘ŚI],r>yg/S0Y5g9}F/:Nz^;zp<Ʉ)+f%IVw}!X5g-QfL'X9g!!Rlzt:>>>$%%qU4MÃ*.\ɓQ|㏈DTf8Pu峲=)|FWף EAt` sʿNu3>g OLФK#wKaT5aKf1k˜LZ>N^&0 eEˠLdFp)[No rgӄaqo cLϛ˩CjblL2?/0{j}>SgXB@)Q4a VBg0ŋhggGy|-u' ճ EA!i2gxX,4opG L&rDcϢwoHy f##h8U$!D iW\)f®ut$O6,<@z_[$Bؔ Z_?yrhW!fv$i7,ܶźӸu˂_bb*߶Kş0jvLG0o,>hnaۏc (!†GHرX7fҺtJYNG9,vеTm9'{:WڰB űuߘpⱐҹѴUN8y7$\vS9I$&ksýTrJ)n2-y]N$QͺB!obQ3fb˄gp}rt3[b:7KؽD@|2I)(.s۬x{@TB!G@V#Xf׬b|,QΗ2:4 ڝ@fF ~Y\2zkbX!!†IB!l!†IB!l!†IB!l!†IB!l!†IB!l>x`4MR%cIH/!DD2)(Z\DBb*!Dcj]M`@Ja29BQbrÿ? Jt<,!DfZz(ìiu3ر&MX m1ucl(hSy i%Ji1? IDATSUP,Li{Ϧ`@y]E p=ĭy!gzX J>j97q VUECCSU2oޛi`$-%(!^i {w_ýq5dHb5{mEm-hmZ{K{޺jQD[֢tt.(b Yf~dH$b#d;y|g\Ƽ#цᐰ/Ѽ{'aFVkߋ @+U8Po\Ç1pd90M2a/Z5\l_ԎO҄Y^[F& \E[CqwIj踶`%ӂgbf;xtX6L~˳ֲu)ᵹ4H^t"#ҥ+AY뤤9r ;}Q(; z痽h$?ϛ죭,yqy~q_?Ϝv<,zA,} rK猧j&Fj]Mbھ|%%o↤_%29vLrYH0Mݩ264&'fW5_禿Ox ?2ֵrϵ qLہ+ dW1w-`Sk&~XY}я~"h+>Zƙ3g,d>Z#GS|ymlRHtr0/۳_^+beH,;Ҁ@zIЌೇftc0T=Y74ٗN/"@U%@e?5@hk4[Ȝ5i{KE_Xo&NLM \>1|g'j1L˴Y^wuNP^wvg׳cP-[d!08}$9H2T 1贁Îj N$Uԡ3_'SL;t>4=DpiN?uÉ8[p"$Z'9q*Qwd+ ;>ʬ^Ow I)Caķ c)}͇:bN^nҐX֤˨xĩ0jή_<Nj!I&#紉 >gzf#[8 ɠ:tAjC___tkVrd".²,J.M]my}Q( ༆~IzvBRGO0rL8R!-}C,?̑%,>D'reĄ*2 {7=*fT#ҍ4KR(G<2^#H )+'x28pn̫ ktQJ9E4m2F*"E ߟ.]f*N%\.n" ǃ-@29SԤmd_]w한iц>۷JaŬLtJɒٝ&]:zX6C~8x7c||jz7+f|ȏ Oً\sdPݛf8bF,,GuZ7Oa圕8"N1[Pqzs93;+~̮Diܵ$_׊mBkKIy-0p+ԏ~cߟtJdd]:HPP׵ǩ ߙ{L9w gD]: J'' oLe⽳9NY7 M{wřJٖF?غE39fy(])7}y۟xsĊ們<)4+pBCB (Xi$LbД -i'--#"RbMвh)HzDGá4\*4x<ݗf6 RnwqEDpc)ؘ)ؘ)NKx\qwIDJs8pёїmYAY'X=<0oyT LӃα=ܽ'7'^eԳ3zXH۵c'exw"eY_$?^}rz>\ڼFo>t0^\Ķlڿic>t;?|g|rV:ZeΔmp|cr5컮"2^s;Я0-lÒ|)\lEnś.`%ϾC`ɢŞx5Vc2?K,u# p&T772a-Nb ~3.{#;$5e'h~;̜mI(Gy鯘yy{6sM~|'193?pN_Qc#&zP8?-]G=֒A#AyV,Zs-cN[?SKS<:33w%ᷔbkN19}/ZpkdаM#x {w#,?aRuugN;{-t#;T77&* f}n$fB⏲""OqvVq~]=UjR-Um)gTI۽/^C/DZj(o!bE|K >ڮ= '1u_F9绚Iscρ Ԭv}/f3xM}$|&OC'LkvY^%o8&q~n=ۙׄ<}w'I=%+}I{KNqN "i6TN6-`II9–ea3\Q1VDũߕ70w_z.RnSwH}K~ehq[,oV4i|I1}hΞ}vzϚ̙ J6֡]Wף 1n/T1{$f,uc$n > 5sOO:n7kp /ǎs(1j}xs.E`ynZ,w/8jx{rtw ߍǠ9n, |o`V@E1Yr>o_a۫0.e{F(B?qI,+vaNz,0B:+ ;>\z"80g,ЋV2-p˓ 8^C#G_# ?Vcqu>t c9MJ@pz cP/ qCO3VI0+;1k ̔WQgB,Q ""RA 7ڝrM&~p~08{Pm;_{i5O2J qj-c|v2_k^Ood4Tx]P3&x:x EVLP X鲔5"1(]+4?1/inp5ϛZ]z u3nfGf >پiBTB%M{\5IbgM_{fQ*({٘ہo@ \Gwؒy;ԤKG˦}q|w,2s:WnYp~3i]n|ʷ鳌[l޻V3х+7ZEr퓇̙!ka8yh$9R²ٵY1_ΊTKq8Ń'0[eGtU*7ED=N d00|ނF>Χ.]0y>2`X^uVV=Z#lׇGТ3<ܔChcTЕj/ᓄt(ъ&:yu$ﵴ şl;1cZn}Ӿ6x~wQb#b 3 V|*˩{c֕[xMvbJ9{JW"a4iJQ^[>ܛ*WT Qi7k $?Uͮ1>?<~}M.[o:?ƻO歛iָY>[ȅ\-L}PjR{+nQOMWDD =H-Χo)\j@DDD N>]*>}8 RhG8^a@DN>MxG])~RSsz&Ƀ# t^ʗ䍂___"##,\<ֵPdYM+""GsDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDlLA@DDDDDļ -paœ\X}BDtT4"%C~un 'c4lАraٔ\FǎcǮDu]k!?:7 iؠ!!eBp\ٔ\F!eBÏ?xƵHɐqdʅV͈ez +RƵHɐqMU }beY3N5EJ )2XZd)m" r)eaf;""eY"q-Rbu\`CDad3Leeps+):RЛaGazm$K~+z.˲[y饗X_)_)^U3g2uT+qo`_rûOη;#sEwݻ7ÇgȐ! \rEЩacsNBBBDzrduodBIs7m}Pb>-Stj8fr[EHHiii|'DEE]s3uZ~# gD]:6{:W#eb0Ku67"ԀeV>}:DEfҲ,7n|^-[zY]$|L=Ư+_eԙ2tزP(.:5Pr*T 00@:D6l sƼv,6 |[~* Zi ZW?5OppaF͞={(_yW~^^X; rڞ 7ﵸa;ΞϪmIݰHroFw<_}w>CfyZ:ԡ9۶8R&MZ{OYO2+UeY9UL7O?#22|$6,^Oa`Ӳ/i|Zi[cNF&ޱavPj x^U63Y}6_ƾ$m;6~^/оF@3VQ8o#|o)ATkُ{P'X;"*ijQ .dƌSrIb͇?S&6=f&3uZG4.;4\и.˲۩ʕ+sqСCׯ_a;HC>/督71f3Tg/+^7U!1YG/''V~[6M[Iof{O DƁrY޾RN<ϴ}mxtc?ƙ/2L}<i;y׹)ɂRSqF&;W#wNJEh[f[9ڍoc%|s3 #@Y%c)^Fg_'aV䊔 nJ V2)]#dz=[p]bFv= M-gԍDMJypA{v:FpP{ɨoiDC\YPת-4޽;:u"..-[mibfǹ,w׼ΤYnBw _W|0'Ӽ8NQ+,{&XB OӽV iF.YPuN^4i҄A}e+$%y1rDN N1I?HXS:43N'>^3sedl+pMŢ2;{%з5]& /_΂JvСCٺu+-̙3yjSf6T{ocrW` xWU"4ǩOyzX$DBLj?Ū8: ~ [7}j@0M35q]pyӷo_7nv_|Î*Խ %!QLjObUJf! aae/X04sYk{N8X8eqv^{ǍO^+:7̲,RSSiРk?t2nܸ\tÖ'߿rUdXX4_ Ҏ`lTea9jЮf.XbvЋvOL<>[ji`w 4+B~.Ը) 2_M M<ܠFDiԀƵHɐq UDlBOWйDBsD>Q+] B,+Ϸָ):sS@@@)k!r3 <^ZdϸM@ 8xАP-"WԴTO&RBŋq-R2g\@AlhYL$PDGES6y?W"%C~un ^/DGEpKETixt{k!?:7jv3D2"/""bc """6 ""bc """6 ""bc """6 ""bc """6 ""bc """6HK>U"y*^1\ϕr߬شU="wU͝:IENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/screenshots/screenshot03.png000066400000000000000000003274231470053624300260440ustar00rootroot00000000000000PNG  IHDRo /HsBIT|d IDATxw|elKz (ISQг"*!V@,)TҤ) ^I eYZdv}; j@B![&SK}v*@hqߴ Pz8& Z(B\z,u`!c:*Z4Kfܘ^hOt|C+ 5syUUw/ߡQaE>kPnنQNȿ(B<1v]T^}i ի\jNBl0h|T22lDc>+fc8m;,$`3&C:PO! YW 9;yjlGk$ [KΐoLؾ+Nl%KSaq5t=xĵ' $:C(#8iy ٱC2ɳ+&|jӾg39II,,ŢwMꨰ흛'(,W&{OX0ITy>qnbGQ2pJp|R]^Vls~.gmmZؽ:  ='pkӺlc35[6p'3myVlb4MO^%<؟@[UqJ]Ŗ/95b:D.|Lc啊-nrZ>w{?ywJp٧)$ثӪg X|׿/q37vo? rmBLhZE=ˎ}GfW47; ,V#=DB&B{[| d}ݔ "okQ_hYٻgȷk`y몢Dxʊmk|91r˗;=9[0bHzwhď/~O?'<:&'U[λf[߅@R}[ғp?oZV⸟FR%ݎ-j]'4Rc&ܐJBF(CPdNGZ /wߞ$ea3Nc¡BxqƥOh$&fH`#jF"+^ǂ5Qgt9}[0e~[i-a}=ijș̌u[xl0UvJvoR9/Dh_ ىYY}yTa̿7Gpa-ʩU-g2˭6/曯\rӬS6g D8VR[HM|V|C ӓٲa+3fSZ[F~./ו(J$b[l'Vnb+>l};+V0~dAp:!M{Na G]_\ȱqAXu5:9ͮ#gǎUëZ$}q,l_W7Ϸ^ v,^¿4[sXL>) sU<<ywwg;TGoٓCH#t~w<~V{;yv>kGN\lױ yqqX]'cS^GNEsqMs5<$f?qR캡z=듩vr/c[wV@*/=hg6sz ħQw@ G e O,K}EL<[.A_ǧQg@(,ʺ.'~4!f-fUUY>aߏ6mu"3z &?9ZѡLxNjFsB*/oX̚λGTl&oUKLK9w 9ƚ7g8y4yFF]Lª*48gJʷ.eNfLx|n?ЌAatFЬYr%SD*u1'oYS8mzJyC`",4t bх6Ü X V|xQ|r5_n҈Zȯ9oZx7qC PbaAo.>@ݛ>,. {~Σ#v6ϱK BA&qyDe1s(06` .-c?;1){|i]G8-nNϳbmލc_Q{QN^lkH}k²^Fpx_!b[ֿ{G[y;m `xarLLTxar` ,?Z\(.ϢfW5, `ή9[3{7xv@ шݞ["VU+S)!- Hg^NDiTbi@Aq|~X-D3oB 3Π?\_sH@ #fhlD@㟤w\"l'9ܒ4m?5cEjbFa|s1jlu^5JpX8^lr0(o}c=˜gVɿCSs-yd&&Wp9HKJ#%bYJ@M$F@L3Yټs7+f42)Eh%7v%W|%/3/ h9(et 9$~.caZ?maO$iA)O,gY'\V?[nװ)^+:uC {2K^oy}Eh /{;m{62r&c[H ć2b0xhZQ_I)lA]XWb|9xNl_|(#]=Wb'bM<ۣQ+*S iԊ  V`W78ayGݢ]wYl%4|`Edc?!@`HA!Mh9f[QAyDGgFweO^7˞ȅv>1}C/KOrB.Q5Cid>A}\( A9~&cisbÈwh>߭?O7o0[g.NWhį;G.{6u;tӪԃpT VA'c}'Q*J|?bIV<Sr66S*k~KŢb؏I&480ǂBˬゾ?(a+,k;zp]ƺrU˫J^__kKEmJw Aԭs)kk?Ba=jcqyW-ll٦ް6f%: +4 AAn^*DDkۉjEJ*[4:.p!.}oL LlĔjEJqoEOYTK7g!=d6Q<ڽ#Oɖ#XfBcJMs1#ٽ[hV;#D|v_S QmqVwpC}a3%I-3ZRT̀oX tِfJ{.> C]q<Ҿ?K{뱤UAx}Rа -8v SnYG)sHq_S9Ʈ9YYg⮛//vvZ[ֽx&,3 0i_^mKĉ2r!pSZfEU5/^A5 QoZIN.Dl/whre1vZ߂>?^! QcZPX9ߌ_d]}P\i;6,)GHOL(c¯Z!aHu2e/ ܱ۠sToTK)g9i?HFJ*Vb ;Qe dKzZfr֕c8:wfNlݷŮ(nEˋm3}r;=nG0E\@D,ѽu}~um1xMw5;-Hy{1[J%J\Ps8|*Uæ($jt|fC/#OOJ|}P\CZ)x(xh9q;ȱm9SWl٩+۾I }nhKCV4z/>;>{SF~~~nn( G̵/ffpWL(9ڧ5q;ȱ/!Kϣs^Gݒ_VAU+3HX}9u1Yɧ@Zڷ&E/tzcV24ϙ%xirǸSc;:ڋ' %fu!p@8jƕ괜+-0΄Ps߿bkZo\lz Bw'8B&B$B!<$B!D@!`!L!ƒI" Bx0IB&B$B!(ݹ=g$$Z{av6&b m扻RtaB lFN؀ß`djZ<9s$قWX]+OӒPeF&IW4k>쵇Iη]|ߢOX DPV(*尜ǏCqRU !׵Gé_pЦ״ vN.y1%c|`ֳex4vTNċp]L5f[/vtz+\!;C_Oa˼/g3a`- rD! ˯<^ˆiѓݛaף]Lܠ438lG!~/Y5bq^xգLji~\}|}#jNw0-I>Ck~pL@;PçplY4²!e]X&}ߑ>^DmMC HT ;MߘwJT`$_B!6|"2l>޾eq@$FR ||ZH Bb "J%Z:i,ȭ--TؼeS3P2  ҂NJ`B W!p3EWxSv>>]tDBtC" I"!IbgT% 3H BAL'C%}6~oij?͂5>_.ҽ{-Cb/itvv/^xB9 ֵ&²^Bҽ[- Osغ{_m\! \o оmnZǦe?qb[^?QLb v۸cPZt [| =!=xz+O ւ).au+h&31wX-R<<72e1 ZB!*Ru?73򖼪n K\؇&}1[mz%B\sU+bŗf[~";B!n(Jci`+3_IV|jxB@$u{djU!ȥ!ƒI" Bx0IB&B$B!<$B!D@!`!(i I pܼܪnBQ|}|](>RRShѬaaU!ʥp#k]UpZb!U.(0F޻Orr l6WuSB` KrB|D n4!B:/Vy"Pt7$B!DIȈBQiVuSBi 0"ȈBQiTBP@ш$tB\-($pw 4(#Gʝ.s%/_#č4 nSfpK۳u#MfP !4JkWakfȄmhI MC. F4M˫4tgMC3vf7/ըa:ǮewS㾜44 ؑWhȡD,<Ҁix{{8j"Z MunқNho,U!Kxbpچ*`ۼR 4 xo8.}2C>]yiO?9~:֍L|{"'~Gn^RA y~C>O.w0d'0o!RqrRz>gS4',ŖL|p5Ն-J_G=ŝ U V}? ֭m*ỤFTUD@5M0Leߖsߗ@ >{/{ǯSO;F)|lc@efxiʣ4 iwJny.j,_ rO^6'adfYc bɡH UU+eD@pWWE]bĻλ|t==<=Nw4ۏݕ ;YHH`a1#^GiW@m?4][9d+/\,w b؎Wq-"1Ԣ߆pKa0ib+$/ @@}956c#}hB==wS}4 2Ԑ;%MGypkR0jU$_:Ҩ;2v t6~Nx>{M lG@L\ubӌf:Eocx@oO%9ԍkWQK?.mh?^ϐÎڢ9x BR8hG+}ףHp@ h5 S.DhʄrTDe] $$jJ I-g]QETUpKEMy˝_&ī.U f7^&s[jR!֬:a. IDAT@F^d){޷dތv*]ERT>=وh)eczbYQ ]}vrRv'c:/_,cཎmsY9;Y0dٜi۞|[.+?X*pFGZl;ߖ/~Psab%Glr,*CՏ͂i iJq#->Exӂ).j xtgM/Ї֧4ߕPX:7Gy ~5h4uz}=5[hjp(ÞcļO$i:IF44@}ryyLÎ2Xt}o,&<>ב~X/@pG؜XwپlօUnTj=bFޒw+sdiՆ*_(^gҤI%3t. D=13h$7 7͎w%|xš1"PC B\2" mKΤŀNüm ut2" ܇p xKL/r4]t /}\_jz'!G".4C(=YVl_/f˛.ܙ *N@Q剀,(nm !ʺYT!UU=Ҁ!eyΈrBQZeWE$@!(EӴJypx{{K!ɹyx{akժsY ,B|s>Ԩ^U@HpryfsU7G!r^^^TFHpvU剀n'"ĢBxGU"{nԩC=EMصkSޚOVͼĺ ۂt5^7ZQ͸>*nJٟ6pܞq o nsi@UURRRhР6 Rkw.`BZ72a@z/2 DgȄanڗܶT| +:M_zhrׄیl6|||0hpUU?i2 0yc 5k8Fcg^0vbNU QId%i$gFѥ7JgpD@Q2L?~ a{ӯnѺ ÿ]C6Qdpo@%o|3{+8Cݗj-6hX4?Mf~vNР<7ugKdtn{xA9bndUD8ٚ1ߌRA y~ j2_'b!R%Mӭwr\<J{p9>i̟gT;_wYkBvq<3K~:3qʎZ8p2hƨYx1~mk3t5Vơ&3D3~o>K?9d)MfG/)yRn}V/xӻOf_!9g}} dAN,/ZΆ*W;1nt̫qiDC#^3N! U[sp'p5Q C:]AG d-9o3aWK唿]rsfb*x~\2.l*r;]íEQP/ /Y¤75?ȌioN?q} ` !n`ob0<]jGM޾9\3A4 6]#WJvZDxPD[8Xz"& 4 6 Uv3ѡPnEkCA }ȝą \e;̺M> Q|!@iaf_>ܟ&qߝr!^PG'쬳t;Q" Ŝf`y%-mOn<@  6s!݊Wdoq_Yak8;.vΖ: nK/%^|?,,CHp0ZPg;6 A}pJM #߆ՅE6tDG\ʧtQg %T-䤃,zbѸxؙ/LhpU#G: ҲÉ-h"O^L}e,MX9 vҗZiYĆrbq~u+r1;cU]c٦ Zv:ͦ<,}DgЋw$~M(iJ*ڟ.gt!U+ilWd+^[%vEQSoOsG@Xh(3ޙJP` v{%3Q|nI ]>ۘą$bt&Bߐ`› rQZŰDj B9_H&X[KDH@JKtC^ "$ dj^t%\]Ӧ]9~2kC\ p̟'8T #70GShܗ43 f3vBnm$T}mX@aeeLp,w%K\YuqC:;^_nsi`0`ZIJJBQb7bOxxz(1+c+>^:,X"vg?ʲ9+9n~sVۡ3JoDac%o16[=NѠKCz;#)6bZpxYQ - Q}ODFg?J gCqʭDy`tr|jnPZMq%b5?Wֿ-̾14o&>;98)VZсg&d^f}%^'hV~ݬd-ؔnk?*>u58$:/ }XC:L\ko ' _^qs(5ZӞ|s1#oɫ`4ٶmΝ+&#^OLL mڴl6Wr ŵNg& 5=}J$7vCXfܹ3FK- :qlp^& s'Կ!вvJ~׎SdV3MήfEߐ!8K0&h݌*>:=o6 qqD@?b?UݐdĸU !.[B!|!L!ƒI" Bx0IB&B$B!<$B! !p+ GUR-QQ%BVHIMEUusDJj  :2%BryZ4kAP`S%m%BYrr 蟱턅UʥIn`f}`(sIUUTU%228>7eL&-_E:_g4MCӴnUټy3:t7hȄmho^ݻwSNzwŔӺ]knn_EWWeʅ3:d+ ݣܻKMLQ4MCUժnU)JiFAZ72a6X]~91nrB lX,r_˿[u1  e[o8l#q|^0w1kNqCeh޼9ϟw)=y'<ǃCo/×ǥi;i=s_:T}__>~#g3 uSj^ϫk;ڻ=?Vϻ4J,W9"l6>>>rTUeɬ'x)ԬQܸ~ۨI<"ۭ^cA*+UDapγb{l}?jEM3leiWQyc'-Tk*Rmq]:6(mt~5>R%׍eN)RӴ/ͨaI`jNSՎZ)#7F"`>B(G{QG &Sg^]8% kZƤI+P̘ 'y|Q=< [g~;ZL}b9M/® q(?y8bۙ8 ~.  a{ӝ_2T{h )CΧZ~:s0|;7ff.-D%W_Wv'3`m0QE/-p,YC6QdpKY/uC2KWugXسv3#Lg?Ѵ^gndp[%frŧ W~?oT~ >s#{Qm~JaR7NK*|"i5')J&EŗO? "<޽wqt݇>荷߹p~}jಃQں_5Y^UU%Z4uw!}ֺ ؛sYc^Yq2};mו@-]cݵֲ3<nJX`L\p Gca~Uo0iE7Xϳs^:|n&)9iݪ$&&UXee: v }; =?kʡ5Y8?Y8v2lGic"Bԡ->Ưd]#ieo~w e񯆢((ɄbobHNIa 2c>t!*-\?/s34aa8(e _WHW^zm">׺>r/" w-c3}/q M.I;Լv-ny}-j#Q Yk ?nϦ}JzTU}}<2&λ–PeKXe_/Σ s&2ޛUǓwVг{/mTѽWc$FC~CLheqiBMDIҝe[_ Yh$'dCőcghދ=DzMtMoܣ׵[k`0`4T}%rsXۋ1w ]*MV^)z(vDЕ߫`0\fӹ>Pkɰ[2 edˢ3՗w N шn.K%53-?q'(B bV2:$rXSiU}U@AtcgDY'H b)vؑ1󟧳NnsvH#n2FzWηi/ץ-V2T}"P%d'SAIH&X4@F\Dsd7׆6W`› r̟'8T #v;Pv-ޞ2F&7`5,4L%(0U.}];ˆ3ԳQ_.'N[8kvNn/K3֦_ځڙrJkgZf~=[K<~IRܠ\Uۗކ .IfBjJ;H !DXX8t߾N( v؈F=wD ^˦bV_wᆲ^̬LݳOE04K\V|,ӕD#eo1Dv'yuл峖\6ʹl+oX/ҠkK|kLJRR۸Ø֛DGEž鮮7/w'f%V m8kwWL&_znIÞdXrγQU6 _q뫬XϏ_ty@ӧ􁫪dע~4=ŏS,,Y>g:;]8VL/~\Bjc ˛޽MwWsiB&=4~>1TBhe8`hBL|vsW[<3QόW'}4ް1P=U)-k'_7[wfsF4X,zoz'u\'}u픹Gu{1IBZ)'Aq#7Tw=1M22a@&Sٵx6sVmHW%:G)8.Ŧ+ :գ͹5<?/oO̝'+_܆x4Oyd{wZݯ Z|"`Y@o=sH콷sWI-$2/p۽lZ55? @@ij$SgV nnf╷虏`<g=Uƭ%nףyLaxG1e 3 'DZI <~W— lzy=W[ C!^UɃZ5`:xŕ\ocg=>m|۹0P_z,'sNm͹O:1/}fJ;W]G(~ң~Xr8= ym|גmyOޒqgv`s9jj~&8_e9;h_?:12Ψ/mF'&T'$Z[*=?{Js:6; q5YP(*II^ˉO~9Y?햘[t`n/ߧeg\/L}OK\GRԅ< BN9?oxu>/7 Krc\xI?ᵪyxpDbz!Ï<Ĩ+~ef c>P=rF.yݻy$x 1ԬJj`/Ᏹ EN0=)a.gd |_; ӟȩ!%D#GǠ3c+2oO3r&‰d&>/sw`xe%f1Ƿz u4Oz±= y>]wY''wܹDs_¡fu0pW?o/| /z-t>?~1yƥ%7ػj!wͪn($чg>o8GrOy8c]Ogn$8{MajjjV#rx3rp`?mU_qPٟ}77/ygiNe%g@W.d[Ͼ_{s9}L<_jiff^~/~y\1S?:`^w@䧂gx~ȿ_+?VCW~Of} c*ozD)uf5{_w^tsҗ{E/˞ ttc|◞w ]~ ;JP^/}-k\?o~7}Oy3^G]^<.zKx[>C~^K9M _xQ~g}4IH?sهo.u'{7sP~k>y_mb5AG{ M|_o}-Xz x҇>=pGw}*𩿿_ >ϟ}{dk޽?`˱lu~!<+n<ԏC- Ԭ6~|=/}'D'_[4 <F#Jq/?cnaS.siƬy(㩿(fM6>|nc_V r(3eI|.Q7#pj3}xo\}bCY<łwXĩ=^T<+7~||=t2 âY'4C'[^y ;x O 3p"&rCTg(!~݇s݇y_|Gx+ oMMMÝ_ Foͳk*pso1&w&>#^y62/x>SxejmֻJ.>#;K!ګz?0Dž_IM!|RK555_"Wcpbf>?׀hJٱku%E) $7|k̾>}Y[/,Bcmg]|?b?vP~Mxetk9jjjV#!5=O_!Ǿ׽dB}==OR>h.{ř}/ ^p? )Wa˳5ÍW7%-oҳ_p<r27sۮst$tj@@MMj=9} 'l{cy&@iO7N;}3\_yֿp :oh}/?:9sN{9^ ,W}y9K>Yգ?hf,aYSSXXX8bWI 1\+yͷ |ǯma+׼?r'|׿?KF"7l;lՀݻ7rҖl9Mne|W֟CW_4~< Ɛ,qR4XISx'QYw}up{#ÿ;@cTL+SEywi=R8|C<TMΥs:fmom7_A]>) <\ x'?k9w x%̈$I3Qu>G 9hCW_d`)GxIf=D2yGi=BlhRP80MuDoWs;[Jbp͝G YO?wȦH-Q a5 !:P)#xZ2ǒQB^:qa@ |NSd9 W/duc—(WME UF ,J (%H%KZ Ɛ )A(Kݔ T F g R̪X xd8 EI' 4]Lt)Ki˖6(Xv,(Bi&8vˍ2mYgT: lQpׂcS3)G5iG SYX#€={LhÚvW4@4ZEHp4gw>#$&)Z )2H Z!'lhⳔ8LM4X\la| Bh3D9G;, Ϟ#DI  @$̦0P/K$0A#lS 4"NT5/o': #eFA =ZzFCIis'V-2=Ϛ"юvhʩO65K ̋ܠ#֒`)dV1tQ a*/VgC&Qr Vo P~ l|8u4Yĕ2'Ȼs%Z{?˭6 P@ ܱaWqlN\3c󸩀;RhWv4A9t X*,RRRC"t$ ~jiJ`RIϹceyxf%|1ˉc Y6ܵ ^r{ saQs׼)EXDLE+tsń;pCErJ :43d-'֒;8f9Le┦h-ijJ23iL(;#n ͉dg %x zr-'پe9AFP0ݦS8#)N^ΞwW>Y8iW?@KP#) [hafm 0 ˔UsTiaqSPzXH *_M!+7}P9mp/)tC qbXPU*rÀvH!QK,s ZOsHbHA( !i58ki%A(,u Z ="-J7"ڹՐ8zHz唽mG/X_b,Z Ť<,lUG!LU0=,-Uaqbi-Zro 5`f<ʻrvDQP3N\;oHU5"l+9lHK=S$`M"Ic0 OQaz,P\~ cT L5eaH?dqoW+Ljj~9$/}K-3Mě7hE$ 5 "3j}Ń{<BDUP)qΡUU)9U]J !JxUeUzOne*:PB b(@U92${G?wƓX @*bBPZbB84+$UAVc!+ S퐕R*R"g958<@I@K$}VHXBdeܣI-P 2Tu BC-]%3xh: pRZe<%$EYVUìELUGAKxr` A[V7-NHք#q$ %eY-G[$^Жb$= R2 EUa 1 `%yJ )eVPO@7`%ؖ:NT(L7`$G*j)pXI\qa1BdClETJ`PxFb=}GVzAU+,X#pTuMtCc{Z"e9=9mCH_2(a4L5$b[1{ CVġWxam=GJunxSazeA9-X?m2C8ki=3 b9{fO2;wdNyDrڔ-XqU}^j*S55#) o()0!j*rY=Xi5 r0ia'NaUO&g4BRz  J*j=LyWU= !R)2F (qDJgSõqժaaD %+Y:wߵgsF"he=*{uhX~T0:>N7:J+EI9F䶪=`*`.XA$r(J!!0pYID"h&,%aX:iД ÑHA#phI* '$@ K:ٺ&Ƃv ^px@IB)KJ6H\;艀qüQ1MI(9)Y*-ZxْŁ^iŬWJN:6pQychiE`&tsGa3MIk B !t⎮%VK6IBa]%{gC[d"+)j/@I֍4pz"r;%|U7#adD*iFJք9##%4,-F;v-#m6 %cfΗ[vvJ(5 rJ0= 78Wԁ@MͪHt>@Vz*q4`PTkqb#.%L{+x DRJ4E4QWXʡ 02)S %M-(\)hdD' ٵgI_)#4%w /dKI A2@\#3!߽sMarzI۪ZQ^('+-$ђxX/4HT%h)08bJzւ:O(jmCG %$ўsړVX7IAG;+qc44̲q2›J ^所6ƄZyڱ1Pĉrk)V ,i4IKr65-BVbxɞSJkh'a([r9~Bi$$NJvv_ YqONn,c+(g kbD,8;JFܓD dy`ih,sݬyC"!1/9_r]Zx `r @0rƢ,-˩a*QDCŠt 40-Vw2L2/=iI*ttPRxekZ [l ;KLhAA#8!HBJ/(`4 ޳C0l E'DKZ!(,]Y1:& Fil i5N%-m;pH*K C3-5wx#'o݈bQkNY41 fגW8ڡ⎹qTQ qd+)reByf4_Ps鉤 1y3+YIKƣKCZv/G{f3t a`ɜi'2و! K)8nricuD/-X &fb Y(-M,%V"&? p$҄aYډ"Vkʡ ,pލ!-J򲚩nnkP0( p!s47XgIGTl`f$dGOj@y8fTaWG)e|F+EZKptEu(P̄nḼVKC8v ƣF°e|V` -FJrx͋)f 5G7/X 8SJܠK$=]l_)B ^S<7fq%P bJgJBZ(x"59( tKB+R`XDY2oQq| ,fHvD3RҐ c4Mg4αq4;0|e(@7DXЖyr,`4,X;ޤqPk[lllhD0HKc%/QkbRJ*ֵ4Z$Lw-VJHP6k$%Vhc"/)J&S­kb˜淑ϕ8bD& v@+ ݀ h9K nWhxGoyi{0шs!]]=$dQ=52֖D’v2>9Os$GOATmHe,w/04P)ʒvC3UۃgFGJ0HqD5#Vq׭MȯݪđF"'`vDP*e@Hy2(4ALBNfҬ`$hdҳ6\!3mIiI&Fb;(PJLbfF(erZghl?gdz4$ 6_\y{&bcn%eD(T*M;VWX b#p"<뒘;v.̌70eN%2 ʴJL1BnX 2bbc׶>B AA(  dvG;ܲsH(B^0Y2, r&1ܢX+A!͝)c ,2{%n7Lke"~[H)IeJ +qް{n:s0mːCi5Ǵc"y [Z$4c4qpYa(0=ڀW ^8~Dr-{[Fc74ƖHޅv,YM#Hx GcvuSnY{a5V$eIræ>3FEDjQIŅP⭥4CnWZ:"Zy,:,vsP#e%M3qIq;~M;^vչ)Gh!mlr1]+,w36L`|z5I@iʪơ,v3r"TRʊi&*4€9wh)^Mn'%A 1zh#$ %bdvyr/P ?&Hh+B{g-lᷖ+$ƒS < hVÒxh%r'}8817k%N=E5 G>Y  -cQ m*8YSVlaJ )?JLZXqPiuq̩~fPC"?aChMQ$ReH)Itt-,KZk, 2CQd,c€!#hjQOE) Ձ!. :Jp}al`$7"AZYr(hU.2")i,dƔ.D˲TPLMءjΑAY'(޴b-uZ uFCԔ VˮHG^X 2KˁXNЍ f5{o|7tY} NG6))3iF ÈZ5A1wQWQ"ω)A4-1+β!$RIS78~DRV*QH%JZXgNH0 rF8} @kMKAO|f"[jc:iC~/%s+es=O 4BLc@]>q^2 BҊJr$3XۧUɲ ֺL5"e1=aңhL)FetB kQ::TcsmPt.L51Y Jcӥ~W SF5@ JjjGjDaDV89J+Ҍ|&5H 9|-iC Yzny'$Z/)ũBE\7# )S͒8w=4 k yaQ2/rV:ܑ /֠`|4(& h(#Ͳ2A=H 59]QgowC %;?sBf/} o7-YɇLyu31oQ/̇>)i8ή_qw.x$Wa*MoOzckN%(% ,/qpYC@(IXkS;}9:)2un/W4)MP9G7@)Asl8+0T9TR$Iɲ!}L Y %E^6cyq(qK%4G'Y.hXYZBeɥiJ^g!ni4܍9}(ȏb;@{$aD-7Z!5X)>M=rʡQQJ8 4†(ILu 3k26 CR(%:ZA8gAҕ?Z!!i6B "ÑC+}5TXZg .iS>)c<.;ExBfWR^{BHxݧ(Ug5,ZPQ }zt{)ݘXZy^뗎Z @Sk|bqHQVt)qfW YQBjkKN_8['DIl$RҋRZ1)55{Ջk~9G=~7?Ȭ8~wi~㏮bK2͟? =|yvGj x;-1=?\|~'Lm834ڋiV|3tyN- .R~Ҋ4MȲ0 %:5h?8Nγ0fYXc(%i!EAYǚa'^zGU|)0kמ7P.^J^8g6=e,˞,0<ۿz*,Vc Z;ȴO7&9XrDڣ ?(cI742Lk<ϙi$yfqy׬ҥMcu*iDt:}vMJSݔbfqzRR g:i1u(O՗z F-eqJP䖂tw IDAT桔d&)RR`,y#OR8,q&R@{hj%VJ ƳS#- J "_#$-EdR $EnP.}KTˮZnSdFق%t߮pQZH^(%^B- XG5i3n;ҦZՇk7*5gPd8k@K!'dIէTXws # GN'ViU} Ьtꕀ^wD S8YFMX H$W XLaژ-CAZIZ),A`oCQo>Û'Vl}O|lz읪vx/-}/oCwk?xOwb<?}/bIm۱8n`?gjϳoz. JkQcJ2DW_s0^0~OLq/ +˯DUK}3%^/-Uޅ3`˜]qӐ&}M$I➹eB5ë 5꒣Q '9^E'O3RED#r]u2),|HqIvbjNMG ҲK(]iE[L}̭Kvl9ILU& $'U_gnZS &KԖ0"ae)Jh3)) R!U<"/5$r!NR"vL,ubVe  %JjX#dWw!O 㜶9P TyT0 ^mxL+Xcĉ]N-0BV% |VuN> k_gs9':lxf%&~y_~p*#ow\?yiqpɈJ>k_^eߐ>K͡kw|p qEX!Hc󅈟oźj T}ܠCE^'zq&MBR FcaJQ%VEJI/ɨJ3[rRFz"(m%^srܠp(I(p΢ĺX'/ rH%ɓWU9 wR)xcGMcf?ie}NQd%"lҔS0M&ĝU𫌎ԙ?U5~^slڽ+xt掯16dۖYWdgD8JoKXgT#J,wsj{+Z0hYX'5?j턋Fs*ݿ*cK±՘+bDD(/Drg>R$Q҆\bYm(Oqz /l-ľ^?KnQ)DR |lJKsF5]]S9J'?,ڂA5_&ʉc2&:iVv_RӪt+T5y^`mY`]Ab(Qi2cRHaktA1 k>{}+;Gn 9!@V 5~'d⼠s*ŴT[*KϽ!p~W8jx! Ԗ8hs,Z$J904<\C }^o4d4.v]fO᜛*wgƖ?6p^ghk$iYo Z4-]'lRkF<gaiavv'().2AI "cp\dY"FSs rku;NKKKR-yN=r'%&95?ϖ9~0;wcIW }>DR9J n}̌\}$ 5ֺ+L)q̦/Сl>K \ɹ^r5,.ICG4K 2CmcGU~QJ/h*B]GOpі DYC 6APR԰WӜz-~賰JYF.!2VЎg3XB+AzT=]rrr~mU˩Wr8fzI^[^é\8݂@DE$0,TƓz6H ,8g,p"Ζϭۏ1ƕ|>+o)<1sq*.3kR)9cB[ O=Aj ~VJgK9jNQ5)e))ieyp(+, y'ᠰ#-,Yp-䃿|3C?͗0!Y'cܟj'Oq_nsw;I!Y=̿'9bvj@Ln_??g9%]| ľ3p&$ypx>K^Z{UZ7ls1u V> ^];?tcSOȠWBy ?'Kfyػˮ(gf%7qF'Kشy ۷o4cE{(3QDa㣌oέ[ul۱-[ټi5ijӊZFsIVU =ExRy|=â4 3_HjCZy)˽ANaZb K88RchOQ*zh(ZUͯ&fUmQt+nciͭ@SՒ0㔕^Rg e-YtЋP88Y  zMvόrvW3g|C]΍/?̇H^R~|5|.ě+p om2QPn5?8ME;^\u\y/zԼcXKxU'.>͞k_ ?;^~ Lp+ާ*{϶ <+4 C(Eyn*-}Wyc{:~iۿ{̝:cp_<፴w;1s<z)KKKזy@RW(EFk~ \ys}LLNq"\U|:cJb~w0N3e|ˣ<~Iss,ni^[:HAVzxB ВX X(XM,NvrƑ w%EXNRZSDcRT%_<ɎZST"L )%9㍈4A)JJ+aDY3yJ$(3 H rOR׬rj=`AhwcBOQ|²MH5( "_ӊʜՄ$-ziV#$4}jB? 8 J4زR'ر$JKZ,/R,eҮnf }X3#-20vw@ZDOkIyT| BW~]}^w^1XBz%\rT7[\{3S,l^gu$;v8x1v^|SxXщ[kY%7\ǡ#'9~$W<ƃ/A ۧ9^ZacLv2=EsrK>wA7Y<[\y_J)hTCZJ1 <'ȝE+š.8ڵORX2d wJ@J/ 69!E"*5%4j"_(C %BVd&O ~J{6k Ғ?CVEZqL% VJWfwF505ڠ^ (4I0&[K[hS4:CC++FZ?U eROW8cbzY&(&)Ee9'7YBH,ɭ%qh֖X\\#5HR rcKnH0"/ l0)n),*4M:sdyy/~ {6o2 qcL~(_Z عsFC{{!| ϿzKE:H聇Qʑ>fC&!8Cw܁/Vx1YƩ'XrnZK寎=1<0y}]x1j#pbuh5 YnYhF[ppe@*5QKPI:y^"+ PSLMM#lgIQ9͊OwQ =V:dֱebFҨ8cXA=Z:F*A iҜeRXɑ:ixnL;LudEAO$$T֐CR+2XZ5"r'iT(kkxFJ?&jơ S'Ʌ SkT|IQ8TP?$j K5DkRJ^LjKf~ cF%HʼnAjxYB6D6o)WhHHnL7v8#c`W]V8y XxԹ=(ןhpn ṰLJ1 lq1MIKM;6`xMk~b6BҕEN-|Ch *Q6XToi أ4/-R $q!5c-Ny-@B;jVt"# |ǚ,ZzzD^4I:vo@QsL>AJT+˝Y!I ldl!;raĮ,B,(8u8IW!5ހ( pr#U~GBacGcQE (`mcR"&'~'7a|7}f6 la=C_+,'3X%#p.sάv[p{D^dqy{SK9d`_B')L(2s&Lnג'O3:9AqH ~S( I3V]ҤL«7C|.AӪ奈;k8 "V:L!CswE.Rj68a0wB:S8?] '9B3=6p^ъ&W$A} |c8Yp>KJ7}v=0vftN磵Fyhz(9ۃ ӚJTEku^쎫 QRB@biMa 8XFkH! Y#:|>dYF?(lvKqnٔc2}ƺ}4`wgS(}Ri7<zN-Eaq BsH]<#|ca'9qd?W]}{/ɓ>ָF_y(,Q @93uqJctu9eoO="Ts6fQiv[$>sFlIs>'W“bX*YjwK:fdd)N;B$}dζMSo}+gmuHFviFu@Jɽ |+FƐ:$tز}7'>ǶmX[[cpb-ƚF*xAxZcA){_;w #Tj-&Fy,,,~^b%Mbk j2v> 8rwfueZedX bcei<熀YɇLyu3m K?R B~ooqKbv(`OsoQ1=4GN_~u\_LrM?ϿƟk3G샧Rc/GlpR"/]LrKp9zεב%?ߕW=4G H3NÔEEjflB ǁ_>GlCw~9mw8?z)eE33`R<,[ZO&cnʦ-ta,r`ΝRU9v8㣣DQwAJ֭[ӧOu,8)QΙ=yt]/QHC*Ak|"P ^&Jdy$PE/*%!@ ml޼iZXcP +'ڦV H}q羟7*D)sO)yޏqb/po.O?x࿉$yg+GO,uďm}ƻ+71-~^~%<̇{߸ii\_^ӭi0eNqdn/7ULGS<}gXWB!(UU`wDyQl޼Cը3+k3I̪/0HDQN>BSjqL!wqÇ T}M{t dƲ/_Bnq!EQ~]oao#߾_l1aĕ@#30Oѹ^m6;cpܾW >W((/ҟ{smgvg.,]QJh4`ZC^^yD_/#AAETP˲;ǝ-*;3sy9w}o{ﱕ#ׅz3~{7$ \>zY,Ӏ,^z\EdY!HI,"$Yg`p -ïV,uc~2F΍3;>g[__ w߻O.gmFڹ]kM"TM~xd YA麜wt|*>(ZN ?\$~U1j9NTB%& ,j0\Jqp2ERU)J =o ;4kihh`.'jǬL{l25|Dž2$my"#2!IiX6&>  'LIHv IG74je sLOW_M _(q a#:pjQ^y0_ou@羶Jt'V|ztIA5 @!s2gb9ڳp~G?]_ >-W! [7|,dg1MGAuW[!8bkDBIT xe ,L' GUzb$eNsWCؓ0l'83hNW dnv ?Ogo`0D^&FLL7KSɕ hSyӱ~=s.@-q}C3x ]#8ڵ 貋ts aÏpћ%r$Mqr%fEwDSgҜW)V]*!M& T|rp(m8vI(ib9 ]q@WȦ#Ebzʅ2pəiϧZ,ӽf5;w@WUx-`nd֞eN*t Ǵhb>&R$C%* #I͇W\Dס'j3z߿ɛڿmD{v=}69}ʶ,޵+GOr7>->\m; dPΟ<Rqڿ X,@0$pɦ;?>?_vi!*"%J{xػMgsގ$G2@Q<>t? H2b0֧6S78į΄h["I2?!]oj3G~۷t!8~hOwo!ҲKݬ}9(-ۗWr`OIqy븤mq:7`‚~UN[?ȇ& WI2k3,W]-⏇[/!@l኏S_ %4|iO)b & "!be%2Q"ۙ;xd92a>G%xRXD8 ,цzFq\AnHav|@ O>=[ؾM'"y4e먚ΡՀT*4X3?ASV⤓P(Ktuę*i>G8NʦKtV q>boڠN&!9(JhV B <ЧOl 19y"xֲeYȲ\T* E0 MSr j9D,2Z&g* (j9f|nH4| m>52PFǵ%ٙIC_}1_y \pMePHT߱,dE: 5mZ(6i%ꢟl%IR_lqR Z¯peJd{02]8؀hoi0=EOE%!FsyR6_M2[Gyfvyhlm'N'@ fe+03=k֯&/E6?( -l޶3>};vҹf-}=N3@|f̪E:!WSlBAZ`rt^֭oG$$TMlC`h8oe˗961 RM(byH߭ ?D0(L؆I4"J051FZafj5&cN`ukn!"؎2=1Lh#bAIP) .`[>tJnGBդR1GRR`9V2<2(tYB8&LNS$qm#Hb ]1A 7U¶B F"K}&ŁD‘0S㣄il'ql bK KD::صkOok.x5|LFS>rӏ9˯n}#/g3j@, -Ʋ JUs,077G*$ ȊN:EOO Raxl|L\t$2bi0p`?H!f'* Tg4dcddRqpA%ΠA>АM @U۷\e+W΁}{QA&\  z6CzڽD6m\cT M ܈cضM0s]ڊɚ=tvjhj)Tjim[--#5˲DfTU%;3$#rAēG?Iv "Q^RX$: T*G8 H,T Q Eb,]A:P=49  B:s?B(G ɦQ$ ``2:us`mS<3ЏoAM2GE4Uf\eOCNwo/aYevrFe dt,L̑GEdE(9Hmh({-=Zر=nNKK}'.<{gJ4zՑfXF<DzjC ٩ZkUUu= B D@<Ub)(jA<ӠX% |j*O4K*VBղ+d USDR(DB2SHJ4X.#:mc 0m ۴PebL$$ZL-6{W09>(l#ٺ3؞H.Mnje33cYٲiW/cbpeݱP:tHP`nbe0+hf MR2%!PTgg8>&R@&!R5,A O(2o b"1$AdʕȲv\!ٙҩ,`IBIVPjDS2fdq*8456R*p}e\#5'TrЃ:s381rEW "`Q!B&edYJ)DIF%"(Pl$؎(" 3sțV؈U\=2H@"%7;(MhJNGRe!MGOeH?k͒"'ŌpDP4ͱ>j@0hn6(߽@w~MObTU{WPd3]˙B%$Q'}6'iK@?gqQ3ˡhg$c1FFYQeѩreڮ iX,_}X2e$S 4m&޾tdJlP5-e;E2L &f (HedL*tDD$!_̡ ن,k3:1MXu$"!Udlt ߩ&)< LQOĂ۷WiSCAG&VGe'hf}1|Y"*{Y(_jij244JZaI5fihhR)b$g}ѓ)Jy0\:Yvyd ZEEJ|%H0Ȳ $+Wt388$dZڈ)GsJ9>(͗d3{H]?$!"dz%mmz=@uxO8S8 " -H{8CX hd͏0HK"܂"xh &E)$Idy QDU8ߗiƶLDIBôdI@X*+tt%ѱEUH誄i@VȒ"'2 Xa;h Ghm$L kٹѩ1ըVr{f[Zqx,N~f0CedYC%[Hw|=m۷?] bz7 ;k>ovhdsL;2AV0|CBC$|j8XER4  $!LO=X?kxOSKcXDGg c]P(C]&Aar0.84YbYW/#)̖ ģUU42(jT (*H!Xd5L V4RIݏLUd UEKEV2<6 H[[#IfcOQ+HVlG'~˔JW%8I,i +8&#שts`nQY kq%&c >a@J"" RM $zHz|ia]Wa;UAL[.@ zD211=C]:WH&3xضM&]Q)R-6 M傠BPDED,+%+q rAp\t]bMR~I)qQMD8Yq6 i(xM6یT3%Ix)JȒDSkcbl4w0]xRT21 &<ǡTP\,/ZR@(DRd$:ۻ0MχD\F8Dq$Y!H!S $}I'AEDAq/ O@-Q|!%W6?ǴQ߳m4A>}\/1/V5 a3|+5peZOR{xq =C}Ç7ͣߺ|;SԱwNXZc"aV;.^ݬ:EXLӀ$@q<@@&HGضpر457n(VE$I&)E(|$WL$iPDYJ(SX A$v1,\D@4LDEjU! è%FFH#̕m":RpɆ:e::Z8D(( x}+SC&@YB(O۲ffAfIeZF&FB1DT e1WҐC=LJHd"/Q)d5+κ,_$,MTT uY]wPDLLL{Ceڱ"gJ*i&Gbrb1QI%9WPu uQ5QQ͕*(#>c9ASINL0U4Ȧ@}D"CJ-t T<ï  H|b*?yb̿9`2Q~{xzßqr.޹/i_sďsw Z7n/J#[)t-D#!sos?} Yg;z/_Jvqn (s~q ?/_Mڟa_?c:?%mٯS2 K[&AH$'CA<ۦ8=F܏E#MpZNZٱ85G8AG"a]zV2 Yf&,kN_X8£C<ޗ8:EuEt6X|O YK>ϓ.mn?x'~ցNH,WgEy s{eno3$Yq.]3U^d<7j:΅v!L]zMzp.!+O}#ؐRugzל͓{vŕ-_;{!c1 Իp9װ⮧qM8NS\XL@D-VTԖ'R5 |B=֞v&F%ϣdjȶ%J0jHl <} l{06%IKk=alۋ

+wٛ`UŎ[?g?k8wV>uNS?:O.Ѽ`?ȧ",4 P?Gma6 pGott26wo}]`?{#\;/dũN{^{hs$'?ˠe+ GH`(pD49jVIu@mʵx&6Yˇ_tA4aFϿo^D{\+9sQNq%T.!mX8N|o0<Ri _bKpf&pY8~K" 6rk̨sㅼ=o̺u̷ͧ}"{I\v͈@;GgݳE8 L K~˞wz i|~VoR{2Sw4kP55׊*J弣|WAf_Nr٭cEH^c~G@ix~|R XeUb\XFQeRBS$˾4 j4kZ0iacTe$Ab׎h}=uQ=v!1>1ITwX jRm/K-؎ ǿI' %bJ0g|ɝ::|?.VY _㲏_C9v/&eYh;~R !b]LiR1Q^y0_ou@羶Jt'V|zwTtIA5 @!s2gb9ϟ~G?]_ >-W! [7|,dg1M",%j_ wmdY6&ӹi[Dp( l<};w"лᔊ$ ,&#I""Q5-vnނ8c[HhL.KM u=YQe$i:IDIfbfL={kcY<r<5< Pj9Vז8} ? jHh%=O2P0s ?%Or\J,o>#flʃ ojp"`_,PX u_*vݍ _|COf7MqzC 0{l.Y_j3mY k+WW7rn"|[|z%'w-\rd>GRWr`OIqy븤mq:7`‚~UN[?ȇ& WI2k3,W]-⏇[/!@l኏S_ %4|iO)bgxx+6 mABӨP*FHDJqr4Fe$k UJ@ufپv >pQ?`{=ly`ZɉZvÙ)[GE* -" a:$P6nDQ@EL ),On`0$Umȍ0"ApEQE H$׽ĉ! $E IS. Dhxaַ/0 R)o`|JK:iX\ӱzƙg#>ժ&(Ɉ3)$A{k#.31\dhϩȺuۑ$ QT2ΌP<LPD4|?л2PuĩǮ]xz6^GϽl Q[I?W7߾Xb;ep 8,)Te}чf/s9kfG& ȲRQUp]p\hmI0:öl4]ge0{7B8AZuldEa <)(|dK/y#pBmY5s֎}:;:9з$ Iij1;g277wR 8Cb/b{.+>o#̯9, 0I߭9/4h*)AXR~V-kض mͽ˩z2\E;Xq;ﺇt9iXûwJ]GK@qr5J{FXI*㙡Ip Էqmsx\  F€Xnڈa5DRdg8'ySx@*ITDTl\aZ)!4& ({&H#!ʊF1 Ox1}_{j2C(C0ԷB} Mw6  ϒh5nYRX$QI'HL*S LqgK@2:U$79.A}}baRA cgK]Q.(G^؏g>,7<`R e!$b6MM>:Z  Dc1<+Pf e~{e!_Q9i dsOed(on-8Qv% A"TmȘv:w~^> jo{ҡf-b:]=#ٲ?XUi r?_USO)@T~4M;vцe'EeMUUU8)he;q4dϮ=!Xh/jf *ģ&tB*t0LB59t`z̀`?X7LC+'q]O>l Jh- _HGJULH M "(K^f57RSUV9NC}BQ.8Hf=mT3f`N@Xt6&K-aۦ5}{ij$xa Ӥ"cνT)%RJbM8G BN;*ݻOFe`˖Hdt` i & `I![rQu啧f PQYLroE=iDO$h8)-osGB Zoi^=q.zzeFf f8OLk`4[*/R9SbIy)!hl+IDt֭% OǢ Fqk\Ώ8^MyGp1c\wRhŷ=$-b(85u4iߥpN94W܍0LXdB}4Φ'~͊UU@53:6F<IXx6~_0ID+9tUPU<$\!HvӾ#^2jRhFJMƬy C`謍0.1 r% eh0~FĎ-hl1*qt|Nΐiٵs'm9̦q]E1:%3Hs{SO }PCں U(B> ,Yųo0#O=/>y`v},p~a(AM0]CPJOXD s'utJE*P5f>ݣS?S-/7Oَs {FI#4D*^ (,i Ts ^-.Lr gr(B-H?I/d5aji# N95TKp15( /uc=Vp囮g%믡*Xm42w0w<f_JM}+VdVWS~ t4s-\;nn&K/R9 Xl%.YMMu-e=~}=ydaRt}U'o;HOd{>h7Yץ{`7[lF;nRJe(?3T.k୫ꧽwFM}]I{/V&uO'}ڳAkw? xj:]9G R *Zٺmw9Rlܲ5kVjJlH'ٹZF\=FmjIl4b׎_ % XChM5C,0"~x]Tg@͠GJ}U>>(W>$۲hPc ,"̛ɒ+ܠn8V#)mkgQpmc ZSط7 r;d^Mt|JzK[Fsf{vRATJ!٨3Ld.p84`v̓߱- b=0[뱏&"u((hE/#A>UJ2^ONZs﹬af&E27ag:_sek|MLk o)8EɗƸv\n 0w@kإ"gŸDb'fFJ! `6\|o.T]\C=>^H8g!41:2=1lr671M#O#t6hR1+]P?s%x(xD#)v9mԴw1s\yJ%0}Q ˱®}jX jgCu¨I}m5^ [AnH_'ކARo?jM?W琨ۑVYʣuQ:pRQ\u5%d"-%M+ ]bˠ ,L`,Co 3DT ]1/C5|czY깼w>G$$^}ϿԾ'o;#Ӳv@N<7~m}2\8$+'Z@~OKy!Ɵ:w?ϟn\ Jr%oN:9Jue-GWKG[-a0Ge**)sVD͌fpq5uyF$$UKv ޻h, D*dsW\ T9e;MC!P;A (j&-MuQ,=pL1(݇.#`ϾZϕ +xR9&ROBQ3C}2ZLMiP5ں83eO]=OIG^3W WW91N/$B CQJ ?~L;E|wwpǷa}s~x'>] }ȿ3yG[^cv ߼;osL]._GK/m|-{nen Q6yOZ.~fZ{+O?9V*\<[|wB#lk_xz>oCw>Nzvv<+ӗ!N% HNV "&j&dꄣqBp]W=;5:s6'!NJ S[Ģ ;oMx#=F 0C1rIꩩ-qdVk3|ô4a477A._&cm$B"N&ex,I[{3,r lv|(m&l$@I*Qc 00 UFc4ANf^^CL1<~ce}c7M U_ύg]G!=#us)۠߾织(e5ǿ/H84ynS=tW΅;w!ύti Knjf`>} ^~lM%c< /7[5*;c_y|j_Ŝz'/zW;gsGf\PS"_Q62τi X[{F dGMvf͚ī0ABAl@ P$R] ̪))}f '$psH!Z͍8q=C!<ߵHG#-*8c)8vtlL(*@ҚWlh@>{oD|;@Z o&=^qk/Qvc՟.> l>e!"q .?|(5Q3T~(G,9Wbe-؉ V=4MCE8b RʫQHmO?/H,fٲڵ)%xCt]l#"-۶`Q5ApDJH__^BL?BQ\EQJ9nwǵ o@P~ "J A[uH)Q4tjJUc~=SP]?1F.SDNgV=wʶ==X/8wmD0DO/0(ƤQ?5 qC(AǽaQ* [.m6\[xF~)lGA ^s8/_f=P^\GOvk{x?K8e֧GkyS_A @?G{QaV_?d+*1x1Z36M[._J Oel[x?oO&deji@q'ǥ`` Np娪 _õWcUUWSܮ̝ڶ,Y2V܂[\f:-&7dE\zU/'*.5QTE=P0Ȝ|hr%7rM祼eku'[hDQt+Tfr Fǻ?|r0m^ A]yؘUl<5oyh+y b6o>_Dr'?1%U6n |[;<"=/0MjZ *;"\9v< 5TXؖK<`M1+V!4 D5Yzv%s(:SȐ  222ơ$GjѾQ|?M`rxG y3| kDzfB<=}Fٽg7nR$HUDhma׎ݤ,Xy/r0\5 A]iP6u߱sX]oj^+YJ%رu ;J(h/ {ω#==?}d:zޑ秮1`IxcYB5m; r}$vt_FDL L쒍빴tTy$\b<6rT eUbNq?3 3Gm@P%siJAzed3\q#`:w_M0ukQT{.k39ȍ @P0nTWMh67NGS5isat=*w$P(A]]$:ugnT]\\q=t  HU؞B<GGB0#A< npoRGPIU{c08:J}CuuIFbt͛ÇRAS<_OitlYvhmIVZe&94`/X{fb"KH|xE%l ͈S0esDc pWaxtV7g|ZIy4um\a u-Yr%IMA>P=n9QՆP8f|tT6cg"384yC6a-Moo?%bltLT*MggR–͛.dM H[42at,_TIt !8Ecddh|.ɦC5F"ng6\ק~e+c<܇GQU|Ct; /7OP^mVPg玧پ)j>jlL_QU᣽ Djڈ3O<#]m/ly|sxxйa6t?Z]z0ȞL`n"%PU ű4d^G1Q(\ä{7#sa`X p"p"<\2+hkmatB#T)aʥgK$GFW$`>/>LYM2EH@{7Ѻ5lhj%=ŚeǓOS܊x[R0NaΡo+/'ƒ+r-coǒK.g|4n}c1pzkJxv+tXvi{Ԁ={mX8PXPI'LjWVp`&µr%jlbaPt R?oӇoPV1S*UUg XODLfP8BUM-o!}άTzuWؐkW\r^3q})Mk`atP]hD!Ǟ9~La{7=SD_>8eg ᡩ o~'mʡOA$"/pt|6O#y4G9v,],m9H)9O+;#o׵aǃ='<g(Ͳ$D5By !.Z  %' |/5A"3 3G5P u}&BɗoNL;%C\._} ( ȗl5#_Ƕ$s(*acj^t!ZUy dRr%݅J~wM 4  Z G'p$3ϼdTV) X{BFS?Ceu-5)ryEQdI*k)LC&N'o3H烀 Ixd^ !prcL}tF}>4 n~+7s5ŪԡoܻY7~?~2*'T*UmES72qCŧ6]F"2ɺ]@@GPCU0׋ "shTTVLy# cd| tM#1<4ݻǢkkHsxbH&%Q[_Snq}*äS"21hY>̅:]&&SBuΞ͖[ Z>D:,c명R6S?31ix{#@]N9>~L@((Y_r\t$Vg$[hd 6/(K!4m o{;˙#-тRpB3HeREx .GUcYSx`iUQDz.Jy]xeKZ:m]Wr&|aR}rӿlY23GݾK8m1sF@#Lj{Yl V.bx,R*S][ Fּi=>FEY$b1V,]HTUxfVT+YNj(œ=DSXd)U(\F]eL)Sz9]K'5K.??) -ٕT&,[-"oer8?BR(F$Bq (3?ޞ ܽc8ƒz$!80!ޛ[9czY깼w>G$N <3J?\~kkNQ͒o׃Ybh斯]IGLm\y>o;GK^$|!n 򆩥J7\ɼ{63sQ q$k䉇*{mϠ46>0B9a{Th3)[߽ ߕ`ɇITCcmH #RAQU_ky3>0E9|p??q(G]vrU{ؼ){{vi͛`쉇N;SSxgP % q L<'4q-Ag\__ԑ-m'Ml˲=ŒEO%wI5άY}\(q/gDp3LEQ_YMhi >54ΊK=w=~y!/sǷ[O,㴉hwwT}o55<u1Fx՗۽ob/Qȵ q>8}]똣'$^_r*si>z͙**r[[vǘ.@-X$XO4bH!-h vC3BJFȷD  9ʺ7`j>^=EhZ%^3|t(244Be=wΖphҨVVLs %%SM,xHhy| PI ,Bfy[TV' ;gnakeCmm-־˞UIױH3.Cy~Ӊ3׏x,Q6: <؞k& h(?#EM{'Sw/@E&`="\ LU",<ʧ.딓Dyt{gx9*Sw ]FߵIRI]]t*ZSEʀ1 Dz> 1NyTu,$`тf8vLG3ŰjL1<~ce}c7M$ǗL:mw>B:<?)N';/&7BlmQvcٗnIq;x'+䃭\t& b(?@󶓍r3W6/t Tu"NX" 6 dI]E]LOB$B#WrQt`DLBټ($6""Uƈ@] ѐdmC\%lx.hHP%}RcY %#yTɣ:DѦ?]*ÇRtZ k(!dhK0h*E>N!!8h X{Nm,PCdak)̊BBApP2}`> [ zTyLA0T/]x>3-r蟰L#z OdH(TT- (g,KC)3 ̩FB&_7^ "AUB>rRr0gi1OzY~aV7/rU*zP5,}UxФ/潷} I>y:'OC_s[:C=ܫ7~fx Z5JAsUyDYCy\y U*8K`}L!p$PqK6!TTi 05"BVEI4TytlO2^)j!tC84t*BLX*}6kz<2HE!蚊l*?0Lrmpy`Y!]'=ll䑚*h(~6VӀj8"B:5T'8gC˓fF$§i$D>Gu|]fsU.?e Yl_Mg=q^?#`{ ٯ {?\tH\u9Ow'^o(g|:4_sk9w%n~j¼^Ɵ|#o=(aFy _X8x2A]%hdᰊ4T6q]e(U—*( P(I%3-c* ғHlյHؖC2S*lP > PBa& C)鑱m|-dɧR8QE9AUUUN8A1GY1%sjYҜ:l`h*tOMM#8% UUq= ¦FX(OB %a e)%nt<)<CUQ$>~9/낦^YW@#!uK}|. |EtM3:>#MTCg(W&nZ"xŷ)+># W080( 9>~1++s'NDZ!Ӕ}%sg^ ?uÍ( )|Ia%5WWF;Y~/;ÜX3w\d;*n3/_X,-\?Z<#`WGʭн2x7=VO@_󮿇?O9b~/eNo;|'SA_+~t[ɷ(~ʿ?͛_e=c=zE1`kKH2U J.ib ;Naw&\LP2^YI)XZ8KOcCd:FSLƊtT2h)801F#% z.+az$,%~cAsV ֆfѐ@k*J ɰ(yG%3ݨR45y55R:sxJp )=NIP(n|ͯ%ҵml6a7] ȭgO3J w!8"Q81|vj0- Ii6/:{Oj-tE{RXJ*fB7%CՐ(Ype ϙmh(`cT(q3aέ>|SOBNZd瞝$*9*=pM85!98rxE0?>y{x .\=p},}!5{vSO1\; ;www!Qt=S z!@1|-|wrso;f_8(snawWQOb:iJ+VtF|so=a^ү yfȁ|3P!f4s 9,7ٽUeqqiZ9uaeڣh{{ _w5nVk͕ ऀ^ q&* %PJ a8P$Y,uS>:;1Yf$,hUQR`_x1d!cc tٽЦcВҔRs799fDJš976Wl^ $P$ ΌQBQIp%Ϧ8;6Phf2( B1LVB(%bʐ!ΏwFLNQAPۿt`4ǞA377Cu:2fM,7=:Q\:{qqnZ0@+RE2ts-n XÕGR9Ν=dsM:'W"D3%KT6T+!gסZί3YgmuŇQzK328MiH8YѣH3tz#c$Q$q{>ǵWⳟZB?bϾ}ubx~ZIV6c(ffn1FyDf*Kp>6?ݮDۂBW 4C+IzN|\>f%o'!Pbߓ>/BDC />SmR6bJZD-Q[Gn4Vȹ:(QvTTE{O7- pfvP09Jd5&VHD*EiTx(h4P8'E #XfD) l qρ#(4" tPPsre-;f{GMKj~Okӿm\qt)xSU~G1{Ogm3'?u7|J=<8x<:j1H<;?x'{s7CijHŃ'V8x`Hwyxzh3g(e^euuSQ0*<3(0-\/22{6?.BlтL\._'ip'Xak_#nbJ~o)VL74ifL4L6zz(-G.5H /@=(4+PƍT@%T4"AVza %+ AH-4EΓZ 0GT"MjuP5]G*D33ӨPpsTE ʠ}XK6bƝ)$0%^ "Ej2a Ji GcPjaJcI- 3ѱBzOk]Cf$7os")襆8wC$T#Fk @k<ϝO fFx)z^̞vr97͓rv7.\u9`ܭswOp[fgcic;|=[auwڟ^n 5oΥU~3GȮ?p[:^gwW=gs_w99~!{#60m5>d8V~'GO# c<ʧ[YȌEx#ֆvtX{J p#=ˮ:~Jhic92d0k}dQZe9GgX 8lDQH^h!Kr1+Ӡ, 4c*e(%D 痙2 $pۑ)=8ނSl>>2dqzf>\{s?qQF5'|xY^/xѷ{W,|L?b;[S| 4#Jxw>̓OќP/阨72_S I藃+DyJeϙV~wsny4JTy+uo;?O7_ʸFkCuhJ^ V2SbJŸ ^jPiVzjY( KY8!@KHkJB1h%BS:GC+-7 ho0 qtJZ9ؒnV kiA?dZI?3,c0| YI -IMC\3YbgDV(8 Ҽ(L+Ee˼ZO웨gXHb,d-Z֎b O^ȩ*9t(7m~ $Ў^Ѡ CU0רBMyvϵPbp=(.e. w9FS: !̈́Q3%~:G]|o?|/0\Ol]={z5]'ff =}EwX]9_%e m_Ϋ{j b('p88S[Z1t A{9I7,F%Kg]Q:@+ɭvFE!rcHJPxKVZF0J((acdƞЬVt׌ݦDW cJ6 kAVpH hh$d%C ۴/sG Ʊ:6Clc2eirhaM8>ǿ2Qr~Uw(%ADb{a!Ȟ=?[F~=0^~?y3= BZVfo<3% yV-acTrW^(AZP:sBR(@Gy ݸS vdrv#ǖ{+&"}5A./IFɋjW8A$a`WՙJ1,шvHL6vF$eJ3v4c fĮ:s s:h [G",)j5t`D$P(%HBԊQQ,M1f;%փ~0/!~SȖG 妍<8%t=VbɥKV1լ2ѨRgJKGx<cʂ2'Sssط wf7}.m<󑚂H˶6{@ tԾg]{9py ż姾?/_I ~ngsǖu5gɿ|xsbPu#w|smS/ɘٗX o5Ls8=N??[so۟ͯk=_#dUl?i IDAT<{yl6 Eƃ{*~apwkyO94p3CbifcSR)H 5R:8QZDOn<4gG,TcEUKMiuIMUpB5T|A(r͐FŮ*tHQsq hO79; l]4[!PhTqD*u['"-kAq8sxŹo7l3恮S}i\r%y%2@pB:BiadyYDRsCMtMB+@ ģFHTI-5c"8`U# l$HZ R (A@ ^XfO?"-Hb{__+ɫoHf͠xa+JvyaPp`JskSxIiԴPIPb'Ԓ^aR-1!R S8O$;w0Yq YCX+x֎ a`5H87H!hO+fZQaAnT"J;o%[ZO)9q(XFI#\I-U"-$2 2WF& BKj 3B/QdE)uX2(=ցDjgLxҖk*|r%GZw/hDgJ+X4!QK`PZJHHIB-X)x<". 0'$&Th0~3荨S)'c@j!ȳހZ vډL`heei1XRJ( `0 c=sT!<ω(r[@`x6^0]UP\XV2B j,#,<);Xc)zAiH O@+J&PzJ!YzJ [8&LsPp4ֳj0:_2Uz@ 8w^&xpy@gHJ6F)Q G V Fld*suE3ӈ58Ks~`c)4㈙V$TXzơG(MiTdc$ZvAFi [?b  wx/X`d$_:J)薞!ưZ 7QT.yVYxC=vΩ8}nD"H(Qpi?RS݌Gakr߇ի4+ c G]>MYW؃}Nw јHj;X^^%?N >!tmv϶yvLʹxnn%$=YYAs *0hNE3PڗtV/3arGٳkI(I~3ZM@;9,]6BlUi /8@K xh-Y Y깘DzZ2,%z _f4b%R{&A)c&Xڰ+E j wqrUe?wuB, #ߟ㨌gꈢ,aI {{wuw??* RI'WϽu9u+OsHډҺ@3\򨺊Љ 1aԯg%aDM3yt8]^Y-:I+`6m+rҊ>L-&N222A*6ICQ"J~DW"☤ePctET'" L> W#mm@}IZ4RmQ |4뀡tHiԼ1s}BӠ>@_L+v1xE ,t ]~ڐؿ=a~<ʡ]IZ+dӌW}JBC#} ZIY +`dH6)jh*FCG t58"^SS!ٖc ehh&ı‹AeUOLhJJX IaU/īTHð>"d oάy.GF0)]2Q,ZVa!fϬX-Pzt*aX8=sD3^w]-Z83&zaJ cN ˴Q*~B]Zd"A(\02m#4Tt05 GY_qI+*)Tp$0thK 2턉R.^\ɷѫ.2}t ; Ӏ1jmJATдAۙbt`۷nbd@N8|Al%̌H(-$NRd"DVE?o}:CC <-41QcB~XxLuqZΞY,:MA" zײtbmb wq=eA-( sԑ||t:]_NZ)"wmǴ͝KY?f@@iBfphwX7iC5E-0HYcA1uLC1[( S*+f: JP0\Z=tZHӛ\dMv<" htr$*ZбM[)ƽTR'k[(D>>3 xt; t%t epLT.ǎ$u0 7Z'B 6%m|7j$j[Оb:JXqĈ|Ƥ--QߏZMQ(TL;T"M5~H*&&Jh"B:$F`D@]St~Ȃ\u*fGt33C%o5TA7b$5X0X^?wPc~{2 .[ơ˖=VpwIO'qG98.sf?qR<٢Oȷwu]jzjMY9=bLi hf݀S!m-ZT<-U92pT{t _SƊYd͘֌NP f44M!QĎ*d]MX3\eiX>㠲46 zAC-2H }A Śp;pnRG:aa;6G2aEDt1L_)<Ɗef ՈQUЗt!|q!9j!ʼn mR#Z=?C ,kSf)~y?2N% EnPab{1aPq}z^41 +YOP39oqRAw~cՈK͏mgmdqq5ML] <_RBLM]k  af%R k !XܕZ}AOў,,βi 0b`Ig[6V%gL Հ99Q_'" #,I7jdKG֦Plךb Ы.*7)a3Qa:#aj $sfvcxU(U}\?D*hM%!<* 0ՐDm6d̚M\wLK4IK,= x 3vlZ#7(2QFYfLWܻ~vâT #:ZSdJQuQ͋9C'pÈ3p0m?Z% m\MlQ7▭e1x$3\Pv 0K{/î 5 m煌Ω5bР'`Y[[ N6jDOC첹i WaeG8XpgDs ,Sl:k++U|N U4@EAnԗg☎4}mI: QtH$ j17fNg? c(-Ur<48&ahJa0 WȦtƪظ/qp/gўJ`wncvb6g#%|C[Ƥ\QXIM)40+p0"ئF ni hff: fa:02F*LXa3dG-ɆQ!3:4( ۨklBxaL**~L N(B'-OW"vsNi҉˜D'vL S!\b'?21m5y\?dt!-$'HQf*Ɏ1fh1Ѵ՗P1Ca`0 15kV pc0 ˠ%cR2PI lZbsҚLL6 :X͈ҙh]Gd6Za[kTjR\F\ơ%qHDVYѬk/ # 9+ɪ!Z@/kqd݃.#pJǧlǠL]X& S~H2HF-&*::Q6b^l?¨UߕdGD&s5]a͠md"q̒nK"@q:^(4@(4ZIvbkl)r̋g1^IKaݎq:qHWڦZvc&2v5[Ɖ1vIhx2:/ܖU?iNJ#,f.XRi2Җԩy1RdCjKxn 1Pcѝ<_! Ԭ@%PbS+x5+梇i[d5E[ WlscR4ZmiMhAHWbGugFJcNJj9>310e9~iP z`j!@#Tnh:$ 04UXQH$P JSSb2Ʉ KFPAW! I{5%8hF6z!`yI['V)tQsi3en@6XH&ers <-fw1 kM \rT[qg RE̐t;֪t؊ffW ;ǔ93b&t"ܷH 89w8옟5ʈ:?eslbgK5䛫 &3P1: L`q{"k:Ei$E7$\ҊPlJr6S kX:+̶nT+c'0֔EIl ԙՙ{֡2$łm*(3:r.LZTkq3TŊ*~8C#$z}Duc<:2<_5pLi8&ADqAHӞ6huLG{6IƱ(-J*QD*0hkّҠ=g1T/ 2F10te2I\c!M hfl(bfp%dQw :K:m2ZVTpR  1)L`< 'd~ņ1fPyϥ2bj)3-H%PiQ?R1-MD*DIN1" 8UHHIVh" Cba05 Scf%mMPK~D{@lR Xt%0IRX:^fhOTh!fPXIJ;<:Re0?^[&xHm$M`G!mfu$RstJDAAH1ei13z2衏Ka-5<2RALf gq$ Sq.)ƍ"B/(/Gygl; !":_8@q#liIZe-/IىLfr5Wߙ$m8VԚmDF3ZLʁG9: ,lz:i ;tيŘΤIXҗ7bb-B!cY|E2^QLu%qJPjI?R5svLl~c#E .z0L +X*Up$AhM$*X#`p\:IW&֒ Iz[S} a'5J;Bw ØmI܀L¤+NtlMS"JhC.5b2دVb:ەvl/ԉ93IepeV^gyMʆӴFKM*~@(:-c㈇QBɘ | T\I& HћQU5plϊY98&",KÏ)>"eĞ ]-T!c(H1iۤ5e4I$0L `)NGGHp/18,FJ'1ac@Ej1k*+2տj鶍HН2lLq!Y>,%Ⱘ5ܶaڊ(-afdCZ)PvDLSљP#%n@I',UEfΚZ:+}Ivgt [N *\FӴ8+lQƏ *~n~|KGwoI1Ib)De# ,r8qvWqO1T ɤT܈b,4yxJ:u,*2NoKjUo'}#VrQS*UH$H`>붎Q)LAc*ҝxh]?ӓ,uD mb<ֱL0V}ʽ[@RѕafWMȤlN+3:UT3-i! &a$cVo Sf 'L5Nwg;e?Ll4zl-ב=ɦʥsgl-Tݹ4і)z!m}I&+dX@>1=NjjQ cA_X?NO.Cw2EZiO[LAhoogdlv' Is뺎R;'UQFFP,V:EeZf}XՇclMەG~+.R& [_`M{WT0=}y|a]S +5y[ ?q][ww^_/RPߓM_=% }1mc?_TÓXN`a] F~?]T{|}w}zNI׮sQۓbr{||.I}valWؚ'z50o%K[s@*xjZ%S 14M7F|2_: زl) H%&\W}u_}f`׿ì{lQv|r;G(39q0 )MST)* lrq8- ˫PujqHƔ+eTDm3ݢ$MR.W0'~RGQDkj\aP0 43+%VȤ -ͦɶ(nm-IG0Ld"s!V:J|3QyXp!8){zzxÅjֿtMGCRBn(9߃A-9d\c z4hfv 2+c0oykϙ3M6SV0ME1<F @?QG^qdz>wܳWE~vtvv׼9b& "S+c,Xܹx)i|ɤiG{#w  rԉx#"0$||' #Rq[*Oe!HRS㦛oVER 0淿=ŶS9BL a{ᅭJGD<Nx[ZPqKj@d*!@~~opגߗLc;#P'_ /B4HS 6c]qZ+s?M>ڳ>Obfrwuƒ:C@p+Ot1-n{a/r[LכÝõ\oa\&}V^r~{cI1~o+5;>8;/{)9SShv^# &=Y,_y# I/>)տ纫.dSǃW|Ǿsއ9a'kv=7X͍kr>ʩ>tjnyl|'7q/?w6l>iJY]u79p!]QpԊ*׌dc;Fb|#}L5 \qP4(}<}9r=_;y? ܕ1G 59:n#Y8%-&V"^ϯ$yJw>ǘkqs&Vn!}+I~O~mә3ms:g5,oҳ9;x$%usǵwRbEi'ˤ 0i!oOٞ'GSN^-^M~n֎e]OGsa0I#381:/lYzX\}Cɪ #B4HS'9" CMb.en\~3kىgҧ)3^}+WDpя?ȉ72ޟ2N'޼kW ZI j)|M 1w`o|yyo6df+O,7Na'Ե:VV,˗zeᇵSSqBq<5#Zߑg|׼Z&x~8\MVp_8vNl&y^2#jJ ϖ! iȈtyx. l#  v>(>d^6aqhJ# $w ݌^ {CT]#  ԀF}B!I qHVb:ivj@" 1 5+58Z`@iTkUi$AɷI&M9bz.}}M?B4H3SV8VbH$3|k@@jVj ":;:3]8qLAcI D4uB! )PB\䫅 +%_H!E!Y!h hBLG BLG@BL7 2 $A$5 $$5 n$Ad@1I D(b:@@!8@B\ʥ\ʥ\ʥ|˟χw=_5>*՘=I\ʥ\ʥ\ʥy%9|³8)r)r)r)#B!2" BM! &Bq@@!8=@ _jx9h`B1edD@!8M=_nI3ƿs8mB\uJ,Oӿ(XMmBqPk@Ȇ_}O6>OI!|w?ϗ'?z6ּV !q G"^&Ybև@ݸX̫bs]w+Xj4UB!yU`d\B>WZiA1 y;ɷ"W.!SyrZ4'(U,0Fhy)37UB!yN/q_#QgBs>/?>D:} GMkB!&y%W?M|2+n\c~w;wM|hw_Ĩ9B!։|?gܧIN|3ce2/{_WUO7IB!DS%Gqϯauq rBd͝P0p܂ /}G弣ȈBT>n|ƻuKBξO !b@@!8I B$B!b!1 BB!AL! O'_//BL x+V>YM^qѫΰuc@3K;h/x~LC?s~ҥKkBky G߸Op/ gw4t+Ìe]:SIζh>]_ޟkN&Փ{zs'F@|m>]o`;?0~9>z8daW)L ց_4pm|a:/>~9Bso t,vŰЈl^ɏ}WݽBfQ#?;t fo;w|fi үÖj_x>qљ.M;?~oZs"_/*lgp;>Χ[NV _0:Ycł ;׭Ińtgj{7.:jzʽu@*͕{է=eogj:Iם|\=+_gO^>ei R^^qw ({k.a+7?3O7p5wߺo]ʟnU!@Ⱥ+>=Vj>=rCKKf˧q `>W;*ք>\I;>kӆ.KWS X}d k{h{p/7;OO~4>O@ނg˞K^kd#"/˾/r<9H}hOӧ}>?k|sys]XIZ'ǟ>w|GwG 72[Se&40^NzX. 4B197~oZ|fvHsϥPc~2鏧uo`&9Xu=" |kn09;7m>$gYXx5][/О'ϹgF˲q^ʥoq?̋6:_&ope|mem1}RZc8-PV O{0ۇbkIu?kR9ֆp/Em/} ޷>gԧO}S={~OT.dyQp}wQ, '50 tΤO؎C* =U_ w΃>s%+7#fL%ʕ}[ r}l%sB4_J6rw- IDAT\'Uշ~1!~ͫ(t*R ojҰh x>L6ڰ8E}ؿQlj-^`{3}O{;sohgxN 'wlnϩ6}FvDAC7:?Ƒ?NGssG:Pu8/;n6윣yE( d>M7oÛ*?(-P6"{ePAQ (OQ2D,E@Y-tdMKi3۴Mu].hNyr<'O\6RzGE楋Y&vh8Mӆ#AB#xU GQSyyi, Aj_Bh2b #g}BrP5:_-v_8hEI35A42Y(or=YSȟ팕s|'ks>ݴΉ%~~DMTUZPx[F7wyY~; M3/_b˓,پ@fa|>&:nWx桁w—Ⱥ/(dG@!NNΐDag [Nio۞:ѹӅpnӇ>_v0]żWw""s|,i/ 1m.>؇typ3]+ Sfsq_[,7?Ōiˈ/e+͎*ÙgoZ'Zp]vh6li3¿bŮۙk)' ڥ/n:<ЇvqdRO0gqnUÀXzLʶlmpe Ee;8mfkʛ45S>RL8Y;R3[N}sWB:[lyh]P0K~c] 5Z s3]IJT-L,ɞ;8B.s!˼g ֮Çj+8Al Gn w%--+mĚr5̎RV"ʐG.|]-쮄4q+А;P 䭛$;̚iO#ͷXNsf]̋\.C6%]H*-y|%*hZ^Bĭ{XLvA^79Hs"YPSC'b(`϶%1uJ ͼV 濾4)f=un(IWۥH;jR7Nޮc)Z;mΙu1/sB{8 V&2 ٓ.%pŴi50fܧi_Hyl,LSsoTMbe4U iZ4{ԧBҮۅ:ZF6rZc۹RLɥ\ 1tQisE\yϡ[ItWsìHKSWײ7k\M|:i6kބY=֦ 4!MU!]H7/v'pː݋xmE Ԡ݇$L^-a|XUìu<)Z7m.'G뢧|.rIS?J\t,=}u:ZRh:r 3}~mv׎eβ;ĒOڏDu6T!t7ZRjaڰDx X-)oeҠ.DzaSެ\dztjK+/Q۳}~qt$ Ƀ3~ZVee밾֖ce>@P||=4ν?n:cfLA/C;Zg .|o<؝N'cü=QF:OuMe>>h>aҢ'hq<`H`HJ]AnđTҮkIH]k$PC=U\އE30vEr$o16gOQpWdI:[S|)]Q9~$1 3x@rIPo9Re7{j 'r{-}wzܝIϪB8Io0Ͻ\|t.\{Dw&tp6'Z¦} +E߾>V|Wؔ~Ucڎ䬻C'E3in_dPDD+8ܞG|uۅh'x[d}FNt:ˏڟ6`?hՓeǕli}1Dѡ0&,|79 {7]}Bx=e6^Pg F}cbG .>E~-t ~#?[A=?9'ӝ j's&.~fG {t0g>[ͶMy?6_i68Ls5%$=Oa_+Ͽlfݬ48*oKW3%ڸ7@ի//^Y܇{k:3zCU{ 2fW]\S {^f]s?ݗ&d@/w{JCtGi{MSƫҢq ؔ/ + a^PKӿ%͞49ʩw:zUIZE6`oL&rzCœpN$y\XVhh??t&#YY5Q2)yxS-ӣmcS`΂yORz"OEVCvy@lُ| ea@mA9W</SBkpŵͶܙ(q2;=ymU6 J /Chm=yj.:4o%Q?0AYޥ|}L~crbK@0nL2+)yxlpSa۰9ӖkM *M($ c/̜_ it:&sqB_nsmp"2_i(:-IkJ$MBdt/~@xO('uAQdO}:koI{KRyɂ&@!(G@!Nșj)Sa7]݋X^v{2PIJl~?K,[?O3)%9I(XeM6Rۂm^ޣ6`m,9暩wˋFҮe^Z\!#ut-C9iSaV}<+8Efh[[Ve e>8ٜ_֕zm?Pj:?ٗx ub.cq4Owv5hҽ͵({mcY$uRR'w U2u21~bԥ?^϶ FA;L۵UOx3_ xAZU /]BDfu9014 =ٗ9nǔ\'L'-zz5> I9^SBMV ԊZ<ۖ)%7~VɐXk/K1!~›ĕhXƪr:5nqTUp3#cG|q3o}|oNP0)R[V[G<=]Y(!UR'%u`y@ ;] ځ;9iPG2oOėOP8 g eC/qjmȝp(rP=3Jʱ,-|BHJcv>HꤤNqhO'f3\ӛ0HꤋɢR$z/'x1%-6ܒ(y%郮p1}P_|poar3m14GYpLlxf?v%uβqd3o$7R%i>Vaep%11˔.]́+,7Q";Yp5[ԬQP!CFΜuLjċ8DdcRяS-[4VtHܜf}wZ[sֳ;~cs3-#Jk|Fwt/դb(ݯ͉3 k᭿Ùd[He- |]='OO[wm,pk؝"݆m-;p[2{0vMTO?7WY_>gCNQDôǸ=}wK@62m9m"#k ٙ><0yRha b5Gޯ'ٻ%vC1|Z] [7:6/~|ka E3.DDj2N 6__i>>]h7ګi>/e"G>Lrkȑ8~+ͅ|$s"`:̇F025',gD'X`<VC 5 eL+.%S+WS`L>~]qfcYF' `8_X><ŧh,Ug}̕XutKǤ*B_K|&#;m:H;5/NFx թKs'9e*s,kjѶ-+;s|w%jAmsjr=v8X/ -#+WSXN 5!<5>j$c[3\7#UI#U}yUKS-6 /+.e&piO#ͷS29J%Z&ks=r8.hK^RII'(xU@i)9"9%%2ѰmS |=9]Y}>.6{F U&sj}\>a)Zٖ5q5nЫd,e9Υ "83St;LS*w$pUSײS0i>W#gzMr9v{I%/iFIK!- (c|?S R|{&ԟGv>]ƷG?j W`ɺo{3j$'L m-3aꕋ)Z٩40fܧiXr.i w&p]:ϥܩk 5rD:Aخi~ hV>noK ǏqrMi Ng8|e-hNJ+ض65Z3l3k|T\F Μ5tXϬN]z=94`+WSPWײeoYyJxY \Y1s5)sDZ)>_mw.h߻(@Lתlve/NnQTT9h׮ۻUie͒?ܺեhݺۗVRmg68hG0 "(kHosC7»XR^Q&_.; %bM g)Y&EFK#cebiY[*}"1o3ͤ?aS9zH5֒eoY*ągq*5z#y{D#+k4%68Hj8웎%]yi, Av}W0X#;?k!3tD&7tiE&S򫪠ݞCӅV&|T4V-+FM-(;GODrs8t&WC,Q~#+OPNmt:F#OAxBmC1JR]P }gEwS!o$Q,MUߵj7Ȟ>}Pf =uTZ6o~4>(<EM}B᣾7dU]E0&?49Bjj*Nʭ<@~S҄pYN?~4[v36k;Irt:6/6x!B8!>LB!sBB^XSnݺȵB!pB!| B&!‡@@!a2B!| B&!‡@@!a2B!| B&!‡@@!a2B!| B&!‡@@!aXQ.^H…RRS&!Qb%*J;k!+ڞ| .^HbR"M5!LX~f%(@I9v+ڝV~mO hҨ !JQBCB_>w~-wp_ۓ@Jj aeHOOld2V&̩]үk{dA!‡kdX(;"(@SBxw| 2R 3}_T*ϸDbd>CRxYxs"y衇x|6;T(lwCA++󃐍d‰=>ԯj5k֬!44ԩ+Yɓ'Dmwp_ۓ=*py^7Kt_TA i"l+Y;S/F%94[=Lg^uJ?궦!oS AoɡRHV,j+ [^xE_R>*Mg'$-'FyV,%_?J5jb6i c.IDATD>&7) ]F+#E:5m6ȡܲwv(չfK%`O1Lӌӹpt֮g_D<+CJ쪕Tנ4%1%D 6]5u hGc23gW?PnT|hQ:cԘ>c̤_.ega Rʨ;@8v'WqcV@2 h_m>sy"پVK\2s.5mWBEFS2:g~gC?S~P)a7'o6g7褲3?GmI?͆yx1)C72vg>9I6s3ߙ6ON)>E2պW#K t*Fh#[^DDDf׮]kf8S5rk.rXF4Gեȳ3S/smퟍl DluA[k{$kJStKBW>ht: %ul}!n8beʅiPwuJ )%(lc][~6LM@E As”9M#Fx~ K1o#Ѥ\~%&rx_k3W5=e/YonjNG[~XA[ # /{;~+ۺ#^&TkzQ[cuWwnuybGq"3EEEzإLCf>0C%KgߕDT`=$+̮{Is_[,XoR œzNl !'}6jl{l(>_DpڨVZ1@qÄ9`@gvt*t61cF%K(͖!Fdh6c&ěa #wLI\|ot5ol&WfU(C-WoQ/sa(|kmɵ$(C)hs;5nnl*Sr߿Sw~JBְ_sNWV\tcmL?x |aNj{]qh,ĶtwE2(^ΟzW-܅X(BHK1%DxC@ؔ>u>5kZ&>> ,5drfUi7+GQ~'5s4m|/B[b~mO <UNC 6 .A:i 4@!!ONAS㬔=',ۗ@AOr~? YO^??Wۘ7u-"쿜h u2;Fҩݬ {vmk-[ӗ?rGYgnjKmulO 5=׎uUu&u=׏㢟8a,붯kkZ-G&::chZBBBP\uɗ.bs\1$'g&UNDEL+7-`ަ$"bdYe[~C?+ߗ_fX̫AZ*sRyPf3i M|/cڷ: --60|Ul}>LZAQ5`&|`<ǯL "zў˖Kmٯd:e\V*zS+(^`욅,z} 'ՌnCP\5X{EKc3}bR:e9gol>%ЪG{L]uR3 ;7F-ȘWJFc{,XܙL&իiذi۷/yemD7KdˤJQAGF=Ճ**E㠿+ WYC/7,QH/8YPUEwetT_gZ6kIZZZᩴZ-1z زFgj|lGn(@^O艨Ahuu-ji8mcIOO/5)v5iPj>3'tJZó꠵,.@ Ȉ#TvqٳI>mq_ۑDEQHIMůu4~PێQ\jPZG\ }XDQ v'yǝ-n+{Ϋ4wNJJ { ޽;iiiRBw1 mzgk4K:/Ealذ]^H8ݯ=B#>& FC>}HKKd29~ r{FǹcOTU*X%Eqүk{G e!STpZBxW=TTsQ*kथq5TpZBxW=.UL…%(TXҥJ;LB!Bd B0!>LB!B)7B!(DZ#;1C E(!B<}-\ JJH!uc جuIENDB`ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/simplescreenrecorder.desktop000066400000000000000000000016251470053624300262610ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=SimpleScreenRecorder Comment=A feature-rich screen recorder that supports X11 and OpenGL Comment[de]=Ein funktionsreicher Bildschirmrekorder, der X11 und OpenGL unterstützt Comment[fr]=Un captureur d'écran complet qui supporte X11 et OpenGL Comment[hu]=Funkciógazdag képernyőrögzítő X11 és OpenGL támogatással Comment[id]=Perekam layar kaya fitur yang mendukung X11 dan OpenGL Comment[lt]=Funkcionali ekrano įrašymo programa, palaikanti X11 ir OpenGL Comment[ru]=Многофункциональный рекордер экрана, который поддерживает X11 и OpenGL Comment[sk]=Vlastnosťami nabitý nahrávač obrazovky, ktorý podporuje X11 a OpenGL Icon=simplescreenrecorder Exec=simplescreenrecorder --logfile Terminal=false Categories=AudioVideo;Video;Recorder;Qt; Keywords=screen recorder;screencast;live streaming;ssr; StartupNotify=true ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/data/simplescreenrecorder.metainfo.xml000066400000000000000000000033101470053624300272020ustar00rootroot00000000000000 simplescreenrecorder.desktop CC0-1.0 GPL-3.0+ SimpleScreenRecorder

A feature-rich screen recorder that supports X11 and OpenGL

SimpleScreenRecorder is a feature-rich screen recorder that supports X11 and OpenGL. It has a Qt-based graphical user interface. It can record the entire screen or part of it, or record OpenGL applications directly. The recording can be paused and resumed at any time. Many different file formats and codecs are supported.

Key features:

  • Synchronizes audio and video properly.
  • Reduces the video frame rate if your computer is too slow.
  • Fully multithreaded: small delays in any of the components will never block the other components, resulting is smoother video and better performance on computers with multiple processors.
  • Can also do live streaming (experimental).
http://files.maartenbaert.be/simplescreenrecorder/screenshot01.png The input settings page http://files.maartenbaert.be/simplescreenrecorder/screenshot02.png The output settings page http://files.maartenbaert.be/simplescreenrecorder/screenshot03.png The recording page http://www.maartenbaert.be/simplescreenrecorder/ ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/000077500000000000000000000000001470053624300213315ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/CMakeLists.txt000066400000000000000000000024241470053624300240730ustar00rootroot00000000000000# 'libssr-glinject' library find_package(Threads REQUIRED) find_package(X11 REQUIRED) find_package(OpenGL REQUIRED) set(sources GLInject.cpp GLInject.h Global.h GLXFrameGrabber.cpp GLXFrameGrabber.h Hook.cpp plthook_elf.c plthook.h ShmStructs.h SSRVideoStreamWriter.cpp SSRVideoStreamWriter.h ) set(include_directories ${X11_X11_INCLUDE_PATH} ${X11_Xfixes_INCLUDE_PATH} ${OPENGL_INCLUDE_DIR} ) set(link_libraries ${CMAKE_THREAD_LIBS_INIT} ${X11_X11_LIB} ${X11_Xfixes_LIB} ${OPENGL_LIBRARIES} -lrt ${CMAKE_DL_LIBS} ) add_library(ssr-glinject SHARED ${sources}) target_include_directories(ssr-glinject PRIVATE ${include_directories}) target_link_libraries(ssr-glinject PRIVATE ${link_libraries}) install(TARGETS ssr-glinject LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}) if(ENABLE_32BIT_GLINJECT) add_library(ssr-glinject32 SHARED ${sources}) target_include_directories(ssr-glinject32 PRIVATE ${include_directories}) target_link_libraries(ssr-glinject32 PRIVATE ${link_libraries}) install(TARGETS ssr-glinject32 LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIB32DIR}) set_target_properties(ssr-glinject32 PROPERTIES COMPILE_FLAGS -m32 LINK_FLAGS -m32) set_target_properties(ssr-glinject32 PROPERTIES OUTPUT_NAME ssr-glinject LIBRARY_OUTPUT_DIRECTORY "lib32") endif() ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/GLInject.cpp000066400000000000000000000051431470053624300234770ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "Global.h" #include "GLInject.h" #include "GLXFrameGrabber.h" GLInject::GLInject() { #ifdef __x86_64__ GLINJECT_PRINT("[SSR-GLInject] GLInject started (64-bit)."); #else GLINJECT_PRINT("[SSR-GLInject] GLInject started (32-bit)."); #endif } GLInject::~GLInject() { while(!m_glx_frame_grabbers.empty()) { delete m_glx_frame_grabbers.back(); m_glx_frame_grabbers.pop_back(); } GLINJECT_PRINT("[SSR-GLInject] GLInject stopped."); } GLXFrameGrabber* GLInject::NewGLXFrameGrabber(Display* display, Window window, GLXDrawable drawable) { GLXFrameGrabber *fg = FindGLXFrameGrabber(display, drawable); if(fg == NULL) { fg = new GLXFrameGrabber(display, window, drawable); m_glx_frame_grabbers.push_back(fg); } return fg; } GLXFrameGrabber* GLInject::FindGLXFrameGrabber(Display* display, GLXDrawable drawable) { for(unsigned int i = 0; i < m_glx_frame_grabbers.size(); ++i) { if(m_glx_frame_grabbers[i]->GetX11Display() == display && m_glx_frame_grabbers[i]->GetGLXDrawable() == drawable) return m_glx_frame_grabbers[i]; } return NULL; } void GLInject::DeleteGLXFrameGrabberByWindow(Display* display, Window window) { for(unsigned int i = m_glx_frame_grabbers.size(); i > 0; ) { --i; if(m_glx_frame_grabbers[i]->GetX11Display() == display && m_glx_frame_grabbers[i]->GetX11Window() == window) { delete m_glx_frame_grabbers[i]; m_glx_frame_grabbers[i] = m_glx_frame_grabbers.back(); m_glx_frame_grabbers.pop_back(); } } } void GLInject::DeleteGLXFrameGrabberByDrawable(Display* display, GLXDrawable drawable) { for(unsigned int i = m_glx_frame_grabbers.size(); i > 0; ) { --i; if(m_glx_frame_grabbers[i]->GetX11Display() == display && m_glx_frame_grabbers[i]->GetGLXDrawable() == drawable) { delete m_glx_frame_grabbers[i]; m_glx_frame_grabbers[i] = m_glx_frame_grabbers.back(); m_glx_frame_grabbers.pop_back(); } } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/GLInject.h000066400000000000000000000024501470053624300231420ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "Global.h" #include #include class GLXFrameGrabber; class GLInject { public: private: std::vector m_glx_frame_grabbers; public: GLInject(); ~GLInject(); GLXFrameGrabber* NewGLXFrameGrabber(Display* display, Window window, GLXDrawable drawable); GLXFrameGrabber* FindGLXFrameGrabber(Display* display, GLXDrawable drawable); void DeleteGLXFrameGrabberByWindow(Display* display, Window window); void DeleteGLXFrameGrabberByDrawable(Display* display, GLXDrawable drawable); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/GLInject.pro000066400000000000000000000007041470053624300235130ustar00rootroot00000000000000# Don't try to compile this with qmake! This file is only meant for Qt Creator, not for actual compilation. QT += core gui TARGET = GLInject TEMPLATE = app QMAKE_CXXFLAGS += -std=c++0x LIBS += -rt -ldl -lGL -lGLU -lX11 -lXfixes SOURCES += \ elfhacks.c \ GLInject.cpp \ GLXFrameGrabber.cpp \ Hook.cpp \ SSRVideoStreamWriter.cpp HEADERS += \ elfhacks.h \ GLInject.h \ Global.h \ GLXFrameGrabber.h \ ShmStructs.h \ SSRVideoStreamWriter.h ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/GLXFrameGrabber.cpp000066400000000000000000000213441470053624300247330ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "Global.h" #include "GLXFrameGrabber.h" #include "ShmStructs.h" #include #include #include #include #define CGLE(code) \ code; \ if(m_debug) CheckGLError(#code); static unsigned int g_glx_frame_grabber_counter = 0; // Print the last OpenGL error if there was one. static void CheckGLError(const char* at) { GLenum error = glGetError(); if(error != GL_NO_ERROR) { GLINJECT_PRINT("Warning: OpenGL error in " << at << ": " << gluErrorString(error)); } } // Returns the OpenGL version as (major * 1000 + minor). So OpenGL 2.1 would be '2001'. static unsigned int GetGLVersion() { // get version string const char *str = (const char*) glGetString(GL_VERSION); if(str == NULL) { GLINJECT_PRINT("Error: Could not get OpenGL version, version string is NULL!"); exit(1); } // read major version unsigned int dot1 = strspn(str, "0123456789"); if(str[dot1] != '.') { GLINJECT_PRINT("Error: Could not get OpenGL version, version string is '" << str << "'!"); exit(1); } unsigned int major = atoi(str); // read minor version unsigned int dot2 = strspn(str + dot1 + 1, "0123456789") + dot1 + 1; if(str[dot2] != '.' && str[dot2] != ' ' && str[dot2] != '\0') { GLINJECT_PRINT("Error: Could not get OpenGL version, version string is '" << str << "'!"); exit(1); } unsigned int minor = atoi(str + dot1 + 1); GLINJECT_PRINT("OpenGL version = " << major << "." << minor << " (" << str << ")."); return major * 1000 + minor; } static void GLImageDrawCursor(Display* dpy, uint8_t* image_data, size_t image_stride, int image_width, int image_height, int recording_area_x, int recording_area_y) { // get the cursor XFixesCursorImage *xcim = XFixesGetCursorImage(dpy); if(xcim == NULL) return; // calculate the position of the cursor int x = xcim->x - xcim->xhot - recording_area_x; int y = xcim->y - xcim->yhot - recording_area_y; // calculate the part of the cursor that's visible int cursor_left = std::max(0, -x), cursor_right = std::min((int) xcim->width, image_width - x); int cursor_top = std::max(0, -y), cursor_bottom = std::min((int) xcim->height, image_height - y); // draw the cursor // XFixesCursorImage uses 'long' instead of 'int' to store the cursor images, which is a bit weird since // 'long' is 64-bit on 64-bit systems and only 32 bits are actually used. The image uses premultiplied alpha. for(int j = cursor_top; j < cursor_bottom; ++j) { unsigned long *cursor_row = xcim->pixels + xcim->width * j; uint8_t *image_row = image_data + image_stride * (image_height - 1 - y - j); for(int i = cursor_left; i < cursor_right; ++i) { unsigned long cursor_pixel = cursor_row[i]; uint8_t *image_pixel = image_row + 4 * (x + i); int cursor_a = (uint8_t) (cursor_pixel >> 24); int cursor_r = (uint8_t) (cursor_pixel >> 16); int cursor_g = (uint8_t) (cursor_pixel >> 8); int cursor_b = (uint8_t) (cursor_pixel >> 0); if(cursor_a == 255) { image_pixel[2] = cursor_r; image_pixel[1] = cursor_g; image_pixel[0] = cursor_b; } else { image_pixel[2] = (image_pixel[2] * (255 - cursor_a) + 127) / 255 + cursor_r; image_pixel[1] = (image_pixel[1] * (255 - cursor_a) + 127) / 255 + cursor_g; image_pixel[0] = (image_pixel[0] * (255 - cursor_a) + 127) / 255 + cursor_b; } } } // free the cursor XFree(xcim); } GLXFrameGrabber::GLXFrameGrabber(Display* display, Window window, GLXDrawable drawable) { m_id = ++g_glx_frame_grabber_counter; m_x11_display = display; m_x11_window = window; m_glx_drawable = drawable; m_gl_version = (unsigned int) -1; // get it later, when the OpenGL context has been selected m_warn_too_small = true; m_warn_too_large = true; m_stream_writer = NULL; // will be created when we get the first frame try { Init(); } catch(...) { Free(); throw; } } GLXFrameGrabber::~GLXFrameGrabber() { Free(); } void GLXFrameGrabber::Init() { GLINJECT_PRINT("[GLXFrameGrabber " << m_id << "] Created GLX frame grabber."); // enable debugging? { const char *ssr_glx_debug = getenv("SSR_GLX_DEBUG"); if(ssr_glx_debug != NULL && atoi(ssr_glx_debug) > 0) { GLINJECT_PRINT("[GLXFrameGrabber " << m_id << "] GLX debugging enabled."); m_debug = true; } else { m_debug = false; } } // showing the cursor requires XFixes (which should be supported on any modern X server, but let's check it anyway) { int event, error; if(XFixesQueryExtension(m_x11_display, &event, &error)) { m_has_xfixes = true; } else { GLINJECT_PRINT("[GLXFrameGrabber " << m_id << "] Warning: XFixes is not supported by server, the cursor will not be recorded."); m_has_xfixes = false; } } } void GLXFrameGrabber::Free() { // destroy stream writer if(m_stream_writer != NULL) { delete m_stream_writer; m_stream_writer = NULL; } GLINJECT_PRINT("[GLXFrameGrabber " << m_id << "] Destroyed GLX frame grabber."); } void GLXFrameGrabber::GrabFrame() { // create stream writer if(m_stream_writer == NULL) { std::string channel; const char *ssr_channel = getenv("SSR_CHANNEL"); if(ssr_channel != NULL) channel = ssr_channel; std::ostringstream source; source << "glx" << std::setw(4) << std::setfill('0') << m_id; m_stream_writer = new SSRVideoStreamWriter(channel, source.str()); } // get the OpenGL version if(m_gl_version == (unsigned int) -1) m_gl_version = GetGLVersion(); // get the size of the window // glXQueryDrawable is buggy, use XGetGeometry instead unsigned int width, height, stride; { Window unused_window; int unused; XGetGeometry(m_x11_display, m_x11_window, &unused_window, &unused, &unused, &width, &height, (unsigned int*) &unused, (unsigned int*) &unused); stride = grow_align16(width * 4); m_stream_writer->UpdateSize(width, height, -(int) stride); } // ignore frames that are too small or too large if(width < 2 || height < 2) { if(m_warn_too_small) { m_warn_too_small = false; GLINJECT_PRINT("[GLXFrameGrabber " << m_id << "] Error: Frame is too small!"); } return; } if(width > SSR_MAX_IMAGE_SIZE || height > SSR_MAX_IMAGE_SIZE) { if(m_warn_too_large) { m_warn_too_large = false; GLINJECT_PRINT("[GLXFrameGrabber " << m_id << "] Error: Frame is too large!"); } return; } // should we capture this frame? unsigned int flags; void *image_data = m_stream_writer->NewFrame(&flags); if(image_data == NULL) return; // detect errors in external code so it won't look like it's my fault :) if(m_debug) CheckGLError(""); // save settings CGLE(glPushAttrib(GL_PIXEL_MODE_BIT)); CGLE(glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT)); int old_pbo, old_fbo_draw, old_fbo_read; CGLE(glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &old_pbo)); CGLE(glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_fbo_draw)); CGLE(glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_fbo_read)); // change settings CGLE(glBindBuffer(GL_PIXEL_PACK_BUFFER, 0)); CGLE(glBindFramebuffer(GL_FRAMEBUFFER, 0)); CGLE(glPixelStorei(GL_PACK_SWAP_BYTES, 0)); CGLE(glPixelStorei(GL_PACK_ROW_LENGTH, stride / 4)); CGLE(glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0)); CGLE(glPixelStorei(GL_PACK_SKIP_PIXELS, 0)); CGLE(glPixelStorei(GL_PACK_SKIP_ROWS, 0)); CGLE(glPixelStorei(GL_PACK_SKIP_IMAGES, 0)); CGLE(glPixelStorei(GL_PACK_ALIGNMENT, 8)); CGLE(glReadBuffer(GL_BACK)); // capture the frame CGLE(glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, image_data)); // draw the cursor if((flags & GLINJECT_FLAG_RECORD_CURSOR) && m_has_xfixes) { int inner_x, inner_y; Window unused_window; if(XTranslateCoordinates(m_x11_display, m_x11_window, DefaultRootWindow(m_x11_display), 0, 0, &inner_x, &inner_y, &unused_window)) { GLImageDrawCursor(m_x11_display, (uint8_t*) image_data, stride, width, height, inner_x, inner_y); } } // write the frame m_stream_writer->NextFrame(); // restore settings CGLE(glBindBuffer(GL_PIXEL_PACK_BUFFER, old_pbo)); CGLE(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_fbo_draw)); CGLE(glBindFramebuffer(GL_READ_FRAMEBUFFER, old_fbo_read)); CGLE(glPopClientAttrib()); CGLE(glPopAttrib()); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/GLXFrameGrabber.h000066400000000000000000000027471470053624300244060ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "Global.h" #include "SSRVideoStreamWriter.h" #include #include class GLXFrameGrabber { private: unsigned int m_id; Display *m_x11_display; Window m_x11_window; GLXDrawable m_glx_drawable; unsigned int m_gl_version; bool m_debug, m_has_xfixes; bool m_warn_too_small, m_warn_too_large; SSRVideoStreamWriter *m_stream_writer; public: GLXFrameGrabber(Display* display, Window window, GLXDrawable drawable); ~GLXFrameGrabber(); private: void Init(); void Free(); public: void GrabFrame(); public: inline Display* GetX11Display() { return m_x11_display; } inline Window GetX11Window() { return m_x11_window; } inline GLXDrawable GetGLXDrawable() { return m_glx_drawable; } }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/Global.h000066400000000000000000000065461470053624300227150ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef GLOBAL_H #define GLOBAL_H #define GL_GLEXT_PROTOTYPES #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Maximum allowed image size (to avoid 32-bit integer overflow) #define SSR_MAX_IMAGE_SIZE 20000 // generic macro to test version numbers #define TEST_MAJOR_MINOR(major, minor, required_major, required_minor) (major > required_major || (major == required_major && minor >= required_minor)) // test GCC version #define TEST_GCC_VERSION(major, minor) TEST_MAJOR_MINOR(__GNUC__, __GNUC_MINOR__, major, minor) // 'override' was added in GCC 4.7 #if !TEST_GCC_VERSION(4, 7) #define override #endif // std::atomic_thread_fence exists in GCC 4.6 but it doesn't link properly for some reason #if !TEST_GCC_VERSION(4, 7) #define atomic_thread_fence atomic_thread_fence_replacement namespace std { inline void atomic_thread_fence_replacement(memory_order) { __sync_synchronize(); } } #endif #define GLINJECT_PRINT(message) { \ std::cerr << "[SSR-GLInject] " << message << std::endl; \ } class SSRStreamException : public std::exception { public: inline virtual const char* what() const throw() override { return "SSRStreamException"; } }; // simple function to do 16-byte alignment inline size_t grow_align16(size_t size) { return (size_t) (size + 15) & ~((size_t) 15); } // high resolution timer inline int64_t hrt_time_micro() { timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t) ts.tv_sec * (uint64_t) 1000000 + (uint64_t) (ts.tv_nsec / 1000); } // Returns the name of the user. inline std::string GetUserName() { std::vector buf(std::max((long) 16384, sysconf(_SC_GETPW_R_SIZE_MAX))); passwd pwd, *result = NULL; if(getpwuid_r(geteuid(), &pwd, buf.data(), buf.size(), &result) == 0 && result != NULL) return std::string(result->pw_name); return "unknownuser"; } template T positive_mod(T x, T y) { T z = x % y; return (z < 0)? z + y : z; } // Generic number-to-string conversion and vice versa // Unlike the standard functions, these are locale-independent, and the functions never throw exceptions. template inline std::string NumToString(T number) { std::ostringstream ss; ss << number; return ss.str(); } template inline bool StringToNum(const std::string& str, T* number) { std::istringstream ss(str); ss >> *number; return !ss.fail(); } #endif // GLOBAL_H ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/Hook.cpp000066400000000000000000000412301470053624300227350ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "Global.h" #include "GLInject.h" #include "GLXFrameGrabber.h" #include "plthook.h" #include #include #include #include // global variable from the standard library that holds all environment variables extern char **environ; // return type of glXGetProcAddressARB typedef void (*GLXextFuncPtr)(void); // hook replacement function prototypes void* glinject_hook_dlopen(const char* filename, int flags); void* glinject_hook_dlmopen(Lmid_t lmid, const char* filename, int flags); void* glinject_hook_dlsym(void* handle, const char* symbol); void* glinject_hook_dlvsym(void* handle, const char* symbol, const char* version); int glinject_hook_execl(const char* filename, const char* arg, ...); int glinject_hook_execlp(const char* filename, const char* arg, ...); int glinject_hook_execle(const char* filename, const char* arg, ...); int glinject_hook_execv(const char* filename, char* const argv[]); int glinject_hook_execvp(const char* filename, char* const argv[]); int glinject_hook_execve(const char* filename, char* const argv[], char* const envp[]); int glinject_hook_execvpe(const char* filename, char* const argv[], char* const envp[]); GLXWindow glinject_hook_glXCreateWindow(Display* dpy, GLXFBConfig config, Window win, const int* attrib_list); void glinject_hook_glXDestroyWindow(Display* dpy, GLXWindow win); int glinject_hook_XDestroyWindow(Display* dpy, Window win); void glinject_hook_glXSwapBuffers(Display* dpy, GLXDrawable drawable); GLXextFuncPtr glinject_hook_glXGetProcAddress(const GLubyte *proc_name); GLXextFuncPtr glinject_hook_glXGetProcAddressARB(const GLubyte *proc_name); // hook table struct GLInjectHook { const char *name; void *address; }; std::initializer_list glinject_hook_table = { {"dlopen" , (void*) &glinject_hook_dlopen}, {"dlmopen" , (void*) &glinject_hook_dlmopen}, {"dlsym" , (void*) &glinject_hook_dlsym}, {"dlvsym" , (void*) &glinject_hook_dlvsym}, {"execl" , (void*) &glinject_hook_execl}, {"execlp" , (void*) &glinject_hook_execlp}, {"execle" , (void*) &glinject_hook_execle}, {"execv" , (void*) &glinject_hook_execv}, {"execvp" , (void*) &glinject_hook_execvp}, {"execve" , (void*) &glinject_hook_execve}, {"execvpe" , (void*) &glinject_hook_execvpe}, {"glXCreateWindow" , (void*) &glinject_hook_glXCreateWindow}, {"glXDestroyWindow" , (void*) &glinject_hook_glXDestroyWindow}, {"XDestroyWindow" , (void*) &glinject_hook_XDestroyWindow}, {"glXSwapBuffers" , (void*) &glinject_hook_glXSwapBuffers}, {"glXGetProcAddress" , (void*) &glinject_hook_glXGetProcAddress}, {"glXGetProcAddressARB", (void*) &glinject_hook_glXGetProcAddressARB}, }; // list of executables that should not be hooked const char* g_glinject_filtered_execs[] = { "ping", "/bin/ping", "/usr/bin/ping", }; // list of libraries that should not be hooked const char* g_glinject_filtered_libs[] = { "libGL.so", "libGLdispatch.so", "libGLESv1.so", "libGLESv1_", // vendor-specific libraries such as libGLESv1_CM_nvidia.so "libGLESv2.so", "libGLESv2_", // vendor-specific libraries such as libGLESv2_nvidia.so "libGLU.so", "libGLX.so", "libGLX_", // vendor-specific libraries such as libGLX_nvidia.so "libnvidia-", // more nvidia libraries including libnvidia-glcore.so "libOpenGL.so", "libX11.so", }; bool GLInjectShouldFilterExec(const char* filename) { for(unsigned int i = 0; i < sizeof(g_glinject_filtered_execs) / sizeof(const char*); ++i) { if(strcmp(g_glinject_filtered_execs[i], filename) == 0) { return true; } } return false; } bool GLInjectShouldFilterLib(const char* filename) { if(filename[0] == '\0') return false; // empty string means the main program const char *slash = strrchr(filename, '/'); if(slash == NULL) return true; // not a normal library for(unsigned int i = 0; i < sizeof(g_glinject_filtered_libs) / sizeof(const char*); ++i) { if(strncmp(slash + 1, g_glinject_filtered_libs[i], strlen(g_glinject_filtered_libs[i])) == 0) return true; } return false; } std::vector GLInjectFilterEnv(char* const* envp) { std::vector out; while(*envp != NULL) { if(strncmp(*envp, "LD_PRELOAD=", 11) != 0) out.push_back(*envp); ++envp; } out.push_back(NULL); return out; } bool GLInjectHookDebug_Init() { const char *ssr_hook_debug = getenv("SSR_HOOK_DEBUG"); if(ssr_hook_debug != NULL && atoi(ssr_hook_debug) > 0) { GLINJECT_PRINT("Hook debugging enabled."); return true; } else { return false; } } bool GLInjectHookAdvanced_Init() { const char *ssr_hook_advanced = getenv("SSR_HOOK_ADVANCED"); if(ssr_hook_advanced != NULL && atoi(ssr_hook_advanced) > 0) { return true; } else { return false; } } bool GLInjectHookDebug() { static bool debug = GLInjectHookDebug_Init(); return debug; } bool GLInjectHookAdvanced() { static bool advanced = GLInjectHookAdvanced_Init(); return advanced; } // PLT hooking (called once for each object) void GLInjectHookByLinkMap(struct link_map* lmap) { bool advanced = GLInjectHookAdvanced(); plthook_t *plthook; if(plthook_open_by_linkmap(&plthook, lmap) == 0) { if(GLInjectHookDebug()) GLINJECT_PRINT(" dlsym = " << (void*) &dlsym); for(const GLInjectHook &hook : glinject_hook_table) { void *expect = dlsym(RTLD_DEFAULT, hook.name); if(GLInjectHookDebug()) GLINJECT_PRINT(" dlsym(" << hook.name << ") = " << expect); Dl_info target_dlinfo; struct link_map *target_lmap = NULL; if(dladdr1(expect, &target_dlinfo, (void**) &target_lmap, RTLD_DL_LINKMAP) != 0) { if(GLInjectHookDebug()) GLINJECT_PRINT(" -> " << target_dlinfo.dli_fname << " : " << ((target_dlinfo.dli_sname)? target_dlinfo.dli_sname : "(no symbol)")); } void *oldfunc; int res; if(advanced) res = plthook_replace_adv(plthook, hook.name, hook.address, &oldfunc, expect); else res = plthook_replace(plthook, hook.name, hook.address, &oldfunc); if(res == 0) { if(GLInjectHookDebug()) GLINJECT_PRINT(" Hooked " << hook.name << " PLT entry in '" << lmap->l_name << "', was " << oldfunc << ", expected " << expect << "."); } } plthook_close(plthook); } } void GLInjectHookByHandle(void* handle) { struct link_map *lmap = NULL; if(dlinfo(handle, RTLD_DI_LINKMAP, &lmap) != 0) { GLINJECT_PRINT("Error: Failed to get link map from library handle!"); } else { GLInjectHookByLinkMap(lmap); } } void GLInjectHookAll() { // get the link table of the glinject library (we can use any global variable for this) Dl_info glinject_dlinfo; struct link_map *glinject_lmap = NULL; if(dladdr1((void*) &glinject_hook_table, &glinject_dlinfo, (void**) &glinject_lmap, RTLD_DL_LINKMAP) == 0) { GLINJECT_PRINT("Error: Failed to get link map of glinject library!"); return; } // replace PLT entries everywhere except in the glinject library void *mainhandle = dlopen(NULL, RTLD_NOW); if(mainhandle == NULL) { GLINJECT_PRINT("Error: Failed to get main program handle!"); return; } struct link_map *lmap = NULL; if(dlinfo(mainhandle, RTLD_DI_LINKMAP, &lmap) != 0) { GLINJECT_PRINT("Error: Failed to get link map of main program!"); return; } while(lmap) { if(GLInjectHookDebug()) GLINJECT_PRINT("Link map " << lmap << " = '" << lmap->l_name << "'."); if(lmap != glinject_lmap && !GLInjectShouldFilterLib(lmap->l_name)) { GLInjectHookByLinkMap(lmap); } else { if(GLInjectHookDebug()) GLINJECT_PRINT(" (filtered)") } lmap = lmap->l_next; } dlclose(mainhandle); } static std::mutex g_hook_mutex; // hook initializer static struct GLInjectHooksInitializer { GLInjectHooksInitializer() { if(GLInjectHookDebug()) GLINJECT_PRINT("Initializing hooks ..."); std::lock_guard lock(g_hook_mutex); GLInjectHookAll(); } ~GLInjectHooksInitializer() { // GLInjectHookAll(); } } glinject_hooks_initializer; // main glinject object and mutex static GLInject *g_glinject = NULL; static std::mutex g_glinject_mutex; void GLInjectInit(); void GLInjectFree(); void GLInjectInit() { if(g_glinject != NULL) return; g_glinject = new GLInject(); atexit(GLInjectFree); } void GLInjectFree() { if(g_glinject != NULL) { delete g_glinject; g_glinject = NULL; } } void* glinject_hook_dlopen(const char* filename, int flags) { // const char *str = "(In glinject_hook_dlopen)\n"; // write(2, str, strlen(str)); if(GLInjectHookDebug()) GLINJECT_PRINT("Captured glinject_hook_dlopen(" << filename << ")"); void *res = dlopen(filename, flags); if(res != NULL) { if(GLInjectHookDebug()) GLINJECT_PRINT("Refreshing hooks ..."); std::lock_guard lock(g_hook_mutex); GLInjectHookAll(); //GLInjectHookByHandle(res); } return res; } void* glinject_hook_dlmopen(Lmid_t lmid, const char* filename, int flags) { // const char *str = "(In glinject_hook_dlmopen)\n"; // write(2, str, strlen(str)); if(GLInjectHookDebug()) GLINJECT_PRINT("Captured glinject_hook_dlmopen(" << filename << ")"); void *res = dlmopen(lmid, filename, flags); if(res != NULL) { if(GLInjectHookDebug()) GLINJECT_PRINT("Refreshing hooks ..."); std::lock_guard lock(g_hook_mutex); GLInjectHookAll(); //GLInjectHookByHandle(res); } return res; } void* glinject_hook_dlsym(void* handle, const char* symbol) { // const char *str = "(In glinject_hook_dlsym)\n"; // write(2, str, strlen(str)); for(const GLInjectHook &hook : glinject_hook_table) { if(strcmp(hook.name, symbol) == 0) { if(GLInjectHookDebug()) GLINJECT_PRINT("Hooked dlsym(" << symbol << ")."); return hook.address; } } return dlsym(handle, symbol); } void* glinject_hook_dlvsym(void* handle, const char* symbol, const char* version) { // const char *str = "(In glinject_hook_dlvsym)\n"; // write(2, str, strlen(str)); for(const GLInjectHook &hook : glinject_hook_table) { if(strcmp(hook.name, symbol) == 0) { if(GLInjectHookDebug()) GLINJECT_PRINT("Hooked dlvsym(" << symbol << ")."); return hook.address; } } return dlvsym(handle, symbol, version); } int glinject_hook_execl(const char* filename, const char* arg, ...) { // const char *str = "(In glinject_hook_execl)\n"; // write(2, str, strlen(str)); std::vector args; args.push_back((char*) arg); va_list vl; va_start(vl, arg); while(args.back() != NULL) { args.push_back(va_arg(vl, char*)); } va_end(vl); if(GLInjectShouldFilterExec(filename)) { std::vector filtered_env = GLInjectFilterEnv(environ); return execve(filename, args.data(), filtered_env.data()); } else { return execv(filename, args.data()); } } int glinject_hook_execlp(const char* filename, const char* arg, ...) { // const char *str = "(In glinject_hook_execlp)\n"; // write(2, str, strlen(str)); std::vector args; args.push_back((char*) arg); va_list vl; va_start(vl, arg); while(args.back() != NULL) { args.push_back(va_arg(vl, char*)); } va_end(vl); if(GLInjectShouldFilterExec(filename)) { std::vector filtered_env = GLInjectFilterEnv(environ); return execvpe(filename, args.data(), filtered_env.data()); } else { return execvp(filename, args.data()); } } int glinject_hook_execle(const char* filename, const char* arg, ...) { // const char *str = "(In glinject_hook_execle)\n"; // write(2, str, strlen(str)); std::vector args; args.push_back((char*) arg); va_list vl; va_start(vl, arg); while(args.back() != NULL) { args.push_back(va_arg(vl, char*)); } char *const *envp = va_arg(vl, char* const*); va_end(vl); if(GLInjectShouldFilterExec(filename)) { std::vector filtered_env = GLInjectFilterEnv(envp); return execve(filename, args.data(), filtered_env.data()); } else { return execve(filename, args.data(), envp); } } int glinject_hook_execv(const char* filename, char* const argv[]) { // const char *str = "(In glinject_hook_execv)\n"; // write(2, str, strlen(str)); if(GLInjectShouldFilterExec(filename)) { std::vector filtered_env = GLInjectFilterEnv(environ); return execve(filename, argv, filtered_env.data()); } else { return execv(filename, argv); } } int glinject_hook_execvp(const char* filename, char* const argv[]) { // const char *str = "(In glinject_hook_execvp)\n"; // write(2, str, strlen(str)); if(GLInjectShouldFilterExec(filename)) { std::vector filtered_env = GLInjectFilterEnv(environ); return execvpe(filename, argv, filtered_env.data()); } else { return execvp(filename, argv); } } int glinject_hook_execve(const char* filename, char* const argv[], char* const envp[]) { // const char *str = "(In glinject_hook_execve)\n"; // write(2, str, strlen(str)); if(GLInjectShouldFilterExec(filename)) { std::vector filtered_env = GLInjectFilterEnv(envp); return execve(filename, argv, filtered_env.data()); } else { return execve(filename, argv, envp); } } int glinject_hook_execvpe(const char* filename, char* const argv[], char* const envp[]) { // const char *str = "(In glinject_hook_execvpe)\n"; // write(2, str, strlen(str)); if(GLInjectShouldFilterExec(filename)) { std::vector filtered_env = GLInjectFilterEnv(envp); return execvpe(filename, argv, filtered_env.data()); } else { return execvpe(filename, argv, envp); } } GLXWindow glinject_hook_glXCreateWindow(Display* dpy, GLXFBConfig config, Window win, const int* attrib_list) { // const char *str = "(In glinject_hook_glXCreateWindow)\n"; // write(2, str, strlen(str)); GLXWindow res = glXCreateWindow(dpy, config, win, attrib_list); if(res == 0) return 0; { std::lock_guard lock(g_glinject_mutex); GLInjectInit(); g_glinject->NewGLXFrameGrabber(dpy, win, res); } return res; } void glinject_hook_glXDestroyWindow(Display* dpy, GLXWindow win) { // const char *str = "(In glinject_hook_glXDestroyWindow)\n"; // write(2, str, strlen(str)); glXDestroyWindow(dpy, win); { std::lock_guard lock(g_glinject_mutex); GLInjectInit(); g_glinject->DeleteGLXFrameGrabberByDrawable(dpy, win); } } int glinject_hook_XDestroyWindow(Display* dpy, Window win) { // const char *str = "(In glinject_hook_XDestroyWindow)\n"; // write(2, str, strlen(str)); int res = XDestroyWindow(dpy, win); { std::lock_guard lock(g_glinject_mutex); GLInjectInit(); g_glinject->DeleteGLXFrameGrabberByWindow(dpy, win); } return res; } void glinject_hook_glXSwapBuffers(Display* dpy, GLXDrawable drawable) { // const char *str = "(In glinject_hook_glXSwapBuffers)\n"; // write(2, str, strlen(str)); { std::lock_guard lock(g_glinject_mutex); GLInjectInit(); GLXFrameGrabber *fg = g_glinject->FindGLXFrameGrabber(dpy, drawable); if(fg == NULL) { GLINJECT_PRINT("Warning: glXSwapBuffers called without existing frame grabber, creating one assuming window == drawable."); fg = g_glinject->NewGLXFrameGrabber(dpy, drawable, drawable); } fg->GrabFrame(); } glXSwapBuffers(dpy, drawable); } GLXextFuncPtr glinject_hook_glXGetProcAddress(const GLubyte *proc_name) { // const char *str = "(In glinject_hook_glXGetProcAddress)\n"; // write(2, str, strlen(str)); for(const GLInjectHook &hook : glinject_hook_table) { if(strcmp(hook.name, (const char*) proc_name) == 0) { std::lock_guard lock(g_glinject_mutex); if(GLInjectHookDebug()) GLINJECT_PRINT("Hooked glXGetProcAddress(" << proc_name << ")."); return (GLXextFuncPtr) hook.address; } } return glXGetProcAddress(proc_name); } GLXextFuncPtr glinject_hook_glXGetProcAddressARB(const GLubyte *proc_name) { // const char *str = "(In glinject_hook_glXGetProcAddressARB)\n"; // write(2, str, strlen(str)); for(const GLInjectHook &hook : glinject_hook_table) { if(strcmp(hook.name, (const char*) proc_name) == 0) { std::lock_guard lock(g_glinject_mutex); if(GLInjectHookDebug()) GLINJECT_PRINT("Hooked glXGetProcAddressARB(" << proc_name << ")."); return (GLXextFuncPtr) hook.address; } } return glXGetProcAddressARB(proc_name); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/SSRVideoStreamWriter.cpp000066400000000000000000000247411470053624300260540ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "SSRVideoStreamWriter.h" #include #include #include #include #include // Returns the program name (i.e. filename of the binary) static std::string GetProgramName() { std::vector temp(10000); ssize_t size = readlink("/proc/self/exe", temp.data(), temp.size()); if(size < 0) return std::string(); std::string path(temp.data(), size); size_t p = path.find_last_of('/'); if(p == std::string::npos) return path; return path.substr(p + 1); } SSRVideoStreamWriter::SSRVideoStreamWriter(const std::string& channel, const std::string& source) { std::string stream_name = NumToString(hrt_time_micro()) + "-" + NumToString(getpid()) + "-" + source + "-" + GetProgramName(); m_channel_directory = "/dev/shm/ssr-" + ((channel.empty())? "channel-" + GetUserName() : channel); m_filename_main = m_channel_directory + "/video-" + stream_name; m_page_size = sysconf(_SC_PAGE_SIZE); m_width = 0; m_height = 0; m_stride = 0; m_next_frame_time = hrt_time_micro(); m_fd_main = -1; m_mmap_ptr_main = MAP_FAILED; m_mmap_size_main = 0; for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { FrameData &fd = m_frame_data[i]; fd.m_filename_frame = m_channel_directory + "/videoframe" + NumToString(i) + "-" + stream_name; fd.m_fd_frame = -1; fd.m_mmap_ptr_frame = MAP_FAILED; fd.m_mmap_size_frame = 0; } try { Init(); } catch(...) { Free(); throw; } } SSRVideoStreamWriter::~SSRVideoStreamWriter() { Free(); } void SSRVideoStreamWriter::Init() { GLINJECT_PRINT("[" << m_filename_main << "] Created video stream."); bool relax_permissions = false; { char *ssr_stream_relax_permissions = getenv("SSR_STREAM_RELAX_PERMISSIONS"); if(ssr_stream_relax_permissions != NULL && atoi(ssr_stream_relax_permissions) > 0) { GLINJECT_PRINT("Warning: Using relaxed file permissions, any user on this machine will be able to read or manipulate the stream!"); relax_permissions = true; } } // create channel directory (permissions may be wrong because of umask, fix this later) if(mkdir(m_channel_directory.c_str(), (relax_permissions)? 0777 : 0700) == -1) { if(errno != EEXIST) { // does the directory already exist? GLINJECT_PRINT("Error: Can't create channel directory!"); throw SSRStreamException(); } } // check ownership and permissions struct stat statinfo; if(lstat(m_channel_directory.c_str(), &statinfo) == -1) { GLINJECT_PRINT("Error: Can't stat channel directory!"); throw SSRStreamException(); } if(!S_ISDIR(statinfo.st_mode) || S_ISLNK(statinfo.st_mode)) { GLINJECT_PRINT("Error: Channel directory is not a regular directory!"); throw SSRStreamException(); } if(statinfo.st_uid == geteuid()) { if(chmod(m_channel_directory.c_str(), (relax_permissions)? 0777 : 0700) == -1) { GLINJECT_PRINT("Error: Can't set channel directory mode!"); throw SSRStreamException(); } } else { if(!relax_permissions) { GLINJECT_PRINT("Error: Channel directory is owned by a different user! " "Choose a different channel name, or enable relaxed file permissions to use it anyway."); throw SSRStreamException(); } } // open frame files for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { FrameData &fd = m_frame_data[i]; fd.m_fd_frame = open(fd.m_filename_frame.c_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, (relax_permissions)? 0666 : 0600); if(fd.m_fd_frame == -1) { GLINJECT_PRINT("Error: Can't open video frame file!"); throw SSRStreamException(); } if(fchmod(fd.m_fd_frame, (relax_permissions)? 0666 : 0600) == -1) { GLINJECT_PRINT("Error: Can't set video frame file mode!"); throw SSRStreamException(); } } // open main file m_fd_main = open(m_filename_main.c_str(), O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, (relax_permissions)? 0666 : 0600); if(m_fd_main == -1) { GLINJECT_PRINT("Error: Can't open video stream file!"); throw SSRStreamException(); } if(fchmod(m_fd_main, (relax_permissions)? 0666 : 0600) == -1) { GLINJECT_PRINT("Error: Can't set video stream file mode!"); throw SSRStreamException(); } // resize main file m_mmap_size_main = (sizeof(GLInjectHeader) + GLINJECT_RING_BUFFER_SIZE * sizeof(GLInjectFrameInfo) + m_page_size - 1) / m_page_size * m_page_size; if(ftruncate(m_fd_main, m_mmap_size_main) == -1) { GLINJECT_PRINT("Error: Can't resize video stream file!"); throw SSRStreamException(); } // map main file m_mmap_ptr_main = mmap(NULL, m_mmap_size_main, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd_main, 0); if(m_mmap_ptr_main == MAP_FAILED) { GLINJECT_PRINT("Error: Can't memory-map video stream file!"); throw SSRStreamException(); } // initialize header GLInjectHeader *header = GetGLInjectHeader(); header->identifier = 0; // will be set later header->ring_buffer_read_pos = 0; header->ring_buffer_write_pos = 0; header->current_width = m_width; header->current_height = m_height; header->frame_counter = 0; // initialize frame info for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { GLInjectFrameInfo *frameinfo = GetGLInjectFrameInfo(i); frameinfo->timestamp = 0; frameinfo->width = 0; frameinfo->height = 0; frameinfo->stride = 0; } // set the identifier to indicate that initialization is complete std::atomic_thread_fence(std::memory_order_release); header->identifier = GLINJECT_IDENTIFIER; std::atomic_thread_fence(std::memory_order_release); } void SSRVideoStreamWriter::Free() { for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { FrameData &fd = m_frame_data[i]; // unmap frame file if(fd.m_mmap_ptr_frame != MAP_FAILED) { munmap(fd.m_mmap_ptr_frame, fd.m_mmap_size_frame); fd.m_mmap_ptr_frame = MAP_FAILED; } // close and unlink frame file if(fd.m_fd_frame != -1) { close(fd.m_fd_frame); fd.m_fd_frame = -1; unlink(fd.m_filename_frame.c_str()); } } // unmap main file if(m_mmap_ptr_main != MAP_FAILED) { munmap(m_mmap_ptr_main, m_mmap_size_main); m_mmap_ptr_main = MAP_FAILED; } // close and unlink main file if(m_fd_main != -1) { close(m_fd_main); m_fd_main = -1; unlink(m_filename_main.c_str()); } GLINJECT_PRINT("[" << m_filename_main << "] Destroyed video stream."); } GLInjectHeader* SSRVideoStreamWriter::GetGLInjectHeader() { return (GLInjectHeader*) m_mmap_ptr_main; } GLInjectFrameInfo* SSRVideoStreamWriter::GetGLInjectFrameInfo(unsigned int frame) { return (GLInjectFrameInfo*) ((char*) m_mmap_ptr_main + sizeof(GLInjectHeader) + frame * sizeof(GLInjectFrameInfo)); } void SSRVideoStreamWriter::UpdateSize(unsigned int width, unsigned int height, int stride) { if(m_width != width || m_height != height) { GLINJECT_PRINT("[" << m_filename_main << "] frame size = " << width << "x" << height << "."); m_width = width; m_height = height; GLInjectHeader *header = GetGLInjectHeader(); header->current_width = m_width; header->current_height = m_height; std::atomic_thread_fence(std::memory_order_release); } m_stride = stride; } void* SSRVideoStreamWriter::NewFrame(unsigned int* flags) { // increment the frame counter GLInjectHeader *header = GetGLInjectHeader(); ++header->frame_counter; std::atomic_thread_fence(std::memory_order_release); // get capture parameters std::atomic_thread_fence(std::memory_order_acquire); *flags = header->capture_flags; if(!(*flags & GLINJECT_FLAG_CAPTURE_ENABLED)) return NULL; // check the timestamp and maybe limit the fps unsigned int target_fps = header->capture_target_fps; int64_t timestamp = hrt_time_micro(); if(target_fps > 0) { int64_t interval = 1000000 / target_fps; if(*flags & GLINJECT_FLAG_LIMIT_FPS) { if(timestamp < m_next_frame_time) { usleep(m_next_frame_time - timestamp); timestamp = hrt_time_micro(); } } else { if(timestamp < m_next_frame_time - interval) return NULL; } m_next_frame_time = std::max(m_next_frame_time + interval, timestamp); } // make sure that at least one frame is available unsigned int read_pos = header->ring_buffer_read_pos; unsigned int write_pos = header->ring_buffer_write_pos; unsigned int frames_used = positive_mod((int) write_pos - (int) read_pos, GLINJECT_RING_BUFFER_SIZE * 2); if(frames_used >= GLINJECT_RING_BUFFER_SIZE) return NULL; // write frame info GLInjectFrameInfo *frameinfo = GetGLInjectFrameInfo(write_pos % GLINJECT_RING_BUFFER_SIZE); frameinfo->timestamp = timestamp; frameinfo->width = m_width; frameinfo->height = m_height; frameinfo->stride = m_stride; // prepare the frame file FrameData &fd = m_frame_data[write_pos % GLINJECT_RING_BUFFER_SIZE]; size_t required_size = (size_t) abs(m_stride) * (size_t) m_height; if(required_size > fd.m_mmap_size_frame) { // calculate new size required_size = (required_size + required_size / 4 + m_page_size - 1) / m_page_size * m_page_size; // unmap frame file if(fd.m_mmap_ptr_frame != MAP_FAILED) { munmap(fd.m_mmap_ptr_frame, fd.m_mmap_size_frame); fd.m_mmap_ptr_frame = MAP_FAILED; fd.m_mmap_size_frame = 0; } // resize frame file if(ftruncate(fd.m_fd_frame, required_size) == -1) { GLINJECT_PRINT("Error: Can't resize video frame file!"); throw SSRStreamException(); } // map frame file fd.m_mmap_ptr_frame = mmap(NULL, required_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.m_fd_frame, 0); if(fd.m_mmap_ptr_frame == MAP_FAILED) { GLINJECT_PRINT("Error: Can't memory-map video frame file!"); throw SSRStreamException(); } fd.m_mmap_size_frame = required_size; } return fd.m_mmap_ptr_frame; } void SSRVideoStreamWriter::NextFrame() { // make sure all changes are visible std::atomic_thread_fence(std::memory_order_release); // go to the next frame GLInjectHeader *header = GetGLInjectHeader(); header->ring_buffer_write_pos = (header->ring_buffer_write_pos + 1) % (GLINJECT_RING_BUFFER_SIZE * 2); std::atomic_thread_fence(std::memory_order_release); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/SSRVideoStreamWriter.h000066400000000000000000000037521470053624300255200ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "Global.h" #include "ShmStructs.h" class SSRVideoStreamWriter { private: struct FrameData { std::string m_filename_frame; int m_fd_frame; void *m_mmap_ptr_frame; size_t m_mmap_size_frame; }; private: std::string m_channel_directory, m_filename_main; size_t m_page_size; unsigned int m_width, m_height; int m_stride; int64_t m_next_frame_time; int m_fd_main, m_file_lock; void *m_mmap_ptr_main; size_t m_mmap_size_main; FrameData m_frame_data[GLINJECT_RING_BUFFER_SIZE]; public: SSRVideoStreamWriter(const std::string& channel, const std::string& source); ~SSRVideoStreamWriter(); private: void Init(); void Free(); GLInjectHeader* GetGLInjectHeader(); GLInjectFrameInfo* GetGLInjectFrameInfo(unsigned int frame); public: // Updates the size of the video stream. void UpdateSize(unsigned int width, unsigned int height, int stride); // Should be called for every new frame. It will determine whether the frame should be captured. // If it should be captured, it will allocate shared memory for the frame and return a pointer. Otherwise it returns NULL. void* NewFrame(unsigned int* flags); // Writes the allocated frame to the ring buffer after it has been captured. void NextFrame(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/ShmStructs.h000066400000000000000000000050741470053624300236270ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include /* A captured video stream is transmitted to SimpleScreenRecorder using shared memory files (i.e. files in /dev/shm). The system is entirely lock-free and thread-safe, but supports only a single reader and a single writer. There are two types of files: >>>> /dev/shm/ssr-video-PID-SOURCE-DESCRIPTION <<<< > PID is the process ID of the application, SOURCE is the source of the stream (should be unique for this process), DESCRIPTION can be anything. > This file contains a GLInjectHeader struct followed by a GLInjectFrameInfo struct for each frame in the ring buffer. >>>> /dev/shm/ssr-videoframeNUM-PID-SOURCE-DESCRIPTION <<<< > NUM is the position of the frame in the ring buffer (starting from zero). > This file contains one video frame. The file size is not constant, it may be enlarged when the video frame size changes. The size can only increase, not decrease. */ // Disable padding to make sure the 32-bit and 64-bit libs are compatible. #pragma pack(push, 1) #define GLINJECT_RING_BUFFER_SIZE 4 #define GLINJECT_IDENTIFIER 0x8af7a476 #define GLINJECT_FLAG_CAPTURE_ENABLED 0x0001 #define GLINJECT_FLAG_RECORD_CURSOR 0x0002 #define GLINJECT_FLAG_LIMIT_FPS 0x0004 struct GLInjectHeader { // identifier uint32_t identifier; // ring buffer uint32_t ring_buffer_read_pos, ring_buffer_write_pos; // video stream info: set by the captured application uint32_t current_width, current_height; uint32_t frame_counter; // capture parameters: set by SimpleScreenRecorder uint32_t capture_flags; uint32_t capture_target_fps; }; struct GLInjectFrameInfo { // frame info: set by the captured application int64_t timestamp; uint32_t width, height; int32_t stride; // stride can be negative, this means the frame is upside-down (this is typical for OpenGL) }; #pragma pack(pop) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/plthook.h000066400000000000000000000057061470053624300231720ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil -*- * * plthook.h -- the header file of plthook * * URL: https://github.com/kubo/plthook * * ------------------------------------------------------ * * Copyright 2013-2014 Kubo Takehiro * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of the authors. * */ #ifndef PLTHOOK_H #define PLTHOOK_H 1 #define PLTHOOK_SUCCESS 0 #define PLTHOOK_FILE_NOT_FOUND 1 #define PLTHOOK_INVALID_FILE_FORMAT 2 #define PLTHOOK_FUNCTION_NOT_FOUND 3 #define PLTHOOK_INVALID_ARGUMENT 4 #define PLTHOOK_OUT_OF_MEMORY 5 #define PLTHOOK_INTERNAL_ERROR 6 #define PLTHOOK_NOT_IMPLEMENTED 7 typedef struct plthook plthook_t; #ifdef __cplusplus extern "C" { #endif int plthook_open(plthook_t **plthook_out, const char *filename); int plthook_open_by_handle(plthook_t **plthook_out, void *handle); int plthook_open_by_address(plthook_t **plthook_out, void *address); int plthook_open_by_linkmap(plthook_t **plthook_out, void *linkmap); int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out); int plthook_enum_adv(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, unsigned int *reltype_out); int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc); int plthook_replace_adv(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc, void *expect); void plthook_close(plthook_t *plthook); const char *plthook_error(void); #ifdef __cplusplus } /* extern "C" */ #endif #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/glinject/plthook_elf.c000066400000000000000000000720501470053624300240070ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil -*- * * plthook_elf.c -- implementation of plthook for ELF format * * URL: https://github.com/kubo/plthook * * ------------------------------------------------------ * * Copyright 2013-2019 Kubo Takehiro * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of the authors. * */ #if defined(__sun) && defined(_XOPEN_SOURCE) && !defined(__EXTENSIONS__) #define __EXTENSIONS__ #endif #if defined(__linux__) && !defined(_GNU_SOURCE) #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #ifdef __sun #include #include #define ELF_TARGET_ALL #endif /* __sun */ #ifdef __FreeBSD__ #include #include #include #endif #include #include #include "plthook.h" #if defined __UCLIBC__ && !defined RTLD_NOLOAD #define RTLD_NOLOAD 0 #endif #ifndef __GNUC__ #define __attribute__(arg) #endif #if defined __FreeBSD__ && defined __i386__ && __ELF_WORD_SIZE == 64 #error 32-bit application on 64-bit OS is not supported. #endif #if !defined(R_X86_64_JUMP_SLOT) && defined(R_X86_64_JMP_SLOT) #define R_X86_64_JUMP_SLOT R_X86_64_JMP_SLOT #endif #if defined __x86_64__ || defined __x86_64 #define R_JUMP_SLOT R_X86_64_JUMP_SLOT #define R_GLOBAL_DATA R_X86_64_GLOB_DAT #elif defined __i386__ || defined __i386 #define R_JUMP_SLOT R_386_JMP_SLOT #define R_GLOBAL_DATA R_386_GLOB_DAT #define R_PC_RELATIVE R_386_PC32 #define PC_RELATIVE_OFFSET 4 #define USE_REL #elif defined __arm__ || defined __arm #define R_JUMP_SLOT R_ARM_JUMP_SLOT #define R_GLOBAL_DATA R_ARM_GLOB_DAT #define USE_REL #elif defined __aarch64__ || defined __aarch64 /* ARM64 */ #define R_JUMP_SLOT R_AARCH64_JUMP_SLOT #define R_GLOBAL_DATA R_AARCH64_GLOB_DAT #elif defined __powerpc64__ #define R_JUMP_SLOT R_PPC64_JMP_SLOT #define R_GLOBAL_DATA R_PPC64_GLOB_DAT #elif defined __powerpc__ #define R_JUMP_SLOT R_PPC_JMP_SLOT #define R_GLOBAL_DATA R_PPC_GLOB_DAT #elif 0 /* disabled because not tested */ && (defined __sparcv9 || defined __sparc_v9__) #define R_JUMP_SLOT R_SPARC_JMP_SLOT #elif 0 /* disabled because not tested */ && (defined __sparc || defined __sparc__) #define R_JUMP_SLOT R_SPARC_JMP_SLOT #elif 0 /* disabled because not tested */ && (defined __ia64 || defined __ia64__) #define R_JUMP_SLOT R_IA64_IPLTMSB #else #error unsupported OS #endif #ifdef USE_REL #define Elf_Plt_Rel Elf_Rel #define PLT_DT_REL DT_REL #define PLT_DT_RELSZ DT_RELSZ #define PLT_DT_RELENT DT_RELENT #else #define Elf_Plt_Rel Elf_Rela #define PLT_DT_REL DT_RELA #define PLT_DT_RELSZ DT_RELASZ #define PLT_DT_RELENT DT_RELAENT #endif #if defined __LP64__ #ifndef ELF_CLASS #define ELF_CLASS ELFCLASS64 #endif #define SIZE_T_FMT "lu" #define ELF_WORD_FMT "u" #ifdef __ANDROID__ #define ELF_XWORD_FMT "llu" #else #define ELF_XWORD_FMT "lu" #endif #define ELF_SXWORD_FMT "ld" #define Elf_Half Elf64_Half #define Elf_Xword Elf64_Xword #define Elf_Sxword Elf64_Sxword #define Elf_Ehdr Elf64_Ehdr #define Elf_Phdr Elf64_Phdr #define Elf_Sym Elf64_Sym #define Elf_Dyn Elf64_Dyn #define Elf_Rel Elf64_Rel #define Elf_Rela Elf64_Rela #ifndef ELF_R_SYM #define ELF_R_SYM ELF64_R_SYM #endif #ifndef ELF_R_TYPE #define ELF_R_TYPE ELF64_R_TYPE #endif #else /* __LP64__ */ #ifndef ELF_CLASS #define ELF_CLASS ELFCLASS32 #endif #define SIZE_T_FMT "u" #ifdef __sun #define ELF_WORD_FMT "lu" #define ELF_XWORD_FMT "lu" #define ELF_SXWORD_FMT "ld" #else #define ELF_WORD_FMT "u" #define ELF_XWORD_FMT "u" #define ELF_SXWORD_FMT "d" #endif #define Elf_Half Elf32_Half #define Elf_Xword Elf32_Word #define Elf_Sxword Elf32_Sword #define Elf_Ehdr Elf32_Ehdr #define Elf_Phdr Elf32_Phdr #define Elf_Sym Elf32_Sym #define Elf_Dyn Elf32_Dyn #define Elf_Rel Elf32_Rel #define Elf_Rela Elf32_Rela #ifndef ELF_R_SYM #define ELF_R_SYM ELF32_R_SYM #endif #ifndef ELF_R_TYPE #define ELF_R_TYPE ELF32_R_TYPE #endif #endif /* __LP64__ */ struct plthook { const Elf_Sym *dynsym; const char *dynstr; size_t dynstr_size; const char *plt_addr_base; const Elf_Plt_Rel *rela_plt; size_t rela_plt_cnt; #ifdef R_GLOBAL_DATA const Elf_Plt_Rel *rela_dyn; size_t rela_dyn_cnt; #endif }; static char errmsg[512]; static size_t page_size; #define ALIGN_ADDR(addr) ((void*)((size_t)(addr) & ~(page_size - 1))) static int plthook_open_executable(plthook_t **plthook_out); static int plthook_open_shared_library(plthook_t **plthook_out, const char *filename); static const Elf_Dyn *find_dyn_by_tag(const Elf_Dyn *dyn, Elf_Sxword tag); static int plthook_open_real(plthook_t **plthook_out, struct link_map *lmap); #if defined __FreeBSD__ || defined __sun static int check_elf_header(const Elf_Ehdr *ehdr); #endif static void set_errmsg(const char *fmt, ...) __attribute__((__format__ (__printf__, 1, 2))); #if defined __ANDROID__ || defined __UCLIBC__ struct dl_iterate_data { char* addr; struct link_map lmap; }; static int dl_iterate_cb(struct dl_phdr_info *info, size_t size, void *cb_data) { struct dl_iterate_data *data = (struct dl_iterate_data*)cb_data; Elf_Half idx = 0; for (idx = 0; idx < info->dlpi_phnum; ++idx) { const Elf_Phdr *phdr = &info->dlpi_phdr[idx]; char* base = (char*)info->dlpi_addr + phdr->p_vaddr; if (base <= data->addr && data->addr < base + phdr->p_memsz) { break; } } if (idx == info->dlpi_phnum) { return 0; } for (idx = 0; idx < info->dlpi_phnum; ++idx) { const Elf_Phdr *phdr = &info->dlpi_phdr[idx]; if (phdr->p_type == PT_DYNAMIC) { data->lmap.l_addr = info->dlpi_addr; data->lmap.l_ld = (Elf_Dyn*)(info->dlpi_addr + phdr->p_vaddr); return 1; } } return 0; } #endif int plthook_open(plthook_t **plthook_out, const char *filename) { *plthook_out = NULL; if (filename == NULL) { return plthook_open_executable(plthook_out); } else { return plthook_open_shared_library(plthook_out, filename); } } int plthook_open_by_handle(plthook_t **plthook_out, void *hndl) { #if defined __ANDROID__ || defined __UCLIBC__ const static char *symbols[] = { "__INIT_ARRAY__", "_end", "_start" }; size_t i; if (hndl == NULL) { set_errmsg("NULL handle"); return PLTHOOK_FILE_NOT_FOUND; } for (i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) { char *addr = dlsym(hndl, symbols[i]); if (addr != NULL) { return plthook_open_by_address(plthook_out, addr - 1); } } set_errmsg("Could not find an address in the specified handle."); return PLTHOOK_INTERNAL_ERROR; #else struct link_map *lmap = NULL; if (hndl == NULL) { set_errmsg("NULL handle"); return PLTHOOK_FILE_NOT_FOUND; } if (dlinfo(hndl, RTLD_DI_LINKMAP, &lmap) != 0) { set_errmsg("dlinfo error"); return PLTHOOK_FILE_NOT_FOUND; } return plthook_open_real(plthook_out, lmap); #endif } int plthook_open_by_address(plthook_t **plthook_out, void *address) { #if defined __FreeBSD__ return PLTHOOK_NOT_IMPLEMENTED; #elif defined __ANDROID__ || defined __UCLIBC__ struct dl_iterate_data data = {0,}; data.addr = address; dl_iterate_phdr(dl_iterate_cb, &data); if (data.lmap.l_ld == NULL) { set_errmsg("Could not find memory region containing address %p", address); return PLTHOOK_INTERNAL_ERROR; } return plthook_open_real(plthook_out, &data.lmap); #else Dl_info info; struct link_map *lmap = NULL; *plthook_out = NULL; if (dladdr1(address, &info, (void**)&lmap, RTLD_DL_LINKMAP) == 0) { set_errmsg("dladdr error"); return PLTHOOK_FILE_NOT_FOUND; } return plthook_open_real(plthook_out, lmap); #endif } int plthook_open_by_linkmap(plthook_t **plthook_out, void *linkmap) { return plthook_open_real(plthook_out, (struct link_map*)linkmap); } static int plthook_open_executable(plthook_t **plthook_out) { #if defined __ANDROID__ || defined __UCLIBC__ return plthook_open_shared_library(plthook_out, NULL); #elif defined __linux__ return plthook_open_real(plthook_out, _r_debug.r_map); #elif defined __sun const char *auxv_file = "/proc/self/auxv"; #define NUM_AUXV_CNT 10 FILE *fp = fopen(auxv_file, "r"); auxv_t auxv; struct r_debug *r_debug = NULL; if (fp == NULL) { set_errmsg("Could not open %s: %s", auxv_file, strerror(errno)); return PLTHOOK_INTERNAL_ERROR; } while (fread(&auxv, sizeof(auxv_t), 1, fp) == 1) { if (auxv.a_type == AT_SUN_LDDATA) { r_debug = (struct r_debug *)auxv.a_un.a_ptr; break; } } fclose(fp); if (r_debug == NULL) { set_errmsg("Could not find r_debug"); return PLTHOOK_INTERNAL_ERROR; } return plthook_open_real(plthook_out, r_debug->r_map); #elif defined __FreeBSD__ return plthook_open_shared_library(plthook_out, NULL); #else set_errmsg("Opening the main program is not supported on this platform."); return PLTHOOK_NOT_IMPLEMENTED; #endif } static int plthook_open_shared_library(plthook_t **plthook_out, const char *filename) { void *hndl = dlopen(filename, RTLD_LAZY | RTLD_NOLOAD); #if defined __ANDROID__ || defined __UCLIBC__ int rv; #else struct link_map *lmap = NULL; #endif if (hndl == NULL) { set_errmsg("dlopen error: %s", dlerror()); return PLTHOOK_FILE_NOT_FOUND; } #if defined __ANDROID__ || defined __UCLIBC__ rv = plthook_open_by_handle(plthook_out, hndl); dlclose(hndl); return rv; #else if (dlinfo(hndl, RTLD_DI_LINKMAP, &lmap) != 0) { set_errmsg("dlinfo error"); dlclose(hndl); return PLTHOOK_FILE_NOT_FOUND; } dlclose(hndl); return plthook_open_real(plthook_out, lmap); #endif } static const Elf_Dyn *find_dyn_by_tag(const Elf_Dyn *dyn, Elf_Sxword tag) { while (dyn->d_tag != DT_NULL) { if (dyn->d_tag == tag) { return dyn; } dyn++; } return NULL; } #ifdef __linux__ static int get_memory_permission(void *address) { unsigned long addr = (unsigned long)address; FILE *fp; char buf[PATH_MAX]; char perms[5]; int bol = 1; fp = fopen("/proc/self/maps", "r"); if (fp == NULL) { set_errmsg("failed to open /proc/self/maps"); return 0; } while (fgets(buf, PATH_MAX, fp) != NULL) { unsigned long start, end; int eol = (strchr(buf, '\n') != NULL); if (bol) { /* The fgets reads from the beginning of a line. */ if (!eol) { /* The next fgets reads from the middle of the same line. */ bol = 0; } } else { /* The fgets reads from the middle of a line. */ if (eol) { /* The next fgets reads from the beginnig of a line. */ bol = 1; } continue; } if (sscanf(buf, "%lx-%lx %4s", &start, &end, perms) != 3) { continue; } if (start <= addr && addr < end) { int prot = 0; if (perms[0] == 'r') { prot |= PROT_READ; } else if (perms[0] != '-') { goto unknown_perms; } if (perms[1] == 'w') { prot |= PROT_WRITE; } else if (perms[1] != '-') { goto unknown_perms; } if (perms[2] == 'x') { prot |= PROT_EXEC; } else if (perms[2] != '-') { goto unknown_perms; } if (perms[3] != 'p') { goto unknown_perms; } if (perms[4] != '\0') { perms[4] = '\0'; goto unknown_perms; } fclose(fp); return prot; } } fclose(fp); set_errmsg("Could not find memory region containing %p", (void*)addr); return 0; unknown_perms: fclose(fp); set_errmsg("Unexcepted memory permission %s at %p", perms, (void*)addr); return 0; } #elif defined __FreeBSD__ static int get_memory_permission(void *address) { uint64_t addr = (uint64_t)address; struct kinfo_vmentry *top; int i, cnt; top = kinfo_getvmmap(getpid(), &cnt); if (top == NULL) { set_errmsg("failed to call kinfo_getvmmap()\n"); return 0; } for (i = 0; i < cnt; i++) { struct kinfo_vmentry *kve = top + i; if (kve->kve_start <= addr && addr < kve->kve_end) { int prot = 0; if (kve->kve_protection & KVME_PROT_READ) { prot |= PROT_READ; } if (kve->kve_protection & KVME_PROT_WRITE) { prot |= PROT_WRITE; } if (kve->kve_protection & KVME_PROT_EXEC) { prot |= PROT_EXEC; } if (prot == 0) { set_errmsg("Unknown kve_protection 0x%x at %p", kve->kve_protection, (void*)addr); } free(top); return prot; } } free(top); set_errmsg("Could not find memory region containing %p", (void*)addr); return 0; } #elif defined(__sun) #define NUM_MAPS 20 static int get_memory_permission(void *address) { unsigned long addr = (unsigned long)address; FILE *fp; prmap_t maps[NUM_MAPS]; size_t num; fp = fopen("/proc/self/map", "r"); if (fp == NULL) { set_errmsg("failed to open /proc/self/map"); return 0; } while ((num = fread(maps, sizeof(prmap_t), NUM_MAPS, fp)) > 0) { size_t i; for (i = 0; i < num; i++) { prmap_t *map = &maps[i]; if (map->pr_vaddr <= addr && addr < map->pr_vaddr + map->pr_size) { int prot = 0; if (map->pr_mflags & MA_READ) { prot |= PROT_READ; } if (map->pr_mflags & MA_WRITE) { prot |= PROT_WRITE; } if (map->pr_mflags & MA_EXEC) { prot |= PROT_EXEC; } if (prot == 0) { set_errmsg("Unknown pr_mflags 0x%x at %p", map->pr_mflags, (void*)addr); } fclose(fp); return prot; } } } fclose(fp); set_errmsg("Could not find memory region containing %p", (void*)addr); return 0; } #else #error Unsupported platform #endif static int plthook_open_real(plthook_t **plthook_out, struct link_map *lmap) { plthook_t plthook = {NULL,}; const Elf_Dyn *dyn; const char *dyn_addr_base = NULL; if (page_size == 0) { page_size = sysconf(_SC_PAGESIZE); } #if defined __linux__ plthook.plt_addr_base = (char*)lmap->l_addr; #if defined __ANDROID__ || defined __UCLIBC__ dyn_addr_base = (const char*)lmap->l_addr; #endif #elif defined __FreeBSD__ || defined __sun const Elf_Ehdr *ehdr = (const Elf_Ehdr*)lmap->l_addr; int rv_ = check_elf_header(ehdr); if (rv_ != 0) { return rv_; } if (ehdr->e_type == ET_DYN) { dyn_addr_base = (const char*)lmap->l_addr; plthook.plt_addr_base = (const char*)lmap->l_addr; } #else #error unsupported OS #endif /* get .dynsym section */ dyn = find_dyn_by_tag(lmap->l_ld, DT_SYMTAB); if (dyn == NULL) { set_errmsg("failed to find DT_SYMTAB"); return PLTHOOK_INTERNAL_ERROR; } plthook.dynsym = (const Elf_Sym*)(dyn_addr_base + dyn->d_un.d_ptr); /* Check sizeof(Elf_Sym) */ dyn = find_dyn_by_tag(lmap->l_ld, DT_SYMENT); if (dyn == NULL) { set_errmsg("failed to find DT_SYMTAB"); return PLTHOOK_INTERNAL_ERROR; } if (dyn->d_un.d_val != sizeof(Elf_Sym)) { set_errmsg("DT_SYMENT size %" ELF_XWORD_FMT " != %" SIZE_T_FMT, dyn->d_un.d_val, sizeof(Elf_Sym)); return PLTHOOK_INTERNAL_ERROR; } /* get .dynstr section */ dyn = find_dyn_by_tag(lmap->l_ld, DT_STRTAB); if (dyn == NULL) { set_errmsg("failed to find DT_STRTAB"); return PLTHOOK_INTERNAL_ERROR; } plthook.dynstr = dyn_addr_base + dyn->d_un.d_ptr; /* get .dynstr size */ dyn = find_dyn_by_tag(lmap->l_ld, DT_STRSZ); if (dyn == NULL) { set_errmsg("failed to find DT_STRSZ"); return PLTHOOK_INTERNAL_ERROR; } plthook.dynstr_size = dyn->d_un.d_val; /* get .rela.plt or .rel.plt section */ dyn = find_dyn_by_tag(lmap->l_ld, DT_JMPREL); if (dyn != NULL) { plthook.rela_plt = (const Elf_Plt_Rel *)(dyn_addr_base + dyn->d_un.d_ptr); dyn = find_dyn_by_tag(lmap->l_ld, DT_PLTRELSZ); if (dyn == NULL) { set_errmsg("failed to find DT_PLTRELSZ"); return PLTHOOK_INTERNAL_ERROR; } plthook.rela_plt_cnt = dyn->d_un.d_val / sizeof(Elf_Plt_Rel); } #ifdef R_GLOBAL_DATA /* get .rela.dyn or .rel.dyn section */ dyn = find_dyn_by_tag(lmap->l_ld, PLT_DT_REL); if (dyn != NULL) { size_t total_size, elem_size; plthook.rela_dyn = (const Elf_Plt_Rel *)(dyn_addr_base + dyn->d_un.d_ptr); dyn = find_dyn_by_tag(lmap->l_ld, PLT_DT_RELSZ); if (dyn == NULL) { set_errmsg("failed to find PLT_DT_RELSZ"); return PLTHOOK_INTERNAL_ERROR; } total_size = dyn->d_un.d_ptr; dyn = find_dyn_by_tag(lmap->l_ld, PLT_DT_RELENT); if (dyn == NULL) { set_errmsg("failed to find PLT_DT_RELENT"); return PLTHOOK_INTERNAL_ERROR; } elem_size = dyn->d_un.d_ptr; plthook.rela_dyn_cnt = total_size / elem_size; } #endif #ifdef R_GLOBAL_DATA if (plthook.rela_plt == NULL && plthook.rela_dyn == NULL) { set_errmsg("failed to find either of DT_JMPREL and DT_REL"); return PLTHOOK_INTERNAL_ERROR; } #else if (plthook.rela_plt == NULL) { set_errmsg("failed to find DT_JMPREL"); return PLTHOOK_INTERNAL_ERROR; } #endif *plthook_out = malloc(sizeof(plthook_t)); if (*plthook_out == NULL) { set_errmsg("failed to allocate memory: %" SIZE_T_FMT " bytes", sizeof(plthook_t)); return PLTHOOK_OUT_OF_MEMORY; } **plthook_out = plthook; return 0; } #if defined __FreeBSD__ || defined __sun static int check_elf_header(const Elf_Ehdr *ehdr) { static const unsigned short s = 1; /* Check endianness at runtime. */ unsigned char elfdata = (*(const char*)&s) ? ELFDATA2LSB : ELFDATA2MSB; if (ehdr == NULL) { set_errmsg("invalid elf header address: NULL"); return PLTHOOK_INTERNAL_ERROR; } if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { set_errmsg("invalid file signature: 0x%02x,0x%02x,0x%02x,0x%02x", ehdr->e_ident[0], ehdr->e_ident[1], ehdr->e_ident[2], ehdr->e_ident[3]); return PLTHOOK_INVALID_FILE_FORMAT; } if (ehdr->e_ident[EI_CLASS] != ELF_CLASS) { set_errmsg("invalid elf class: 0x%02x", ehdr->e_ident[EI_CLASS]); return PLTHOOK_INVALID_FILE_FORMAT; } if (ehdr->e_ident[EI_DATA] != elfdata) { set_errmsg("invalid elf data: 0x%02x", ehdr->e_ident[EI_DATA]); return PLTHOOK_INVALID_FILE_FORMAT; } if (ehdr->e_ident[EI_VERSION] != EV_CURRENT) { set_errmsg("invalid elf version: 0x%02x", ehdr->e_ident[EI_VERSION]); return PLTHOOK_INVALID_FILE_FORMAT; } if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) { set_errmsg("invalid file type: 0x%04x", ehdr->e_type); return PLTHOOK_INVALID_FILE_FORMAT; } if (ehdr->e_version != EV_CURRENT) { set_errmsg("invalid object file version: %" ELF_WORD_FMT, ehdr->e_version); return PLTHOOK_INVALID_FILE_FORMAT; } if (ehdr->e_ehsize != sizeof(Elf_Ehdr)) { set_errmsg("invalid elf header size: %u", ehdr->e_ehsize); return PLTHOOK_INVALID_FILE_FORMAT; } if (ehdr->e_phentsize != sizeof(Elf_Phdr)) { set_errmsg("invalid program header table entry size: %u", ehdr->e_phentsize); return PLTHOOK_INVALID_FILE_FORMAT; } return 0; } #endif static int check_rel(const plthook_t *plthook, const Elf_Plt_Rel *plt, Elf_Xword r_type, const char **name_out, void ***addr_out) { if (ELF_R_TYPE(plt->r_info) == r_type) { size_t idx = ELF_R_SYM(plt->r_info); idx = plthook->dynsym[idx].st_name; if (idx + 1 > plthook->dynstr_size) { set_errmsg("too big section header string table index: %" SIZE_T_FMT, idx); return PLTHOOK_INVALID_FILE_FORMAT; } *name_out = plthook->dynstr + idx; *addr_out = (void**)(plthook->plt_addr_base + plt->r_offset); return 0; } return -1; } static int check_rel_adv(const plthook_t *plthook, const Elf_Plt_Rel *plt, Elf_Xword r_type, const char **name_out, void ***addr_out, unsigned int *reltype_out) { if (ELF_R_TYPE(plt->r_info) == r_type) { size_t idx = ELF_R_SYM(plt->r_info); idx = plthook->dynsym[idx].st_name; if (idx + 1 > plthook->dynstr_size) { set_errmsg("too big section header string table index: %" SIZE_T_FMT, idx); return PLTHOOK_INVALID_FILE_FORMAT; } *name_out = plthook->dynstr + idx; *addr_out = (void**)(plthook->plt_addr_base + plt->r_offset); *reltype_out = r_type; return 0; } return -1; } int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out) { while (*pos < plthook->rela_plt_cnt) { const Elf_Plt_Rel *plt = plthook->rela_plt + *pos; int rv = check_rel(plthook, plt, R_JUMP_SLOT, name_out, addr_out); (*pos)++; if (rv >= 0) { return rv; } } #ifdef R_GLOBAL_DATA while (*pos < plthook->rela_plt_cnt + plthook->rela_dyn_cnt) { const Elf_Plt_Rel *plt = plthook->rela_dyn + (*pos - plthook->rela_plt_cnt); int rv = check_rel(plthook, plt, R_GLOBAL_DATA, name_out, addr_out); (*pos)++; if (rv >= 0) { return rv; } } #endif *name_out = NULL; *addr_out = NULL; return EOF; } int plthook_enum_adv(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, unsigned int *reltype_out) { // fprintf(stderr, "plthook_enum: started, pos = %u, plthook->rela_plt_cnt = %lu, plthook->rela_dyn_cnt = %lu\n", *pos, plthook->rela_plt_cnt, plthook->rela_dyn_cnt); while (*pos < plthook->rela_plt_cnt) { const Elf_Plt_Rel *plt = plthook->rela_plt + *pos; int rv = check_rel_adv(plthook, plt, R_JUMP_SLOT, name_out, addr_out, reltype_out); // fprintf(stderr, "plthook_enum: check_rel returned %d\n", rv); (*pos)++; if (rv >= 0) { return rv; } } #ifdef R_GLOBAL_DATA while (*pos < plthook->rela_plt_cnt + plthook->rela_dyn_cnt) { const Elf_Plt_Rel *plt = plthook->rela_dyn + (*pos - plthook->rela_plt_cnt); int rv = check_rel_adv(plthook, plt, R_GLOBAL_DATA, name_out, addr_out, reltype_out); #ifdef R_PC_RELATIVE if (rv == -1) { rv = check_rel_adv(plthook, plt, R_PC_RELATIVE, name_out, addr_out, reltype_out); // if (rv == 0) { // fprintf(stderr, "plthook_enum: found R_PC_RELATIVE (%s, %p, %p)\n", *name_out, *addr_out, **addr_out); // rv = -1; // } } #endif (*pos)++; if (rv >= 0) { return rv; } } #endif *name_out = NULL; *addr_out = NULL; return EOF; } int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc) { size_t funcnamelen = strlen(funcname); unsigned int pos = 0; const char *name; void **addr; int rv; if (plthook == NULL) { set_errmsg("invalid argument: The first argument is null."); return PLTHOOK_INVALID_ARGUMENT; } while ((rv = plthook_enum(plthook, &pos, &name, &addr)) == 0) { if (strncmp(name, funcname, funcnamelen) == 0) { if (name[funcnamelen] == '\0' || name[funcnamelen] == '@') { int prot = get_memory_permission(addr); if (prot == 0) { return PLTHOOK_INTERNAL_ERROR; } if (!(prot & PROT_WRITE)) { if (mprotect(ALIGN_ADDR(addr), page_size, PROT_READ | PROT_WRITE) != 0) { set_errmsg("Could not change the process memory permission at %p: %s", ALIGN_ADDR(addr), strerror(errno)); return PLTHOOK_INTERNAL_ERROR; } } if (oldfunc) { *oldfunc = *addr; } *addr = funcaddr; if (!(prot & PROT_WRITE)) { mprotect(ALIGN_ADDR(addr), page_size, prot); } return 0; } } } if (rv == EOF) { set_errmsg("no such function: %s", funcname); rv = PLTHOOK_FUNCTION_NOT_FOUND; } return rv; } int plthook_replace_adv(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc, void *expect) { size_t funcnamelen = strlen(funcname); unsigned int pos = 0; const char *name; void **addr; unsigned int reltype; int rv; unsigned int found = 0; if (plthook == NULL) { set_errmsg("invalid argument: The first argument is null."); return PLTHOOK_INVALID_ARGUMENT; } while ((rv = plthook_enum_adv(plthook, &pos, &name, &addr, &reltype)) == 0) { if (strncmp(name, funcname, funcnamelen) == 0) { if (name[funcnamelen] == '\0' || name[funcnamelen] == '@') { int prot = get_memory_permission(addr); if (prot == 0) { return PLTHOOK_INTERNAL_ERROR; } if (!(prot & PROT_WRITE)) { if (mprotect(ALIGN_ADDR(addr), page_size, PROT_READ | PROT_WRITE) != 0) { set_errmsg("Could not change the process memory permission at %p: %s", ALIGN_ADDR(addr), strerror(errno)); return PLTHOOK_INTERNAL_ERROR; } } #ifdef R_PC_RELATIVE if (reltype == R_PC_RELATIVE) { if (oldfunc) { *oldfunc = (void*)(((char*)addr + PC_RELATIVE_OFFSET) + (ptrdiff_t)*addr); } *addr = (void*) ((char*)funcaddr - ((char*)addr + PC_RELATIVE_OFFSET)); } else { #endif if (oldfunc) { *oldfunc = *addr; } *addr = funcaddr; #ifdef R_PC_RELATIVE } #endif if (!(prot & PROT_WRITE)) { mprotect(ALIGN_ADDR(addr), page_size, prot); } #ifdef R_PC_RELATIVE if (reltype == R_PC_RELATIVE) { if (oldfunc && *oldfunc != expect && *oldfunc != funcaddr) { fprintf(stderr, "PLTHOOKS WARNING: relocation of %s at %p was %p, expected %p, changed to %p\n", funcname, addr, *oldfunc, expect, funcaddr); } } #endif found++; } } } if (rv == EOF) { if (found == 0) { set_errmsg("no such function: %s", funcname); rv = PLTHOOK_FUNCTION_NOT_FOUND; } else { rv = 0; } } return rv; } void plthook_close(plthook_t *plthook) { if (plthook != NULL) { free(plthook); } } const char *plthook_error(void) { return errmsg; } static void set_errmsg(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(errmsg, sizeof(errmsg) - 1, fmt, ap); va_end(ap); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/notes.txt000066400000000000000000000251711470053624300214310ustar00rootroot00000000000000In this text file I keep notes about design decisions and issues I came across while writing this program, so I don't make the same mistakes twice. This should also make it easier to understand the code later. #### Keep the front-end and back-end completely separated #### The front-end (GUI) calls the back-end, not the other way around. This way the front-end can easily be replaced with something else (e.g. command-line). The back-end can use Qt Core classes like QString and QThread, but not GUI classes. Logging is done with the 'Logger' class. This should become an abstract class, in order to make it easier to show log results in different places (e.g. stderr for the command-line interface, and a text area for the GUI). #### Back-end design #### The back-end uses a 'push' pipeline design. Classes push their results to the next stage, and the next stage doesn't need to know where these results came from (only where they are supposed to go). This makes it very easy to use the same output class with different input classes. It should also be possible to use the same input class with different output classes by making the output class abstract, but I haven't done this yet because it wasn't needed so far. A push design is a better idea than a pull design in this case, because it makes switching inputs while recording trivial. The pipeline: +---------------+ +--------------+ | X11Input | +--------------+ | | +-------+ | or | ---> | | ===> | VideoEncoder | ===> | | | GLInjectInput | | | | | | | +---------------+ | Synchronizer | +--------------+ | Muxer | | | | | | | | | | ALSAInput | ---> | | ===> | AudioEncoder | ===> | | | | +--------------+ | | +-------+ +---------------+ +--------------+ "--->" means the result is sent to the next stage and processed immediately. "===>" means the result is stored in a queue, and will be processed by the next stage later. - X11Input: Captures the screen, or a part of it. Originally I used the 'x11grab' device from libav/ffmpeg, but this added overhead and was less flexible. - GLInjectInput: Captures frames from an OpenGL program (which was launched with GLInjectLauncher). - ALSAInput: Records audio using ALSA. Real PulseAudio support would be nice too (not a priority though). - Synchronizer: Synchronizes audio and video based on the timestamps of the frames. This class is needed even if there is no audio, because it still has to translate the timestamps to 'pts' values (which are not the same). I've hijacked the AVFrame::pkt_dts field to store the timestamps because it wasn't used (and because I want to avoid confusion with AVFrame::pts which has a very different meaning). The synchronizer drops or inserts frames of video/audio to make sure both have the same length. The synchronizer also stitches together separate segments (when the recording is paused and resumed later). This is more complex than it sounds, trust me :). The synchronizer will also do sample format conversions if needed. - VideoEncoder/AudioEncoder: Encodes the video/audio frames and generates packets. Inherits BaseEncoder. All the hard work is done by libav/ffmpeg, this is basically a wrapper. - Muxer: Combines video and audio packets with the required metadata and writes it to a file. Again, all the hard work is done by libav/ffmpeg. #### How to use libav/ffmpeg #### Libav/ffmpeg is very confusing and the documentation doesn't really help either. It took a long time to figure out how it all works. The list below describes how I *think* libav/ffmpeg should be used. Note that the order of the function calls is very important. For example, calling avcodec_close before av_write_trailer appears to cause segfaults inside libav/ffmpeg. Create muxer: - av_guess_format: get AVOutputFormat pointer - avformat_alloc_context: get format context - avio_open: open output file Create encoders: - avcodec_find_encoder_by_name: get AVCodec pointer - avformat_new_stream: create stream for codec - avcodec_open2: open codec Start muxer: - avformat_write_header: write the header Actual encoding (flushing is optional): - avcodec_encode_video2/avcodec_encode_audio2: encode a frame - av_interleaved_write_frame: write a packet (i.e. a frame) Stop muxer: - av_write_trailer: write the trailer and free internal data Destroy encoders: - avcodec_close: close codec Destroy muxer: - avio_close: close output file - av_freep(formatcontext->stream[i]->codec): free codec context - av_freep(formatcontext->stream[i]): free stream - av_free(formatcontext): free format context #### Multithreading #### When you write a multithreaded application, you have to think about deadlocks. A simple and reliable way to avoid deadlocks is to give all mutexes a strict (partial) order, and only ever lock them in that order (the order of unlocking is irrelevant but is usually the reverse of the order of locking). This is automatically accomplished by using a 'push' pipeline design where each class only has pointers to successive stages, because this makes it impossible to lock mutexes in the wrong order, as long as all mutexes are private and locks are never held after a function call returns. There's one exception in my pipeline: Muxer has a pointer to VideoEncoder/AudioEncoder which it uses to tell them to stop, and to delete them. This seems to be unavoidable because of the design of libav/ffmpeg. Special care has to be taken here to avoid deadlocks: BaseEncoder::Stop and 'delete BaseEncoder' are called while no locks in Muxer are held by the thread. There's also an issue that's not very well documented: just using volatile is not enough to guarantee correct behaviour. The 'volatile' keyword is meant to stop some compiler optimisations, not to ensure correct multithreaded behaviour. Some of the problems with 'volatile': - It only synchronizes access between two volatile variables, not between a volatile and a normal one. So in order to synchronize multiple variables correctly, you would have to make them ALL volatile, which makes things slow and is simply impossible for STL containers. The solution is to use mutexes instead. Apparently volatile is not even needed here because compilers should be smart enough to understand what mutexes do. - For lock-free variables (i.e. flags like 'should_finish', 'should_stop', 'error_occurred', ...), volatile will indeed stop compiler optimisations, and even if synchronization with other variables isn't a problem, it is still possible that the CPU itself will reorder memory accesses, or that memory operations won't be visible to other CPU cores because of memory caching issues. This can only be fixed by inserting memory fences (which is also done by mutexes). C++11 provides these in the header. #### OpenGL recording #### OpenGL recording is usually called 'GLInject' in the code. It consists of three parts: - A small library that is injected into the program that will be recorded. The library overrides some system functions (most importantly glXSwapBuffers) so it can capture the frames before they are displayed on the screen. It should be relatively easy to draw extra things using OpenGL (such as the frame rate), but the program doesn't do that right now. Frames are sent to the main program using shared memory. - The GLInjectLauncher class, which creates the shared memory circular buffer that is used for communication with the injected library. It can also launch the program that will be recorded, or alternatively just print the command to do so. - The GLInjectInput class, which reads frames from the shared memory circular buffer, scales them to the correct output size, flips them vertically (because OpenGL returns images upside-down), and sends them to the synchronizer. The timestamp is also read from shared memory, which is more accurate than using the time the frame was received. #### Video corruption #### If the video is corrupted after some change to the back-end, check the timestamps. In most of the cases I've seen, the muxer was simply messing up the interleaving of the video and audio frames. The typical result is video that starts fine, then gets corrupted, and then freezes after a few seconds. Video can also get corrupted if the frame rate is too low (like 1 fps). This may be a problem with the player though, I'm not sure. #### Dealing with old versions of libav/ffmpeg #### This is very annoying. Libav/ffmpeg likes to change its API a lot. The API is usually backwards compatible, but the old functions often have some disadvantages. So for now I'm supporting both old and new versions of the API, based on the preprocessor definitions from the libav/ffmpeg headers. Libav/ffmpeg has a very useful document that keeps track of all API changes, it's located in 'doc/APIchanges'. The best way to find out how the old API actually worked is to search for the commit that removed the function (just search for the function name), go back one commit, go to the corresponding tree, and look at the example code (e.g. output-example.c for libavformat). #### GUI #### The GUI uses a wizard-like layout to show all the settings, rather than separate settings windows. This makes all settings immediately visible, so you're less likely to waste an hour of time because you forgot to turn on the microphone, for example. It also means all settings are easily discoverable. Yes, it means the user has to press 'continue' a few times. But I assume that many of these settings will be changed very often, if not every time. Opening separate windows to change settings, or just to check if they are still correct, would take a far more time. #### Capturing hotkeys #### Capturing key presses system-wide is a lot more annoying than I expected. I'm using XGrabKey because this appears to be the right way to do it. But with this function you can't capture all keys, apparently. Function keys are blocked. Ubuntu 12.04 only blocked function keys with no modifiers (and since X considers Num Lock a modifier, it wasn't really blocking anything), but Xubuntu 12.10 appears to block them completely. So for now I'm just relying on the letter keys. An unexpected but useful side-effect of XGrabKey is that will capture key presses *without* sending the actual key press to the program that you are recording (or any other program actually). This means conflicts are less likely - instead of choosing a hotkey that isn't used by anything, the user can now simply choose any hotkey that he doesn't currently need. And how often do you really *need* something like Ctrl+R to use a program? ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/postinstall000077500000000000000000000006071470053624300220370ustar00rootroot00000000000000#!/bin/bash # You should run this (as root) after installation/uninstallation to make sure the libraries, desktop entry and icon will be found. set -e cd "$( dirname "${BASH_SOURCE[0]}" )" if [ x"$( whoami )" != x"root" ]; then echo "Error: postinstall should be run as root" exit 1 fi ldconfig update-desktop-database -q gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/scripts/000077500000000000000000000000001470053624300212215ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/scripts/ssr-glinject000077500000000000000000000050011470053624300235470ustar00rootroot00000000000000#!/bin/bash set -e usage() { echo "Usage: ssr-glinject [OPTIONS] [--] COMMAND" >& 2 echo "" >& 2 echo "Options:" >& 2 echo " --help Show this help message." >& 2 echo " --hook-debug Print details related to the function hooking process." >& 2 echo " --hook-advanced Use advanced hooking techniques that can handle more" >& 2 echo " unusual situations, but are more likely to break things." >& 2 echo " --glx-debug Enables GLX debugging. This may reduce the performance" >& 2 echo " and print lots of error messages, but it is useful to" >& 2 echo " track down bugs." >& 2 echo " --relax-permissions Uses mode 666 instead of 600 for shared memory, so that" >& 2 echo " other users can record the stream. This is insecure and" >& 2 echo " should not be used on a computer that can be accessed by" >& 2 echo " other users that you don't trust." >& 2 echo " --channel=CHANNEL Channel name to use. The default is 'channel-USERNAME'." >& 2 echo "" >& 2 echo "This script uses LD_PRELOAD to inject the GLInject library into the given" >& 2 echo "command, so that SimpleScreenRecorder can record it. It should be safe to use" >& 2 echo "this on all applications (including command-line programs and shell scripts)." >& 2 echo "If the program doesn't use OpenGL, it should have no effect. If you find a" >& 2 echo "program that crashes or behaves incorrectly when GLInject is used, please submit" >& 2 echo "a bug report." >& 2 } export SSR_HOOK_DEBUG=0 export SSR_HOOK_ADVANCED=0 export SSR_GLX_DEBUG=0 export SSR_STREAM_RELAX_PERMISSIONS=0 while [ $# -gt 0 ] do if [ x"$1" = x"--" ] then shift break elif [ x"$1" = x"--help" ] then usage exit elif [ x"$1" = x"--hook-debug" ] then export SSR_HOOK_DEBUG=1 shift elif [ x"$1" = x"--hook-advanced" ] then export SSR_HOOK_ADVANCED=1 shift elif [ x"$1" = x"--glx-debug" ] then export SSR_GLX_DEBUG=1 shift elif [ x"$1" = x"--relax-permissions" ] then export SSR_STREAM_RELAX_PERMISSIONS=1 shift elif [ x"${1:0:10}" = x"--channel=" ] then export SSR_CHANNEL="${1:10}" shift elif [ x"${1:0:1}" = x"-" ] then echo "ssr-glinject: Unknown option '$1'!" >& 2 usage exit 1 else break fi done echo "ssr-glinject: LD_PRELOAD = $LD_PRELOAD:libssr-glinject.so" >& 2 || true echo "ssr-glinject: command = $@" >& 2 || true LD_PRELOAD="$LD_PRELOAD:libssr-glinject.so" exec "$@" ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/simple-build-and-install000077500000000000000000000101061470053624300242500ustar00rootroot00000000000000#!/bin/bash # Run this if you just want to build and install the program and you don't care about all the details. # Any additional arguments will be passed on to 'cmake'. # The following environment variables can be set to TRUE or FALSE to override the auto-detected values: # - ENABLE_32BIT_GLINJECT # - ENABLE_X86_ASM # - ENABLE_FFMPEG_VERSIONS # - WITH_QT5 # - WITH_GLINJECT set -e cd "$( dirname "${BASH_SOURCE[0]}" )" if [ x"$( whoami )" = x"root" ]; then echo "Error: don't run this script as root, this will mess up file permissions" exit 1 fi export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig" if [ -z "$ENABLE_32BIT_GLINJECT" ] || [ -z "$ENABLE_X86_ASM" ] || [ -z "$WITH_GLINJECT" ]; then echo "Detecting architecture ..." case "$( uname -m )" in "i386"|"i486"|"i586"|"i686") : ${ENABLE_32BIT_GLINJECT:="FALSE"} : ${ENABLE_X86_ASM:="TRUE"} : ${WITH_GLINJECT:="TRUE"} ;; "x86_64") : ${ENABLE_32BIT_GLINJECT:="TRUE"} : ${ENABLE_X86_ASM:="TRUE"} : ${WITH_GLINJECT:="TRUE"} ;; *) : ${ENABLE_32BIT_GLINJECT:="FALSE"} : ${ENABLE_X86_ASM:="FALSE"} : ${WITH_GLINJECT:="FALSE"} ;; esac fi if [ -z "$ENABLE_FFMPEG_VERSIONS" ]; then echo "Detecting ffmpeg/libav ..." if ! pkg-config --exists libavcodec; then echo " Error: libavcodec development package not found, make sure ffmpeg or libav development packages are installed." exit 1 fi LIBAVCODEC_INCLUDE_DIR="$( pkg-config --variable=includedir libavcodec )" HAS_FFMPEG=$( grep -c "This file is part of FFmpeg." $LIBAVCODEC_INCLUDE_DIR/libavcodec/avcodec.h || true ) HAS_LIBAV=$( grep -c "This file is part of Libav." $LIBAVCODEC_INCLUDE_DIR/libavcodec/avcodec.h || true ) if [ $HAS_FFMPEG -gt 0 ]; then if [ $HAS_LIBAV -gt 0 ]; then echo " Error: Detected ffmpeg AND libav, this should not happen!" exit 1 else echo " Detected ffmpeg." ENABLE_FFMPEG_VERSIONS="TRUE" fi else if [ $HAS_LIBAV -gt 0 ]; then echo " Detected libav." ENABLE_FFMPEG_VERSIONS="FALSE" else echo " Error: Detection failed." exit 1 fi fi fi if [ -z "$WITH_QT5" ]; then echo "Detecting Qt version ..." if pkg-config --exists "Qt5Gui >= 5.7"; then echo " Detected Qt5 (version $( pkg-config --modversion Qt5Gui ))." WITH_QT5="TRUE" elif pkg-config --exists "QtGui >= 4.8"; then echo " Detected Qt4 (version $( pkg-config --modversion QtGui ))." WITH_QT5="FALSE" else echo " Error: Qt development package not found, make sure that either Qt4 (4.8 or newer) or Qt5 (5.7 or newer) is installed." exit 1 fi fi echo "Auto-detected options:" echo " ENABLE_32BIT_GLINJECT = $ENABLE_32BIT_GLINJECT" echo " ENABLE_X86_ASM = $ENABLE_X86_ASM" echo " ENABLE_FFMPEG_VERSIONS = $ENABLE_FFMPEG_VERSIONS" echo " WITH_QT5 = $WITH_QT5" echo " WITH_GLINJECT = $WITH_GLINJECT" PREFIX="/usr" OPTIONS=() OPTIONS+=("-DENABLE_32BIT_GLINJECT=$ENABLE_32BIT_GLINJECT") OPTIONS+=("-DENABLE_X86_ASM=$ENABLE_X86_ASM") OPTIONS+=("-DENABLE_FFMPEG_VERSIONS=$ENABLE_FFMPEG_VERSIONS") OPTIONS+=("-DWITH_QT5=$WITH_QT5") OPTIONS+=("-DWITH_GLINJECT=$WITH_GLINJECT") if [ x"$WITH_QT5" == x"TRUE" ]; then export QT_SELECT="qt5" else export QT_SELECT="qt4" OPTIONS+=("-DCMAKE_AUTOMOC_MOC_OPTIONS=-D_SYS_SYSMACROS_H_OUTER") fi echo "Entering build-release directory ..." rm -rf build-release mkdir build-release cd build-release echo "Running cmake ..." cmake -DCMAKE_INSTALL_PREFIX="$PREFIX" -DCMAKE_BUILD_TYPE=Release "${OPTIONS[@]}" "$@" .. echo "Compiling ..." make -j "$( nproc )" if [ x"$WITH_GLINJECT" == x"TRUE" ]; then echo "Removing old GLInject libraries ..." sudo rm -f "/usr/lib/libssr-glinject."* sudo rm -f "/usr/lib64/libssr-glinject."* sudo rm -f "/usr/lib/x86_64-linux-gnu/libssr-glinject."* if [ x"$ENABLE_32BIT_GLINJECT" == x"TRUE" ]; then sudo rm -f "/usr/lib32/libssr-glinject."* sudo rm -f "/usr/lib/i386-linux-gnu/libssr-glinject."* sudo rm -f "/usr/lib/i686-linux-gnu/libssr-glinject."* fi fi echo "Installing ..." sudo make install echo "Leaving build-release directory ..." cd .. echo "Running post-install script ..." sudo ./postinstall echo "Done." ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/simple-uninstall000077500000000000000000000010231470053624300227540ustar00rootroot00000000000000#!/bin/bash # Run this if you used 'simple-build-and-install' and you want to uninstall the program again. set -e cd "$( dirname "${BASH_SOURCE[0]}" )" if [ x"$( whoami )" = x"root" ]; then echo "Error: don't run this script as root, this will mess up file permissions" exit 1 fi echo "Entering build-release directory ..." cd build-release echo "Uninstalling ..." sudo xargs rm < install_manifest.txt echo "Leaving build-release directory ..." cd .. echo "Running post-install script ..." sudo ./postinstall echo "Done." ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/000077500000000000000000000000001470053624300203215ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/000077500000000000000000000000001470053624300206275ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/AVWrapper.cpp000066400000000000000000000112731470053624300232060ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "AVWrapper.h" #if !SSR_USE_AV_LOCKMGR_REGISTER_DEPRECATED int lock_manager(void** m, AVLockOp op) { std::mutex *&mutex = *(std::mutex**) m; switch(op) { case AV_LOCK_CREATE: mutex = new std::mutex(); break; case AV_LOCK_DESTROY: delete mutex; break; case AV_LOCK_OBTAIN: mutex->lock(); break; case AV_LOCK_RELEASE: mutex->unlock(); break; } return 0; } #endif void DeleteFrameDataPointer(void* opaque, uint8_t* data) { Q_UNUSED(data); std::shared_ptr *ptr = (std::shared_ptr*) opaque; delete ptr; } class AVGlobal { public: AVGlobal() { #if !SSR_USE_AV_REGISTER_ALL_DEPRECATED av_register_all(); #endif #if !SSR_USE_AV_LOCKMGR_REGISTER_DEPRECATED av_lockmgr_register(&lock_manager); #endif #if SSR_USE_AVFORMAT_NETWORK_INIT avformat_network_init(); #endif } } g_av_global; AVFrameWrapper::AVFrameWrapper(const std::shared_ptr& refcounted_data) { m_refcounted_data = refcounted_data; #if SSR_USE_AV_FRAME_ALLOC m_frame = av_frame_alloc(); #else m_frame = avcodec_alloc_frame(); #endif if(m_frame == NULL) std::bad_alloc(); #if SSR_USE_AVFRAME_EXTENDED_DATA // ffmpeg docs say that extended_data should point to data if it isn't used m_frame->extended_data = m_frame->data; #endif } AVFrameWrapper::~AVFrameWrapper() { if(m_frame != NULL) { #if SSR_USE_AV_FRAME_FREE av_frame_free(&m_frame); #elif SSR_USE_AVCODEC_FREE_FRAME avcodec_free_frame(&m_frame); #else av_free(m_frame); #endif } } #if SSR_USE_AVCODEC_SEND_RECEIVE AVFrame* AVFrameWrapper::Release() { assert(m_frame != NULL); std::shared_ptr *ptr = new std::shared_ptr(m_refcounted_data); m_frame->buf[0] = av_buffer_create(m_refcounted_data->GetData(), m_refcounted_data->GetSize(), &DeleteFrameDataPointer, ptr, AV_BUFFER_FLAG_READONLY); if(m_frame->buf[0] == NULL) { delete ptr; throw std::bad_alloc(); } AVFrame *frame = m_frame; m_frame = NULL; return frame; } #endif AVPacketWrapper::AVPacketWrapper() { #if SSR_USE_AV_PACKET_ALLOC m_packet = av_packet_alloc(); if(m_packet == NULL) std::bad_alloc(); #else m_packet = new AVPacket; m_free_on_destruct = true; av_init_packet(m_packet); m_packet->data = NULL; m_packet->size = 0; #endif } AVPacketWrapper::AVPacketWrapper(size_t size) { #if SSR_USE_AV_PACKET_ALLOC m_packet = av_packet_alloc(); if(m_packet == NULL) std::bad_alloc(); #else m_packet = new AVPacket; m_free_on_destruct = true; #endif if(av_new_packet(m_packet, size) != 0) throw std::bad_alloc(); } AVPacketWrapper::~AVPacketWrapper() { #if SSR_USE_AV_PACKET_ALLOC // This isn't documented anywhere (as usual) and the FFmpeg examples haven't been updated to their latest API yet, // but my guess is that it is safe to call this function even if the data has also been sent to the muxer, // because the internal reference counting in FFmpeg should ensure that the data is not deleted when it is still needed. // This wasn't the case with their old API which didn't do reference counting yet. av_packet_free(&m_packet); #else if(m_free_on_destruct) av_free_packet(m_packet); delete m_packet; #endif } bool AVFormatIsInstalled(const QString& format_name) { return (av_guess_format(format_name.toUtf8().constData(), NULL, NULL) != NULL); } bool AVCodecIsInstalled(const QString& codec_name) { return (avcodec_find_encoder_by_name(codec_name.toUtf8().constData()) != NULL); } bool AVCodecSupportsPixelFormat(const AVCodec* codec, AVPixelFormat pixel_fmt) { const AVPixelFormat *p = codec->pix_fmts; if(p == NULL) return true; // NULL means 'unknown' or 'any', assume it is supported while(*p != AV_PIX_FMT_NONE) { if(*p == pixel_fmt) return true; ++p; } return false; } bool AVCodecSupportsSampleFormat(const AVCodec* codec, AVSampleFormat sample_fmt) { const AVSampleFormat *p = codec->sample_fmts; if(p == NULL) return true; // NULL means 'unknown' or 'any', assume it is supported while(*p != AV_SAMPLE_FMT_NONE) { if(*p == sample_fmt) return true; ++p; } return false; } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/AVWrapper.h000066400000000000000000000106431470053624300226530ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #if !SSR_USE_AV_CODEC_ID #define AV_CODEC_ID_NONE CODEC_ID_NONE #endif #if !SSR_USE_AV_PIX_FMT #define AVPixelFormat PixelFormat #define AV_PIX_FMT_NONE PIX_FMT_NONE #define AV_PIX_FMT_PAL8 PIX_FMT_PAL8 #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 #define AV_PIX_FMT_BGRA PIX_FMT_BGRA #define AV_PIX_FMT_RGBA PIX_FMT_RGBA #define AV_PIX_FMT_ABGR PIX_FMT_ABGR #define AV_PIX_FMT_ARGB PIX_FMT_ARGB #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P #define AV_PIX_FMT_NV12 PIX_FMT_NV12 #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 #endif #if !SSR_USE_AV_CODEC_CAP #define AV_CODEC_CAP_DELAY CODEC_CAP_DELAY #define AV_CODEC_CAP_EXPERIMENTAL CODEC_CAP_EXPERIMENTAL #define AV_CODEC_CAP_VARIABLE_FRAME_SIZE CODEC_CAP_VARIABLE_FRAME_SIZE #endif #if !SSR_USE_AV_CODEC_FLAG #define AV_CODEC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER #define AV_CODEC_FLAG_QSCALE CODEC_FLAG_QSCALE #endif // A trivial class that holds (aligned) frame data. This makes it easy to implement reference counting through std::shared_ptr. class AVFrameData { private: uint8_t *m_data; size_t m_size; public: inline AVFrameData(size_t size) { m_data = (uint8_t*) av_malloc(size); if(m_data == NULL) throw std::bad_alloc(); m_size = size; } inline ~AVFrameData() { av_free(m_data); } inline uint8_t* GetData() { return m_data; } inline size_t GetSize() { return m_size; } }; // A wrapper around AVFrame to manage memory allocation and reference counting. // Note: This reference counting mechanism is unrelated to the mechanism added in later versions of ffmpeg/libav. class AVFrameWrapper { private: AVFrame *m_frame; std::shared_ptr m_refcounted_data; public: AVFrameWrapper(const std::shared_ptr& refcounted_data); ~AVFrameWrapper(); AVFrameWrapper(const AVFrameWrapper&) = delete; AVFrameWrapper& operator=(const AVFrameWrapper&) = delete; #if SSR_USE_AVCODEC_SEND_RECEIVE // This function transfers ownership of the data to the AVFrame, and then releases ownership of the AVFrame itself. // This turns the AVFrame into a stand-alone object which relies on the ffmpeg/libav reference counting mechanism to free the data. // The returned frame should be freed with av_frame_free. AVFrame* Release(); #endif public: inline AVFrame* GetFrame() { return m_frame; } inline uint8_t* GetRawData() { return m_refcounted_data->GetData(); } inline std::shared_ptr GetFrameData() { return m_refcounted_data; } }; // A wrapper around AVPacket to manage memory allocation. There is no copying or reference counting in this case. class AVPacketWrapper { private: AVPacket *m_packet; #if !SSR_USE_AV_PACKET_ALLOC bool m_free_on_destruct; #endif public: AVPacketWrapper(); AVPacketWrapper(size_t size); ~AVPacketWrapper(); AVPacketWrapper(const AVPacketWrapper&) = delete; AVPacketWrapper& operator=(const AVPacketWrapper&) = delete; public: inline AVPacket* GetPacket() { return m_packet; } inline void SetFreeOnDestruct(bool free_on_destruct) { #if !SSR_USE_AV_PACKET_ALLOC m_free_on_destruct = free_on_destruct; #endif } }; bool AVFormatIsInstalled(const QString& format_name); bool AVCodecIsInstalled(const QString& codec_name); bool AVCodecSupportsPixelFormat(const AVCodec* codec, AVPixelFormat pixel_fmt); bool AVCodecSupportsSampleFormat(const AVCodec* codec, AVSampleFormat sample_fmt); #if !SSR_USE_AV_CODEC_IS_ENCODER inline int av_codec_is_encoder(const AVCodec* codec) { return (codec != NULL && (codec->encode != NULL || codec->encode2 != NULL)); } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastResampler.cpp000066400000000000000000000246701470053624300241140ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastResampler.h" #include "Logger.h" #include "CPUFeatures.h" /* This resampler is based on the resampling algorithm described here: https://ccrma.stanford.edu/~jos/resample/resample.html The Speex resampler (a.k.a. Public Parrot Hack) uses almost the same algorithm. This implementation tries to find a balance between performance and quality. The quality is comparable to quality level 3 of the Speex resampler, however this implementation is roughly 3 times faster (mostly because of the SSE2-optimized floating point code). This resampler can handle non-fractional resampling ratios and is suitable for drift correction, but it is not a full variable-rate resampler though: the filter coefficients are calculated for one specific resampling ratio, independent of the drift ratio. The resampling ratio can be changed, but this will result in a small glitch. The drift ratio can be changed at any time without introducing glitches, but since the filter coefficients won't be updated, large drift ratios will result in aliasing (at least for downsampling). It is not meant for corrections larger than a few percent. */ // Kaiser window function (beta = 7) // Stats for sinc filter with length 44: // - best cutoff = 0.9060 * nyquist_freq // - minimum stopband attenuation = 71.97 dB // - average stopband attenuation = 87.92 dB #define KAISER7_TABLE_LENGTH 64 float kaiser7_table[67] = { 0.99920941f, 1.00000000f, 0.99920941f, 0.99684085f, 0.99290397f, 0.98741477f, 0.98039552f, 0.97187462f, 0.96188648f, 0.95047123f, 0.93767458f, 0.92354751f, 0.90814596f, 0.89153054f, 0.87376619f, 0.85492177f, 0.83506970f, 0.81428555f, 0.79264763f, 0.77023650f, 0.74713462f, 0.72342583f, 0.69919492f, 0.67452719f, 0.64950799f, 0.62422230f, 0.59875427f, 0.57318683f, 0.54760127f, 0.52207683f, 0.49669039f, 0.47151607f, 0.44662491f, 0.42208462f, 0.39795925f, 0.37430898f, 0.35118989f, 0.32865378f, 0.30674805f, 0.28551550f, 0.26499434f, 0.24521804f, 0.22621536f, 0.20801032f, 0.19062228f, 0.17406592f, 0.15835140f, 0.14348447f, 0.12946657f, 0.11629501f, 0.10396318f, 0.09246071f, 0.08177374f, 0.07188512f, 0.06277467f, 0.05441946f, 0.04679404f, 0.03987076f, 0.03362001f, 0.02801054f, 0.02300971f, 0.01858379f, 0.01469823f, 0.01131792f, 0.00840746f, 0.00593141f, 0.00000000f, }; // filter properties #define FILTER_BASE_LENGTH 44.0f // typical number of zero crossings for sinc filter #define FILTER_BASE_SETS 256.0f // typical number of filter samples per zero crossing #define FILTER_CUTOFF 0.9060f // bandwidth of sinc filter (relative to lowest Nyquist frequency) // This function calculates window function values based on cubic interpolation (Catmull-Rom spline). inline float WindowFunction(float* table, unsigned int table_length, float x) { x = fabs(x * (float) table_length); unsigned int index = (int) x; // float-to-int cast is faster than float-to-uint if(index >= table_length) { return 0.0f; } else { float *val = table + index; float t = x - (float) index, s = 1.0f - t; return s * s * (s * val[1] + t * (val[1] * 3.0f + (val[2] - val[0]) * 0.5f)) + t * t * (t * val[2] + s * (val[2] * 3.0f + (val[1] - val[3]) * 0.5f)); } } // sinc function = sin(x*pi)/(x*pi) inline float Sinc(float x) { if(fabs(x) < 0.0001f) return 1.0f; x *= (float) M_PI; return sinf(x) / x; } FastResampler::FastResampler(unsigned int channels, float gain) { assert(channels != 0); // settings m_channels = channels; m_gain = gain; m_resample_ratio = 0.0; m_drift_ratio = 0.0; // filter coefficient sets m_filter_length = 0; m_filter_rows = 0; // CPU feature detection #if SSR_USE_X86_ASM if(CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2()) { switch(m_channels) { case 1: m_firfilter2_ptr = &FastResampler_FirFilter2_C1_SSE2; break; case 2: m_firfilter2_ptr = &FastResampler_FirFilter2_C2_SSE2; break; default: m_firfilter2_ptr = &FastResampler_FirFilter2_Cn_SSE2; break; } } else { #endif switch(m_channels) { case 1: m_firfilter2_ptr = &FastResampler_FirFilter2_C1_Fallback; break; case 2: m_firfilter2_ptr = &FastResampler_FirFilter2_C2_Fallback; break; default: m_firfilter2_ptr = &FastResampler_FirFilter2_Cn_Fallback; break; } #if SSR_USE_X86_ASM } #endif } unsigned int FastResampler::Resample(double resample_ratio, double drift_ratio, const float* samples_in, unsigned int sample_count_in, TempBuffer* samples_out, unsigned int sample_offset_out) { // check the resampling ratio if(resample_ratio < 1.0e-3 || resample_ratio > 1.0e3) { Logger::LogError("[FastResampler::Resample] " + Logger::tr("Error: Resample ratio is out of range!")); throw ResamplerException(); } if(drift_ratio < 1.0e-1 || drift_ratio > 1.0e1) { Logger::LogError("[FastResampler::Resample] " + Logger::tr("Error: Drift ratio is out of range!")); throw ResamplerException(); } // should we flush old samples first? if((m_resample_ratio != resample_ratio || samples_in == NULL) && m_filter_length != 0) { // pad with zero samples unsigned int pad = m_filter_length / 2 * m_channels; std::fill_n(m_samples_memory.Reserve(pad), pad, 0.0f); m_samples_memory.Push(pad); // reserve memory (with some margin since floating-point isn't 100% accurate) unsigned int available = m_samples_memory.GetSize() / m_channels; samples_out->Alloc((sample_offset_out + (unsigned int) lrint((double) available / (m_resample_ratio * m_drift_ratio) * 1.001) + 4) * m_channels, (sample_offset_out != 0)); // resample std::pair done = ResampleBatch(m_samples_memory.GetData(), available, samples_out->GetData() + sample_offset_out * m_channels); sample_offset_out += done.second; } // is there new input data? if(samples_in == NULL) { ResetResamplerState(); return sample_offset_out; } // update filter if the resample ratio changes if(m_resample_ratio != resample_ratio) { Logger::LogInfo("[FastResampler::Resample] " + Logger::tr("Resample ratio is %1 (was %2).").arg(resample_ratio, 0, 'f', 4).arg(m_resample_ratio, 0, 'f', 4)); m_resample_ratio = resample_ratio; UpdateFilterCoefficients(); ResetResamplerState(); } m_drift_ratio = drift_ratio; // save input samples m_samples_memory.Push(samples_in, sample_count_in * m_channels); // resample one batch at a time for( ; ; ) { // reserve memory (with some margin since floating-point isn't 100% accurate) unsigned int available = m_samples_memory.GetSize() / m_channels; unsigned int batch = std::min(available, m_filter_length * 256); // needs to be limited to avoid some numerical problems samples_out->Alloc((sample_offset_out + (unsigned int) lrint((double) batch / (m_resample_ratio * m_drift_ratio) * 1.001) + 4) * m_channels, (sample_offset_out != 0)); // resample std::pair done = ResampleBatch(m_samples_memory.GetData(), batch, samples_out->GetData() + sample_offset_out * m_channels); m_samples_memory.Pop(done.first * m_channels); sample_offset_out += done.second; // is this the last batch? if(batch == available) break; } return sample_offset_out; } double FastResampler::GetInputLatency() { if(m_filter_length == 0) return 0.0; int samples_left = (int) (m_samples_memory.GetSize() / m_channels) - (int) (m_filter_length / 2 - 1); return (double) samples_left - m_time / (double) m_filter_rows; } double FastResampler::GetOutputLatency() { if(m_filter_length == 0) return 0.0; return GetInputLatency() / (m_resample_ratio * m_drift_ratio); } void FastResampler::UpdateFilterCoefficients() { // calculate filter parameters float filter_cutoff = FILTER_CUTOFF / (float) fmax(1.0, m_resample_ratio); m_filter_length = lrint(FILTER_BASE_LENGTH / filter_cutoff * 0.25f) * 4; m_filter_rows = std::max(1u, (unsigned int) lrint(FILTER_BASE_SETS * filter_cutoff)); // allocate memory for coefficients m_filter_coefficients.Alloc(m_filter_length * (m_filter_rows + 1)); // generate coefficients float *coef = m_filter_coefficients.GetData(); float window = 1.0f / (float) (m_filter_length / 2); for(unsigned int j = 0; j <= m_filter_rows; ++j) { float shift = 1.0f - (float) j / (float) m_filter_rows - (float) (m_filter_length / 2); for(unsigned int i = 0; i < m_filter_length; ++i) { float x = (float) i + shift; *(coef++) = WindowFunction(kaiser7_table, KAISER7_TABLE_LENGTH, x * window) * Sinc(x * filter_cutoff) * filter_cutoff * m_gain; } } } void FastResampler::ResetResamplerState() { m_time = 0.0; m_samples_memory.Clear(); unsigned int pad = (m_filter_length / 2 - 1) * m_channels; std::fill_n(m_samples_memory.Reserve(pad), pad, 0.0f); m_samples_memory.Push(pad); } std::pair FastResampler::ResampleBatch(float* samples_in, unsigned int sample_count_in, float* samples_out) { // prepare for resampling double step = (double) m_filter_rows * m_resample_ratio * m_drift_ratio; // actual resampling unsigned int output_samples = 0; for( ; ; ) { // calculate the input sample and filter row unsigned int index = (int) m_time; // float-to-int cast is faster than float-to-uint unsigned int sample = index / m_filter_rows, row = index % m_filter_rows; // do we have the required input samples? if(sample + m_filter_length > sample_count_in) { unsigned int input_samples = std::min(sample_count_in, sample); m_time -= input_samples * m_filter_rows; return std::make_pair(input_samples, output_samples); } // calculate the next sample float *coef1 = m_filter_coefficients.GetData() + m_filter_length * row; float *coef2 = coef1 + m_filter_length; float frac = m_time - (double) index; float *input = samples_in + sample * m_channels; m_firfilter2_ptr(m_channels, m_filter_length, coef1, coef2, frac, input, samples_out); samples_out += m_channels; ++output_samples; // increase the time m_time += step; } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastResampler.h000066400000000000000000000040421470053624300235500ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "TempBuffer.h" #include "QueueBuffer.h" #include "FastResampler_FirFilter.h" #include class FastResampler { private: #if SSR_USE_X86_ASM // CPU feature detection bool m_use_sse2; #endif // settings unsigned int m_channels; float m_gain; double m_resample_ratio, m_drift_ratio; // filter coefficient sets unsigned int m_filter_length, m_filter_rows; TempBuffer m_filter_coefficients; // resampler state double m_time; QueueBuffer m_samples_memory; // function pointers FirFilter2Ptr m_firfilter2_ptr; public: FastResampler(unsigned int channels, float gain); // Processes input audio and writes the resampled audio to a queue. 'samples_in' can be NULL to flush the resampler. unsigned int Resample(double resample_ratio, double drift_ratio, const float* samples_in, unsigned int sample_count_in, TempBuffer* samples_out, unsigned int sample_offset_out); // Returns the total resampler latency in input samples. double GetInputLatency(); // Returns the total resampler latency in output samples. double GetOutputLatency(); private: void UpdateFilterCoefficients(); void ResetResamplerState(); std::pair ResampleBatch(float* samples_in, unsigned int sample_count_in, float* samples_out); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastResampler_FirFilter.h000066400000000000000000000035311470053624300255200ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" typedef void (*FirFilter2Ptr)(unsigned int, unsigned int, float*, float*, float, float*, float*); void FastResampler_FirFilter2_C1_Fallback(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output); void FastResampler_FirFilter2_C2_Fallback(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output); void FastResampler_FirFilter2_Cn_Fallback(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output); #if SSR_USE_X86_ASM void FastResampler_FirFilter2_C1_SSE2(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output); void FastResampler_FirFilter2_C2_SSE2(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output); void FastResampler_FirFilter2_Cn_SSE2(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output); #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastResampler_FirFilter_Fallback.cpp000066400000000000000000000063431470053624300276360ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastResampler_FirFilter.h" void FastResampler_FirFilter2_C1_Fallback(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output) { Q_UNUSED(channels); float sum[4] = {0.0f}; for(unsigned int i = 0; i < filter_length / 4; ++i) { float filter_value[4] = { coef1[0] + (coef2[0] - coef1[0]) * frac, coef1[1] + (coef2[1] - coef1[1]) * frac, coef1[2] + (coef2[2] - coef1[2]) * frac, coef1[3] + (coef2[3] - coef1[3]) * frac, }; coef1 += 4; coef2 += 4; sum[0] += input[0] * filter_value[0]; sum[1] += input[1] * filter_value[1]; sum[2] += input[2] * filter_value[2]; sum[3] += input[3] * filter_value[3]; input += 4; } output[0] = sum[0] + sum[1] + sum[2] + sum[3]; } void FastResampler_FirFilter2_C2_Fallback(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output) { Q_UNUSED(channels); float sum[4] = {0.0f}; for(unsigned int i = 0; i < filter_length / 4; ++i) { float filter_value[4] = { coef1[0] + (coef2[0] - coef1[0]) * frac, coef1[1] + (coef2[1] - coef1[1]) * frac, coef1[2] + (coef2[2] - coef1[2]) * frac, coef1[3] + (coef2[3] - coef1[3]) * frac, }; coef1 += 4; coef2 += 4; sum[0] += input[0] * filter_value[0]; sum[1] += input[1] * filter_value[0]; sum[2] += input[2] * filter_value[1]; sum[3] += input[3] * filter_value[1]; sum[0] += input[4] * filter_value[2]; sum[1] += input[5] * filter_value[2]; sum[2] += input[6] * filter_value[3]; sum[3] += input[7] * filter_value[3]; input += 8; } output[0] = sum[0] + sum[2]; output[1] = sum[1] + sum[3]; } void FastResampler_FirFilter2_Cn_Fallback(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output) { Q_UNUSED(channels); for(unsigned int c = 0; c < channels; ++c) { float sum[4] = {0.0f}; float *input2 = input + c; for(unsigned int i = 0; i < filter_length / 4; ++i) { float filter_value[4] = { coef1[0] + (coef2[0] - coef1[0]) * frac, coef1[1] + (coef2[1] - coef1[1]) * frac, coef1[2] + (coef2[2] - coef1[2]) * frac, coef1[3] + (coef2[3] - coef1[3]) * frac, }; coef1 += 4; coef2 += 4; sum[0] += *input2 * filter_value[0]; input2 += channels; sum[1] += *input2 * filter_value[1]; input2 += channels; sum[2] += *input2 * filter_value[2]; input2 += channels; sum[3] += *input2 * filter_value[3]; input2 += channels; } output[c] = sum[0] + sum[1] + sum[2] + sum[3]; } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastResampler_FirFilter_SSE2.cpp000066400000000000000000000072071470053624300266530ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastResampler_FirFilter.h" #if SSR_USE_X86_ASM #include // sse #include // sse2 void FastResampler_FirFilter2_C1_SSE2(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output) { Q_UNUSED(channels); __m128 sum = _mm_setzero_ps(); __m128 v_frac = _mm_set1_ps(frac); for(unsigned int i = 0; i < filter_length / 4; ++i) { __m128 v_coef1 = _mm_load_ps(coef1), v_coef2 = _mm_load_ps(coef2); coef1 += 4; coef2 += 4; __m128 filter_value = _mm_add_ps(v_coef1, _mm_mul_ps(_mm_sub_ps(v_coef2, v_coef1), v_frac)); __m128 v_input = _mm_loadu_ps(input); input += 4; sum = _mm_add_ps(sum, _mm_mul_ps(v_input, filter_value)); } __m128 sum2 = _mm_add_ps(sum, _mm_shuffle_ps(sum, sum, 0x0e)); __m128 sum3 = _mm_add_ss(sum2, _mm_shuffle_ps(sum2, sum2, 0x01)); _mm_store_ss(output, sum3); } void FastResampler_FirFilter2_C2_SSE2(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output) { Q_UNUSED(channels); __m128 sum = _mm_setzero_ps(); __m128 v_frac = _mm_set1_ps(frac); for(unsigned int i = 0; i < filter_length / 4; ++i) { __m128 v_coef1 = _mm_load_ps(coef1), v_coef2 = _mm_load_ps(coef2); coef1 += 4; coef2 += 4; __m128 filter_value = _mm_add_ps(v_coef1, _mm_mul_ps(_mm_sub_ps(v_coef2, v_coef1), v_frac)); __m128 v_input1 = _mm_loadu_ps(input), v_input2 = _mm_loadu_ps(input + 4); input += 8; sum = _mm_add_ps(sum, _mm_mul_ps(v_input1, _mm_unpacklo_ps(filter_value, filter_value))); sum = _mm_add_ps(sum, _mm_mul_ps(v_input2, _mm_unpackhi_ps(filter_value, filter_value))); } __m128 sum2 = _mm_add_ps(sum, _mm_shuffle_ps(sum, sum, 0xee)); _mm_store_sd((double*) output, _mm_castps_pd(sum2)); } void FastResampler_FirFilter2_Cn_SSE2(unsigned int channels, unsigned int filter_length, float* coef1, float* coef2, float frac, float* input, float* output) { Q_UNUSED(channels); for(unsigned int c = 0; c < channels; ++c) { __m128 sum = _mm_setzero_ps(); __m128 v_frac = _mm_set1_ps(frac); float *input2 = input + c; for(unsigned int i = 0; i < filter_length / 4; ++i) { __m128 v_coef1 = _mm_load_ps(coef1), v_coef2 = _mm_load_ps(coef2); coef1 += 4; coef2 += 4; __m128 filter_value = _mm_add_ps(v_coef1, _mm_mul_ps(_mm_sub_ps(v_coef2, v_coef1), v_frac)); __m128 v_input1 = _mm_load_ss(input2); input2 += channels; __m128 v_input2 = _mm_load_ss(input2); input2 += channels; __m128 v_input3 = _mm_load_ss(input2); input2 += channels; __m128 v_input4 = _mm_load_ss(input2); input2 += channels; __m128 v_input = _mm_movelh_ps(_mm_unpacklo_ps(v_input1, v_input2), _mm_unpacklo_ps(v_input3, v_input4)); sum = _mm_add_ps(sum, _mm_mul_ps(v_input, filter_value)); } __m128 sum2 = _mm_add_ps(sum, _mm_shuffle_ps(sum, sum, 0x0e)); __m128 sum3 = _mm_add_ss(sum2, _mm_shuffle_ps(sum2, sum2, 0x01)); _mm_store_ss(output + c, sum3); } } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler.cpp000066400000000000000000000310301470053624300233570ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastScaler.h" #include "Logger.h" #include "AVWrapper.h" #include "CPUFeatures.h" #include "TempBuffer.h" #include "FastScaler_Convert.h" #include "FastScaler_Scale.h" FastScaler::FastScaler() { #if SSR_USE_X86_ASM m_warn_alignment = true; #endif m_warn_swscale = true; m_sws_context = NULL; } FastScaler::~FastScaler() { if(m_sws_context != NULL) { sws_freeContext(m_sws_context); m_sws_context = NULL; } } void FastScaler::Scale(unsigned int in_width, unsigned int in_height, AVPixelFormat in_format, int in_colorspace, const uint8_t* const* in_data, const int* in_stride, unsigned int out_width, unsigned int out_height, AVPixelFormat out_format, int out_colorspace, uint8_t* const* out_data, const int* out_stride) { // faster BGRA scaling if(in_format == AV_PIX_FMT_BGRA && out_format == AV_PIX_FMT_BGRA) { Scale_BGRA(in_width, in_height, in_data[0], in_stride[0], out_width, out_height, out_data[0], out_stride[0]); return; } // faster BGRA to YUV444 conversion if(in_format == AV_PIX_FMT_BGRA && out_format == AV_PIX_FMT_YUV444P && out_colorspace == SWS_CS_ITU709) { if(in_width == out_width && in_height == out_height) { Convert_BGRA_YUV444(in_width, in_height, in_data[0], in_stride[0], out_data, out_stride); } else { TempBuffer scaled; int scaled_stride = grow_align16(out_width * 4); scaled.Alloc(scaled_stride * out_height); Scale_BGRA(in_width, in_height, in_data[0], in_stride[0], out_width, out_height, scaled.GetData(), scaled_stride); Convert_BGRA_YUV444(out_width, out_height, scaled.GetData(), scaled_stride, out_data, out_stride); } return; } // faster BGRA to YUV422 conversion if(in_format == AV_PIX_FMT_BGRA && out_format == AV_PIX_FMT_YUV422P && out_colorspace == SWS_CS_ITU709) { if(in_width == out_width && in_height == out_height) { Convert_BGRA_YUV422(in_width, in_height, in_data[0], in_stride[0], out_data, out_stride); } else { TempBuffer scaled; int scaled_stride = grow_align16(out_width * 4); scaled.Alloc(scaled_stride * out_height); Scale_BGRA(in_width, in_height, in_data[0], in_stride[0], out_width, out_height, scaled.GetData(), scaled_stride); Convert_BGRA_YUV422(out_width, out_height, scaled.GetData(), scaled_stride, out_data, out_stride); } return; } // faster BGRA to YUV420 conversion if(in_format == AV_PIX_FMT_BGRA && out_format == AV_PIX_FMT_YUV420P && out_colorspace == SWS_CS_ITU709) { if(in_width == out_width && in_height == out_height) { Convert_BGRA_YUV420(in_width, in_height, in_data[0], in_stride[0], out_data, out_stride); } else { TempBuffer scaled; int scaled_stride = grow_align16(out_width * 4); scaled.Alloc(scaled_stride * out_height); Scale_BGRA(in_width, in_height, in_data[0], in_stride[0], out_width, out_height, scaled.GetData(), scaled_stride); Convert_BGRA_YUV420(out_width, out_height, scaled.GetData(), scaled_stride, out_data, out_stride); } return; } // faster BGRA to NV12 conversion if(in_format == AV_PIX_FMT_BGRA && out_format == AV_PIX_FMT_NV12 && out_colorspace == SWS_CS_ITU709) { if(in_width == out_width && in_height == out_height) { Convert_BGRA_NV12(in_width, in_height, in_data[0], in_stride[0], out_data, out_stride); } else { TempBuffer scaled; int scaled_stride = grow_align16(out_width * 4); scaled.Alloc(scaled_stride * out_height); Scale_BGRA(in_width, in_height, in_data[0], in_stride[0], out_width, out_height, scaled.GetData(), scaled_stride); Convert_BGRA_NV12(out_width, out_height, scaled.GetData(), scaled_stride, out_data, out_stride); } return; } // faster BGRA to BGR conversion if(in_format == AV_PIX_FMT_BGRA && out_format == AV_PIX_FMT_BGR24) { if(in_width == out_width && in_height == out_height) { Convert_BGRA_BGR(in_width, in_height, in_data[0], in_stride[0], out_data[0], out_stride[0]); } else { TempBuffer scaled; int scaled_stride = grow_align16(out_width * 4); scaled.Alloc(scaled_stride * out_height); Scale_BGRA(in_width, in_height, in_data[0], in_stride[0], out_width, out_height, scaled.GetData(), scaled_stride); Convert_BGRA_BGR(out_width, out_height, scaled.GetData(), scaled_stride, out_data[0], out_stride[0]); } return; } if(m_warn_swscale) { m_warn_swscale = false; Logger::LogWarning("[FastScaler::Scale] " + Logger::tr("Warning: No fast pixel format conversion available (%1,%2 -> %3,%4), using swscale instead. " "This is not a problem, but performance will be worse.").arg(in_format).arg(in_colorspace).arg(out_format).arg(out_colorspace)); } m_sws_context = sws_getCachedContext(m_sws_context, in_width, in_height, in_format, out_width, out_height, out_format, SWS_BILINEAR, NULL, NULL, NULL); if(m_sws_context == NULL) { Logger::LogError("[FastScaler::Scale] " + Logger::tr("Error: Can't get swscale context!", "Don't translate 'swscale'")); throw LibavException(); } sws_setColorspaceDetails(m_sws_context, sws_getCoefficients(in_colorspace), 0, sws_getCoefficients(out_colorspace), 0, 0, 1 << 16, 1 << 16); sws_scale(m_sws_context, in_data, in_stride, 0, in_height, out_data, out_stride); } void FastScaler::Convert_BGRA_YUV444(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { #if SSR_USE_X86_ASM if(CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2() && CPUFeatures::HasSSE3() && CPUFeatures::HasSSSE3()) { if((uintptr_t) out_data[0] % 16 == 0 && out_stride[0] % 16 == 0 && (uintptr_t) out_data[1] % 16 == 0 && out_stride[1] % 16 == 0 && (uintptr_t) out_data[2] % 16 == 0 && out_stride[2] % 16 == 0) { Convert_BGRA_YUV444_SSSE3(width, height, in_data, in_stride, out_data, out_stride); } else { if(m_warn_alignment) { m_warn_alignment = false; Logger::LogWarning("[FastScaler::Convert_BGRA_YUV444] " + Logger::tr("Warning: Memory is not properly aligned for SSE, using fallback converter instead. " "This is not a problem, but performance will be worse.", "Don't translate 'fallback'")); } Convert_BGRA_YUV444_Fallback(width, height, in_data, in_stride, out_data, out_stride); } return; } #endif Convert_BGRA_YUV444_Fallback(width, height, in_data, in_stride, out_data, out_stride); } void FastScaler::Convert_BGRA_YUV422(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { assert(width % 2 == 0); #if SSR_USE_X86_ASM if(CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2() && CPUFeatures::HasSSE3() && CPUFeatures::HasSSSE3()) { if((uintptr_t) out_data[0] % 16 == 0 && out_stride[0] % 16 == 0 && (uintptr_t) out_data[1] % 16 == 0 && out_stride[1] % 16 == 0 && (uintptr_t) out_data[2] % 16 == 0 && out_stride[2] % 16 == 0) { Convert_BGRA_YUV422_SSSE3(width, height, in_data, in_stride, out_data, out_stride); } else { if(m_warn_alignment) { m_warn_alignment = false; Logger::LogWarning("[FastScaler::Convert_BGRA_YUV422] " + Logger::tr("Warning: Memory is not properly aligned for SSE, using fallback converter instead. " "This is not a problem, but performance will be worse.", "Don't translate 'fallback'")); } Convert_BGRA_YUV422_Fallback(width, height, in_data, in_stride, out_data, out_stride); } return; } #endif Convert_BGRA_YUV422_Fallback(width, height, in_data, in_stride, out_data, out_stride); } void FastScaler::Convert_BGRA_YUV420(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { assert(width % 2 == 0 && height % 2 == 0); #if SSR_USE_X86_ASM if(CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2() && CPUFeatures::HasSSE3() && CPUFeatures::HasSSSE3()) { if((uintptr_t) out_data[0] % 16 == 0 && out_stride[0] % 16 == 0 && (uintptr_t) out_data[1] % 16 == 0 && out_stride[1] % 16 == 0 && (uintptr_t) out_data[2] % 16 == 0 && out_stride[2] % 16 == 0) { Convert_BGRA_YUV420_SSSE3(width, height, in_data, in_stride, out_data, out_stride); } else { if(m_warn_alignment) { m_warn_alignment = false; Logger::LogWarning("[FastScaler::Convert_BGRA_YUV420] " + Logger::tr("Warning: Memory is not properly aligned for SSE, using fallback converter instead. " "This is not a problem, but performance will be worse.", "Don't translate 'fallback'")); } Convert_BGRA_YUV420_Fallback(width, height, in_data, in_stride, out_data, out_stride); } return; } #endif Convert_BGRA_YUV420_Fallback(width, height, in_data, in_stride, out_data, out_stride); } void FastScaler::Convert_BGRA_NV12(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* const out_data[2], const int out_stride[2]) { assert(width % 2 == 0 && height % 2 == 0); #if SSR_USE_X86_ASM if(CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2() && CPUFeatures::HasSSE3() && CPUFeatures::HasSSSE3()) { if((uintptr_t) out_data[0] % 16 == 0 && out_stride[0] % 16 == 0 && (uintptr_t) out_data[1] % 16 == 0 && out_stride[1] % 16 == 0) { Convert_BGRA_NV12_SSSE3(width, height, in_data, in_stride, out_data, out_stride); } else { if(m_warn_alignment) { m_warn_alignment = false; Logger::LogWarning("[FastScaler::Convert_BGRA_NV12] " + Logger::tr("Warning: Memory is not properly aligned for SSE, using fallback converter instead. " "This is not a problem, but performance will be worse.", "Don't translate 'fallback'")); } Convert_BGRA_NV12_Fallback(width, height, in_data, in_stride, out_data, out_stride); } return; } #endif Convert_BGRA_NV12_Fallback(width, height, in_data, in_stride, out_data, out_stride); } void FastScaler::Convert_BGRA_BGR(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride) { #if SSR_USE_X86_ASM if(CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2() && CPUFeatures::HasSSE3() && CPUFeatures::HasSSSE3()) { if((uintptr_t) out_data % 16 == 0 && out_stride % 16 == 0) { Convert_BGRA_BGR_SSSE3(width, height, in_data, in_stride, out_data, out_stride); } else { if(m_warn_alignment) { m_warn_alignment = false; Logger::LogWarning("[FastScaler::Convert_BGRA_BGR] " + Logger::tr("Warning: Memory is not properly aligned for SSE, using fallback converter instead. " "This is not a problem, but performance will be worse.", "Don't translate 'fallback'")); } Convert_BGRA_BGR_Fallback(width, height, in_data, in_stride, out_data, out_stride); } return; } #endif Convert_BGRA_BGR_Fallback(width, height, in_data, in_stride, out_data, out_stride); } void FastScaler::Scale_BGRA(unsigned int in_width, unsigned int in_height, const uint8_t* in_data, int in_stride, unsigned int out_width, unsigned int out_height, uint8_t* out_data, int out_stride) { #if SSR_USE_X86_ASM if(CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2() && CPUFeatures::HasSSE3() && CPUFeatures::HasSSSE3()) { if((uintptr_t) out_data % 16 == 0 && out_stride % 16 == 0) { Scale_BGRA_SSSE3(in_width, in_height, in_data, in_stride, out_width, out_height, out_data, out_stride); } else { if(m_warn_alignment) { m_warn_alignment = false; Logger::LogWarning("[FastScaler::Scale_BGRA] " + Logger::tr("Warning: Memory is not properly aligned for SSE, using fallback converter instead. " "This is not a problem, but performance will be worse.", "Don't translate 'fallback'")); } Scale_BGRA_Fallback(in_width, in_height, in_data, in_stride, out_width, out_height, out_data, out_stride); } return; } #endif Scale_BGRA_Fallback(in_width, in_height, in_data, in_stride, out_width, out_height, out_data, out_stride); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler.h000066400000000000000000000043421470053624300230320ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "AVWrapper.h" class FastScaler { private: #if SSR_USE_X86_ASM bool m_warn_alignment; #endif bool m_warn_swscale; SwsContext *m_sws_context; public: FastScaler(); ~FastScaler(); void Scale(unsigned int in_width, unsigned int in_height, AVPixelFormat in_format, int in_colorspace, const uint8_t* const* in_data, const int* in_stride, unsigned int out_width, unsigned int out_height, AVPixelFormat out_format, int out_colorspace, uint8_t* const* out_data, const int* out_stride); private: void Convert_BGRA_YUV444(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_YUV422(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_YUV420(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_NV12(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* const out_data[2], const int out_stride[2]); void Convert_BGRA_BGR(unsigned int width, unsigned int height, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride); void Scale_BGRA(unsigned int in_width, unsigned int in_height, const uint8_t* in_data, int in_stride, unsigned int out_width, unsigned int out_height, uint8_t* out_data, int out_stride); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler_Convert.h000066400000000000000000000044511470053624300245330ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" void Convert_BGRA_YUV444_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_YUV422_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_YUV420_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_NV12_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[2], const int out_stride[2]); void Convert_BGRA_BGR_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride); #if SSR_USE_X86_ASM void Convert_BGRA_YUV444_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_YUV422_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_YUV420_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]); void Convert_BGRA_NV12_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[2], const int out_stride[2]); void Convert_BGRA_BGR_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride); #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler_Convert_Fallback.cpp000066400000000000000000000205641470053624300266500ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastScaler_Convert.h" /* Color space standards are a mess. ==== BT.601 ==== Y = 16 + round(( 66 * R + 129 * G + 25 * B) / 256) U = 128 + round(( -38 * R + -74 * G + 112 * B) / 256) V = 128 + round(( 112 * R + -94 * G + -18 * B) / 256) ==== BT.709 ==== Y = 16 + round(( 47 * R + 157 * G + 16 * B) / 256) U = 128 + round(( -26 * R + -86 * G + 112 * B) / 256) V = 128 + round(( 112 * R + -102 * G + -10 * B) / 256) The converters below are currently hard-coded for BT.709. */ /* ==== Fallback BGRA-to-YUV444/YUV422/YUV420/NV12 Converter ==== Nothing special, just plain C code. - YUV444: one-to-one mapping - YUV422: takes blocks of 2x1 pixels, produces 2x1 Y and 1x1 U/V values - YUV420: takes blocks of 2x2 pixels, produces 2x2 Y and 1x1 U/V values - NV12: like YUV420, but U/V are in the same plane */ void Convert_BGRA_YUV444_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { const int offset_y = 128 + (16 << 8), offset_uv = 128 + (128 << 8); for(unsigned int j = 0; j < h; ++j) { const uint32_t *rgb = (const uint32_t*) (in_data + in_stride * (int) j); uint8_t *yuv_y = out_data[0] + out_stride[0] * (int) j; uint8_t *yuv_u = out_data[1] + out_stride[1] * (int) j; uint8_t *yuv_v = out_data[2] + out_stride[2] * (int) j; for(unsigned int i = 0; i < w; ++i) { uint32_t c = *(rgb++); int r = (int) ((c >> 16) & 0xff); int g = (int) ((c >> 8) & 0xff); int b = (int) ((c ) & 0xff); *(yuv_y++) = ( 47 * r + 157 * g + 16 * b + offset_y) >> 8; *(yuv_u++) = (-26 * r + -86 * g + 112 * b + offset_uv) >> 8; *(yuv_v++) = (112 * r + -102 * g + -10 * b + offset_uv) >> 8; } } } void Convert_BGRA_YUV422_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { assert(w % 2 == 0); const int offset_y = 128 + (16 << 8), offset_uv = (128 + (128 << 8)) << 1; for(unsigned int j = 0; j < h; ++j) { const uint32_t *rgb = (const uint32_t*) (in_data + in_stride * (int) j); uint8_t *yuv_y = out_data[0] + out_stride[0] * (int) j; uint8_t *yuv_u = out_data[1] + out_stride[1] * (int) j; uint8_t *yuv_v = out_data[2] + out_stride[2] * (int) j; for(unsigned int i = 0; i < w / 2; ++i) { uint32_t c1 = rgb[0], c2 = rgb[1]; rgb += 2; int r1 = (int) ((c1 >> 16) & 0xff), r2 = (int) ((c2 >> 16) & 0xff); int g1 = (int) ((c1 >> 8) & 0xff), g2 = (int) ((c2 >> 8) & 0xff); int b1 = (int) ((c1 ) & 0xff), b2 = (int) ((c2 ) & 0xff); yuv_y[0] = (47 * r1 + 157 * g1 + 16 * b1 + offset_y) >> 8; yuv_y[1] = (47 * r2 + 157 * g2 + 16 * b2 + offset_y) >> 8; yuv_y += 2; int sr = r1 + r2; int sg = g1 + g2; int sb = b1 + b2; *(yuv_u++) = (-26 * sr + -86 * sg + 112 * sb + offset_uv) >> 9; *(yuv_v++) = (112 * sr + -102 * sg + -10 * sb + offset_uv) >> 9; } } } void Convert_BGRA_YUV420_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { assert(w % 2 == 0 && h % 2 == 0); const int offset_y = 128 + (16 << 8), offset_uv = (128 + (128 << 8)) << 2; for(unsigned int j = 0; j < h / 2; ++j) { const uint32_t *rgb1 = (const uint32_t*) (in_data + in_stride * (int) j * 2); const uint32_t *rgb2 = (const uint32_t*) (in_data + in_stride * ((int) j * 2 + 1)); uint8_t *yuv_y1 = out_data[0] + out_stride[0] * (int) j * 2; uint8_t *yuv_y2 = out_data[0] + out_stride[0] * ((int) j * 2 + 1); uint8_t *yuv_u = out_data[1] + out_stride[1] * (int) j; uint8_t *yuv_v = out_data[2] + out_stride[2] * (int) j; for(unsigned int i = 0; i < w / 2; ++i) { uint32_t c1 = rgb1[0], c2 = rgb1[1], c3 = rgb2[0], c4 = rgb2[1]; rgb1 += 2; rgb2 += 2; int r1 = (int) ((c1 >> 16) & 0xff), r2 = (int) ((c2 >> 16) & 0xff), r3 = (int) ((c3 >> 16) & 0xff), r4 = (int) ((c4 >> 16) & 0xff); int g1 = (int) ((c1 >> 8) & 0xff), g2 = (int) ((c2 >> 8) & 0xff), g3 = (int) ((c3 >> 8) & 0xff), g4 = (int) ((c4 >> 8) & 0xff); int b1 = (int) ((c1 ) & 0xff), b2 = (int) ((c2 ) & 0xff), b3 = (int) ((c3 ) & 0xff), b4 = (int) ((c4 ) & 0xff); yuv_y1[0] = (47 * r1 + 157 * g1 + 16 * b1 + offset_y) >> 8; yuv_y1[1] = (47 * r2 + 157 * g2 + 16 * b2 + offset_y) >> 8; yuv_y2[0] = (47 * r3 + 157 * g3 + 16 * b3 + offset_y) >> 8; yuv_y2[1] = (47 * r4 + 157 * g4 + 16 * b4 + offset_y) >> 8; yuv_y1 += 2; yuv_y2 += 2; int sr = r1 + r2 + r3 + r4; int sg = g1 + g2 + g3 + g4; int sb = b1 + b2 + b3 + b4; *(yuv_u++) = (-26 * sr + -86 * sg + 112 * sb + offset_uv) >> 10; *(yuv_v++) = (112 * sr + -102 * sg + -10 * sb + offset_uv) >> 10; } } } void Convert_BGRA_NV12_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[2], const int out_stride[2]) { assert(w % 2 == 0 && h % 2 == 0); const int offset_y = 128 + (16 << 8), offset_uv = (128 + (128 << 8)) << 2; for(unsigned int j = 0; j < h / 2; ++j) { const uint32_t *rgb1 = (const uint32_t*) (in_data + in_stride * (int) j * 2); const uint32_t *rgb2 = (const uint32_t*) (in_data + in_stride * ((int) j * 2 + 1)); uint8_t *yuv_y1 = out_data[0] + out_stride[0] * (int) j * 2; uint8_t *yuv_y2 = out_data[0] + out_stride[0] * ((int) j * 2 + 1); uint8_t *yuv_uv = out_data[1] + out_stride[1] * (int) j; for(unsigned int i = 0; i < w / 2; ++i) { uint32_t c1 = rgb1[0], c2 = rgb1[1], c3 = rgb2[0], c4 = rgb2[1]; rgb1 += 2; rgb2 += 2; int r1 = (int) ((c1 >> 16) & 0xff), r2 = (int) ((c2 >> 16) & 0xff), r3 = (int) ((c3 >> 16) & 0xff), r4 = (int) ((c4 >> 16) & 0xff); int g1 = (int) ((c1 >> 8) & 0xff), g2 = (int) ((c2 >> 8) & 0xff), g3 = (int) ((c3 >> 8) & 0xff), g4 = (int) ((c4 >> 8) & 0xff); int b1 = (int) ((c1 ) & 0xff), b2 = (int) ((c2 ) & 0xff), b3 = (int) ((c3 ) & 0xff), b4 = (int) ((c4 ) & 0xff); yuv_y1[0] = (47 * r1 + 157 * g1 + 16 * b1 + offset_y) >> 8; yuv_y1[1] = (47 * r2 + 157 * g2 + 16 * b2 + offset_y) >> 8; yuv_y2[0] = (47 * r3 + 157 * g3 + 16 * b3 + offset_y) >> 8; yuv_y2[1] = (47 * r4 + 157 * g4 + 16 * b4 + offset_y) >> 8; yuv_y1 += 2; yuv_y2 += 2; int sr = r1 + r2 + r3 + r4; int sg = g1 + g2 + g3 + g4; int sb = b1 + b2 + b3 + b4; yuv_uv[0] = (-26 * sr + -86 * sg + 112 * sb + offset_uv) >> 10; yuv_uv[1] = (112 * sr + -102 * sg + -10 * sb + offset_uv) >> 10; yuv_uv += 2; } } } /* ==== Fallback BGRA-to-BGR Converter ==== Nothing special, just plain C code. - BGR: converts blocks of 8x1 pixels */ void Convert_BGRA_BGR_Fallback(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride) { for(unsigned int j = 0; j < h; ++j) { const uint8_t *in = in_data + in_stride * (int) j; uint8_t *out = out_data + out_stride * (int) j; for(unsigned int i = 0; i < w / 8; ++i) { uint64_t c0 = ((uint64_t*) in)[0]; uint64_t c1 = ((uint64_t*) in)[1]; uint64_t c2 = ((uint64_t*) in)[2]; uint64_t c3 = ((uint64_t*) in)[3]; in += 32; ((uint64_t*) out)[0] = ((c0 & UINT64_C(0x0000000000ffffff)) ) | ((c0 & UINT64_C(0x00ffffff00000000)) >> 8) | ((c1 & UINT64_C(0x000000000000ffff)) << 48); ((uint64_t*) out)[1] = ((c1 & UINT64_C(0x0000000000ff0000)) >> 16) | ((c1 & UINT64_C(0x00ffffff00000000)) >> 24) | ((c2 & UINT64_C(0x0000000000ffffff)) << 32) | ((c2 & UINT64_C(0x000000ff00000000)) << 24); ((uint64_t*) out)[2] = ((c2 & UINT64_C(0x00ffff0000000000)) >> 40) | ((c3 & UINT64_C(0x0000000000ffffff)) << 16) | ((c3 & UINT64_C(0x00ffffff00000000)) << 8); out += 24; } for(unsigned int i = 0; i < (w & 7); ++i) { uint32_t c = *((uint32_t*) in); in += 4; out[0] = c; out[1] = c >> 8; out[2] = c >> 16; out += 3; } } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler_Convert_SSSE3.cpp000066400000000000000000000476701470053624300260200ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastScaler_Convert.h" #if SSR_USE_X86_ASM #include // sse #include // sse2 #include // sse3 #include // ssse3 /* ==== SSSE3 BGRA-to-YUV444/YUV420 Converter ==== Uses the same principle as the fallback converter, but uses 16-bit integers so it can do 8 operations at once. - YUV444: takes blocks of 16x1 pixels, produces 16x1 Y/U/V values - YUV422: takes blocks of 16x1 pixels, produces 16x1 Y and 8x1 U/V values - YUV420: takes blocks of 16x2 pixels, produces 16x2 Y and 8x1 U/V values The code uses interleaving to reduce the number of shuffles. So for example the order for red is [ r0 r4 r1 r5 r2 r6 r3 r7 ]. For the averaging of 2x2 blocks, it uses 32-bit horizontal addition instead of 16-bit because of this interleaving. The order of the final result is [ sr0 sr2 sr1 sr3 sr4 sr6 sr5 sr7 ]. If the width is not a multiple of 8/16, the remainder (right edge of the image) is converted without SSSE3. This converter is about 4 times faster than the fallback converter. */ #define ReadBGRAInterleaved(ptr1, ptr2, ca, cb, r, g, b) \ __m128i ca = _mm_loadu_si128((__m128i*) (ptr1)), cb = _mm_loadu_si128((__m128i*) (ptr2)); \ __m128i r = _mm_or_si128(_mm_and_si128(_mm_srli_si128(ca, 2), v_byte1), _mm_and_si128( cb , v_byte3)); \ __m128i g = _mm_or_si128(_mm_and_si128(_mm_srli_si128(ca, 1), v_byte1), _mm_and_si128(_mm_slli_si128(cb, 1), v_byte3)); \ __m128i b = _mm_or_si128(_mm_and_si128( ca , v_byte1), _mm_and_si128(_mm_slli_si128(cb, 2), v_byte3)); #define Convert_RGB_Y(r, g, b, y) \ __m128i y = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(r, v_mat_yr), _mm_mullo_epi16(g, v_mat_yg)), _mm_add_epi16(_mm_mullo_epi16(b, v_mat_yb), v_offset_y)); #define Convert_RGB_U(r, g, b, u) \ __m128i u = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(r, v_mat_ur), _mm_mullo_epi16(g, v_mat_ug)), _mm_add_epi16(_mm_mullo_epi16(b, v_mat_ub_vr), v_offset_uv)); #define Convert_RGB_V(r, g, b, v) \ __m128i v = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(r, v_mat_ub_vr), _mm_mullo_epi16(g, v_mat_vg)), _mm_add_epi16(_mm_mullo_epi16(b, v_mat_vb), v_offset_uv)); #define WritePlaneInterleaved(ptr, y1, y2, sh1, sh2) \ _mm_stream_si128((__m128i*) (ptr), _mm_or_si128(_mm_shuffle_epi8(y1, sh1), _mm_shuffle_epi8(y2, sh2))); void Convert_BGRA_YUV444_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { assert((uintptr_t) out_data[0] % 16 == 0 && out_stride[0] % 16 == 0); assert((uintptr_t) out_data[1] % 16 == 0 && out_stride[1] % 16 == 0); assert((uintptr_t) out_data[2] % 16 == 0 && out_stride[2] % 16 == 0); __m128i v_byte1 = _mm_set1_epi32(0x000000ff); __m128i v_byte3 = _mm_set1_epi32(0x00ff0000); __m128i v_mat_yr = _mm_set1_epi16(47); __m128i v_mat_yg = _mm_set1_epi16(157); __m128i v_mat_yb = _mm_set1_epi16(16); __m128i v_mat_ur = _mm_set1_epi16(-26); __m128i v_mat_ug = _mm_set1_epi16(-86); __m128i v_mat_ub_vr = _mm_set1_epi16(112); __m128i v_mat_vg = _mm_set1_epi16(-102); __m128i v_mat_vb = _mm_set1_epi16(-10); __m128i v_offset_y = _mm_set1_epi16((int16_t) (128 + (16 << 8))); __m128i v_offset_uv = _mm_set1_epi16((int16_t) (128 + (128 << 8))); __m128i v_shuffle1 = _mm_setr_epi8(1, 5, 9, 13, 3, 7, 11, 15, -1, -1, -1, -1, -1, -1, -1, -1); __m128i v_shuffle2 = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 1, 5, 9, 13, 3, 7, 11, 15); const int offset_y = 128 + (16 << 8), offset_uv = 128 + (128 << 8); for(unsigned int j = 0; j < h; ++j) { const uint32_t *rgb = (const uint32_t*) (in_data + in_stride * (int) j); uint8_t *yuv_y = out_data[0] + out_stride[0] * (int) j; uint8_t *yuv_u = out_data[1] + out_stride[1] * (int) j; uint8_t *yuv_v = out_data[2] + out_stride[2] * (int) j; for(unsigned int i = 0; i < w / 16; ++i) { ReadBGRAInterleaved(rgb , rgb + 4, ca1, cb1, r1, g1, b1); ReadBGRAInterleaved(rgb + 8, rgb + 12, ca2, cb2, r2, g2, b2); _mm_prefetch(rgb + 48, _MM_HINT_T0); rgb += 16; Convert_RGB_Y(r1, g1, b1, y1); Convert_RGB_Y(r2, g2, b2, y2); WritePlaneInterleaved(yuv_y, y1, y2, v_shuffle1, v_shuffle2); yuv_y += 16; Convert_RGB_U(r1, g1, b1, u1); Convert_RGB_U(r2, g2, b2, u2); WritePlaneInterleaved(yuv_u, u1, u2, v_shuffle1, v_shuffle2); yuv_u += 16; Convert_RGB_V(r1, g1, b1, v1); Convert_RGB_V(r2, g2, b2, v2); WritePlaneInterleaved(yuv_v, v1, v2, v_shuffle1, v_shuffle2); yuv_v += 16; } for(unsigned int i = 0; i < (w & 15); ++i) { uint32_t c = *(rgb++); int r = (int) ((c >> 16) & 0xff); int g = (int) ((c >> 8) & 0xff); int b = (int) ((c ) & 0xff); *(yuv_y++) = ( 47 * r + 157 * g + 16 * b + offset_y) >> 8; *(yuv_u++) = (-26 * r + -86 * g + 112 * b + offset_uv) >> 8; *(yuv_v++) = (112 * r + -102 * g + -10 * b + offset_uv) >> 8; } } _mm_sfence(); } void Convert_BGRA_YUV422_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { assert(w % 2 == 0); assert((uintptr_t) out_data[0] % 16 == 0 && out_stride[0] % 16 == 0); assert((uintptr_t) out_data[1] % 16 == 0 && out_stride[1] % 16 == 0); assert((uintptr_t) out_data[2] % 16 == 0 && out_stride[2] % 16 == 0); __m128i v_byte1 = _mm_set1_epi32(0x000000ff); __m128i v_byte3 = _mm_set1_epi32(0x00ff0000); __m128i v_mat_yr = _mm_set1_epi16(47); __m128i v_mat_yg = _mm_set1_epi16(157); __m128i v_mat_yb = _mm_set1_epi16(16); __m128i v_mat_ur = _mm_set1_epi16(-26); __m128i v_mat_ug = _mm_set1_epi16(-86); __m128i v_mat_ub_vr = _mm_set1_epi16(112); __m128i v_mat_vg = _mm_set1_epi16(-102); __m128i v_mat_vb = _mm_set1_epi16(-10); __m128i v_offset_y = _mm_set1_epi16((int16_t) (128 + (16 << 8))); __m128i v_offset_uv = _mm_set1_epi16((int16_t) (128 + (128 << 8))); __m128i v_shuffle1 = _mm_setr_epi8(1, 5, 9, 13, 3, 7, 11, 15, -1, -1, -1, -1, -1, -1, -1, -1); __m128i v_shuffle2 = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 1, 5, 9, 13, 3, 7, 11, 15); __m128i v_shuffle3 = _mm_setr_epi8(1, 5, 3, 7, 9, 13, 11, 15, -1, -1, -1, -1, -1, -1, -1, -1); const int offset_y = 128 + (16 << 8), offset_uv = (128 + (128 << 8)) << 1; for(unsigned int j = 0; j < h; ++j) { const uint32_t *rgb = (const uint32_t*) (in_data + in_stride * (int) j); uint8_t *yuv_y = out_data[0] + out_stride[0] * (int) j; uint8_t *yuv_u = out_data[1] + out_stride[1] * (int) j; uint8_t *yuv_v = out_data[2] + out_stride[2] * (int) j; for(unsigned int i = 0; i < w / 16; ++i) { ReadBGRAInterleaved(rgb , rgb + 4, ca1, cb1, r1, g1, b1); ReadBGRAInterleaved(rgb + 8, rgb + 12, ca2, cb2, r2, g2, b2); _mm_prefetch(rgb + 48, _MM_HINT_T0); rgb += 16; Convert_RGB_Y(r1, g1, b1, y1); Convert_RGB_Y(r2, g2, b2, y2); WritePlaneInterleaved(yuv_y, y1, y2, v_shuffle1, v_shuffle2); yuv_y += 16; __m128i ra = _mm_srli_epi16(_mm_hadd_epi32(r1, r2), 1); __m128i ga = _mm_srli_epi16(_mm_hadd_epi32(g1, g2), 1); __m128i ba = _mm_srli_epi16(_mm_hadd_epi32(b1, b2), 1); __m128i u = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(ra, v_mat_ur), _mm_mullo_epi16(ga, v_mat_ug)), _mm_add_epi16(_mm_mullo_epi16(ba, v_mat_ub_vr), v_offset_uv)); _mm_storel_epi64((__m128i*) yuv_u, _mm_shuffle_epi8(u, v_shuffle3)); yuv_u += 8; __m128i v = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(ra, v_mat_ub_vr), _mm_mullo_epi16(ga, v_mat_vg)), _mm_add_epi16(_mm_mullo_epi16(ba, v_mat_vb), v_offset_uv)); _mm_storel_epi64((__m128i*) yuv_v, _mm_shuffle_epi8(v, v_shuffle3)); yuv_v += 8; } for(unsigned int i = 0; i < (w & 15) / 2; ++i) { uint32_t c1 = rgb[0], c2 = rgb[1]; rgb += 2; int r1 = (int) ((c1 >> 16) & 0xff), r2 = (int) ((c2 >> 16) & 0xff); int g1 = (int) ((c1 >> 8) & 0xff), g2 = (int) ((c2 >> 8) & 0xff); int b1 = (int) ((c1 ) & 0xff), b2 = (int) ((c2 ) & 0xff); yuv_y[0] = (47 * r1 + 157 * g1 + 16 * b1 + offset_y) >> 8; yuv_y[1] = (47 * r2 + 157 * g2 + 16 * b2 + offset_y) >> 8; yuv_y += 2; int sr = r1 + r2; int sg = g1 + g2; int sb = b1 + b2; *(yuv_u++) = (-26 * sr + -86 * sg + 112 * sb + offset_uv) >> 9; *(yuv_v++) = (112 * sr + -102 * sg + -10 * sb + offset_uv) >> 9; } } _mm_sfence(); } void Convert_BGRA_YUV420_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[3], const int out_stride[3]) { assert(w % 2 == 0 && h % 2 == 0); assert((uintptr_t) out_data[0] % 16 == 0 && out_stride[0] % 16 == 0); assert((uintptr_t) out_data[1] % 16 == 0 && out_stride[1] % 16 == 0); assert((uintptr_t) out_data[2] % 16 == 0 && out_stride[2] % 16 == 0); __m128i v_byte1 = _mm_set1_epi32(0x000000ff); __m128i v_byte3 = _mm_set1_epi32(0x00ff0000); __m128i v_mat_yr = _mm_set1_epi16(47); __m128i v_mat_yg = _mm_set1_epi16(157); __m128i v_mat_yb = _mm_set1_epi16(16); __m128i v_mat_ur = _mm_set1_epi16(-26); __m128i v_mat_ug = _mm_set1_epi16(-86); __m128i v_mat_ub_vr = _mm_set1_epi16(112); __m128i v_mat_vg = _mm_set1_epi16(-102); __m128i v_mat_vb = _mm_set1_epi16(-10); __m128i v_offset_y = _mm_set1_epi16((int16_t) (128 + (16 << 8))); __m128i v_offset_uv = _mm_set1_epi16((int16_t) (128 + (128 << 8))); __m128i v_2 = _mm_set1_epi16(2); __m128i v_shuffle1 = _mm_setr_epi8(1, 5, 9, 13, 3, 7, 11, 15, -1, -1, -1, -1, -1, -1, -1, -1); __m128i v_shuffle2 = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 1, 5, 9, 13, 3, 7, 11, 15); __m128i v_shuffle3 = _mm_setr_epi8(1, 5, 3, 7, 9, 13, 11, 15, -1, -1, -1, -1, -1, -1, -1, -1); const int offset_y = 128 + (16 << 8), offset_uv = (128 + (128 << 8)) << 2; for(unsigned int j = 0; j < h / 2; ++j) { const uint32_t *rgb1 = (const uint32_t*) (in_data + in_stride * (int) (j * 2)); const uint32_t *rgb2 = (const uint32_t*) (in_data + in_stride * (int) (j * 2 + 1)); uint8_t *yuv_y1 = out_data[0] + out_stride[0] * (int) (j * 2); uint8_t *yuv_y2 = out_data[0] + out_stride[0] * (int) (j * 2 + 1); uint8_t *yuv_u = out_data[1] + out_stride[1] * (int) j; uint8_t *yuv_v = out_data[2] + out_stride[2] * (int) j; for(unsigned int i = 0; i < w / 16; ++i) { __m128i ra, ga, ba; { ReadBGRAInterleaved(rgb1 , rgb1 + 4, ca1, cb1, r1, g1, b1); ReadBGRAInterleaved(rgb1 + 8, rgb1 + 12, ca2, cb2, r2, g2, b2); rgb1 += 16; Convert_RGB_Y(r1, g1, b1, y1); Convert_RGB_Y(r2, g2, b2, y2); WritePlaneInterleaved(yuv_y1, y1, y2, v_shuffle1, v_shuffle2); yuv_y1 += 16; _mm_prefetch(rgb1 + 16, _MM_HINT_T0); ra = _mm_hadd_epi32(r1, r2); ga = _mm_hadd_epi32(g1, g2); ba = _mm_hadd_epi32(b1, b2); } { ReadBGRAInterleaved(rgb2 , rgb2 + 4, ca1, cb1, r1, g1, b1); ReadBGRAInterleaved(rgb2 + 8, rgb2 + 12, ca2, cb2, r2, g2, b2); rgb2 += 16; Convert_RGB_Y(r1, g1, b1, y1); Convert_RGB_Y(r2, g2, b2, y2); WritePlaneInterleaved(yuv_y2, y1, y2, v_shuffle1, v_shuffle2); yuv_y2 += 16; _mm_prefetch(rgb2 + 16, _MM_HINT_T0); ra = _mm_add_epi16(ra, _mm_hadd_epi32(r1, r2)); ga = _mm_add_epi16(ga, _mm_hadd_epi32(g1, g2)); ba = _mm_add_epi16(ba, _mm_hadd_epi32(b1, b2)); } { ra = _mm_srli_epi16(_mm_add_epi16(ra, v_2), 2); ga = _mm_srli_epi16(_mm_add_epi16(ga, v_2), 2); ba = _mm_srli_epi16(_mm_add_epi16(ba, v_2), 2); __m128i u = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(ra, v_mat_ur), _mm_mullo_epi16(ga, v_mat_ug)), _mm_add_epi16(_mm_mullo_epi16(ba, v_mat_ub_vr), v_offset_uv)); _mm_storel_epi64((__m128i*) yuv_u, _mm_shuffle_epi8(u, v_shuffle3)); yuv_u += 8; __m128i v = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(ra, v_mat_ub_vr), _mm_mullo_epi16(ga, v_mat_vg)), _mm_add_epi16(_mm_mullo_epi16(ba, v_mat_vb), v_offset_uv)); _mm_storel_epi64((__m128i*) yuv_v, _mm_shuffle_epi8(v, v_shuffle3)); yuv_v += 8; } } for(unsigned int i = 0; i < (w & 15) / 2; ++i) { uint32_t c1 = rgb1[0], c2 = rgb1[1], c3 = rgb2[0], c4 = rgb2[1]; rgb1 += 2; rgb2 += 2; int r1 = (int) ((c1 >> 16) & 0xff), r2 = (int) ((c2 >> 16) & 0xff), r3 = (int) ((c3 >> 16) & 0xff), r4 = (int) ((c4 >> 16) & 0xff); int g1 = (int) ((c1 >> 8) & 0xff), g2 = (int) ((c2 >> 8) & 0xff), g3 = (int) ((c3 >> 8) & 0xff), g4 = (int) ((c4 >> 8) & 0xff); int b1 = (int) ((c1 ) & 0xff), b2 = (int) ((c2 ) & 0xff), b3 = (int) ((c3 ) & 0xff), b4 = (int) ((c4 ) & 0xff); yuv_y1[0] = (47 * r1 + 157 * g1 + 16 * b1 + offset_y) >> 8; yuv_y1[1] = (47 * r2 + 157 * g2 + 16 * b2 + offset_y) >> 8; yuv_y2[0] = (47 * r3 + 157 * g3 + 16 * b3 + offset_y) >> 8; yuv_y2[1] = (47 * r4 + 157 * g4 + 16 * b4 + offset_y) >> 8; yuv_y1 += 2; yuv_y2 += 2; int sr = r1 + r2 + r3 + r4; int sg = g1 + g2 + g3 + g4; int sb = b1 + b2 + b3 + b4; *(yuv_u++) = (-26 * sr + -86 * sg + 112 * sb + offset_uv) >> 10; *(yuv_v++) = (112 * sr + -102 * sg + -10 * sb + offset_uv) >> 10; } } _mm_sfence(); } void Convert_BGRA_NV12_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const out_data[2], const int out_stride[2]) { assert(w % 2 == 0 && h % 2 == 0); assert((uintptr_t) out_data[0] % 16 == 0 && out_stride[0] % 16 == 0); assert((uintptr_t) out_data[1] % 16 == 0 && out_stride[1] % 16 == 0); __m128i v_byte1 = _mm_set1_epi32(0x000000ff); __m128i v_byte3 = _mm_set1_epi32(0x00ff0000); __m128i v_mat_yr = _mm_set1_epi16(47); __m128i v_mat_yg = _mm_set1_epi16(157); __m128i v_mat_yb = _mm_set1_epi16(16); __m128i v_mat_ur = _mm_set1_epi16(-26); __m128i v_mat_ug = _mm_set1_epi16(-86); __m128i v_mat_ub_vr = _mm_set1_epi16(112); __m128i v_mat_vg = _mm_set1_epi16(-102); __m128i v_mat_vb = _mm_set1_epi16(-10); __m128i v_offset_y = _mm_set1_epi16((int16_t) (128 + (16 << 8))); __m128i v_offset_uv = _mm_set1_epi16((int16_t) (128 + (128 << 8))); __m128i v_2 = _mm_set1_epi16(2); __m128i v_shuffle1 = _mm_setr_epi8( 1, 5, 9, 13, 3, 7, 11, 15, -1, -1, -1, -1, -1, -1, -1, -1); __m128i v_shuffle2 = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 1, 5, 9, 13, 3, 7, 11, 15); __m128i v_shuffle3 = _mm_setr_epi8( 1, -1, 5, -1, 3, -1, 7, -1, 9, -1, 13, -1, 11, -1, 15, -1); __m128i v_shuffle4 = _mm_setr_epi8(-1, 1, -1, 5, -1, 3, -1, 7, -1, 9, -1, 13, -1, 11, -1, 15); const int offset_y = 128 + (16 << 8), offset_uv = (128 + (128 << 8)) << 2; for(unsigned int j = 0; j < h / 2; ++j) { const uint32_t *rgb1 = (const uint32_t*) (in_data + in_stride * (int) (j * 2)); const uint32_t *rgb2 = (const uint32_t*) (in_data + in_stride * (int) (j * 2 + 1)); uint8_t *yuv_y1 = out_data[0] + out_stride[0] * (int) (j * 2); uint8_t *yuv_y2 = out_data[0] + out_stride[0] * (int) (j * 2 + 1); uint8_t *yuv_uv = out_data[1] + out_stride[1] * (int) j; for(unsigned int i = 0; i < w / 16; ++i) { __m128i ra, ga, ba; { ReadBGRAInterleaved(rgb1 , rgb1 + 4, ca1, cb1, r1, g1, b1); ReadBGRAInterleaved(rgb1 + 8, rgb1 + 12, ca2, cb2, r2, g2, b2); rgb1 += 16; Convert_RGB_Y(r1, g1, b1, y1); Convert_RGB_Y(r2, g2, b2, y2); WritePlaneInterleaved(yuv_y1, y1, y2, v_shuffle1, v_shuffle2); yuv_y1 += 16; _mm_prefetch(rgb1 + 16, _MM_HINT_T0); ra = _mm_hadd_epi32(r1, r2); ga = _mm_hadd_epi32(g1, g2); ba = _mm_hadd_epi32(b1, b2); } { ReadBGRAInterleaved(rgb2 , rgb2 + 4, ca1, cb1, r1, g1, b1); ReadBGRAInterleaved(rgb2 + 8, rgb2 + 12, ca2, cb2, r2, g2, b2); rgb2 += 16; Convert_RGB_Y(r1, g1, b1, y1); Convert_RGB_Y(r2, g2, b2, y2); WritePlaneInterleaved(yuv_y2, y1, y2, v_shuffle1, v_shuffle2); yuv_y2 += 16; _mm_prefetch(rgb2 + 16, _MM_HINT_T0); ra = _mm_add_epi16(ra, _mm_hadd_epi32(r1, r2)); ga = _mm_add_epi16(ga, _mm_hadd_epi32(g1, g2)); ba = _mm_add_epi16(ba, _mm_hadd_epi32(b1, b2)); } { ra = _mm_srli_epi16(_mm_add_epi16(ra, v_2), 2); ga = _mm_srli_epi16(_mm_add_epi16(ga, v_2), 2); ba = _mm_srli_epi16(_mm_add_epi16(ba, v_2), 2); __m128i u = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(ra, v_mat_ur), _mm_mullo_epi16(ga, v_mat_ug)), _mm_add_epi16(_mm_mullo_epi16(ba, v_mat_ub_vr), v_offset_uv)); __m128i v = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(ra, v_mat_ub_vr), _mm_mullo_epi16(ga, v_mat_vg)), _mm_add_epi16(_mm_mullo_epi16(ba, v_mat_vb), v_offset_uv)); WritePlaneInterleaved(yuv_uv, u, v, v_shuffle3, v_shuffle4); yuv_uv += 16; } } for(unsigned int i = 0; i < (w & 15) / 2; ++i) { uint32_t c1 = rgb1[0], c2 = rgb1[1], c3 = rgb2[0], c4 = rgb2[1]; rgb1 += 2; rgb2 += 2; int r1 = (int) ((c1 >> 16) & 0xff), r2 = (int) ((c2 >> 16) & 0xff), r3 = (int) ((c3 >> 16) & 0xff), r4 = (int) ((c4 >> 16) & 0xff); int g1 = (int) ((c1 >> 8) & 0xff), g2 = (int) ((c2 >> 8) & 0xff), g3 = (int) ((c3 >> 8) & 0xff), g4 = (int) ((c4 >> 8) & 0xff); int b1 = (int) ((c1 ) & 0xff), b2 = (int) ((c2 ) & 0xff), b3 = (int) ((c3 ) & 0xff), b4 = (int) ((c4 ) & 0xff); yuv_y1[0] = (47 * r1 + 157 * g1 + 16 * b1 + offset_y) >> 8; yuv_y1[1] = (47 * r2 + 157 * g2 + 16 * b2 + offset_y) >> 8; yuv_y2[0] = (47 * r3 + 157 * g3 + 16 * b3 + offset_y) >> 8; yuv_y2[1] = (47 * r4 + 157 * g4 + 16 * b4 + offset_y) >> 8; yuv_y1 += 2; yuv_y2 += 2; int sr = r1 + r2 + r3 + r4; int sg = g1 + g2 + g3 + g4; int sb = b1 + b2 + b3 + b4; yuv_uv[0] = (-26 * sr + -86 * sg + 112 * sb + offset_uv) >> 10; yuv_uv[1] = (112 * sr + -102 * sg + -10 * sb + offset_uv) >> 10; yuv_uv += 2; } } _mm_sfence(); } /* ==== SSSE3 BGRA-to-BGR Converter ==== Same as the fallback converter, but with a larger block size and shuffles instead of shifts and bitwise or. - BGR: converts blocks of 16x1 pixels */ void Convert_BGRA_BGR_SSSE3(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride) { assert((uintptr_t) out_data % 16 == 0 && out_stride % 16 == 0); __m128i v_shuffle1 = _mm_setr_epi8( 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, -1, -1, -1, -1); __m128i v_shuffle2 = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 4); __m128i v_shuffle3 = _mm_setr_epi8( 5, 6, 8, 9, 10, 12, 13, 14, -1, -1, -1, -1, -1, -1, -1, -1); __m128i v_shuffle4 = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 4, 5, 6, 8, 9); __m128i v_shuffle5 = _mm_setr_epi8(10, 12, 13, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); __m128i v_shuffle6 = _mm_setr_epi8(-1, -1, -1, -1, 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14); for(unsigned int j = 0; j < h; ++j) { const uint8_t *in = in_data + in_stride * (int) j; uint8_t *out = out_data + out_stride * (int) j; for(unsigned int i = 0; i < w / 16; ++i) { __m128i c0 = _mm_loadu_si128((__m128i*) (in )); __m128i c1 = _mm_loadu_si128((__m128i*) (in + 16)); __m128i c2 = _mm_loadu_si128((__m128i*) (in + 32)); __m128i c3 = _mm_loadu_si128((__m128i*) (in + 48)); //_mm_prefetch(in + 192, _MM_HINT_T0); in += 64; _mm_stream_si128((__m128i*) (out ), _mm_or_si128(_mm_shuffle_epi8(c0, v_shuffle1), _mm_shuffle_epi8(c1, v_shuffle2))); _mm_stream_si128((__m128i*) (out + 16), _mm_or_si128(_mm_shuffle_epi8(c1, v_shuffle3), _mm_shuffle_epi8(c2, v_shuffle4))); _mm_stream_si128((__m128i*) (out + 32), _mm_or_si128(_mm_shuffle_epi8(c2, v_shuffle5), _mm_shuffle_epi8(c3, v_shuffle6))); out += 48; } for(unsigned int i = 0; i < (w & 15); ++i) { uint32_t c = *((uint32_t*) in); in += 4; out[0] = c; out[1] = c >> 8; out[2] = c >> 16; out += 3; } } _mm_sfence(); } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler_Scale.h000066400000000000000000000022371470053624300241420ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" void Scale_BGRA_Fallback(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, unsigned int out_w, unsigned int out_h, uint8_t* out_data, int out_stride); #if SSR_USE_X86_ASM void Scale_BGRA_SSSE3(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, unsigned int out_w, unsigned int out_h, uint8_t* out_data, int out_stride); #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler_Scale_Fallback.cpp000066400000000000000000000217221470053624300262540ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastScaler_Scale.h" #include "FastScaler_Scale_Generic.h" #include "TempBuffer.h" /* ==== Fallback MipMapper ==== Uses 'wannabe-SIMD': 4x 16-bit values in normal 64-bit registers. This works as long as overflow is avoided. Performs best on 64-bit systems, but even on 32-bit it should still be reasonably good. It's important that this function is force-inlined because this allows the compiler to eliminate the inner loops for common mipmap factors. */ inline __attribute__((always_inline)) void MipMap_BGRA_Fallback_Dynamic(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride, unsigned int mx, unsigned int my) { const uint64_t mask = vec4x16(0xff); const uint64_t offset = vec4x16(1u << (mx + my - 1)); unsigned int wrem = in_w & ((1u << mx) - 1); unsigned int hrem = in_h & ((1u << my) - 1); for(unsigned int out_j = 0; out_j < (in_h >> my); ++out_j) { const uint32_t *in = (const uint32_t*) (in_data + in_stride * (int) (out_j << my)); uint32_t *out = (uint32_t*) (out_data + out_stride * (int) out_j); for(unsigned int out_i = 0; out_i < (in_w >> mx); ++out_i) { uint64_t sum = 0; const uint32_t *in2 = in; for(unsigned int mj = 0; mj < (1u << my); ++mj) { for(unsigned int mi = 0; mi < (1u << mx); ++mi) { uint64_t c = in2[mi]; sum += ((c << 24) | c) & mask; } in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } in += (1u << mx); uint64_t q = ((sum + offset) >> (mx + my)) & mask; *(out++) = ((uint32_t) (q >> 24)) | ((uint32_t) q); } if(wrem != 0) { uint64_t sum = 0; const uint32_t *in2 = in; for(unsigned int mj = 0; mj < (1u << my); ++mj) { for(unsigned int mi = 0; mi < wrem - 1; ++mi) { uint64_t c = in2[mi]; sum += ((c << 24) | c) & mask; } uint64_t c = in2[wrem - 1]; sum += (((c << 24) | c) & mask) * ((1u << mx) - (wrem - 1)); in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } uint64_t q = ((sum + offset) >> (mx + my)) & mask; *out = ((uint32_t) (q >> 24)) | ((uint32_t) q); } } if(hrem != 0) { unsigned int out_j = in_h >> my; const uint32_t *in = (const uint32_t*) (in_data + in_stride * (int) (out_j << my)); uint32_t *out = (uint32_t*) (out_data + out_stride * (int) out_j); for(unsigned int out_i = 0; out_i < (in_w >> mx); ++out_i) { uint64_t sum = 0; const uint32_t *in2 = in; for(unsigned int mj = 0; mj < hrem - 1; ++mj) { for(unsigned int mi = 0; mi < (1u << mx); ++mi) { uint64_t c = in2[mi]; sum += ((c << 24) | c) & mask; } in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } for(unsigned int mi = 0; mi < (1u << mx); ++mi) { uint64_t c = in2[mi]; sum += (((c << 24) | c) & mask) * ((1u << my) - (hrem - 1)); } in += (1u << mx); uint64_t q = ((sum + offset) >> (mx + my)) & mask; *(out++) = ((uint32_t) (q >> 24)) | ((uint32_t) q); } if(wrem != 0) { uint64_t sum = 0; const uint32_t *in2 = in; for(unsigned int mj = 0; mj < hrem - 1; ++mj) { for(unsigned int mi = 0; mi < wrem - 1; ++mi) { uint64_t c = in2[mi]; sum += ((c << 24) | c) & mask; } uint64_t c = in2[wrem - 1]; sum += (((c << 24) | c) & mask) * ((1u << mx) - (wrem - 1)); in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } for(unsigned int mi = 0; mi < wrem - 1; ++mi) { uint64_t c = in2[mi]; sum += (((c << 24) | c) & mask) * ((1u << my) - (hrem - 1)); } uint64_t c = in2[wrem - 1]; sum += (((c << 24) | c) & mask) * ((1u << my) - (hrem - 1)) * ((1u << mx) - (wrem - 1)); uint64_t q = ((sum + offset) >> (mx + my)) & mask; *out = ((uint32_t) (q >> 24)) | ((uint32_t) q); } } } void MipMap_BGRA_Fallback(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride, unsigned int mx, unsigned int my) { assert(mx + my <= 8); switch((mx << 4) | my) { case 0x00: assert(false); break; case 0x01: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 0, 1); break; case 0x02: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 0, 2); break; case 0x03: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 0, 3); break; case 0x10: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 1, 0); break; case 0x11: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 1, 1); break; case 0x12: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 1, 2); break; case 0x13: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 1, 3); break; case 0x20: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 2, 0); break; case 0x21: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 2, 1); break; case 0x22: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 2, 2); break; case 0x23: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 2, 3); break; case 0x30: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 3, 0); break; case 0x31: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 3, 1); break; case 0x32: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 3, 2); break; case 0x33: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 3, 3); break; default: MipMap_BGRA_Fallback_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, mx, my); break; } } /* ==== Fallback Bilinear Scaler ==== Uses 'wannabe-SIMD' like the mipmapper. It's slightly less efficient here because of the multiplications, but still much faster than plain 32-bit integers. */ void Bilinear_BGRA_Fallback(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, unsigned int out_w, unsigned int out_h, uint8_t* out_data, int out_stride, unsigned int mx, unsigned int my) { assert(in_w > 1 && in_h > 1); //TODO// support size 1? assert(out_w > 1 && out_h > 1); //TODO// support size 1? assert(in_w < (1 << 28) && in_h < (1 << 28)); assert(out_w < (1 << 28) && out_h < (1 << 28)); // precompute horizontal offsets and fractions TempBuffer x_offset_table, x_fraction_table; x_offset_table.Alloc(out_w); x_fraction_table.Alloc(out_w); for(unsigned int out_i = 0; out_i < out_w; ++out_i) { Bilinear_MapIndex(out_i, in_w, out_w, mx, x_offset_table[out_i], x_fraction_table[out_i]); } const uint64_t mask = vec4x16(0xff); const uint64_t offset = vec4x16(128); // scale for(unsigned int out_j = 0; out_j < out_h; ++out_j) { unsigned int y_offset, y_fraction; Bilinear_MapIndex(out_j, in_h, out_h, my, y_offset, y_fraction); unsigned int y_fraction_inv = 256 - y_fraction; unsigned int *x_offset_ptr = x_offset_table.GetData(), *x_fraction_ptr = x_fraction_table.GetData(); const uint32_t *in1 = (const uint32_t*) (in_data + in_stride * (int) y_offset); const uint32_t *in2 = (const uint32_t*) (in_data + in_stride * ((int) y_offset + 1)); uint32_t *out = (uint32_t*) (out_data + out_stride * (int) out_j); for(unsigned int out_i = 0; out_i < out_w; ++out_i) { unsigned int x_offset = *(x_offset_ptr++), x_fraction = *(x_fraction_ptr++), x_fraction_inv = 256 - x_fraction; uint64_t c[4] = {in1[x_offset], in1[x_offset + 1], in2[x_offset], in2[x_offset + 1]}; uint64_t p[4] = {((c[0] << 24) | c[0]) & mask, ((c[1] << 24) | c[1]) & mask, ((c[2] << 24) | c[2]) & mask, ((c[3] << 24) | c[3]) & mask}; uint64_t q[2] = {((p[0] * x_fraction_inv + p[1] * x_fraction + offset) >> 8) & mask, ((p[2] * x_fraction_inv + p[3] * x_fraction + offset) >> 8) & mask}; uint64_t r = ((q[0] * y_fraction_inv + q[1] * y_fraction + offset) >> 8) & mask; *(out++) = ((uint32_t) (r >> 24)) | ((uint32_t) r); } } } void Scale_BGRA_Fallback(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, unsigned int out_w, unsigned int out_h, uint8_t* out_data, int out_stride) { Scale_BGRA_Generic(in_w, in_h, in_data, in_stride, out_w, out_h, out_data, out_stride, MipMap_BGRA_Fallback, Bilinear_BGRA_Fallback); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler_Scale_Generic.cpp000066400000000000000000000044321470053624300261300ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastScaler_Scale_Generic.h" #include "TempBuffer.h" void Scale_BGRA_Generic(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, unsigned int out_w, unsigned int out_h, uint8_t* out_data, int out_stride, MipMapFunction mipmap_function, BilinearFunction bilinear_function) { // no scaling? if(in_w == out_w && in_h == out_h) { if(in_stride == out_stride) { memcpy(out_data, in_data, in_stride * in_h); } else { for(unsigned int out_j = 0; out_j < out_h; ++out_j) { memcpy(out_data, in_data, in_w * 4); in_data += in_stride; out_data += out_stride; } } return; } // calculate mipmap factors unsigned int mx = 0, my = 0; while((out_w << (mx + 1)) <= in_w) ++mx; while((out_h << (my + 1)) <= in_h) ++my; if(mx + my > 8) { if(mx <= 4) my = 8 - mx; else if(my <= 4) mx = 8 - my; else mx = my = 4; } // pure mipmap scaling? if((out_w << mx) == in_w && (out_h << my) == in_h) { mipmap_function(in_w, in_h, in_data, in_stride, out_data, out_stride, mx, my); return; } // create mipmap TempBuffer mipmap; if(mx != 0 || my != 0) { unsigned int mipmap_w = ((in_w - 1) >> mx) + 1, mipmap_h = ((in_h - 1) >> my) + 1; int mipmap_stride = grow_align16(mipmap_w * 4); mipmap.Alloc(mipmap_stride * mipmap_h); mipmap_function(in_w, in_h, in_data, in_stride, mipmap.GetData(), mipmap_stride, mx, my); in_data = mipmap.GetData(); in_stride = mipmap_stride; } bilinear_function(in_w, in_h, in_data, in_stride, out_w, out_h, out_data, out_stride, mx, my); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler_Scale_Generic.h000066400000000000000000000040001470053624300255640ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" inline uint64_t vec4x16(uint16_t x) { return ((uint64_t) x << 48) | ((uint64_t) x << 32) | ((uint64_t) x << 16) | ((uint64_t) x); } inline void Bilinear_MapIndex(unsigned int out_i, unsigned int in_w, unsigned int out_w, unsigned int mipmap, unsigned int& offset, unsigned int& fraction) { uint64_t inter = (((uint64_t) out_i << 8) + 128) * (uint64_t) in_w; unsigned int div = out_w << mipmap; int64_t ii = (int64_t) ((inter + (uint64_t) (div >> 1)) / (uint64_t) div) - 128; int off = ii >> 8; int max_offset = (int) ((in_w - 1) >> mipmap) - 1; if(off < 0) { offset = 0; fraction = 0; } else if(off > max_offset) { offset = max_offset; fraction = 256; } else { offset = off; fraction = ii & 255; } } typedef void (*MipMapFunction)(unsigned int, unsigned int, const uint8_t*, int, uint8_t*, int, unsigned int, unsigned int); typedef void (*BilinearFunction)(unsigned int, unsigned int, const uint8_t*, int, unsigned int, unsigned int, uint8_t*, int, unsigned int, unsigned int); void Scale_BGRA_Generic(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, unsigned int out_w, unsigned int out_h, uint8_t* out_data, int out_stride, MipMapFunction mipmap_function, BilinearFunction bilinear_function); ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/FastScaler_Scale_SSSE3.cpp000066400000000000000000000376131470053624300254230ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "FastScaler_Scale.h" #include "FastScaler_Scale_Generic.h" #include "TempBuffer.h" #if SSR_USE_X86_ASM #include // sse #include // sse2 #include // sse3 #include // ssse3 /* ==== SSSE3 MipMapper ==== Very similar to the fallback mipmapper. There are three different SSSE3 kernels depending on the horizontal mipmap factor (mx). The principle is the same as with 'wannabe-SIMD', but here we want to use larger reads/writes so horizontal addition is used. This complicates the loops a lot and this is the reason why there are three different kernels: the first one has no horizontal addition, the second one has one horizontal addition, and the third one has three horizontal additions. The horizontal additions are slower and not associative, so they are avoided as much as possible by delaying them until the end. The remainders (edges of the image that require special attention) don't use SSSE3 because it's not worth it. You won't see huge improvements compared to the fallback mipmapper, since both algorithms are usually limited by the memory bandwidth. It's important that this function is force-inlined because this allows the compiler to eliminate the inner loops for common mipmap factors. */ inline __attribute__((always_inline)) void MipMap_BGRA_SSSE3_Dynamic(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride, unsigned int mx, unsigned int my) { assert((uintptr_t) out_data % 16 == 0 && out_stride % 16 == 0); __m128i v_mask = _mm_set1_epi16(0xff); __m128i v_offset = _mm_set1_epi16(1u << (mx + my - 1)); const uint64_t mask = vec4x16(0xff); const uint64_t offset = vec4x16(1u << (mx + my - 1)); unsigned int wrem = in_w & ((1u << mx) - 1); unsigned int hrem = in_h & ((1u << my) - 1); for(unsigned int out_j = 0; out_j < (in_h >> my); ++out_j) { const uint32_t *in = (const uint32_t*) (in_data + in_stride * (int) (out_j << my)); uint32_t *out = (uint32_t*) (out_data + out_stride * (int) out_j); unsigned int blockrem; if(mx == 0) { for(unsigned int out_i = 0; out_i < (in_w >> (mx + 2)); ++out_i) { __m128i sum1br = _mm_setzero_si128(), sum1ga = _mm_setzero_si128(); const uint32_t *in2 = in; for(unsigned int mj = 0; mj < (1u << my); ++mj) { __m128i c1 = _mm_loadu_si128((__m128i*) in2); sum1br = _mm_add_epi16(sum1br, _mm_and_si128(c1, v_mask)); sum1ga = _mm_add_epi16(sum1ga, _mm_and_si128(_mm_srli_si128(c1, 1), v_mask)); in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } in += 4; __m128i qbr = _mm_srli_epi16(_mm_add_epi16(sum1br, v_offset), my); __m128i qga = _mm_srli_epi16(_mm_add_epi16(sum1ga, v_offset), my); _mm_stream_si128((__m128i*) out, _mm_or_si128(qbr, _mm_slli_si128(qga, 1))); out += 4; } blockrem = (in_w >> mx) & 3; } else if(mx == 1) { for(unsigned int out_i = 0; out_i < (in_w >> (mx + 2)); ++out_i) { __m128i sum1br = _mm_setzero_si128(), sum1ga = _mm_setzero_si128(), sum2br = _mm_setzero_si128(), sum2ga = _mm_setzero_si128(); const uint32_t *in2 = in; for(unsigned int mj = 0; mj < (1u << my); ++mj) { __m128i c1 = _mm_loadu_si128((__m128i*) in2); __m128i c2 = _mm_loadu_si128((__m128i*) (in2 + 4)); sum1br = _mm_add_epi16(sum1br, _mm_and_si128(c1, v_mask)); sum1ga = _mm_add_epi16(sum1ga, _mm_and_si128(_mm_srli_si128(c1, 1), v_mask)); sum2br = _mm_add_epi16(sum2br, _mm_and_si128(c2, v_mask)); sum2ga = _mm_add_epi16(sum2ga, _mm_and_si128(_mm_srli_si128(c2, 1), v_mask)); in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } in += 8; __m128i qbr = _mm_srli_epi16(_mm_add_epi16(_mm_hadd_epi32(sum1br, sum2br), v_offset), 1 + my); __m128i qga = _mm_srli_epi16(_mm_add_epi16(_mm_hadd_epi32(sum1ga, sum2ga), v_offset), 1 + my); _mm_stream_si128((__m128i*) out, _mm_or_si128(qbr, _mm_slli_si128(qga, 1))); out += 4; } blockrem = (in_w >> mx) & 3; } else { for(unsigned int out_i = 0; out_i < (in_w >> (mx + 1)); ++out_i) { __m128i sum1br = _mm_setzero_si128(), sum1ga = _mm_setzero_si128(), sum2br = _mm_setzero_si128(), sum2ga = _mm_setzero_si128(); const uint32_t *in2 = in; for(unsigned int mj = 0; mj < (1u << my); ++mj) { for(unsigned int mi = 0; mi < (1u << (mx - 2)); ++mi) { __m128i c1 = _mm_loadu_si128((__m128i*) (in2 + mi * 4)); sum1br = _mm_add_epi16(sum1br, _mm_and_si128(c1, v_mask)); sum1ga = _mm_add_epi16(sum1ga, _mm_and_si128(_mm_srli_si128(c1, 1), v_mask)); } for(unsigned int mi = (1u << (mx - 2)); mi < (1u << (mx - 1)); ++mi) { __m128i c2 = _mm_loadu_si128((__m128i*) (in2 + mi * 4)); sum2br = _mm_add_epi16(sum2br, _mm_and_si128(c2, v_mask)); sum2ga = _mm_add_epi16(sum2ga, _mm_and_si128(_mm_srli_si128(c2, 1), v_mask)); } in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } in += (1u << (mx + 1)); __m128i q = _mm_srli_epi16(_mm_add_epi16(_mm_hadd_epi32(_mm_hadd_epi32(sum1br, sum2br), _mm_hadd_epi32(sum1ga, sum2ga)), v_offset), mx + my); #if defined(__x86_64__) && TEST_GCC_VERSION(4, 8) _mm_stream_si64((long long*) out, _mm_cvtsi128_si64(_mm_or_si128(q, _mm_srli_si128(q, 7)))); #else _mm_storel_epi64((__m128i*) out, _mm_or_si128(q, _mm_srli_si128(q, 7))); #endif out += 2; } blockrem = (in_w >> mx) & 1; } for(unsigned int out_i = 0; out_i < blockrem; ++out_i) { uint64_t sum = 0; const uint32_t *in2 = in; for(unsigned int mj = 0; mj < (1u << my); ++mj) { for(unsigned int mi = 0; mi < (1u << mx); ++mi) { uint64_t c = in2[mi]; sum += ((c << 24) | c) & mask; } in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } in += (1u << mx); uint64_t q = ((sum + offset) >> (mx + my)) & mask; *(out++) = ((uint32_t) (q >> 24)) | ((uint32_t) q); } if(wrem != 0) { uint64_t sum = 0; const uint32_t *in2 = in; for(unsigned int mj = 0; mj < (1u << my); ++mj) { for(unsigned int mi = 0; mi < wrem - 1; ++mi) { uint64_t c = in2[mi]; sum += ((c << 24) | c) & mask; } uint64_t c = in2[wrem - 1]; sum += (((c << 24) | c) & mask) * ((1u << mx) - (wrem - 1)); in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } uint64_t q = ((sum + offset) >> (mx + my)) & mask; *out = ((uint32_t) (q >> 24)) | ((uint32_t) q); } } if(hrem != 0) { unsigned int out_j = in_h >> my; const uint32_t *in = (const uint32_t*) (in_data + in_stride * (int) (out_j << my)); uint32_t *out = (uint32_t*) (out_data + out_stride * (int) out_j); for(unsigned int out_i = 0; out_i < (in_w >> mx); ++out_i) { uint64_t sum = 0; const uint32_t *in2 = in; for(unsigned int mj = 0; mj < hrem - 1; ++mj) { for(unsigned int mi = 0; mi < (1u << mx); ++mi) { uint64_t c = in2[mi]; sum += ((c << 24) | c) & mask; } in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } for(unsigned int mi = 0; mi < (1u << mx); ++mi) { uint64_t c = in2[mi]; sum += (((c << 24) | c) & mask) * ((1u << my) - (hrem - 1)); } in += (1u << mx); uint64_t q = ((sum + offset) >> (mx + my)) & mask; *(out++) = ((uint32_t) (q >> 24)) | ((uint32_t) q); } if(wrem != 0) { uint64_t sum = 0; const uint32_t *in2 = in; for(unsigned int mj = 0; mj < hrem - 1; ++mj) { for(unsigned int mi = 0; mi < wrem - 1; ++mi) { uint64_t c = in2[mi]; sum += ((c << 24) | c) & mask; } uint64_t c = in2[wrem - 1]; sum += (((c << 24) | c) & mask) * ((1u << mx) - (wrem - 1)); in2 = (const uint32_t*) ((const uint8_t*) in2 + in_stride); } for(unsigned int mi = 0; mi < wrem - 1; ++mi) { uint64_t c = in2[mi]; sum += (((c << 24) | c) & mask) * ((1u << my) - (hrem - 1)); } uint64_t c = in2[wrem - 1]; sum += (((c << 24) | c) & mask) * ((1u << my) - (hrem - 1)) * ((1u << mx) - (wrem - 1)); uint64_t q = ((sum + offset) >> (mx + my)) & mask; *out = ((uint32_t) (q >> 24)) | ((uint32_t) q); } } _mm_sfence(); } void MipMap_BGRA_SSSE3(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, uint8_t* out_data, int out_stride, unsigned int mx, unsigned int my) { assert(mx + my <= 8); switch((mx << 4) | my) { case 0x00: assert(false); break; case 0x01: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 0, 1); break; case 0x02: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 0, 2); break; case 0x03: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 0, 3); break; case 0x10: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 1, 0); break; case 0x11: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 1, 1); break; case 0x12: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 1, 2); break; case 0x13: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 1, 3); break; case 0x20: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 2, 0); break; case 0x21: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 2, 1); break; case 0x22: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 2, 2); break; case 0x23: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 2, 3); break; case 0x30: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 3, 0); break; case 0x31: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 3, 1); break; case 0x32: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 3, 2); break; case 0x33: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, 3, 3); break; default: MipMap_BGRA_SSSE3_Dynamic(in_w, in_h, in_data, in_stride, out_data, out_stride, mx, my); break; } } /* ==== SSSE3 Bilinear Scaler ==== Same principle as the fallback scaler, but this version produces two pixels per iteration. That means it can read 64-bit blocks and write 64-bit blocks, and the shuffles are also more efficient than just shifting. */ void Bilinear_BGRA_SSSE3(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, unsigned int out_w, unsigned int out_h, uint8_t* out_data, int out_stride, unsigned int mx, unsigned int my) { assert(in_w > 1 && in_h > 1); //TODO// support size 1? assert(out_w > 1 && out_h > 1); //TODO// support size 1? assert(in_w < (1 << 28) && in_h < (1 << 28)); assert(out_w < (1 << 28) && out_h < (1 << 28)); assert((uintptr_t) out_data % 16 == 0 && out_stride % 16 == 0); // precompute horizontal offsets and fractions TempBuffer x_offset_table; TempBuffer x_fraction_table; x_offset_table.Alloc(out_w); x_fraction_table.Alloc(out_w); for(unsigned int out_i = 0; out_i < out_w; ++out_i) { unsigned int x_fraction; Bilinear_MapIndex(out_i, in_w, out_w, mx, x_offset_table[out_i], x_fraction); x_fraction_table[out_i] = ((uint64_t) x_fraction << 48) | ((uint64_t) x_fraction << 32) | ((uint64_t) x_fraction << 16) | ((uint64_t) x_fraction); } // constants __m128i v_128 = _mm_set1_epi16(128); __m128i v_256 = _mm_set1_epi16(256); __m128i v_shuffle1 = _mm_setr_epi8( 0, -1, 1, -1, 2, -1, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1); __m128i v_shuffle2 = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 0, -1, 1, -1, 2, -1, 3, -1); __m128i v_shuffle3 = _mm_setr_epi8( 4, -1, 5, -1, 6, -1 , 7, -1, -1, -1, -1, -1, -1, -1, -1, -1); __m128i v_shuffle4 = _mm_setr_epi8(-1, -1, -1, -1, -1, -1, -1, -1, 4, -1, 5, -1, 6, -1, 7, -1); __m128i v_shuffle5 = _mm_setr_epi8( 1, 3, 5, 7, 9, 11, 13, 15, -1, -1, -1, -1, -1, -1, -1, -1); // scale for(unsigned int out_j = 0; out_j < out_h; ++out_j) { unsigned int y_offset, y_fraction; Bilinear_MapIndex(out_j, in_h, out_h, my, y_offset, y_fraction); __m128i vy_fraction = _mm_set1_epi16(y_fraction); __m128i vy_fraction_inv = _mm_sub_epi16(v_256, vy_fraction); unsigned int *x_offset_ptr = x_offset_table.GetData(); uint64_t *x_fraction_ptr = x_fraction_table.GetData(); const uint32_t *in1 = (const uint32_t*) (in_data + in_stride * (int) y_offset); const uint32_t *in2 = (const uint32_t*) (in_data + in_stride * ((int) y_offset + 1)); uint32_t *out = (uint32_t*) (out_data + out_stride * (int) out_j); for(unsigned int out_i = 0; out_i < out_w / 2; ++out_i) { unsigned int x_offset1 = x_offset_ptr[0]; unsigned int x_offset2 = x_offset_ptr[1]; __m128i vx_fraction = _mm_load_si128((__m128i*) x_fraction_ptr); __m128i vx_fraction_inv = _mm_sub_epi16(v_256, vx_fraction); x_offset_ptr += 2; x_fraction_ptr += 2; __m128i c1a = _mm_loadl_epi64((__m128i*) (in1 + x_offset1)); __m128i c2a = _mm_loadl_epi64((__m128i*) (in1 + x_offset2)); __m128i c1b = _mm_loadl_epi64((__m128i*) (in2 + x_offset1)); __m128i c2b = _mm_loadl_epi64((__m128i*) (in2 + x_offset2)); //_mm_prefetch(in1 + x_offset2 + 64, _MM_HINT_T0); //_mm_prefetch(in2 + x_offset2 + 64, _MM_HINT_T0); __m128i p1 = _mm_or_si128(_mm_shuffle_epi8(c1a, v_shuffle1), _mm_shuffle_epi8(c2a, v_shuffle2)); __m128i p2 = _mm_or_si128(_mm_shuffle_epi8(c1a, v_shuffle3), _mm_shuffle_epi8(c2a, v_shuffle4)); __m128i q1 = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(p1, vx_fraction_inv), v_128), _mm_mullo_epi16(p2, vx_fraction)), 8); __m128i p3 = _mm_or_si128(_mm_shuffle_epi8(c1b, v_shuffle1), _mm_shuffle_epi8(c2b, v_shuffle2)); __m128i p4 = _mm_or_si128(_mm_shuffle_epi8(c1b, v_shuffle3), _mm_shuffle_epi8(c2b, v_shuffle4)); __m128i q2 = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(p3, vx_fraction_inv), v_128), _mm_mullo_epi16(p4, vx_fraction)), 8); __m128i r = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(q1, vy_fraction_inv), v_128), _mm_mullo_epi16(q2, vy_fraction)); _mm_storel_epi64((__m128i*) out, _mm_shuffle_epi8(r, v_shuffle5)); out += 2; } if(out_w & 1) { unsigned int x_offset1 = x_offset_ptr[0]; __m128i vx_fraction = _mm_loadl_epi64((__m128i*) x_fraction_ptr); __m128i vx_fraction_inv = _mm_sub_epi16(v_256, vx_fraction); __m128i c1a = _mm_loadl_epi64((__m128i*) (in1 + x_offset1)); __m128i c1b = _mm_loadl_epi64((__m128i*) (in2 + x_offset1)); __m128i p1 = _mm_shuffle_epi8(c1a, v_shuffle1); __m128i p2 = _mm_shuffle_epi8(c1a, v_shuffle3); __m128i q1 = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(p1, vx_fraction_inv), v_128), _mm_mullo_epi16(p2, vx_fraction)), 8); __m128i p3 = _mm_shuffle_epi8(c1b, v_shuffle1); __m128i p4 = _mm_shuffle_epi8(c1b, v_shuffle3); __m128i q2 = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(p3, vx_fraction_inv), v_128), _mm_mullo_epi16(p4, vx_fraction)), 8); __m128i r = _mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(q1, vy_fraction_inv), v_128), _mm_mullo_epi16(q2, vy_fraction)); *out = _mm_cvtsi128_si32(_mm_shuffle_epi8(r, v_shuffle5)); } } } void Scale_BGRA_SSSE3(unsigned int in_w, unsigned int in_h, const uint8_t* in_data, int in_stride, unsigned int out_w, unsigned int out_h, uint8_t* out_data, int out_stride) { Scale_BGRA_Generic(in_w, in_h, in_data, in_stride, out_w, out_h, out_data, out_stride, MipMap_BGRA_SSSE3, Bilinear_BGRA_SSSE3); } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/000077500000000000000000000000001470053624300217265ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/ALSAInput.cpp000066400000000000000000000417561470053624300242070ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "ALSAInput.h" #if SSR_USE_ALSA #include "Logger.h" #include "TempBuffer.h" // Artificial delay after the first samples have been received (in microseconds). Any samples received during this time will be dropped. // This is needed because the first samples sometimes have weird timestamps, especially when PulseAudio is active // (I've seen one situation where PulseAudio instantly 'captures' 2 seconds of silence when the recording is started). // It also eliminates the clicking sound when the microphone is started for the first time. const int64_t ALSAInput::START_DELAY = 100000; static void ALSARecoverAfterOverrun(snd_pcm_t* pcm) { Logger::LogWarning("[ALSARecoverAfterOverrun] " + Logger::tr("Warning: An overrun has occurred, some samples were lost.", "Don't translate 'overrun'")); if(snd_pcm_prepare(pcm) < 0) { Logger::LogError("[ALSARecoverAfterOverrun] " + Logger::tr("Error: Can't recover device after overrun!", "Don't translate 'overrun'")); throw ALSAException(); } if(snd_pcm_start(pcm) < 0) { Logger::LogError("[ALSARecoverAfterOverrun] " + Logger::tr("Error: Can't start PCM device after overrun!", "Don't translate 'overrun'")); throw ALSAException(); } } ALSAInput::ALSAInput(const QString& source_name, unsigned int sample_rate) { m_source_name = source_name; m_sample_rate = sample_rate; m_sample_format = AV_SAMPLE_FMT_S16; // default, may change later m_convert_24_to_32 = false; m_channels = 2; // always 2 channels because the synchronizer and encoder don't support anything else at this point m_period_size = 1024; // number of samples per period m_buffer_size = m_period_size * 8; // number of samples in the buffer m_alsa_pcm = NULL; try { Init(); } catch(...) { Free(); throw; } } ALSAInput::~ALSAInput() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[ALSAInput::~ALSAInput] " + Logger::tr("Stopping input thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } std::vector ALSAInput::GetSourceList() { std::vector list; /* This code is based on the ALSA device detection code used in PortAudio. I just ported it to C++ and improved it a bit. All credit goes to the PortAudio devs, they saved me a lot of time :). */ // these ALSA plugins are blacklisted because they are always defined but rarely useful // 'pulse' and 'jack' are also blacklisted because the native backends are more reliable std::vector plugin_blacklist = { "cards", "default", "sysdefault", "hw", "plughw", "plug", "dmix", "dsnoop", "shm", "tee", "file", "null", "front", "rear", "center_lfe", "side", "surround40", "surround41", "surround50", "surround51", "surround71", "iec958", "spdif", "hdmi", "modem", "phoneline", "oss", "pulse", "jack", "speex", "speexrate", "samplerate", "lavrate", "a52", "upmix", "vdownmix", "usbstream", }; std::sort(plugin_blacklist.begin(), plugin_blacklist.end()); // the 'default' PCM must be first, so add it explicitly list.push_back(Source("default", "Default source")); Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Generating source list ...")); snd_ctl_card_info_t *alsa_card_info = NULL; snd_pcm_info_t *alsa_pcm_info = NULL; snd_ctl_t *alsa_ctl = NULL; try { // allocate card and PCM info structure if(snd_ctl_card_info_malloc(&alsa_card_info) < 0) { throw std::bad_alloc(); } if(snd_pcm_info_malloc(&alsa_pcm_info) < 0) { throw std::bad_alloc(); } // update the ALSA configuration snd_config_update_free_global(); if(snd_config_update() < 0) { Logger::LogError("[ALSAInput::GetSourceList] " + Logger::tr("Error: Could not update ALSA configuration!")); throw ALSAException(); } // find all PCM plugins (by parsing the config file) snd_config_t *alsa_config_pcms = NULL; if(snd_config_search(snd_config, "pcm", &alsa_config_pcms) == 0) { snd_config_iterator_t i, next; snd_config_for_each(i, next, alsa_config_pcms) { snd_config_t *alsa_config_pcm = snd_config_iterator_entry(i); // get the name const char *id = NULL; if(snd_config_get_id(alsa_config_pcm, &id) < 0 || id == NULL) continue; std::string plugin_name = id; // ignore the plugin if it is blacklisted if(std::binary_search(plugin_blacklist.begin(), plugin_blacklist.end(), plugin_name)) continue; // try to get the description std::string plugin_description; snd_config_t *alsa_config_description = NULL; if(snd_config_search(alsa_config_pcm, "hint.description", &alsa_config_description) == 0) { const char *str = NULL; if(snd_config_get_string(alsa_config_description, &str) >= 0 && str != NULL) { plugin_description = str; } } // if there is no description, ignore it, because it's probably not meant to be used if(plugin_description.empty()) continue; // if there is no description, use the type instead /*if(plugin_description.empty()) { snd_config_t *alsa_config_type = NULL; if(snd_config_search(alsa_config_pcm, "type", &alsa_config_type) >= 0) { const char *str = NULL; if(snd_config_get_string(alsa_config_type, &str) >= 0 && str != NULL) { plugin_description = std::string(str) + " plugin"; } } }*/ // add to list Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Found plugin: [%1] %2").arg(QString::fromStdString(plugin_name)).arg(QString::fromStdString(plugin_description))); list.push_back(Source(plugin_name, plugin_description)); } } // find all sound cards int card = -1; while(snd_card_next(&card) == 0 && card >= 0) { // try to open the card std::string card_name = "hw:" + NumToString(card); if(snd_ctl_open(&alsa_ctl, card_name.c_str(), 0) < 0) { Logger::LogWarning("[ALSAInput::GetSourceList] " + Logger::tr("Warning: Could not open sound card %1.").arg(card)); continue; } // get card info if(snd_ctl_card_info(alsa_ctl, alsa_card_info) < 0) { Logger::LogWarning("[ALSAInput::GetSourceList] " + Logger::tr("Warning: Could not get info for sound card %1.").arg(card)); continue; } std::string card_description = snd_ctl_card_info_get_name(alsa_card_info); Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Found card: [%1] %2").arg(QString::fromStdString(card_name)).arg(QString::fromStdString(card_description))); // find all devices for this card int device = -1; bool should_add_shared = true; while(snd_ctl_pcm_next_device(alsa_ctl, &device) == 0 && device >= 0) { // get device info snd_pcm_info_set_device(alsa_pcm_info, device); snd_pcm_info_set_subdevice(alsa_pcm_info, 0); snd_pcm_info_set_stream(alsa_pcm_info, SND_PCM_STREAM_CAPTURE); if(snd_ctl_pcm_info(alsa_ctl, alsa_pcm_info) < 0) continue; // not a capture device // add a shared source if needed if(should_add_shared) { list.push_back(Source("sysdefault:" + NumToString(card), card_description + " (shared)")); should_add_shared = false; } // get device description std::string device_name = "hw:" + NumToString(card) + "," + NumToString(device); std::string device_description = card_description + ": " + snd_pcm_info_get_name(alsa_pcm_info); // add to list Logger::LogInfo("[ALSAInput::GetSourceList] " + Logger::tr("Found device: [%1] %2").arg(QString::fromStdString(device_name)).arg(QString::fromStdString(device_description))); list.push_back(Source(device_name, device_description)); } // close the card snd_ctl_close(alsa_ctl); alsa_ctl = NULL; } // free card and PCM info struction snd_pcm_info_free(alsa_pcm_info); alsa_pcm_info = NULL; snd_ctl_card_info_free(alsa_card_info); alsa_card_info = NULL; } catch(...) { if(alsa_ctl != NULL) { snd_ctl_close(alsa_ctl); alsa_ctl = NULL; } if(alsa_pcm_info != NULL) { snd_pcm_info_free(alsa_pcm_info); alsa_pcm_info = NULL; } if(alsa_card_info != NULL) { snd_ctl_card_info_free(alsa_card_info); alsa_card_info = NULL; } // don't re-throw exception } return list; } void ALSAInput::Init() { snd_pcm_hw_params_t *alsa_hw_params = NULL; snd_pcm_format_mask_t *alsa_format_mask = NULL; try { // allocate parameter structure if(snd_pcm_hw_params_malloc(&alsa_hw_params) < 0) { throw std::bad_alloc(); } // allocate format mask structure if(snd_pcm_format_mask_malloc(&alsa_format_mask) < 0) { throw std::bad_alloc(); } // open PCM device if(snd_pcm_open(&m_alsa_pcm, m_source_name.toUtf8().constData(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't open PCM device!")); throw ALSAException(); } if(snd_pcm_hw_params_any(m_alsa_pcm, alsa_hw_params) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't get PCM hardware parameters!")); throw ALSAException(); } // set access type if(snd_pcm_hw_params_set_access(m_alsa_pcm, alsa_hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set access type!")); throw ALSAException(); } // set sample format snd_pcm_format_mask_none(alsa_format_mask); snd_pcm_format_mask_set(alsa_format_mask, SND_PCM_FORMAT_S16_LE); snd_pcm_format_mask_set(alsa_format_mask, SND_PCM_FORMAT_S24_LE); snd_pcm_format_mask_set(alsa_format_mask, SND_PCM_FORMAT_S32_LE); snd_pcm_format_mask_set(alsa_format_mask, SND_PCM_FORMAT_FLOAT_LE); if(snd_pcm_hw_params_set_format_mask(m_alsa_pcm, alsa_hw_params, alsa_format_mask) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set sample format mask!")); throw ALSAException(); } snd_pcm_format_t sample_format; if(snd_pcm_hw_params_set_format_first(m_alsa_pcm, alsa_hw_params, &sample_format) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set sample format!")); throw ALSAException(); } const char *format_str = NULL; switch(sample_format) { case SND_PCM_FORMAT_S16_LE: { m_sample_format = AV_SAMPLE_FMT_S16; format_str = "s16"; break; } case SND_PCM_FORMAT_S24_LE: { m_sample_format = AV_SAMPLE_FMT_S32; m_convert_24_to_32 = true; format_str = "s24"; break; } case SND_PCM_FORMAT_S32_LE: { m_sample_format = AV_SAMPLE_FMT_S32; format_str = "s32"; break; } case SND_PCM_FORMAT_FLOAT_LE: { m_sample_format = AV_SAMPLE_FMT_FLT; format_str = "f32"; break; } default: assert(false); } Logger::LogInfo("[ALSAInput::InputThread] " + Logger::tr("Using sample format %1.").arg(format_str)); // set sample rate unsigned int rate = m_sample_rate; if(snd_pcm_hw_params_set_rate_near(m_alsa_pcm, alsa_hw_params, &rate, NULL) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set sample rate!")); throw ALSAException(); } if(rate != m_sample_rate) { Logger::LogWarning("[ALSAInput::Init] " + Logger::tr("Warning: Sample rate %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_sample_rate).arg(rate)); m_sample_rate = rate; } // set channel count unsigned int channels = m_channels; if(snd_pcm_hw_params_set_channels_near(m_alsa_pcm, alsa_hw_params, &channels) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set channel count!")); throw ALSAException(); } if(channels != m_channels) { Logger::LogWarning("[ALSAInput::Init] " + Logger::tr("Warning: Channel count %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_channels).arg(channels)); m_channels = channels; } // set period size snd_pcm_uframes_t period_size = m_period_size; if(snd_pcm_hw_params_set_period_size_near(m_alsa_pcm, alsa_hw_params, &period_size, NULL) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set period size!")); throw ALSAException(); } if(period_size != m_period_size) { Logger::LogWarning("[ALSAInput::Init] " + Logger::tr("Warning: Period size %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_period_size).arg(period_size)); m_period_size = period_size; } // set buffer size snd_pcm_uframes_t buffer_size = m_buffer_size; if(snd_pcm_hw_params_set_buffer_size_near(m_alsa_pcm, alsa_hw_params, &buffer_size) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't set buffer size!")); throw ALSAException(); } if(buffer_size != m_buffer_size) { Logger::LogWarning("[ALSAInput::Init] " + Logger::tr("Warning: Buffer size %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_buffer_size).arg(buffer_size)); m_buffer_size = buffer_size; } // apply parameters if(snd_pcm_hw_params(m_alsa_pcm, alsa_hw_params) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't apply PCM hardware parameters!")); throw ALSAException(); } // free format mask structure snd_pcm_format_mask_free(alsa_format_mask); alsa_format_mask = NULL; // free parameter structure snd_pcm_hw_params_free(alsa_hw_params); alsa_hw_params = NULL; } catch(...) { if(alsa_format_mask != NULL) { snd_pcm_format_mask_free(alsa_format_mask); alsa_format_mask = NULL; } if(alsa_hw_params != NULL) { snd_pcm_hw_params_free(alsa_hw_params); alsa_hw_params = NULL; } throw; } // start PCM device if(snd_pcm_start(m_alsa_pcm) < 0) { Logger::LogError("[ALSAInput::Init] " + Logger::tr("Error: Can't start PCM device!")); throw ALSAException(); } // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&ALSAInput::InputThread, this); } void ALSAInput::Free() { if(m_alsa_pcm != NULL) { snd_pcm_close(m_alsa_pcm); m_alsa_pcm = NULL; } } void ALSAInput::InputThread() { try { Logger::LogInfo("[ALSAInput::InputThread] " + Logger::tr("Input thread started.")); // allocate buffer TempBuffer buffer; switch(m_sample_format) { case AV_SAMPLE_FMT_S16: buffer.Alloc(m_period_size * m_channels * sizeof(int16_t)); break; case AV_SAMPLE_FMT_S32: buffer.Alloc(m_period_size * m_channels * sizeof(int32_t)); break; case AV_SAMPLE_FMT_FLT: buffer.Alloc(m_period_size * m_channels * sizeof(float)); break; default: assert(false); } bool has_first_samples = false; int64_t first_timestamp = 0; // value won't be used, but GCC gives a warning otherwise while(!m_should_stop) { // wait for new samples int wait = snd_pcm_wait(m_alsa_pcm, 100); if(wait < 0) { if(wait == -EPIPE) { ALSARecoverAfterOverrun(m_alsa_pcm); PushAudioHole(); continue; } else { Logger::LogError("[ALSAInput::InputThread] " + Logger::tr("Error: Can't wait for new samples!")); throw ALSAException(); } } else if(wait == 0) { continue; } int64_t timestamp = hrt_time_micro(); // read the samples snd_pcm_sframes_t samples_read = snd_pcm_readi(m_alsa_pcm, buffer.GetData(), m_period_size); if(samples_read < 0) { if(samples_read == -EPIPE) { ALSARecoverAfterOverrun(m_alsa_pcm); PushAudioHole(); continue; } else { Logger::LogError("[ALSAInput::InputThread] " + Logger::tr("Error: Can't read samples!")); throw ALSAException(); } } else if(samples_read == 0) { continue; } // skip the first samples if(has_first_samples) { if(timestamp > first_timestamp + START_DELAY) { // convert if needed if(m_convert_24_to_32) { int32_t *samples = (int32_t*) buffer.GetData(); for(unsigned int i = 0; i < (unsigned int) samples_read * m_channels; ++i) { samples[i] <<= 8; } } // push the samples int64_t time = timestamp - (int64_t) samples_read * (int64_t) 1000000 / (int64_t) m_sample_rate; PushAudioSamples(m_channels, m_sample_rate, m_sample_format, samples_read, buffer.GetData(), time); } } else { has_first_samples = true; first_timestamp = timestamp; } } Logger::LogInfo("[ALSAInput::InputThread] " + Logger::tr("Input thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[ALSAInput::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[ALSAInput::InputThread] " + Logger::tr("Unknown exception in input thread.")); } } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/ALSAInput.h000066400000000000000000000034231470053624300236410ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #if SSR_USE_ALSA #include "SourceSink.h" #include "MutexDataPair.h" #include class ALSAInput : public AudioSource { public: struct Source { std::string m_name, m_description; inline Source(const std::string& name, const std::string& description) : m_name(name), m_description(description) {} }; private: static const int64_t START_DELAY; private: QString m_source_name; AVSampleFormat m_sample_format; bool m_convert_24_to_32; unsigned int m_sample_rate, m_channels; unsigned int m_period_size, m_buffer_size; snd_pcm_t *m_alsa_pcm; std::thread m_thread; std::atomic m_should_stop, m_error_occurred; public: ALSAInput(const QString& source_name, unsigned int sample_rate); ~ALSAInput(); // Returns whether an error has occurred in the input thread. // This function is thread-safe. inline bool HasErrorOccurred() { return m_error_occurred; } public: static std::vector GetSourceList(); private: void Init(); void Free(); void InputThread(); }; #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/GLInjectInput.cpp000066400000000000000000000251161470053624300251160ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "GLInjectInput.h" #if SSR_USE_OPENGL_RECORDING #include "Logger.h" #include "AVWrapper.h" #include "SSRVideoStreamWatcher.h" #include "SSRVideoStreamReader.h" // Escapes characters so the string can be used in a shell command. It may not be 100% secure (character encoding can complicate things). // But it doesn't really matter, an attacker that can change GLInject options could just as easily change the actual GLInject command. // I copied the list of special characters from the PHP documentation: // http://be2.php.net/manual/en/function.escapeshellcmd.php static QString ShellEscape(QString str) { char specials[] = "\\#&;`|*?~<>^()[]{}$\x0a\xff\""; // backslash must be first for(unsigned int i = 0; i < sizeof(specials); ++i) { str.replace(QChar(specials[i]), QString("\\") + QChar(specials[i])); } return str; } // The highest expected latency between GLInject and the input thread. const int64_t GLInjectInput::MAX_COMMUNICATION_LATENCY = 100000; bool ExecuteDetached(const char* command, const char* working_directory) { // set up feedback pipe int feedback_pipe[2]; if(pipe2(feedback_pipe, O_CLOEXEC) == -1) { return false; } // fork process pid_t pid1 = fork(); if(pid1 == -1) { // fork failed close(feedback_pipe[0]); close(feedback_pipe[1]); return false; } else if(pid1 == 0) { // we are the first child close(feedback_pipe[0]); setsid(); // fork again pid_t pid2 = fork(); if(pid2 == -1) { // fork failed, send feedback char buf = 1; ssize_t wres = write(feedback_pipe[1], &buf, 1); Q_UNUSED(wres); close(feedback_pipe[1]); _exit(EXIT_FAILURE); } else if(pid2 == 0) { // we are the second child int devnull; do { devnull = open("/dev/null", O_RDWR); } while(devnull == -1 && errno == EINTR); if(devnull == -1) { // failed, send feedback char buf = 1; ssize_t wres = write(feedback_pipe[1], &buf, 1); Q_UNUSED(wres); close(feedback_pipe[1]); _exit(EXIT_FAILURE); } // redirect stdin, stdout and stderr to /dev/null int res; do { res = dup2(devnull, STDIN_FILENO); } while(res == -1 && errno == EINTR); do { res = dup2(devnull, STDOUT_FILENO); } while(res == -1 && errno == EINTR); do { res = dup2(devnull, STDERR_FILENO); } while(res == -1 && errno == EINTR); do { res = close(devnull); } while(res == -1 && errno == EINTR); // try to change working directory if(working_directory != NULL) { if(chdir(working_directory) == -1) { // failed, send feedback char buf = 1; ssize_t wres = write(feedback_pipe[1], &buf, 1); Q_UNUSED(wres); close(feedback_pipe[1]); _exit(EXIT_FAILURE); } } // try to execute command do { res = execl("/bin/sh", "/bin/sh", "-c", command, (char*) NULL); } while(res == -1 and errno == EINTR); // failed, send feedback char buf = 1; ssize_t wres = write(feedback_pipe[1], &buf, 1); Q_UNUSED(wres); close(feedback_pipe[1]); _exit(EXIT_FAILURE); } else { // we are still the first child close(feedback_pipe[1]); _exit(EXIT_SUCCESS); } } else { // we are still the original close(feedback_pipe[1]); char buf; ssize_t num; do { num = read(feedback_pipe[0], &buf, 1); } while(num == -1 && errno == EINTR); close(feedback_pipe[0]); // wait for first process int res, status; do { res = waitpid(pid1, &status, 0); } while(res == -1 && errno == EINTR); return (num == 0); } } GLInjectInput::GLInjectInput(const QString& channel, bool relax_permissions, bool record_cursor, bool limit_fps, unsigned int target_fps) { m_channel = channel; m_relax_permissions = relax_permissions; m_flags = ((record_cursor)? GLINJECT_FLAG_RECORD_CURSOR : 0) | ((limit_fps)? GLINJECT_FLAG_LIMIT_FPS : 0); m_target_fps = target_fps; try { Init(); } catch(...) { Free(); throw; } } GLInjectInput::~GLInjectInput() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[GLInjectInput::~GLInjectInput] " + Logger::tr("Stopping input thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } void GLInjectInput::GetCurrentSize(unsigned int* width, unsigned int* height) { SharedLock lock(&m_shared_data); if(lock->m_stream_reader == NULL) { *width = *height = 0; } else { lock->m_stream_reader->GetCurrentSize(width, height); } } double GLInjectInput::GetFPS() { SharedLock lock(&m_shared_data); if(lock->m_stream_reader == NULL) return 0.0; return lock->m_stream_reader->GetFPS(); } void GLInjectInput::SetCapturing(bool capturing) { SharedLock lock(&m_shared_data); lock->m_capturing = capturing; if(lock->m_stream_reader != NULL) lock->m_stream_reader->ChangeCaptureParameters(m_flags | ((lock->m_capturing)? GLINJECT_FLAG_CAPTURE_ENABLED : 0), m_target_fps); } bool GLInjectInput::LaunchApplication(const QString& channel, bool relax_permissions, const QString& command, const QString& working_directory) { // prepare command QString full_command = "LD_PRELOAD=\"libssr-glinject.so\" "; full_command += "SSR_CHANNEL=\"" + ShellEscape(channel) + "\" "; if(relax_permissions) full_command += "SSR_STREAM_RELAX_PERMISSIONS=1 "; full_command += command; // execute QByteArray encoded_working_directory = QFile::encodeName(working_directory); QByteArray encoded_full_command = full_command.toLocal8Bit(); return ExecuteDetached(encoded_full_command.constData(), (encoded_working_directory.isEmpty())? NULL : encoded_working_directory.constData()); } void GLInjectInput::Init() { // initialize shared data { SharedLock lock(&m_shared_data); lock->m_capturing = false; lock->m_stream_watcher.reset(new SSRVideoStreamWatcher(m_channel.toStdString(), m_relax_permissions)); } // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&GLInjectInput::InputThread, this); } void GLInjectInput::Free() { SharedLock lock(&m_shared_data); lock->m_stream_reader.reset(); lock->m_stream_watcher.reset(); } bool GLInjectInput::SwitchStream(SharedData* lock, const SSRVideoStream& stream) { try { // create the stream reader std::unique_ptr stream_reader(new SSRVideoStreamReader(m_channel.toStdString(), stream)); // initialize the stream stream_reader->ChangeCaptureParameters(m_flags | ((lock->m_capturing)? GLINJECT_FLAG_CAPTURE_ENABLED : 0), m_target_fps); stream_reader->Clear(); // if everything is okay, use the new stream reader lock->m_stream_reader = std::move(stream_reader); } catch(...) { Logger::LogError("[GLInjectInput::SwitchStream] " + Logger::tr("Error: Could not read stream, this usually means that the stream was already gone.")); return false; } return true; } void GLInjectInput::StreamAddCallback(const SSRVideoStream& stream, void* userdata) { GLInjectInput *input = (GLInjectInput*) userdata; SharedData *lock = input->m_shared_data.data(); // data is already locked, this is only a callback function input->SwitchStream(lock, stream); } void GLInjectInput::StreamRemoveCallback(const SSRVideoStream& stream, size_t pos, void* userdata) { GLInjectInput *input = (GLInjectInput*) userdata; SharedData *lock = input->m_shared_data.data(); // data is already locked, this is only a callback function if(lock->m_stream_reader != NULL) { if(lock->m_stream_reader->GetStream() == stream) { lock->m_stream_reader.reset(); auto &streams = lock->m_stream_watcher->GetStreams(); for(size_t i = pos; i > 0; ) { --i; if(input->SwitchStream(lock, streams[i])) break; } } } } void GLInjectInput::InputThread() { try { Logger::LogInfo("[GLInjectInput::InputThread] " + Logger::tr("Input thread started.")); // deal with pre-existing streams { SharedLock lock(&m_shared_data); auto &streams = lock->m_stream_watcher->GetStreams(); for(size_t i = streams.size(); i > 0; ) { --i; if(SwitchStream(lock.get(), streams[i])) break; } } int64_t next_watcher_update = hrt_time_micro(); while(!m_should_stop) { // try to get a frame int64_t timestamp; unsigned int width, height; int stride; void *data; { SharedLock lock(&m_shared_data); // update stream watcher if(hrt_time_micro() >= next_watcher_update) { lock->m_stream_watcher->HandleChanges(&StreamAddCallback, &StreamRemoveCallback, this); next_watcher_update = hrt_time_micro() + 200000; } // do we have a stream reader? if(lock->m_stream_reader == NULL) { PushVideoPing(hrt_time_micro() - MAX_COMMUNICATION_LATENCY); lock.lock().unlock(); // release lock before sleep usleep(20000); continue; } // is a frame ready? data = lock->m_stream_reader->GetFrame(×tamp, &width, &height, &stride); if(data == NULL) { PushVideoPing(hrt_time_micro() - MAX_COMMUNICATION_LATENCY); lock.lock().unlock(); // release lock before sleep usleep(20000); continue; } } // if the stride is negative, change the pointer // this is needed because OpenGL stores frames upside-down if(stride < 0) { data = (char*) data + (size_t) (-stride) * (size_t) (height - 1); } // push the frame // we can do this even when we don't have the lock because only this thread will change the stream reader const uint8_t *image_data[1] = {(uint8_t*) data}; int image_stride[1] = {stride}; PushVideoFrame(width, height, image_data, image_stride, AV_PIX_FMT_BGRA, SWS_CS_DEFAULT, timestamp); // go to the next frame { SharedLock lock(&m_shared_data); lock->m_stream_reader->NextFrame(); } } Logger::LogInfo("[GLInjectInput::InputThread] " + Logger::tr("Input thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[GLInjectInput::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[GLInjectInput::InputThread] " + Logger::tr("Unknown exception in input thread.")); } } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/GLInjectInput.h000066400000000000000000000052671470053624300245700ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #if SSR_USE_OPENGL_RECORDING #include "SourceSink.h" #include "MutexDataPair.h" class SSRVideoStream; class SSRVideoStreamWatcher; class SSRVideoStreamReader; class GLInjectInput : public VideoSource { private: struct SharedData { bool m_capturing; std::unique_ptr m_stream_watcher; std::unique_ptr m_stream_reader; }; typedef MutexDataPair::Lock SharedLock; private: static const int64_t MAX_COMMUNICATION_LATENCY; private: QString m_channel; bool m_relax_permissions; unsigned int m_flags; unsigned int m_target_fps; std::thread m_thread; MutexDataPair m_shared_data; std::atomic m_should_stop, m_error_occurred; public: GLInjectInput(const QString& channel, bool relax_permissions, bool record_cursor, bool limit_fps, unsigned int target_fps); ~GLInjectInput(); // Reads the current size of the stream. If the stream hasn't been started yet, this will be 0x0. // This function is thread-safe. void GetCurrentSize(unsigned int* width, unsigned int* height); // Returns the total number of captured frames. // This function is thread-safe. double GetFPS(); // Start/stop capturing. // This function is thread-safe. void SetCapturing(bool capturing); // Returns whether an error has occurred in the input thread. // This function is thread-safe. inline bool HasErrorOccurred() { return m_error_occurred; } // Launches an application with OpenGL injection. Returns whether successful. static bool LaunchApplication(const QString& channel, bool relax_permissions, const QString& command, const QString& working_directory); private: void Init(); void Free(); bool SwitchStream(SharedData* lock, const SSRVideoStream& stream); static void StreamAddCallback(const SSRVideoStream& stream, void* userdata); static void StreamRemoveCallback(const SSRVideoStream& stream, size_t pos, void* userdata); void InputThread(); }; #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/JACKInput.cpp000066400000000000000000000266361470053624300241770ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "JACKInput.h" #if SSR_USE_JACK #include "SampleCast.h" #include "Logger.h" #if SSR_USE_JACK_METADATA #include #endif // Size of the ring buffer (samples). const unsigned int JACKInput::RING_BUFFER_SIZE = 1024 * 32; JACKInput::JACKInput(bool connect_system_capture, bool connect_system_playback) { m_connect_system_capture = connect_system_capture; m_connect_system_playback = connect_system_playback; m_channels = 2; // always 2 channels because the synchronizer and encoder don't support anything else at this point m_jackthread_sample_rate = 0; // the sample rate is set by JACK m_jackthread_hole = false; m_jack_client = NULL; try { Init(); } catch(...) { Free(); throw; } } JACKInput::~JACKInput() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[JACKInput::~JACKInput] " + Logger::tr("Stopping input thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } void JACKInput::Init() { m_message_queue.Reset(RING_BUFFER_SIZE * m_channels * sizeof(float)); m_jack_client = jack_client_open("SimpleScreenRecorder", JackNoStartServer, NULL); if(m_jack_client == NULL) { Logger::LogError("[JACKInput::Init] " + Logger::tr("Error: Could not connect to JACK!")); throw JACKException(); } m_jack_ports.resize(m_channels, NULL); for(unsigned int i = 0; i < m_channels; ++i) { std::string port_name = "in_" + NumToString(i + 1); m_jack_ports[i] = jack_port_register(m_jack_client, port_name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); if(m_jack_ports[i] == NULL) { Logger::LogError("[JACKInput::Init] " + Logger::tr("Error: Could not create JACK port!")); throw JACKException(); } } #if SSR_USE_JACK_METADATA if(m_channels == 1) { jack_set_property(m_jack_client, jack_port_uuid(m_jack_ports[0]), JACK_METADATA_PRETTY_NAME, "Input Mono", "text/plain"); } if(m_channels == 2) { jack_set_property(m_jack_client, jack_port_uuid(m_jack_ports[0]), JACK_METADATA_PRETTY_NAME, "Input Left", "text/plain"); jack_set_property(m_jack_client, jack_port_uuid(m_jack_ports[1]), JACK_METADATA_PRETTY_NAME, "Input Right", "text/plain"); } #endif if(jack_set_process_callback(m_jack_client, ProcessCallback, this) != 0) { Logger::LogError("[JACKInput::Init] " + Logger::tr("Error: Could not set JACK process callback!")); throw JACKException(); } if(jack_set_sample_rate_callback(m_jack_client, SampleRateCallback, this) != 0) { Logger::LogError("[JACKInput::Init] " + Logger::tr("Error: Could not set JACK sample rate callback!")); throw JACKException(); } if(jack_set_xrun_callback(m_jack_client, XRunCallback, this) != 0) { Logger::LogError("[JACKInput::Init] " + Logger::tr("Error: Could not set JACK xrun callback!")); throw JACKException(); } if(jack_set_port_connect_callback(m_jack_client, PortConnectCallback, this) != 0) { Logger::LogError("[JACKInput::Init] " + Logger::tr("Error: Could not set JACK port connect callback!")); throw JACKException(); } if(jack_activate(m_jack_client) != 0) { Logger::LogError("[JACKInput::Init] " + Logger::tr("Error: Could not activate JACK client!")); throw JACKException(); } for(unsigned int i = 0; i < m_channels; ++i) { std::string port_name_full = std::string(jack_get_client_name(m_jack_client)) + ":in_" + NumToString(i + 1); if(m_connect_system_capture) { std::string capture_name = "system:capture_" + NumToString(i + 1); Logger::LogInfo("[JACKInput::Init] " + Logger::tr("Connecting port %1 to %2.") .arg(QString::fromStdString(capture_name)).arg(QString::fromStdString(port_name_full))); jack_connect(m_jack_client, capture_name.c_str(), port_name_full.c_str()); } if(m_connect_system_playback) { std::string playback_name = "system:playback_" + NumToString(i + 1); jack_port_t *port = jack_port_by_name(m_jack_client, playback_name.c_str()); if(port != NULL) { const char **connected_ports = jack_port_get_all_connections(m_jack_client, port); if(connected_ports != NULL) { for(const char **p = connected_ports; *p != NULL; ++p) { Logger::LogInfo("[JACKInput::Init] " + Logger::tr("Connecting port %1 to %2.") .arg(*p).arg(QString::fromStdString(port_name_full))); jack_connect(m_jack_client, *p, port_name_full.c_str()); } jack_free(connected_ports); } } } } // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&JACKInput::InputThread, this); } void JACKInput::Free() { if(m_jack_client != NULL) { jack_deactivate(m_jack_client); } for(unsigned int i = 0; i < m_jack_ports.size(); ++i) { if(m_jack_ports[i] != NULL) { jack_port_unregister(m_jack_client, m_jack_ports[i]); m_jack_ports[i] = NULL; } } if(m_jack_client != NULL) { jack_client_close(m_jack_client); m_jack_client = NULL; } } int JACKInput::ProcessCallback(jack_nframes_t nframes, void* arg) { JACKInput *input = (JACKInput*) arg; // deal with holes if(input->m_jackthread_hole) { char *message = input->m_message_queue.PrepareWriteMessage(sizeof(enum_eventtype)); if(message == NULL) return 0; *((enum_eventtype*) message) = EVENTTYPE_HOLE; input->m_message_queue.WriteMessage(); input->m_jackthread_hole = false; } // This function is called from a real-time thread, so it's not a good idea to do actual work here. // The data is moved to a queue, and a second thread will do the work as usual. //TODO// if nframes is small, then combine multiple blocks into one? char *message = input->m_message_queue.PrepareWriteMessage(sizeof(enum_eventtype) + sizeof(EventData) + nframes * input->m_channels * sizeof(float)); if(message == NULL) { input->m_jackthread_hole = true; return 0; } *((enum_eventtype*) message) = EVENTTYPE_DATA; message += sizeof(enum_eventtype); ((EventData*) message)->m_timestamp = hrt_time_micro() - (int64_t) nframes * (int64_t) 1000000 / (int64_t) input->m_jackthread_sample_rate; ((EventData*) message)->m_sample_rate = input->m_jackthread_sample_rate; ((EventData*) message)->m_sample_count = nframes; message += sizeof(EventData); for(unsigned int p = 0; p < input->m_channels; ++p) { SampleCopy(nframes, (float*) jack_port_get_buffer(input->m_jack_ports[p], nframes), 1, (float*) message + p, input->m_channels); } input->m_message_queue.WriteMessage(); return 0; } int JACKInput::SampleRateCallback(jack_nframes_t nframes, void* arg) { // This callback is called from the notification thread (not the realtime processing thread), so sadly the timing can never be fully accurate. JACKInput *input = (JACKInput*) arg; input->m_jackthread_sample_rate = nframes; input->m_jackthread_hole = true; return 0; } int JACKInput::XRunCallback(void* arg) { // This callback is called from the notification thread (not the realtime processing thread), so sadly the timing can never be fully accurate. JACKInput *input = (JACKInput*) arg; input->m_jackthread_hole = true; return 0; } void JACKInput::PortConnectCallback(jack_port_id_t a, jack_port_id_t b, int connect, void* arg) { // This callback is called from the notification thread (not the realtime processing thread), so sadly the timing can never be fully accurate. // To make things worse, we're not allowed to connect/disconnect ports from this thread, so we have to send a command to the input thread instead. JACKInput *input = (JACKInput*) arg; if(input->m_connect_system_playback) { jack_port_t *port_a = jack_port_by_id(input->m_jack_client, a); if(port_a == NULL) return; jack_port_t *port_b = jack_port_by_id(input->m_jack_client, b); if(port_b == NULL) return; const char *port_a_name = jack_port_name(port_a); const char *port_b_name = jack_port_name(port_b); for(unsigned int i = 0; i < input->m_channels; ++i) { std::string playback_name = "system:playback_" + NumToString(i + 1); if(port_b_name == playback_name) { std::string port_name_full = std::string(jack_get_client_name(input->m_jack_client)) + ":in_" + NumToString(i + 1); SharedLock lock(&input->m_shared_data); ConnectCommand cmd; cmd.m_connect = connect; cmd.m_source = port_a_name; cmd.m_destination = port_name_full; lock->m_connect_commands.push_back(cmd); } } } } void JACKInput::InputThread() { try { Logger::LogInfo("[JACKInput::InputThread] " + Logger::tr("Input thread started.")); while(!m_should_stop) { // process connect commands // JACK will send notifications when we connect/disconnect ports, so holding the lock while doing this is a bad idea. // It seems that JACK is designed in such a way that a single misbehaving application can lock up the entire server, so let's avoid that. std::vector connect_commands; { SharedLock lock(&m_shared_data); connect_commands.swap(lock->m_connect_commands); } for(ConnectCommand &cmd : connect_commands) { if(cmd.m_connect) { Logger::LogInfo("[JACKInput::InputThread] " + Logger::tr("Connecting port %1 to %2.") .arg(QString::fromStdString(cmd.m_source)).arg(QString::fromStdString(cmd.m_destination))); jack_connect(m_jack_client, cmd.m_source.c_str(), cmd.m_destination.c_str()); } else { Logger::LogInfo("[JACKInput::InputThread] " + Logger::tr("Disconnecting port %1 from %2.") .arg(QString::fromStdString(cmd.m_source)).arg(QString::fromStdString(cmd.m_destination))); jack_disconnect(m_jack_client, cmd.m_source.c_str(), cmd.m_destination.c_str()); } } // is there a new message? unsigned int message_size; char *message = m_message_queue.PrepareReadMessage(&message_size); if(message == NULL) { usleep(20000); continue; } // read the message assert(message_size >= sizeof(enum_eventtype)); enum_eventtype type = *((enum_eventtype*) message); message += sizeof(enum_eventtype); if(type == EVENTTYPE_HOLE) { PushAudioHole(); } if(type == EVENTTYPE_DATA) { assert(message_size >= sizeof(enum_eventtype) + sizeof(EventData)); assert(message_size >= sizeof(enum_eventtype) + sizeof(EventData) + ((EventData*) message)->m_sample_count * m_channels * sizeof(float)); PushAudioSamples(m_channels, ((EventData*) message)->m_sample_rate, AV_SAMPLE_FMT_FLT, ((EventData*) message)->m_sample_count, (uint8_t*) (message + sizeof(EventData)), ((EventData*) message)->m_timestamp); } // go to next message m_message_queue.ReadMessage(); } Logger::LogInfo("[JACKInput::InputThread] " + Logger::tr("Input thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[JACKInput::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[JACKInput::InputThread] " + Logger::tr("Unknown exception in input thread.")); } } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/JACKInput.h000066400000000000000000000045071470053624300236350ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #if SSR_USE_JACK #include "SourceSink.h" #include "LockFreeMessageQueue.h" #include class JACKInput : public AudioSource { public: static const unsigned int RING_BUFFER_SIZE; private: enum enum_eventtype : int { EVENTTYPE_HOLE, EVENTTYPE_DATA, }; struct EventData { int64_t m_timestamp; unsigned int m_sample_rate, m_sample_count; }; struct ConnectCommand { bool m_connect; std::string m_source, m_destination; }; struct SharedData { std::vector m_connect_commands; }; typedef MutexDataPair::Lock SharedLock; private: bool m_connect_system_capture, m_connect_system_playback; unsigned int m_channels; std::atomic m_jackthread_sample_rate; std::atomic m_jackthread_hole; LockFreeMessageQueue m_message_queue; jack_client_t *m_jack_client; std::vector m_jack_ports; std::thread m_thread; MutexDataPair m_shared_data; std::atomic m_should_stop, m_error_occurred; public: JACKInput(bool connect_system_capture, bool connect_system_playback); ~JACKInput(); // Returns whether an error has occurred in the input thread. // This function is thread-safe. inline bool HasErrorOccurred() { return m_error_occurred; } private: void Init(); void Free(); static int ProcessCallback(jack_nframes_t nframes, void* arg); static int SampleRateCallback(jack_nframes_t nframes, void* arg); static int XRunCallback(void* arg); static void PortConnectCallback(jack_port_id_t a, jack_port_id_t b, int connect, void* arg); void InputThread(); }; #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/PipeWireInput.cpp000066400000000000000000000237631470053624300252110ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "PipeWireInput.h" #if SSR_USE_V4L2 #include "Logger.h" #include "AVWrapper.h" #include "Synchronizer.h" #include "VideoEncoder.h" #include #include PipeWireInput::PipeWireInput(const QString& node_id, unsigned int width, unsigned int height, unsigned int frame_rate) { m_node_id = node_id; m_width = width; m_height = height; m_frame_rate = frame_rate; m_pixel_format = AV_PIX_FMT_NONE; m_colorspace = SWS_CS_DEFAULT; m_buffers = 4; m_loop = nullptr; m_stream = nullptr; if(m_width == 0 || m_height == 0) { Logger::LogError("[PipeWireInput::Init] " + Logger::tr("Error: Width or height is zero!")); throw PipeWireException(); } if(m_width > SSR_MAX_IMAGE_SIZE || m_height > SSR_MAX_IMAGE_SIZE) { Logger::LogError("[PipeWireInput::Init] " + Logger::tr("Error: Width or height is too large, the maximum width and height is %1!").arg(SSR_MAX_IMAGE_SIZE)); throw PipeWireException(); } if(m_width % 2 != 0 || m_height % 2 != 0) { Logger::LogError("[PipeWireInput::Init] " + Logger::tr("Error: Width or height is not an even number!")); throw PipeWireException(); } try { Init(); } catch(...) { Free(); throw; } } PipeWireInput::~PipeWireInput() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[PipeWireInput::~PipeWireInput] " + Logger::tr("Stopping input thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } void PipeWireInput::GetCurrentSize(unsigned int *width, unsigned int *height) { *width = m_width; *height = m_height; } double PipeWireInput::GetFPS() { int64_t timestamp = hrt_time_micro(); uint32_t frame_counter = m_frame_counter; unsigned int time = timestamp - m_fps_last_timestamp; if(time > 500000) { unsigned int frames = frame_counter - m_fps_last_counter; m_fps_last_timestamp = timestamp; m_fps_last_counter = frame_counter; m_fps_current = (double) frames / ((double) time * 1.0e-6); } return m_fps_current; } void PipeWireInput::Init() { pw_init(nullptr, nullptr); m_loop = pw_main_loop_new(nullptr); if(!m_loop) { Logger::LogError("[PipeWireInput::Init] " + Logger::tr("Error: Failed to create main loop!")); throw PipeWireException(); } memset(&m_stream_events, 0, sizeof(m_stream_events)); m_stream_events.version = PW_VERSION_STREAM_EVENTS; m_stream_events.process = &PipeWireInput::OnProcess; m_stream_events.param_changed = &PipeWireInput::OnParamChange; m_stream = pw_stream_new_simple( pw_main_loop_get_loop(m_loop), "SimpleScreenRecorder", pw_properties_new( PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", nullptr), // TODO memory leak? &m_stream_events, this); if(!m_stream) { Logger::LogError("[PipeWireInput::Init] " + Logger::tr("Error: Failed to create stream!")); throw PipeWireException(); } // pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); // TODO uint8_t buffer[1024]; spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct spa_rectangle temp_size = SPA_RECTANGLE(m_width, m_height); struct spa_rectangle temp_size_min = SPA_RECTANGLE(2, 2); struct spa_rectangle temp_size_max = SPA_RECTANGLE(SSR_MAX_IMAGE_SIZE, SSR_MAX_IMAGE_SIZE); struct spa_fraction temp_framerate = SPA_FRACTION(m_frame_rate, 1); struct spa_fraction temp_framerate_min = SPA_FRACTION(0, 1); struct spa_fraction temp_framerate_max = SPA_FRACTION(1000, 1); const struct spa_pod *params[1]; params[0] = (const struct spa_pod*) spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(12, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGR, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_Y444, SPA_VIDEO_FORMAT_Y42B, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_NV12), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&temp_size, &temp_size_min, &temp_size_max), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(&temp_framerate, &temp_framerate_min, &temp_framerate_max)); int res = pw_stream_connect(m_stream, PW_DIRECTION_INPUT, pw_properties_parse_int(m_node_id.toUtf8().constData()), (pw_stream_flags) (PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params, 1); if(res != 0) { Logger::LogError("[PipeWireInput::Init] " + Logger::tr("Error: Failed to connect stream!")); throw PipeWireException(); } // initialize frame counter m_frame_counter = 0; m_fps_last_timestamp = hrt_time_micro(); m_fps_last_counter = 0; m_fps_current = 0.0; // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&PipeWireInput::InputThread, this); } void PipeWireInput::Free() { if(m_stream) { pw_stream_destroy(m_stream); m_stream = nullptr; } if(m_loop) { pw_main_loop_destroy(m_loop); m_loop = nullptr; } pw_deinit(); } void PipeWireInput::InputThread() { try { Logger::LogInfo("[PipeWireInput::InputThread] " + Logger::tr("Input thread started.")); struct pw_loop *loop = pw_main_loop_get_loop(m_loop); while(!m_should_stop) { int result = pw_loop_iterate(loop, 100); if(result < 0) { Logger::LogError("[PipewireInput::InputThread] " + Logger::tr("Error in main loop: %1").arg(spa_strerror(result))); break; } else if(result == 0) { PushVideoPing(hrt_time_micro() - 100000); } } Logger::LogInfo("[PipeWireInput::InputThread] " + Logger::tr("Input thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[PipeWireInput::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[PipeWireInput::InputThread] " + Logger::tr("Unknown exception in input thread.")); } } void PipeWireInput::OnProcess(void *userdata) { PipeWireInput *input = static_cast(userdata); pw_buffer *b; struct spa_buffer *buf; if((b = pw_stream_dequeue_buffer(input->m_stream)) == nullptr) { Logger::LogWarning("[PipeWireInput::OnProcess] " + Logger::tr("Warning: Failed to dequeue buffer!")); return; } buf = b->buffer; if(buf->datas[0].data == nullptr) return; int64_t timestamp = hrt_time_micro(); ++input->m_frame_counter; if(input->m_pixel_format == AV_PIX_FMT_NONE) { Logger::LogError("[PipeWireInput::OnProcess] " + Logger::tr("Error: Unknown pixel format!")); } else { std::vector image_data(buf->n_datas); std::vector image_stride(buf->n_datas); for(size_t i = 0; i < buf->n_datas; ++i) { image_data[i] = (uint8_t*) buf->datas[i].data + buf->datas[i].chunk->offset % buf->datas[i].maxsize; image_stride[i] = buf->datas[i].chunk->stride; } input->PushVideoFrame( input->m_width, input->m_height, image_data.data(), image_stride.data(), input->m_pixel_format, input->m_colorspace, timestamp); } pw_stream_queue_buffer(input->m_stream, b); } void PipeWireInput::OnParamChange(void *userdata, uint32_t id, const struct spa_pod *param) { PipeWireInput *input = static_cast(userdata); struct spa_video_info info; if(id != SPA_PARAM_Format || param == nullptr) { // Logger::LogError("[PipeWireInput::OnParamChange] " + Logger::tr("Error: Not a format change!")); return; } if(spa_format_parse(param, &info.media_type, &info.media_subtype) < 0) { Logger::LogError("[PipeWireInput::OnParamChange] " + Logger::tr("Error: Failed to parse format!")); return; } if(info.media_type != SPA_MEDIA_TYPE_video || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { Logger::LogError("[PipeWireInput::OnParamChange] " + Logger::tr("Error: Invalid media type!")); return; } if(spa_format_video_raw_parse(param, &info.info.raw) < 0) { Logger::LogError("[PipeWireInput::OnParamChange] " + Logger::tr("Error: Failed to parse video format!")); return; } input->m_width = info.info.raw.size.width; input->m_height = info.info.raw.size.height; switch(info.info.raw.format) { case SPA_VIDEO_FORMAT_BGRx: input->m_pixel_format = AV_PIX_FMT_BGRA; break; case SPA_VIDEO_FORMAT_RGBx: input->m_pixel_format = AV_PIX_FMT_RGBA; break; case SPA_VIDEO_FORMAT_BGRA: input->m_pixel_format = AV_PIX_FMT_BGRA; break; case SPA_VIDEO_FORMAT_RGBA: input->m_pixel_format = AV_PIX_FMT_RGBA; break; case SPA_VIDEO_FORMAT_BGR: input->m_pixel_format = AV_PIX_FMT_BGR24; break; case SPA_VIDEO_FORMAT_RGB: input->m_pixel_format = AV_PIX_FMT_RGB24; break; case SPA_VIDEO_FORMAT_Y444: input->m_pixel_format = AV_PIX_FMT_YUV444P; break; case SPA_VIDEO_FORMAT_Y42B: input->m_pixel_format = AV_PIX_FMT_YUV422P; break; case SPA_VIDEO_FORMAT_I420: input->m_pixel_format = AV_PIX_FMT_YUV420P; break; case SPA_VIDEO_FORMAT_YUY2: input->m_pixel_format = AV_PIX_FMT_YUYV422; break; case SPA_VIDEO_FORMAT_NV12: input->m_pixel_format = AV_PIX_FMT_NV12; break; default: Logger::LogError("[PipeWireInput::OnParamChange] " + Logger::tr("Error: Unknown pixel format!")); } Logger::LogInfo("[PipeWireInput::OnParamChange] " + Logger::tr("Video format: %1x%2 %3") .arg(info.info.raw.size.width) .arg(info.info.raw.size.height) .arg(spa_debug_type_find_name(spa_type_video_format, info.info.raw.format))); } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/PipeWireInput.h000066400000000000000000000046261470053624300246530ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "SourceSink.h" #include "MutexDataPair.h" #if SSR_USE_PIPEWIRE #include #include #include #include class PipeWireInput : public VideoSource { private: struct PipeWireBuffer { void *m_data; size_t m_size; }; private: QString m_node_id; unsigned int m_width, m_height, m_frame_rate; AVPixelFormat m_pixel_format; int m_colorspace; unsigned int m_buffers; std::atomic m_frame_counter; int64_t m_fps_last_timestamp; uint32_t m_fps_last_counter; double m_fps_current; pw_main_loop *m_loop; pw_stream_events m_stream_events; pw_stream *m_stream; spa_hook m_stream_listener; std::vector m_pw_buffers; std::thread m_thread; std::atomic m_should_stop, m_error_occurred; public: PipeWireInput(const QString& node_id, unsigned int width, unsigned int height, unsigned int frame_rate); ~PipeWireInput(); // Reads the current size of the stream. // This function is thread-safe. void GetCurrentSize(unsigned int* width, unsigned int* height); // Returns the total number of captured frames. // This function is thread-safe. double GetFPS(); // Returns whether an error has occurred in the input thread. // This function is thread-safe. inline bool HasErrorOccurred() { return m_error_occurred; } private: void Init(); void Free(); private: void AllocateBuffers(unsigned int width, unsigned int height); void FreeBuffers(); private: void InputThread(); static void OnProcess(void *userdata); static void OnParamChange(void *userdata, uint32_t id, const struct spa_pod *param); }; #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/PulseAudioInput.cpp000066400000000000000000000342701470053624300255320ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "PulseAudioInput.h" #if SSR_USE_PULSEAUDIO #include "Logger.h" // Artificial delay after the first samples have been received (in microseconds). Any samples received during this time will be dropped. // This is needed because the first samples sometimes have weird timestamps, especially when PulseAudio is active // (I've seen one situation where PulseAudio instantly 'captures' 2 seconds of silence when the recording is started). // It also eliminates the clicking sound when the microphone is started for the first time. const int64_t PulseAudioInput::START_DELAY = 100000; static void PulseAudioIterate(pa_mainloop* mainloop) { if(pa_mainloop_prepare(mainloop, 100000) < 0) { Logger::LogError("[PulseAudioIterate] " + Logger::tr("Error: pa_mainloop_prepare failed!", "Don't translate 'pa_mainloop_prepare'")); throw PulseAudioException(); } if(pa_mainloop_poll(mainloop) < 0) { Logger::LogError("[PulseAudioIterate] " + Logger::tr("Error: pa_mainloop_poll failed!", "Don't translate 'pa_mainloop_poll'")); throw PulseAudioException(); } if(pa_mainloop_dispatch(mainloop) < 0) { Logger::LogError("[PulseAudioIterate] " + Logger::tr("Error: pa_mainloop_dispatch failed!", "Don't translate 'pa_mainloop_dispatch'")); throw PulseAudioException(); } } static void PulseAudioConnect(pa_mainloop** mainloop, pa_context** context) { // create PulseAudio main loop *mainloop = pa_mainloop_new(); if(*mainloop == NULL) { Logger::LogError("[PulseAudioConnect] " + Logger::tr("Error: Could not create main loop!")); throw PulseAudioException(); } // connect to PulseAudio *context = pa_context_new(pa_mainloop_get_api(*mainloop), "SimpleScreenRecorder"); if(*context == NULL) { Logger::LogError("[PulseAudioConnect] " + Logger::tr("Error: Could not create context!")); throw PulseAudioException(); } if(pa_context_connect(*context, NULL, PA_CONTEXT_NOAUTOSPAWN , NULL) < 0) { #if SSR_USE_ALSA Logger::LogError("[PulseAudioConnect] " + Logger::tr("Error: Could not connect! Reason: %1\n" "It is possible that your system doesn't use PulseAudio. Try using the ALSA backend instead.") .arg(pa_strerror(pa_context_errno(*context)))); #else Logger::LogError("[PulseAudioConnect] " + Logger::tr("Error: Could not connect! Reason: %1\n" "It is possible that your system doesn't use PulseAudio.") .arg(pa_strerror(pa_context_errno(*context)))); #endif throw PulseAudioException(); } // wait until the connection is ready for( ; ; ) { PulseAudioIterate(*mainloop); pa_context_state_t state = pa_context_get_state(*context); if(state == PA_CONTEXT_READY) break; if(!PA_CONTEXT_IS_GOOD(state)) { Logger::LogError("[PulseAudioConnect] " + Logger::tr("Error: Connection attempt failed! Reason: %1").arg(pa_strerror(pa_context_errno(*context)))); throw PulseAudioException(); } } } static void PulseAudioDisconnect(pa_mainloop** mainloop, pa_context** context) { if(*context != NULL) { pa_context_disconnect(*context); pa_context_unref(*context); *context = NULL; } if(*mainloop != NULL) { pa_mainloop_free(*mainloop); *mainloop = NULL; } } static void PulseAudioConnectStream(pa_mainloop* mainloop, pa_context* context, pa_stream** stream, const QString& source_name, unsigned int sample_rate, unsigned int channels, unsigned int period_size) { pa_sample_spec sample_spec; sample_spec.format = PA_SAMPLE_S16LE; sample_spec.rate = sample_rate; sample_spec.channels = channels; pa_buffer_attr buffer_attr; buffer_attr.fragsize = period_size * channels * sizeof(int16_t); buffer_attr.maxlength = (uint32_t) -1; buffer_attr.minreq = (uint32_t) -1; buffer_attr.prebuf = (uint32_t) -1; buffer_attr.tlength = (uint32_t) -1; // create a stream *stream = pa_stream_new(context, "SimpleScreenRecorder input", &sample_spec, NULL); if(*stream == NULL) { Logger::LogError("[PulseAudioConnectStream] " + Logger::tr("Error: Could not create stream! Reason: %1").arg(pa_strerror(pa_context_errno(context)))); throw PulseAudioException(); } // connect the stream if(pa_stream_connect_record(*stream, source_name.toUtf8().constData(), &buffer_attr, (pa_stream_flags_t) (/*PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE |*/ PA_STREAM_ADJUST_LATENCY)) < 0) { Logger::LogError("[PulseAudioConnectStream] " + Logger::tr("Error: Could not connect stream! Reason: %1").arg(pa_strerror(pa_context_errno(context)))); throw PulseAudioException(); } // wait until the stream is ready for( ; ; ) { PulseAudioIterate(mainloop); pa_stream_state_t state = pa_stream_get_state(*stream); if(state == PA_STREAM_READY) break; if(!PA_STREAM_IS_GOOD(state)) { Logger::LogError("[PulseAudioConnectStream] " + Logger::tr("Error: Stream connection attempt failed! Reason: %1").arg(pa_strerror(pa_context_errno(context)))); throw PulseAudioException(); } } } static void PulseAudioDisconnectStream(pa_stream** stream) { if(*stream != NULL) { pa_stream_disconnect(*stream); pa_stream_unref(*stream); *stream = NULL; } } static void PulseAudioCompleteOperation(pa_mainloop* mainloop, pa_operation** operation) { if(*operation == NULL) return; // wait until the operation is done for( ; ; ) { PulseAudioIterate(mainloop); pa_operation_state_t state = pa_operation_get_state(*operation); if(state == PA_OPERATION_DONE || state == PA_OPERATION_CANCELLED) break; } // delete it pa_operation_unref(*operation); operation = NULL; } static void PulseAudioCancelOperation(pa_mainloop* mainloop, pa_operation** operation) { if(*operation == NULL) return; // cancel it pa_operation_cancel(*operation); // wait until the operation is done for( ; ; ) { PulseAudioIterate(mainloop); pa_operation_state_t state = pa_operation_get_state(*operation); if(state == PA_OPERATION_DONE || state == PA_OPERATION_CANCELLED) break; } // delete it pa_operation_unref(*operation); operation = NULL; } PulseAudioInput::PulseAudioInput(const QString& source_name, unsigned int sample_rate) { m_source_name = source_name; m_sample_rate = sample_rate; m_channels = 2; m_pa_mainloop = NULL; m_pa_context = NULL; m_pa_stream = NULL; m_pa_period_size = 1024; // number of samples per period try { Init(); } catch(...) { Free(); throw; } } PulseAudioInput::~PulseAudioInput() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[PulseAudioInput::~PulseAudioInput] Stopping input thread ..."); m_should_stop = true; m_thread.join(); } // free everything Free(); } static void SourceNamesCallback(pa_context* context, const pa_source_info* info, int eol, void* userdata) { Q_UNUSED(context); if(!eol) { Logger::LogInfo("[SourceNamesCallback] " + Logger::tr("Found source: [%1] %2").arg(info->name).arg(info->description)); std::vector &list = *((std::vector*) userdata); list.push_back(PulseAudioInput::Source(info->name, info->description)); } } std::vector PulseAudioInput::GetSourceList() { std::vector list; Logger::LogInfo("[PulseAudioInput::GetSourceList] " + Logger::tr("Generating source list ...")); pa_mainloop *mainloop = NULL; pa_context *context = NULL; pa_operation *operation = NULL; try { PulseAudioConnect(&mainloop, &context); operation = pa_context_get_source_info_list(context, SourceNamesCallback, &list); if(operation == NULL) { Logger::LogError("[PulseAudioInput::GetSourceList] " + Logger::tr("Error: Could not get names of sources! Reason: %1").arg(pa_strerror(pa_context_errno(context)))); throw PulseAudioException(); } PulseAudioCompleteOperation(mainloop, &operation); PulseAudioDisconnect(&mainloop, &context); } catch(...) { PulseAudioCancelOperation(mainloop, &operation); PulseAudioDisconnect(&mainloop, &context); // don't re-throw exception } return list; } void PulseAudioInput::Init() { PulseAudioConnect(&m_pa_mainloop, &m_pa_context); PulseAudioConnectStream(m_pa_mainloop, m_pa_context, &m_pa_stream, m_source_name, m_sample_rate, m_channels, m_pa_period_size); m_stream_is_monitor = false; m_stream_suspended = false; m_stream_moved = false; pa_stream_set_suspended_callback(m_pa_stream, SuspendedCallback, this); pa_stream_set_moved_callback(m_pa_stream, MovedCallback, this); DetectMonitor(); // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&PulseAudioInput::InputThread, this); } void PulseAudioInput::Free() { PulseAudioDisconnectStream(&m_pa_stream); PulseAudioDisconnect(&m_pa_mainloop, &m_pa_context); } void PulseAudioInput::DetectMonitor() { pa_operation *operation = NULL; try { operation = pa_context_get_source_info_by_index(m_pa_context, pa_stream_get_device_index(m_pa_stream), SourceInfoCallback, this); if(operation == NULL) { Logger::LogError("[PulseAudioInput::Init] " + Logger::tr("Error: Could not get source info! Reason: %1").arg(pa_strerror(pa_context_errno(m_pa_context)))); throw PulseAudioException(); } PulseAudioCompleteOperation(m_pa_mainloop, &operation); if(m_stream_is_monitor) { Logger::LogInfo("[PulseAudioInput::InputThread] " + Logger::tr("Stream is a monitor.")); } else { Logger::LogInfo("[PulseAudioInput::InputThread] " + Logger::tr("Stream is not a monitor.")); } } catch(...) { PulseAudioCancelOperation(m_pa_mainloop, &operation); throw; } } void PulseAudioInput::SourceInfoCallback(pa_context* context, const pa_source_info* info, int eol, void* userdata) { Q_UNUSED(context); if(!eol) { PulseAudioInput *input = (PulseAudioInput*) userdata; input->m_stream_is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX); } } void PulseAudioInput::SuspendedCallback(pa_stream* stream, void* userdata) { PulseAudioInput *input = (PulseAudioInput*) userdata; if(pa_stream_is_suspended(stream)) input->m_stream_suspended = true; } void PulseAudioInput::MovedCallback(pa_stream* stream, void* userdata) { Q_UNUSED(stream); PulseAudioInput *input = (PulseAudioInput*) userdata; input->m_stream_moved = true; } void PulseAudioInput::InputThread() { try { Logger::LogInfo("[PulseAudioInput::InputThread] " + Logger::tr("Input thread started.")); std::vector buffer; bool has_first_samples = false; int64_t first_timestamp = 0; // value won't be used, but GCC gives a warning otherwise while(!m_should_stop) { PulseAudioIterate(m_pa_mainloop); // try to read samples const void *data; size_t bytes; if(pa_stream_peek(m_pa_stream, &data, &bytes) < 0) { Logger::LogError("[PulseAudioInput::InputThread] " + Logger::tr("Error: pa_stream_peek failed!", "Don't translate 'pa_stream_peek'")); throw PulseAudioException(); } if(data == NULL) { if(bytes > 0) { // skip hole PushAudioHole(); pa_stream_drop(m_pa_stream); } } else { // deal with half samples from the last peek (I don't think this will ever happen, but just in case ...) unsigned int samples = (buffer.size() + bytes) / (m_channels * 2); unsigned int bytes_left = (buffer.size() + bytes) % (m_channels * 2); uint8_t *push_data; if(buffer.size() > 0) { size_t p = buffer.size(); buffer.resize(p + bytes - bytes_left); memcpy(buffer.data() + p, data, bytes - bytes_left); push_data = buffer.data(); } else { push_data = (uint8_t*) data; } int64_t timestamp = hrt_time_micro(); // skip the first samples if(has_first_samples) { if(timestamp > first_timestamp + START_DELAY) { // get the latency // The latency can be negative for monitors, this means that we got the samples before they were actually played. // But for some reason, PulseAudio doesn't like signed integers ... /*pa_usec_t latency_magnitude; int latency_negative; pa_stream_get_latency(m_pa_stream, &latency_magnitude, &latency_negative); int64_t latency = (latency_negative)? -(int64_t) latency_magnitude : latency_magnitude;*/ // push the samples /*int64_t time = timestamp - latency;*/ int64_t time = timestamp; if(!m_stream_is_monitor) { time -= (int64_t) samples * (int64_t) 1000000 / (int64_t) m_sample_rate; } PushAudioSamples(m_channels, m_sample_rate, AV_SAMPLE_FMT_S16, samples, push_data, time); } } else { has_first_samples = true; first_timestamp = timestamp; } // store remaining bytes buffer.clear(); if(bytes_left > 0) { buffer.resize(bytes_left); memcpy(buffer.data(), (uint8_t*) data + bytes - bytes_left, bytes_left); } // drop the samples that we have read pa_stream_drop(m_pa_stream); } // is the stream suspended? if(m_stream_suspended) { m_stream_suspended = false; Logger::LogWarning("[PulseAudioInput::InputThread] " + Logger::tr("Warning: Audio source was suspended. The current segment will be stopped until the source is resumed.")); PushAudioHole(); } if(m_stream_moved) { m_stream_moved = false; Logger::LogWarning("[PulseAudioInput::InputThread] " + Logger::tr("Warning: Stream was moved to a different source.")); PushAudioHole(); DetectMonitor(); } } Logger::LogInfo("[PulseAudioInput::InputThread] " + Logger::tr("Input thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[PulseAudioInput::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[PulseAudioInput::InputThread] " + Logger::tr("Unknown exception in input thread.")); } } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/PulseAudioInput.h000066400000000000000000000043251470053624300251750ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #if SSR_USE_PULSEAUDIO #include "SourceSink.h" #include "MutexDataPair.h" #include #include #include #include #include class PulseAudioInput : public AudioSource { public: struct Source { std::string m_name, m_description; inline Source(const std::string& name, const std::string& description) : m_name(name), m_description(description) {} }; private: static const int64_t START_DELAY; private: QString m_source_name; unsigned int m_sample_rate, m_channels; pa_mainloop *m_pa_mainloop; pa_context *m_pa_context; pa_stream *m_pa_stream; unsigned int m_pa_period_size; bool m_stream_is_monitor; bool m_stream_suspended, m_stream_moved; std::thread m_thread; std::atomic m_should_stop, m_error_occurred; public: PulseAudioInput(const QString& source_name, unsigned int sample_rate); ~PulseAudioInput(); // Returns whether an error has occurred in the input thread. // This function is thread-safe. inline bool HasErrorOccurred() { return m_error_occurred; } public: static std::vector GetSourceList(); private: void Init(); void Free(); void DetectMonitor(); static void SourceInfoCallback(pa_context* context, const pa_source_info* info, int eol, void* userdata); static void SuspendedCallback(pa_stream* stream, void* userdata); static void MovedCallback(pa_stream* stream, void* userdata); void InputThread(); }; #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/SSRVideoStream.h000066400000000000000000000021451470053624300247130ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" struct SSRVideoStream { std::string m_stream_name; int64_t m_creation_time; unsigned int m_process_id; inline bool operator==(const SSRVideoStream& other) const { return (m_stream_name == other.m_stream_name); } inline bool operator<(const SSRVideoStream& other) const { return (m_creation_time < other.m_creation_time); } }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/SSRVideoStreamReader.cpp000066400000000000000000000210171470053624300263700ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "SSRVideoStreamReader.h" #include "Logger.h" #include #include #include #include #include SSRVideoStreamReader::SSRVideoStreamReader(const std::string& channel, const SSRVideoStream& stream) { m_stream = stream; m_channel_directory = "/dev/shm/ssr-" + ((channel.empty())? "channel-" + GetUserName() : channel); m_filename_main = m_channel_directory + "/video-" + stream.m_stream_name; m_page_size = sysconf(_SC_PAGE_SIZE); m_fd_main = -1; m_mmap_ptr_main = MAP_FAILED; m_mmap_size_main = 0; for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { FrameData &fd = m_frame_data[i]; fd.m_filename_frame = m_channel_directory + "/videoframe" + NumToString(i) + "-" + stream.m_stream_name; fd.m_fd_frame = -1; fd.m_mmap_ptr_frame = MAP_FAILED; fd.m_mmap_size_frame = 0; } try { Init(); } catch(...) { Free(); throw; } } SSRVideoStreamReader::~SSRVideoStreamReader() { Free(); } void SSRVideoStreamReader::Init() { Logger::LogInfo("[SSRVideoStreamReader::Init] " + Logger::tr("Created video stream reader.")); // open main file m_fd_main = open(m_filename_main.c_str(), O_RDWR | O_CLOEXEC); if(m_fd_main == -1) { Logger::LogError("[SSRVideoStreamReader::Init] " + Logger::tr("Error: Can't open video stream file!")); throw SSRStreamException(); } // resize main file m_mmap_size_main = (sizeof(GLInjectHeader) + GLINJECT_RING_BUFFER_SIZE * sizeof(GLInjectFrameInfo) + m_page_size - 1) / m_page_size * m_page_size; if(ftruncate(m_fd_main, m_mmap_size_main) == -1) { Logger::LogError("[SSRVideoStreamReader::Init] " + Logger::tr("Error: Can't resize video stream file!")); throw SSRStreamException(); } // map main file m_mmap_ptr_main = mmap(NULL, m_mmap_size_main, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd_main, 0); if(m_mmap_ptr_main == MAP_FAILED) { Logger::LogError("[SSRVideoStreamReader::Init] " + Logger::tr("Error: Can't memory-map video stream file!")); throw SSRStreamException(); } // open frame files for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { FrameData &fd = m_frame_data[i]; fd.m_fd_frame = open(fd.m_filename_frame.c_str(), O_RDWR | O_CLOEXEC); if(fd.m_fd_frame == -1) { Logger::LogError("[SSRVideoStreamReader::Init] " + Logger::tr("Error: Can't open video frame file!")); throw SSRStreamException(); } } // initialize header GLInjectHeader *header = GetGLInjectHeader(); header->capture_flags = 0; header->capture_target_fps = 0; std::atomic_thread_fence(std::memory_order_release); // initialize frame counter std::atomic_thread_fence(std::memory_order_acquire); m_fps_last_timestamp = hrt_time_micro(); m_fps_last_counter = header->frame_counter; m_fps_current = 0.0; } void SSRVideoStreamReader::Free() { for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { FrameData &fd = m_frame_data[i]; // unmap frame file if(fd.m_mmap_ptr_frame != MAP_FAILED) { munmap(fd.m_mmap_ptr_frame, fd.m_mmap_size_frame); fd.m_mmap_ptr_frame = MAP_FAILED; } // close frame file if(fd.m_fd_frame != -1) { close(fd.m_fd_frame); fd.m_fd_frame = -1; } } // unmap main file if(m_mmap_ptr_main != MAP_FAILED) { munmap(m_mmap_ptr_main, m_mmap_size_main); m_mmap_ptr_main = MAP_FAILED; } // close main file if(m_fd_main != -1) { close(m_fd_main); m_fd_main = -1; } Logger::LogInfo("[SSRVideoStreamReader::Init] " + Logger::tr("Destroyed video stream reader.")); } void SSRVideoStreamReader::GetCurrentSize(unsigned int* width, unsigned int* height) { GLInjectHeader *header = GetGLInjectHeader(); std::atomic_thread_fence(std::memory_order_acquire); if(header->identifier != GLINJECT_IDENTIFIER) { *width = 0; *height = 0; return; } std::atomic_thread_fence(std::memory_order_acquire); *width = header->current_width; *height = header->current_height; } double SSRVideoStreamReader::GetFPS() { GLInjectHeader *header = GetGLInjectHeader(); int64_t timestamp = hrt_time_micro(); std::atomic_thread_fence(std::memory_order_acquire); uint32_t frame_counter = header->frame_counter; unsigned int time = timestamp - m_fps_last_timestamp; if(time > 500000) { unsigned int frames = frame_counter - m_fps_last_counter; m_fps_last_timestamp = timestamp; m_fps_last_counter = frame_counter; m_fps_current = (double) frames / ((double) time * 1.0e-6); } return m_fps_current; } void SSRVideoStreamReader::ChangeCaptureParameters(unsigned int flags, unsigned int target_fps) { GLInjectHeader *header = GetGLInjectHeader(); header->capture_flags = flags; header->capture_target_fps = target_fps; std::atomic_thread_fence(std::memory_order_release); } void SSRVideoStreamReader::Clear() { GLInjectHeader *header = GetGLInjectHeader(); std::atomic_thread_fence(std::memory_order_acquire); header->ring_buffer_read_pos = header->ring_buffer_write_pos; std::atomic_thread_fence(std::memory_order_release); } void* SSRVideoStreamReader::GetFrame(int64_t* timestamp, unsigned int* width, unsigned int* height, int* stride) { // make sure that the stream has been initialized GLInjectHeader *header = GetGLInjectHeader(); std::atomic_thread_fence(std::memory_order_acquire); if(header->identifier != GLINJECT_IDENTIFIER) return NULL; // make sure that at least one frame is available std::atomic_thread_fence(std::memory_order_acquire); unsigned int read_pos = header->ring_buffer_read_pos; unsigned int write_pos = header->ring_buffer_write_pos; if(read_pos == write_pos) return NULL; // read frame info GLInjectFrameInfo *frameinfo = GetGLInjectFrameInfo(read_pos % GLINJECT_RING_BUFFER_SIZE); std::atomic_thread_fence(std::memory_order_acquire); *timestamp = frameinfo->timestamp; *width = frameinfo->width; *height = frameinfo->height; *stride = frameinfo->stride; // verify the size (should never happen unless someone is messing with the files) if(*width < 2 || *height < 2) return NULL; if(*width > SSR_MAX_IMAGE_SIZE || *height > SSR_MAX_IMAGE_SIZE) return NULL; if(abs(*stride) > SSR_MAX_IMAGE_SIZE * 4) return NULL; // read frame FrameData &fd = m_frame_data[read_pos % GLINJECT_RING_BUFFER_SIZE]; size_t required_size = (size_t) abs(*stride) * (size_t) *height; if(required_size > fd.m_mmap_size_frame) { // calculate new size required_size = (required_size + m_page_size - 1) / m_page_size * m_page_size; // unmap frame file if(fd.m_mmap_ptr_frame != MAP_FAILED) { munmap(fd.m_mmap_ptr_frame, fd.m_mmap_size_frame); fd.m_mmap_ptr_frame = MAP_FAILED; fd.m_mmap_size_frame = 0; } // check frame file size { struct stat statinfo; if(fstat(fd.m_fd_frame, &statinfo) == -1 || (size_t) statinfo.st_size < required_size) { Logger::LogError("[SSRVideoStreamReader::GetFrame] " + Logger::tr("Error: Size of video frame file is incorrect!")); throw SSRStreamException(); } required_size = statinfo.st_size / m_page_size * m_page_size; } // map frame file fd.m_mmap_ptr_frame = mmap(NULL, required_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd.m_fd_frame, 0); if(fd.m_mmap_ptr_frame == MAP_FAILED) { Logger::LogError("[SSRVideoStreamReader::GetFrame] " + Logger::tr("Error: Can't memory-map video frame file!")); throw SSRStreamException(); } fd.m_mmap_size_frame = required_size; } return fd.m_mmap_ptr_frame; } void SSRVideoStreamReader::NextFrame() { // tell the compiler that it should read the entire frame before continuing // This is essentially a 'load-store' fence, which should be provided by both acquire and release fences, // but I'm not completely sure so let's use both just to be safe. std::atomic_thread_fence(std::memory_order_acq_rel); // go to the next frame GLInjectHeader *header = GetGLInjectHeader(); header->ring_buffer_read_pos = (header->ring_buffer_read_pos + 1) % (GLINJECT_RING_BUFFER_SIZE * 2); std::atomic_thread_fence(std::memory_order_release); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/SSRVideoStreamReader.h000066400000000000000000000050551470053624300260410ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "SSRVideoStream.h" #include "../glinject/ShmStructs.h" class SSRVideoStreamReader { private: struct FrameData { std::string m_filename_frame; int m_fd_frame; void *m_mmap_ptr_frame; size_t m_mmap_size_frame; }; private: SSRVideoStream m_stream; std::string m_channel_directory, m_filename_main; size_t m_page_size; int64_t m_fps_last_timestamp; uint32_t m_fps_last_counter; double m_fps_current; int m_fd_main, m_file_lock; void *m_mmap_ptr_main; size_t m_mmap_size_main; FrameData m_frame_data[GLINJECT_RING_BUFFER_SIZE]; public: SSRVideoStreamReader(const std::string& channel, const SSRVideoStream& stream); ~SSRVideoStreamReader(); private: void Init(); void Free(); public: // Reads the current size of the stream. If the stream hasn't been started yet, this will be 0x0. void GetCurrentSize(unsigned int* width, unsigned int* height); // Returns the current fps. double GetFPS(); // Changes the capture parameters. void ChangeCaptureParameters(unsigned int flags, unsigned int target_fps); // Clears the ring buffer (i.e. drops all frames). void Clear(); // Checks whether a new frame is available, and returns a pointer to the frame memory if it is. Otherwise it returns NULL. void* GetFrame(int64_t* timestamp, unsigned int* width, unsigned int* height, int* stride); // Drops the current frame and goes to the next frame. void NextFrame(); public: // Returns the stream that is being read. inline const SSRVideoStream& GetStream() { return m_stream; } private: inline GLInjectHeader* GetGLInjectHeader() { return (GLInjectHeader*) m_mmap_ptr_main; } inline GLInjectFrameInfo* GetGLInjectFrameInfo(unsigned int frame) { return (GLInjectFrameInfo*) ((char*) m_mmap_ptr_main + sizeof(GLInjectHeader) + frame * sizeof(GLInjectFrameInfo)); } }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/SSRVideoStreamWatcher.cpp000066400000000000000000000212471470053624300265700ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "SSRVideoStreamWatcher.h" #include "Logger.h" #include #include #ifdef __linux__ #include #endif #include #include #include static bool ProcessExists(pid_t pid) { if(kill(pid, 0) == 0) return true; // no errors, process exists if(errno == ESRCH) return false; // process does not exist return true; // process exists, but isn't owned by us } static bool SSRVideoStreamParse(const std::string& filename, SSRVideoStream* stream) { // check the prefix std::string prefix = "video-"; if(filename.compare(0, prefix.length(), prefix) != 0) return false; // split into parts size_t pos = prefix.length(); std::array parts; for(size_t i = 0; i < parts.size(); ++i) { size_t dash = filename.find_first_of('-', pos); if(dash == std::string::npos) return false; parts[i] = filename.substr(pos, dash - pos); pos = dash + 1; } // save the parts stream->m_stream_name = filename.substr(prefix.length()); if(!StringToNum(parts[0], &stream->m_creation_time)) return false; if(!StringToNum(parts[1], &stream->m_process_id)) return false; return true; } SSRVideoStreamWatcher::SSRVideoStreamWatcher(const std::string& channel, bool relax_permissions) { m_channel_directory = "/dev/shm/ssr-" + ((channel.empty())? "channel-" + GetUserName() : channel); m_relax_permissions = relax_permissions; m_fd_notify = -1; try { Init(); } catch(...) { Free(); throw; } } SSRVideoStreamWatcher::~SSRVideoStreamWatcher() { Free(); } void SSRVideoStreamWatcher::Init() { // create channel directory (permissions may be wrong because of umask, fix this later) if(mkdir(m_channel_directory.c_str(), (m_relax_permissions)? 0777 : 0700) == -1) { if(errno != EEXIST) { // does the directory already exist? Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't create channel directory!")); throw SSRStreamException(); } } // directory already exists, check ownership and permissions struct stat statinfo; if(lstat(m_channel_directory.c_str(), &statinfo) == -1) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't stat channel directory!")); throw SSRStreamException(); } if(!S_ISDIR(statinfo.st_mode) || S_ISLNK(statinfo.st_mode)) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Channel directory is not a regular directory!")); throw SSRStreamException(); } if(statinfo.st_uid == geteuid()) { if(chmod(m_channel_directory.c_str(), (m_relax_permissions)? 0777 : 0700) == -1) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't set channel directory mode!")); throw SSRStreamException(); } } else { if(!m_relax_permissions) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Channel directory is owned by a different user! " "Choose a different channel name, or enable relaxed file permissions to use it anyway.")); throw SSRStreamException(); } } #ifdef __linux__ // initialize inotify m_fd_notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); if(m_fd_notify == -1) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't initialize inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } // watch channel directory if(inotify_add_watch(m_fd_notify, m_channel_directory.c_str(), IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO) == -1) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't watch channel directory!")); throw SSRStreamException(); } #endif // get all the files that existed already DIR *dir = NULL; try { // open directory dir = opendir(m_channel_directory.c_str()); if(dir == NULL) { Logger::LogError("[SSRVideoStreamWatcher::Init] " + Logger::tr("Error: Can't open channel directory!")); throw SSRStreamException(); } // get list of files for( ; ; ) { // get one file dirent *d = readdir(dir); if(d == NULL) break; // parse the name SSRVideoStream stream; if(!SSRVideoStreamParse(d->d_name, &stream)) continue; // add the stream Logger::LogInfo("[SSRVideoStreamWatcher::Init] " + Logger::tr("Added pre-existing stream %1.").arg(d->d_name)); m_streams.push_back(stream); } // close directory closedir(dir); dir = NULL; // sort by creation time std::sort(m_streams.begin(), m_streams.end()); } catch(...) { if(dir != NULL) { closedir(dir); dir = NULL; } throw; } } void SSRVideoStreamWatcher::Free() { if(m_fd_notify != -1) { close(m_fd_notify); m_fd_notify = -1; } } void SSRVideoStreamWatcher::HandleChanges(AddCallback add_callback, RemoveCallback remove_callback, void* userdata) { #ifdef __linux__ // find out how much we can read int len; if(ioctl(m_fd_notify, FIONREAD, &len) == -1) { Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Can't get read length from inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } if(len > 0) { // read all the changes std::vector buffer(len); if(read(m_fd_notify, buffer.data(), buffer.size()) != (ssize_t) buffer.size()) { Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Can't read from inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } // parse the changes size_t pos = 0; while(pos < buffer.size()) { // read the event structure if(buffer.size() - pos < sizeof(inotify_event)) { Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Received partial event from inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } inotify_event *event = (inotify_event*) (buffer.data() + pos); pos += sizeof(inotify_event); // ignore events with no name if(event->len == 0) continue; // read the name if(buffer.size() - pos < event->len) { Logger::LogError("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Error: Received partial name from inotify!", "don't translate 'inotify'")); throw SSRStreamException(); } std::string name(buffer.data() + pos); // the name may be padded with null bytes that should be ignored pos += event->len; // parse the name SSRVideoStream stream; if(!SSRVideoStreamParse(name, &stream)) continue; // handle events if(event->mask & (IN_CREATE | IN_MOVED_TO)) { if(std::find(m_streams.begin(), m_streams.end(), stream) == m_streams.end()) { Logger::LogInfo("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Added stream %1.").arg(QString::fromStdString(stream.m_stream_name))); m_streams.push_back(stream); add_callback(stream, userdata); } } if(event->mask & (IN_DELETE | IN_MOVED_FROM)) { auto p = std::find(m_streams.begin(), m_streams.end(), stream); if(p != m_streams.end()) { Logger::LogInfo("[SSRVideoStreamWatcher::GetChanges] " + Logger::tr("Removed stream %1.").arg(QString::fromStdString(stream.m_stream_name))); size_t index = p - m_streams.begin(); m_streams.erase(p); remove_callback(stream, index, userdata); } } } } // delete abandoned streams for(unsigned int j = m_streams.size(); j > 0; ) { --j; // does the process still exist? if(ProcessExists(m_streams[j].m_process_id)) continue; // if the process is dead, delete the files for(unsigned int i = 0; i < GLINJECT_RING_BUFFER_SIZE; ++i) { std::string filename = m_channel_directory + "/videoframe" + NumToString(i) + "-" + m_streams[j].m_stream_name; unlink(filename.c_str()); } std::string filename = m_channel_directory + "/video-" + m_streams[j].m_stream_name; unlink(filename.c_str()); Logger::LogInfo("[SSRVideoStreamWatcher::Init] " + Logger::tr("Deleted abandoned stream %1.").arg(QString::fromStdString(m_streams[j].m_stream_name))); // remove the stream SSRVideoStream stream = m_streams[j]; m_streams.erase(m_streams.begin() + j); remove_callback(stream, j, userdata); } #endif } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/SSRVideoStreamWatcher.h000066400000000000000000000031131470053624300262250ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "SSRVideoStream.h" #include "../glinject/ShmStructs.h" class SSRVideoStreamWatcher { public: typedef void (*AddCallback)(const SSRVideoStream&, void*); typedef void (*RemoveCallback)(const SSRVideoStream&, size_t, void*); private: std::string m_channel_directory; bool m_relax_permissions; int m_fd_notify; std::vector m_streams; public: SSRVideoStreamWatcher(const std::string& channel, bool relax_permissions); ~SSRVideoStreamWatcher(); private: void Init(); void Free(); public: // Handles all changes since the last call to this function. void HandleChanges(AddCallback add_callback, RemoveCallback remove_callback, void* userdata); // Returns a list of streams that are currently active. inline const std::vector& GetStreams() { return m_streams; } }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/V4L2Input.cpp000066400000000000000000000225731470053624300241520ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "V4L2Input.h" #if SSR_USE_V4L2 #include "Logger.h" #include "AVWrapper.h" #include "Synchronizer.h" #include "VideoEncoder.h" V4L2Input::V4L2Input(const QString& device, unsigned int width, unsigned int height) { m_device = device; m_width = width; m_height = height; m_colorspace = SWS_CS_DEFAULT; m_buffers = 4; m_v4l2_device = -1; m_v4l2_buffers.resize(m_buffers, V4L2Buffer{MAP_FAILED, 0}); if(m_width == 0 || m_height == 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Width or height is zero!")); throw V4L2Exception(); } if(m_width > SSR_MAX_IMAGE_SIZE || m_height > SSR_MAX_IMAGE_SIZE) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Width or height is too large, the maximum width and height is %1!").arg(SSR_MAX_IMAGE_SIZE)); throw V4L2Exception(); } if(m_width % 2 != 0 || m_height % 2 != 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Width or height is not an even number!")); throw V4L2Exception(); } try { Init(); } catch(...) { Free(); throw; } } V4L2Input::~V4L2Input() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[V4L2Input::~V4L2Input] " + Logger::tr("Stopping input thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } void V4L2Input::GetCurrentSize(unsigned int *width, unsigned int *height) { *width = m_width; *height = m_height; } double V4L2Input::GetFPS() { int64_t timestamp = hrt_time_micro(); uint32_t frame_counter = m_frame_counter; unsigned int time = timestamp - m_fps_last_timestamp; if(time > 500000) { unsigned int frames = frame_counter - m_fps_last_counter; m_fps_last_timestamp = timestamp; m_fps_last_counter = frame_counter; m_fps_current = (double) frames / ((double) time * 1.0e-6); } return m_fps_current; } void V4L2Input::Init() { // open device m_v4l2_device = v4l2_open(QFile::encodeName(m_device).constData(), O_RDWR); if(m_v4l2_device == -1) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Can't open V4L2 device!")); throw V4L2Exception(); } // check capabilities v4l2_capability caps; memset(&caps, 0, sizeof(caps)); if(v4l2_ioctl(m_v4l2_device, VIDIOC_QUERYCAP, &caps) < 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Can't read capabilities of V4L2 device!")); throw V4L2Exception(); } if((caps.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: V4L2 device does not support video capture!")); throw V4L2Exception(); } if((caps.capabilities & V4L2_CAP_STREAMING) == 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: V4L2 device does not support streaming io!")); throw V4L2Exception(); } /*if((caps.capabilities & V4L2_CAP_READWRITE) == 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: V4L2 device does not support read io!")); throw V4L2Exception(); }*/ // set format v4l2_format format; memset(&format, 0, sizeof(format)); format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; format.fmt.pix.width = m_width; format.fmt.pix.height = m_height; format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; format.fmt.pix.field = V4L2_FIELD_ANY; if(v4l2_ioctl(m_v4l2_device, VIDIOC_S_FMT, &format) < 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Can't set capture format!")); throw V4L2Exception(); } if(format.fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: V4L2 device does not support YUYV pixel format!")); throw V4L2Exception(); } if(format.fmt.pix.width != m_width || format.fmt.pix.height != m_height) { Logger::LogWarning("[V4L2Input::Init] " + Logger::tr("Warning: Resolution %1x%2 is not supported, using %3x%4 instead. " "The video will be scaled.") .arg(m_width).arg(m_height).arg(format.fmt.pix.width).arg(format.fmt.pix.height)); m_width = format.fmt.pix.width; m_height = format.fmt.pix.height; } const char *colorspace_str = NULL; switch(format.fmt.pix.colorspace) { case V4L2_COLORSPACE_SMPTE170M: { m_colorspace = SWS_CS_SMPTE170M; colorspace_str = "smpte170m"; break; } case V4L2_COLORSPACE_SMPTE240M: { m_colorspace = SWS_CS_SMPTE240M; colorspace_str = "smpte240m"; break; } case V4L2_COLORSPACE_REC709: { m_colorspace = SWS_CS_ITU709; colorspace_str = "itu709"; break; } case V4L2_COLORSPACE_470_SYSTEM_M: case V4L2_COLORSPACE_470_SYSTEM_BG: case V4L2_COLORSPACE_JPEG: { m_colorspace = SWS_CS_ITU601; colorspace_str = "itu601"; break; } #ifdef SWS_CS_BT2020 case V4L2_COLORSPACE_BT2020: { m_colorspace = SWS_CS_BT2020; colorspace_str = "bt2020"; break; } #endif default: { m_colorspace = SWS_CS_DEFAULT; colorspace_str = "default"; break; } } Logger::LogInfo("[V4L2Input::Init] " + Logger::tr("Using color space %1.").arg(colorspace_str)); m_v4l2_bytes_per_line = (format.fmt.pix.bytesperline == 0)? 2 * format.fmt.pix.width : format.fmt.pix.bytesperline; // request buffers v4l2_requestbuffers reqbufs; memset(&reqbufs, 0, sizeof(reqbufs)); reqbufs.count = m_buffers; reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbufs.memory = V4L2_MEMORY_MMAP; if(v4l2_ioctl(m_v4l2_device, VIDIOC_REQBUFS, &reqbufs) < 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Buffer request failed!")); throw V4L2Exception(); } for(unsigned int i = 0; i < m_buffers; ++i) { v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if(v4l2_ioctl(m_v4l2_device, VIDIOC_QUERYBUF, &buf) < 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Buffer query failed!")); throw V4L2Exception(); } m_v4l2_buffers[i].m_size = buf.length; m_v4l2_buffers[i].m_data = mmap(0, buf.length, PROT_READ, MAP_SHARED, m_v4l2_device, buf.m.offset); if(m_v4l2_buffers[i].m_data == MAP_FAILED) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Buffer mmap failed!")); throw V4L2Exception(); } } // queue the buffers for(unsigned int i = 0; i < m_buffers; ++i) { v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if(v4l2_ioctl(m_v4l2_device, VIDIOC_QBUF, &buf) < 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Buffer queue failed!")); throw V4L2Exception(); } } // start stream int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(v4l2_ioctl(m_v4l2_device, VIDIOC_STREAMON, &type) < 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Failed to start stream!")); throw V4L2Exception(); } // initialize frame counter m_frame_counter = 0; m_fps_last_timestamp = hrt_time_micro(); m_fps_last_counter = 0; m_fps_current = 0.0; // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&V4L2Input::InputThread, this); } void V4L2Input::Free() { for(unsigned int i = 0; i < m_buffers; ++i) { if(m_v4l2_buffers[i].m_data != MAP_FAILED) { munmap(m_v4l2_buffers[i].m_data, m_v4l2_buffers[i].m_size); m_v4l2_buffers[i].m_data = MAP_FAILED; } } if(m_v4l2_device != -1) { v4l2_close(m_v4l2_device); m_v4l2_device = -1; } } void V4L2Input::InputThread() { try { Logger::LogInfo("[V4L2Input::InputThread] " + Logger::tr("Input thread started.")); while(!m_should_stop) { // dequeue a buffer v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if(ioctl(m_v4l2_device, VIDIOC_DQBUF, &buf) < 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Buffer dequeue failed!")); throw V4L2Exception(); } // record the timestamp int64_t timestamp = hrt_time_micro(); // increase the frame counter ++m_frame_counter; // push the frame const uint8_t *image_data[1] = {(uint8_t*) m_v4l2_buffers[buf.index].m_data}; int image_stride[1] = {(int) m_v4l2_bytes_per_line}; PushVideoFrame(m_width, m_height, image_data, image_stride, AV_PIX_FMT_YUYV422, m_colorspace, timestamp); // requeue the buffer if(v4l2_ioctl(m_v4l2_device, VIDIOC_QBUF, &buf) < 0) { Logger::LogError("[V4L2Input::Init] " + Logger::tr("Error: Buffer requeue failed!")); throw V4L2Exception(); } } Logger::LogInfo("[V4L2Input::InputThread] " + Logger::tr("Input thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[V4L2Input::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[V4L2Input::InputThread] " + Logger::tr("Unknown exception in input thread.")); } } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/V4L2Input.h000066400000000000000000000041241470053624300236070ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "SourceSink.h" #include "MutexDataPair.h" #if SSR_USE_V4L2 #include #include class V4L2Input : public VideoSource { private: struct V4L2Buffer { void *m_data; size_t m_size; }; private: QString m_device; unsigned int m_width, m_height; int m_colorspace; unsigned int m_buffers; std::atomic m_frame_counter; int64_t m_fps_last_timestamp; uint32_t m_fps_last_counter; double m_fps_current; int m_v4l2_device; std::vector m_v4l2_buffers; unsigned int m_v4l2_bytes_per_line; std::thread m_thread; std::atomic m_should_stop, m_error_occurred; public: V4L2Input(const QString& device, unsigned int width, unsigned int height); ~V4L2Input(); // Reads the current size of the stream. // This function is thread-safe. void GetCurrentSize(unsigned int* width, unsigned int* height); // Returns the total number of captured frames. // This function is thread-safe. double GetFPS(); // Returns whether an error has occurred in the input thread. // This function is thread-safe. inline bool HasErrorOccurred() { return m_error_occurred; } private: void Init(); void Free(); private: void AllocateImage(unsigned int width, unsigned int height); void FreeImage(); void UpdateScreenConfiguration(); private: void InputThread(); }; #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/X11Input.cpp000066400000000000000000000556421470053624300240370ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file contains code from x11grab.c (part of ffmpeg/libav). The copyright information for x11grab.c is: >> FFmpeg/Libav integration: >> Copyright (C) 2006 Clemens Fruhwirth >> Edouard Gomez >> >> This file contains code from grab.c: >> Copyright (c) 2000-2001 Fabrice Bellard >> >> This file contains code from the xvidcap project: >> Copyright (C) 1997-1998 Rasca, Berlin >> 2003-2004 Karl H. Beckers, Frankfurt This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "X11Input.h" #include "Logger.h" #include "AVWrapper.h" #include "Synchronizer.h" #include "VideoEncoder.h" /* The code in this file is based on the MIT-SHM example code and the x11grab device in libav/ffmpeg (which is GPL): http://www.xfree86.org/current/mit-shm.html https://git.libav.org/?p=libav.git;a=blob;f=libavdevice/x11grab.c I am doing the recording myself instead of just using x11grab (as I originally planned) because this is more flexible. */ // Converts a X11 image format to a format that libav/ffmpeg understands. static AVPixelFormat X11ImageGetPixelFormat(XImage* image) { switch(image->bits_per_pixel) { case 8: return AV_PIX_FMT_PAL8; case 16: { if(image->red_mask == 0xf800 && image->green_mask == 0x07e0 && image->blue_mask == 0x001f) return AV_PIX_FMT_RGB565; if(image->red_mask == 0x7c00 && image->green_mask == 0x03e0 && image->blue_mask == 0x001f) return AV_PIX_FMT_RGB555; break; } case 24: { if(image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) return AV_PIX_FMT_BGR24; if(image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) return AV_PIX_FMT_RGB24; break; } case 32: { if(image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) return AV_PIX_FMT_BGRA; if(image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) return AV_PIX_FMT_RGBA; if(image->red_mask == 0xff000000 && image->green_mask == 0x00ff0000 && image->blue_mask == 0x0000ff00) return AV_PIX_FMT_ABGR; if(image->red_mask == 0x0000ff00 && image->green_mask == 0x00ff0000 && image->blue_mask == 0xff000000) return AV_PIX_FMT_ARGB; break; } } Logger::LogError("[X11ImageGetPixelFormat] " + Logger::tr("Error: Unsupported X11 image pixel format!") + "\n" " bits_per_pixel = " + QString::number(image->bits_per_pixel) + ", red_mask = 0x" + QString::number(image->red_mask, 16) + ", green_mask = 0x" + QString::number(image->green_mask, 16) + ", blue_mask = 0x" + QString::number(image->blue_mask, 16)); throw X11Exception(); } // clears a rectangular area of an image (i.e. sets the memory to zero, which will most likely make the image black) static void X11ImageClearRectangle(XImage* image, unsigned int x, unsigned int y, unsigned int w, unsigned int h) { // check the image format if(image->bits_per_pixel % 8 != 0) return; unsigned int pixel_bytes = image->bits_per_pixel / 8; // fill the rectangle with zeros for(unsigned int j = 0; j < h; ++j) { uint8_t *image_row = (uint8_t*) image->data + image->bytes_per_line * (y + j); memset(image_row + pixel_bytes * x, 0, pixel_bytes * w); } } // Draws the current cursor at the current position on the image. Requires XFixes. // Note: In the original code from x11grab, the variables for red and blue are swapped // (which doesn't change the result, but it's confusing). // Note 2: This function assumes little-endianness. // Note 3: This function only supports 24-bit and 32-bit images (it does nothing for other bit depths). static void X11ImageDrawCursor(Display* dpy, XImage* image, int recording_area_x, int recording_area_y) { // check the image format unsigned int pixel_bytes, r_offset, g_offset, b_offset; if(image->bits_per_pixel == 24 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) { pixel_bytes = 3; r_offset = 2; g_offset = 1; b_offset = 0; } else if(image->bits_per_pixel == 24 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) { pixel_bytes = 3; r_offset = 0; g_offset = 1; b_offset = 2; } else if(image->bits_per_pixel == 32 && image->red_mask == 0xff0000 && image->green_mask == 0x00ff00 && image->blue_mask == 0x0000ff) { pixel_bytes = 4; r_offset = 2; g_offset = 1; b_offset = 0; } else if(image->bits_per_pixel == 32 && image->red_mask == 0x0000ff && image->green_mask == 0x00ff00 && image->blue_mask == 0xff0000) { pixel_bytes = 4; r_offset = 0; g_offset = 1; b_offset = 2; } else if(image->bits_per_pixel == 32 && image->red_mask == 0xff000000 && image->green_mask == 0x00ff0000 && image->blue_mask == 0x0000ff00) { pixel_bytes = 4; r_offset = 3; g_offset = 2; b_offset = 1; } else if(image->bits_per_pixel == 32 && image->red_mask == 0x0000ff00 && image->green_mask == 0x00ff0000 && image->blue_mask == 0xff000000) { pixel_bytes = 4; r_offset = 1; g_offset = 2; b_offset = 3; } else { return; } // get the cursor XFixesCursorImage *xcim = XFixesGetCursorImage(dpy); if(xcim == NULL) return; // calculate the position of the cursor int x = xcim->x - xcim->xhot - recording_area_x; int y = xcim->y - xcim->yhot - recording_area_y; // calculate the part of the cursor that's visible int cursor_left = std::max(0, -x), cursor_right = std::min((int) xcim->width, image->width - x); int cursor_top = std::max(0, -y), cursor_bottom = std::min((int) xcim->height, image->height - y); // draw the cursor // XFixesCursorImage uses 'long' instead of 'int' to store the cursor images, which is a bit weird since // 'long' is 64-bit on 64-bit systems and only 32 bits are actually used. The image uses premultiplied alpha. for(int j = cursor_top; j < cursor_bottom; ++j) { unsigned long *cursor_row = xcim->pixels + xcim->width * j; uint8_t *image_row = (uint8_t*) image->data + image->bytes_per_line * (y + j); for(int i = cursor_left; i < cursor_right; ++i) { unsigned long cursor_pixel = cursor_row[i]; uint8_t *image_pixel = image_row + pixel_bytes * (x + i); int cursor_a = (uint8_t) (cursor_pixel >> 24); int cursor_r = (uint8_t) (cursor_pixel >> 16); int cursor_g = (uint8_t) (cursor_pixel >> 8); int cursor_b = (uint8_t) (cursor_pixel >> 0); if(cursor_a == 255) { image_pixel[r_offset] = cursor_r; image_pixel[g_offset] = cursor_g; image_pixel[b_offset] = cursor_b; } else { image_pixel[r_offset] = (image_pixel[r_offset] * (255 - cursor_a) + 127) / 255 + cursor_r; image_pixel[g_offset] = (image_pixel[g_offset] * (255 - cursor_a) + 127) / 255 + cursor_g; image_pixel[b_offset] = (image_pixel[b_offset] * (255 - cursor_a) + 127) / 255 + cursor_b; } } } // free the cursor XFree(xcim); } X11Input::X11Input(unsigned int x, unsigned int y, unsigned int width, unsigned int height, bool record_cursor, bool follow_cursor, bool follow_full_screen) { m_x = x; m_y = y; m_width = width; m_height = height; m_record_cursor = record_cursor; m_follow_cursor = follow_cursor; m_follow_fullscreen = follow_full_screen; m_x11_display = NULL; m_x11_image = NULL; m_x11_shm_info.shmseg = 0; m_x11_shm_info.shmid = -1; m_x11_shm_info.shmaddr = (char*) -1; m_x11_shm_info.readOnly = false; m_x11_shm_server_attached = false; m_screen_bbox = Rect(m_x, m_y, m_x + m_width, m_y + m_height); { SharedLock lock(&m_shared_data); lock->m_current_x = m_x; lock->m_current_y = m_y; lock->m_current_width = m_width; lock->m_current_height = m_height; } if(m_width == 0 || m_height == 0) { Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Width or height is zero!")); throw X11Exception(); } if(m_width > SSR_MAX_IMAGE_SIZE || m_height > SSR_MAX_IMAGE_SIZE) { Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Width or height is too large, the maximum width and height is %1!").arg(SSR_MAX_IMAGE_SIZE)); throw X11Exception(); } try { Init(); } catch(...) { Free(); throw; } } X11Input::~X11Input() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[X11Input::~X11Input] " + Logger::tr("Stopping input thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } void X11Input::GetCurrentRectangle(unsigned int *x, unsigned int *y, unsigned int *width, unsigned int *height) { SharedLock lock(&m_shared_data); *x = lock->m_current_x; *y = lock->m_current_y; *width = lock->m_current_width; *height = lock->m_current_height; } void X11Input::GetCurrentSize(unsigned int *width, unsigned int *height) { SharedLock lock(&m_shared_data); *width = lock->m_current_width; *height = lock->m_current_height; } double X11Input::GetFPS() { int64_t timestamp = hrt_time_micro(); uint32_t frame_counter = m_frame_counter; unsigned int time = timestamp - m_fps_last_timestamp; if(time > 500000) { unsigned int frames = frame_counter - m_fps_last_counter; m_fps_last_timestamp = timestamp; m_fps_last_counter = frame_counter; m_fps_current = (double) frames / ((double) time * 1.0e-6); } return m_fps_current; } void X11Input::Init() { // do the X11 stuff // we need a separate display because the existing one would interfere with what Qt is doing in some cases m_x11_display = XOpenDisplay(NULL); //QX11Info::display(); if(m_x11_display == NULL) { Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't open X display!", "Don't translate 'display'")); throw X11Exception(); } m_x11_screen = DefaultScreen(m_x11_display); //QX11Info::appScreen(); m_x11_root = RootWindow(m_x11_display, m_x11_screen); //QX11Info::appRootWindow(m_x11_screen); m_x11_visual = DefaultVisual(m_x11_display, m_x11_screen); //(Visual*) QX11Info::appVisual(m_x11_screen); m_x11_depth = DefaultDepth(m_x11_display, m_x11_screen); //QX11Info::appDepth(m_x11_screen); m_x11_use_shm = XShmQueryExtension(m_x11_display); if(m_x11_use_shm) { Logger::LogInfo("[X11Input::Init] " + Logger::tr("Using X11 shared memory.")); } else { Logger::LogInfo("[X11Input::Init] " + Logger::tr("Not using X11 shared memory.")); } // showing the cursor requires XFixes (which should be supported on any modern X server, but let's check it anyway) if(m_record_cursor) { int event, error; if(!XFixesQueryExtension(m_x11_display, &event, &error)) { Logger::LogWarning("[X11Input::Init] " + Logger::tr("Warning: XFixes is not supported by X server, the cursor has been hidden.", "Don't translate 'XFixes'")); m_record_cursor = false; } } // get screen configuration information, so we can replace the unused areas with black rectangles (rather than showing random uninitialized memory) // this is also used by the mouse following code to make sure that the rectangle stays on the screen UpdateScreenConfiguration(); // initialize frame counter m_frame_counter = 0; m_fps_last_timestamp = hrt_time_micro(); m_fps_last_counter = 0; m_fps_current = 0.0; // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&X11Input::InputThread, this); } void X11Input::Free() { FreeImage(); if(m_x11_display != NULL) { XCloseDisplay(m_x11_display); m_x11_display = NULL; } } void X11Input::AllocateImage(unsigned int width, unsigned int height) { assert(m_x11_use_shm); if(m_x11_shm_server_attached && m_x11_image->width == (int) width && m_x11_image->height == (int) height) { return; // reuse existing image } FreeImage(); m_x11_image = XShmCreateImage(m_x11_display, m_x11_visual, m_x11_depth, ZPixmap, NULL, &m_x11_shm_info, width, height); if(m_x11_image == NULL) { Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't create shared image!")); throw X11Exception(); } m_x11_shm_info.shmid = shmget(IPC_PRIVATE, m_x11_image->bytes_per_line * m_x11_image->height, IPC_CREAT | 0700); if(m_x11_shm_info.shmid == -1) { Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't get shared memory!")); throw X11Exception(); } m_x11_shm_info.shmaddr = (char*) shmat(m_x11_shm_info.shmid, NULL, SHM_RND); if(m_x11_shm_info.shmaddr == (char*) -1) { Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't attach to shared memory!")); throw X11Exception(); } m_x11_image->data = m_x11_shm_info.shmaddr; if(!XShmAttach(m_x11_display, &m_x11_shm_info)) { Logger::LogError("[X11Input::Init] " + Logger::tr("Error: Can't attach server to shared memory!")); throw X11Exception(); } m_x11_shm_server_attached = true; } void X11Input::FreeImage() { if(m_x11_shm_server_attached) { XShmDetach(m_x11_display, &m_x11_shm_info); m_x11_shm_server_attached = false; } if(m_x11_shm_info.shmaddr != (char*) -1) { shmdt(m_x11_shm_info.shmaddr); m_x11_shm_info.shmaddr = (char*) -1; } if(m_x11_shm_info.shmid != -1) { shmctl(m_x11_shm_info.shmid, IPC_RMID, NULL); m_x11_shm_info.shmid = -1; } if(m_x11_image != NULL) { XDestroyImage(m_x11_image); m_x11_image = NULL; } } void X11Input::UpdateScreenConfiguration() { Logger::LogInfo("[X11Input::Init] " + Logger::tr("Detecting screen configuration ...")); // get screen rectangles m_screen_rects.clear(); int event_base, error_base; if(XineramaQueryExtension(m_x11_display, &event_base, &error_base)) { int num_screens; XineramaScreenInfo *screens = XineramaQueryScreens(m_x11_display, &num_screens); try { for(int i = 0; i < num_screens; ++i) { m_screen_rects.emplace_back(screens[i].x_org, screens[i].y_org, screens[i].x_org + screens[i].width, screens[i].y_org + screens[i].height); } } catch(...) { XFree(screens); throw; } XFree(screens); } else { Logger::LogWarning("[X11Input::Init] " + Logger::tr("Warning: Xinerama is not supported by X server, multi-monitor support may not work properly.", "Don't translate 'Xinerama'")); return; } // make sure that we have at least one monitor if(m_screen_rects.size() == 0) { Logger::LogWarning("[X11Input::Init] " + Logger::tr("Warning: No monitors detected, multi-monitor support may not work properly.")); return; } // log the screen rectangles for(size_t i = 0; i < m_screen_rects.size(); ++i) { Rect &rect = m_screen_rects[i]; Logger::LogInfo("[X11Input::Init] " + Logger::tr("Screen %1:").arg(i) + " x1 = " + QString::number(rect.m_x1) + ", y1 = " + QString::number(rect.m_y1) + ", x2 = " + QString::number(rect.m_x2) + ", y2 = " + QString::number(rect.m_y2)); } // calculate bounding box m_screen_bbox = m_screen_rects[0]; for(size_t i = 1; i < m_screen_rects.size(); ++i) { Rect &rect = m_screen_rects[i]; if(rect.m_x1 < m_screen_bbox.m_x1) m_screen_bbox.m_x1 = rect.m_x1; if(rect.m_y1 < m_screen_bbox.m_y1) m_screen_bbox.m_y1 = rect.m_y1; if(rect.m_x2 > m_screen_bbox.m_x2) m_screen_bbox.m_x2 = rect.m_x2; if(rect.m_y2 > m_screen_bbox.m_y2) m_screen_bbox.m_y2 = rect.m_y2; } if(m_screen_bbox.m_x1 >= m_screen_bbox.m_x2 || m_screen_bbox.m_y1 >= m_screen_bbox.m_y2 || m_screen_bbox.m_x2 - m_screen_bbox.m_x1 > SSR_MAX_IMAGE_SIZE || m_screen_bbox.m_y2 - m_screen_bbox.m_y1 > SSR_MAX_IMAGE_SIZE) { Logger::LogError("[X11Input::UpdateScreenConfiguration] " + Logger::tr("Error: Invalid screen bounding box!") + "\n" " x1 = " + QString::number(m_screen_bbox.m_x1) + ", y1 = " + QString::number(m_screen_bbox.m_y1) + ", x2 = " + QString::number(m_screen_bbox.m_x2) + ", y2 = " + QString::number(m_screen_bbox.m_y2)); throw X11Exception(); } /*qDebug() << "m_screen_rects:"; for(Rect &rect : m_screen_rects) { qDebug() << " rect" << rect.m_x1 << rect.m_y1 << rect.m_x2 << rect.m_y2; } qDebug() << "m_screen_bbox:"; qDebug() << " rect" << m_screen_bbox.m_x1 << m_screen_bbox.m_y1 << m_screen_bbox.m_x2 << m_screen_bbox.m_y2;*/ // calculate dead space m_screen_dead_space = {m_screen_bbox}; for(size_t i = 0; i < m_screen_rects.size(); ++i) { /*qDebug() << "PARTIAL m_screen_dead_space:"; for(Rect &rect : m_screen_dead_space) { qDebug() << " rect" << rect.m_x1 << rect.m_y1 << rect.m_x2 << rect.m_y2; }*/ Rect &subtract = m_screen_rects[i]; std::vector result; for(Rect &rect : m_screen_dead_space) { if(rect.m_x1 < subtract.m_x2 && rect.m_y1 < subtract.m_y2 && subtract.m_x1 < rect.m_x2 && subtract.m_y1 < rect.m_y2) { unsigned int mid_y1 = std::max(rect.m_y1, subtract.m_y1); unsigned int mid_y2 = std::min(rect.m_y2, subtract.m_y2); if(rect.m_y1 < subtract.m_y1) result.emplace_back(rect.m_x1, rect.m_y1, rect.m_x2, subtract.m_y1); if(rect.m_x1 < subtract.m_x1) result.emplace_back(rect.m_x1, mid_y1, subtract.m_x1, mid_y2); if(subtract.m_x2 < rect.m_x2) result.emplace_back(subtract.m_x2, mid_y1, rect.m_x2, mid_y2); if(subtract.m_y2 < rect.m_y2) result.emplace_back(rect.m_x1, subtract.m_y2, rect.m_x2, rect.m_y2); } else { result.emplace_back(rect); } } m_screen_dead_space = std::move(result); } // log the dead space rectangles for(size_t i = 0; i < m_screen_dead_space.size(); ++i) { Rect &rect = m_screen_dead_space[i]; Logger::LogInfo("[X11Input::Init] " + Logger::tr("Dead space %1:").arg(i) + " x1 = " + QString::number(rect.m_x1) + ", y1 = " + QString::number(rect.m_y1) + ", x2 = " + QString::number(rect.m_x2) + ", y2 = " + QString::number(rect.m_y2)); } /*qDebug() << "m_screen_dead_space:"; for(Rect &rect : m_screen_dead_space) { qDebug() << " rect" << rect.m_x1 << rect.m_y1 << rect.m_x2 << rect.m_y2; }*/ } void X11Input::InputThread() { try { Logger::LogInfo("[X11Input::InputThread] " + Logger::tr("Input thread started.")); unsigned int grab_x = m_x, grab_y = m_y, grab_width = m_width, grab_height = m_height; bool has_initial_cursor = false; int64_t last_timestamp = hrt_time_micro(); while(!m_should_stop) { // sleep int64_t next_timestamp = CalculateNextVideoTimestamp(); int64_t timestamp = hrt_time_micro(); if(next_timestamp == SINK_TIMESTAMP_NONE) { usleep(20000); continue; } else if(next_timestamp != SINK_TIMESTAMP_ASAP) { int64_t wait = next_timestamp - timestamp; if(wait > 21000) { // the thread can't sleep for too long because it still has to check the m_should_stop flag periodically usleep(20000); continue; } else if(wait > 0) { usleep(wait); timestamp = hrt_time_micro(); } } // follow the cursor if(m_follow_cursor) { int mouse_x, mouse_y, dummy; Window dummy_win; unsigned int dummy_mask; if(XQueryPointer(m_x11_display, m_x11_root, &dummy_win, &dummy_win, &dummy, &dummy, &mouse_x, &mouse_y, &dummy_mask)) { if(m_follow_fullscreen) { for(Rect &rect : m_screen_rects) { if(mouse_x >= (int) rect.m_x1 && mouse_y >= (int) rect.m_y1 && mouse_x < (int) rect.m_x2 && mouse_y < (int) rect.m_y2) { grab_x = rect.m_x1; grab_y = rect.m_y1; grab_width = rect.m_x2 - rect.m_x1; grab_height = rect.m_y2 - rect.m_y1; break; } } } else { int grab_x_target = (mouse_x - (int) grab_width / 2) >> 1; int grab_y_target = (mouse_y - (int) grab_height / 2) >> 1; int frac = (has_initial_cursor)? lrint(1024.0 * exp(-1e-5 * (double) (timestamp - last_timestamp))) : 0; grab_x_target = (grab_x_target + ((int) (grab_x >> 1) - grab_x_target) * frac / 1024) << 1; grab_y_target = (grab_y_target + ((int) (grab_y >> 1) - grab_y_target) * frac / 1024) << 1; grab_x = clamp(grab_x_target, (int) m_screen_bbox.m_x1, (int) m_screen_bbox.m_x2 - (int) grab_width); grab_y = clamp(grab_y_target, (int) m_screen_bbox.m_y1, (int) m_screen_bbox.m_y2 - (int) grab_height); } } has_initial_cursor = true; } // save current size { SharedLock lock(&m_shared_data); if(lock->m_current_x != grab_x || lock->m_current_y != grab_y || lock->m_current_width != grab_width || lock->m_current_height != grab_height) { lock->m_current_x = grab_x; lock->m_current_y = grab_y; lock->m_current_width = grab_width; lock->m_current_height = grab_height; emit CurrentRectangleChanged(); } } // get the image if(m_x11_use_shm) { AllocateImage(grab_width, grab_height); if(!XShmGetImage(m_x11_display, m_x11_root, m_x11_image, grab_x, grab_y, AllPlanes)) { Logger::LogError("[X11Input::InputThread] " + Logger::tr("Error: Can't get image (using shared memory)!\n" " Usually this means the recording area is not completely inside the screen. Or did you change the screen resolution?")); throw X11Exception(); } } else { if(m_x11_image != NULL) { XDestroyImage(m_x11_image); m_x11_image = NULL; } m_x11_image = XGetImage(m_x11_display, m_x11_root, grab_x, grab_y, grab_width, grab_height, AllPlanes, ZPixmap); if(m_x11_image == NULL) { Logger::LogError("[X11Input::InputThread] " + Logger::tr("Error: Can't get image (not using shared memory)!\n" " Usually this means the recording area is not completely inside the screen. Or did you change the screen resolution?")); throw X11Exception(); } } // clear the dead space for(size_t i = 0; i < m_screen_dead_space.size(); ++i) { Rect rect = m_screen_dead_space[i]; if(rect.m_x1 < grab_x) rect.m_x1 = grab_x; if(rect.m_y1 < grab_y) rect.m_y1 = grab_y; if(rect.m_x2 > grab_x + grab_width) rect.m_x2 = grab_x + grab_width; if(rect.m_y2 > grab_y + grab_height) rect.m_y2 = grab_y + grab_height; if(rect.m_x2 > rect.m_x1 && rect.m_y2 > rect.m_y1) X11ImageClearRectangle(m_x11_image, rect.m_x1 - grab_x, rect.m_y1 - grab_y, rect.m_x2 - rect.m_x1, rect.m_y2 - rect.m_y1); } // draw the cursor if(m_record_cursor) { X11ImageDrawCursor(m_x11_display, m_x11_image, grab_x, grab_y); } // increase the frame counter ++m_frame_counter; // push the frame const uint8_t *image_data[1] = {(uint8_t*) m_x11_image->data}; int image_stride[1] = {m_x11_image->bytes_per_line}; AVPixelFormat x11_image_format = X11ImageGetPixelFormat(m_x11_image); PushVideoFrame(grab_width, grab_height, image_data, image_stride, x11_image_format, SWS_CS_DEFAULT, timestamp); last_timestamp = timestamp; } Logger::LogInfo("[X11Input::InputThread] " + Logger::tr("Input thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[X11Input::InputThread] " + Logger::tr("Exception '%1' in input thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[X11Input::InputThread] " + Logger::tr("Unknown exception in input thread.")); } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Input/X11Input.h000066400000000000000000000055721470053624300235010ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "SourceSink.h" #include "MutexDataPair.h" class X11Input : public QObject, public VideoSource { Q_OBJECT private: struct Rect { unsigned int m_x1, m_y1, m_x2, m_y2; inline Rect() {} inline Rect(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) : m_x1(x1), m_y1(y1), m_x2(x2), m_y2(y2) {} }; struct SharedData { unsigned int m_current_x, m_current_y, m_current_width, m_current_height; }; typedef MutexDataPair::Lock SharedLock; private: unsigned int m_x, m_y, m_width, m_height; bool m_record_cursor, m_follow_cursor, m_follow_fullscreen; std::atomic m_frame_counter; int64_t m_fps_last_timestamp; uint32_t m_fps_last_counter; double m_fps_current; Display *m_x11_display; int m_x11_screen; Window m_x11_root; Visual *m_x11_visual; int m_x11_depth; bool m_x11_use_shm; XImage *m_x11_image; XShmSegmentInfo m_x11_shm_info; bool m_x11_shm_server_attached; Rect m_screen_bbox; std::vector m_screen_rects; std::vector m_screen_dead_space; std::thread m_thread; MutexDataPair m_shared_data; std::atomic m_should_stop, m_error_occurred; public: X11Input(unsigned int x, unsigned int y, unsigned int width, unsigned int height, bool record_cursor, bool follow_cursor, bool follow_fullscreen); ~X11Input(); // Reads the current recording rectangle. // This function is thread-safe. void GetCurrentRectangle(unsigned int* x, unsigned int* y, unsigned int* width, unsigned int* height); // Reads the current size of the stream. // This function is thread-safe. void GetCurrentSize(unsigned int* width, unsigned int* height); // Returns the total number of captured frames. // This function is thread-safe. double GetFPS(); // Returns whether an error has occurred in the input thread. // This function is thread-safe. inline bool HasErrorOccurred() { return m_error_occurred; } private: void Init(); void Free(); private: void AllocateImage(unsigned int width, unsigned int height); void FreeImage(); void UpdateScreenConfiguration(); private: void InputThread(); signals: void CurrentRectangleChanged(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/000077500000000000000000000000001470053624300221275ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/AudioEncoder.cpp000066400000000000000000000220441470053624300251760ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "AudioEncoder.h" #include "Logger.h" #include "AVWrapper.h" #include "Muxer.h" const std::vector AudioEncoder::SUPPORTED_SAMPLE_FORMATS = { {"f32i", AV_SAMPLE_FMT_FLT}, {"s16i", AV_SAMPLE_FMT_S16}, #if SSR_USE_AVUTIL_PLANAR_SAMPLE_FMT {"f32p", AV_SAMPLE_FMT_FLTP}, {"s16p", AV_SAMPLE_FMT_S16P}, #endif }; const unsigned int AudioEncoder::DEFAULT_FRAME_SAMPLES = 1024; AudioEncoder::AudioEncoder(Muxer* muxer, AVStream* stream, AVCodecContext *codec_context, AVCodec* codec, AVDictionary** options) : BaseEncoder(muxer, stream, codec_context, codec, options) { #if !SSR_USE_AVCODEC_ENCODE_AUDIO2 // allocate a temporary buffer if(GetCodecContext()->frame_size <= 1) { // This is really weird, the old API uses the size of the *output* buffer to determine the number of // input samples if the number of input samples (i.e. frame_size) is not fixed (i.e. frame_size <= 1). m_temp_buffer.resize(DEFAULT_FRAME_SAMPLES * GetChannels() * av_get_bits_per_sample(GetCodecContext()->codec_id) / 8); } else { m_temp_buffer.resize(std::max(FF_MIN_BUFFER_SIZE, 256 * 1024)); } #endif StartThread(); } AudioEncoder::~AudioEncoder() { StopThread(); } unsigned int AudioEncoder::GetFrameSize() { #if SSR_USE_AVCODEC_ENCODE_AUDIO2 return (GetCodecContext()->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)? DEFAULT_FRAME_SAMPLES : GetCodecContext()->frame_size; #else return (GetCodecContext()->frame_size <= 1)? DEFAULT_FRAME_SAMPLES : GetCodecContext()->frame_size; #endif } AVSampleFormat AudioEncoder::GetSampleFormat() { return GetCodecContext()->sample_fmt; } unsigned int AudioEncoder::GetChannels() { #if SSR_USE_AV_CHANNEL_LAYOUT return GetCodecContext()->ch_layout.nb_channels; #else return GetCodecContext()->channels; #endif } unsigned int AudioEncoder::GetSampleRate() { return GetCodecContext()->sample_rate; } bool AudioEncoder::AVCodecIsSupported(const QString& codec_name) { // we have to break const correctness for compatibility with older ffmpeg versions AVCodec *codec = (AVCodec*) avcodec_find_encoder_by_name(codec_name.toUtf8().constData()); if(codec == NULL) return false; if(!av_codec_is_encoder(codec)) return false; if(codec->type != AVMEDIA_TYPE_AUDIO) return false; for(unsigned int i = 0; i < SUPPORTED_SAMPLE_FORMATS.size(); ++i) { if(AVCodecSupportsSampleFormat(codec, SUPPORTED_SAMPLE_FORMATS[i].m_format)) { //qDebug() << codec_name << "supported by" << SUPPORTED_SAMPLE_FORMATS[i].m_name; return true; } } return false; } void AudioEncoder::PrepareStream(AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options, const std::vector >& codec_options, unsigned int bit_rate, unsigned int channels, unsigned int sample_rate) { if(channels == 0) { Logger::LogError("[AudioEncoder::PrepareStream] " + Logger::tr("Error: Channel count is zero.")); throw LibavException(); } if(sample_rate == 0) { Logger::LogError("[AudioEncoder::PrepareStream] " + Logger::tr("Error: Sample rate is zero.")); throw LibavException(); } codec_context->bit_rate = bit_rate; #if SSR_USE_AV_CHANNEL_LAYOUT av_channel_layout_default(&codec_context->ch_layout, channels); #else codec_context->channels = channels; codec_context->channel_layout = (channels == 1)? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO; #endif codec_context->sample_rate = sample_rate; codec_context->time_base.num = 1; codec_context->time_base.den = sample_rate; #if SSR_USE_AVSTREAM_TIME_BASE stream->time_base = codec_context->time_base; #endif codec_context->thread_count = 1; // parse options QString sample_format_name; for(unsigned int i = 0; i < codec_options.size(); ++i) { const QString &key = codec_options[i].first, &value = codec_options[i].second; if(key == "threads") { codec_context->thread_count = ParseCodecOptionInt(key, value, 1, 100); } else if(key == "qscale") { codec_context->flags |= AV_CODEC_FLAG_QSCALE; codec_context->global_quality = lrint(ParseCodecOptionDouble(key, value, -1.0e6, 1.0e6, FF_QP2LAMBDA)); } else if(key == "sampleformat") { sample_format_name = value; } else { av_dict_set(options, key.toUtf8().constData(), value.toUtf8().constData(), 0); } } // choose the sample format codec_context->sample_fmt = AV_SAMPLE_FMT_NONE; for(unsigned int i = 0; i < SUPPORTED_SAMPLE_FORMATS.size(); ++i) { if(!sample_format_name.isEmpty() && sample_format_name != SUPPORTED_SAMPLE_FORMATS[i].m_name) continue; if(!AVCodecSupportsSampleFormat(codec, SUPPORTED_SAMPLE_FORMATS[i].m_format)) continue; Logger::LogInfo("[AudioEncoder::PrepareStream] " + Logger::tr("Using sample format %1.").arg(SUPPORTED_SAMPLE_FORMATS[i].m_name)); codec_context->sample_fmt = SUPPORTED_SAMPLE_FORMATS[i].m_format; break; } if(codec_context->sample_fmt == AV_SAMPLE_FMT_NONE) { Logger::LogError("[AudioEncoder::PrepareStream] " + Logger::tr("Error: Encoder requires an unsupported sample format!")); throw LibavException(); } } bool AudioEncoder::EncodeFrame(AVFrameWrapper* frame) { if(frame != NULL) { #if SSR_USE_AVFRAME_NB_SAMPLES assert((unsigned int) frame->GetFrame()->nb_samples == GetFrameSize()); #endif #if SSR_USE_AVFRAME_CHANNELS #if SSR_USE_AV_CHANNEL_LAYOUT assert(frame->GetFrame()->ch_layout.nb_channels == GetCodecContext()->ch_layout.nb_channels); #else assert(frame->GetFrame()->channels == GetCodecContext()->channels); #endif #endif #if SSR_USE_AVFRAME_SAMPLE_RATE assert(frame->GetFrame()->sample_rate == GetCodecContext()->sample_rate); #endif #if SSR_USE_AVFRAME_FORMAT assert(frame->GetFrame()->format == GetCodecContext()->sample_fmt); #endif } #if SSR_USE_AVCODEC_SEND_RECEIVE // send a frame AVFrame *avframe = (frame == NULL)? NULL : frame->Release(); try { if(avcodec_send_frame(GetCodecContext(), avframe) < 0) { Logger::LogError("[AudioEncoder::EncodeFrame] " + Logger::tr("Error: Sending of audio frame failed!")); throw LibavException(); } } catch(...) { av_frame_free(&avframe); throw; } av_frame_free(&avframe); // try to receive a packet for( ; ; ) { std::unique_ptr packet(new AVPacketWrapper()); int res = avcodec_receive_packet(GetCodecContext(), packet->GetPacket()); if(res == 0) { // we have a packet, send the packet to the muxer GetMuxer()->AddPacket(GetStream()->index, std::move(packet)); IncrementPacketCounter(); } else if(res == AVERROR(EAGAIN)) { // we have no packet return true; } else if(res == AVERROR_EOF) { // this is the end of the stream return false; } else { Logger::LogError("[AudioEncoder::EncodeFrame] " + Logger::tr("Error: Receiving of audio packet failed!")); throw LibavException(); } } #elif SSR_USE_AVCODEC_ENCODE_AUDIO2 // allocate a packet std::unique_ptr packet(new AVPacketWrapper()); // encode the frame int got_packet; if(avcodec_encode_audio2(GetCodecContext(), packet->GetPacket(), (frame == NULL)? NULL : frame->GetFrame(), &got_packet) < 0) { Logger::LogError("[AudioEncoder::EncodeFrame] " + Logger::tr("Error: Encoding of audio frame failed!")); throw LibavException(); } // do we have a packet? if(got_packet) { // send the packet to the muxer GetMuxer()->AddPacket(GetStream()->index, std::move(packet)); IncrementPacketCounter(); return true; } else { return false; } #else // encode the frame short *data = (frame == NULL)? NULL : (short*) frame->GetFrame()->data[0]; int bytes_encoded = avcodec_encode_audio(GetCodecContext(), m_temp_buffer.data(), m_temp_buffer.size(), data); if(bytes_encoded < 0) { Logger::LogError("[AudioEncoder::EncodeFrame] " + Logger::tr("Error: Encoding of audio frame failed!")); throw LibavException(); } // do we have a packet? if(bytes_encoded > 0) { // allocate a packet std::unique_ptr packet(new AVPacketWrapper(bytes_encoded)); // copy the data memcpy(packet->GetPacket()->data, m_temp_buffer.data(), bytes_encoded); // set the timestamp // note: pts will be rescaled and stream_index will be set by Muxer if(GetCodecContext()->coded_frame != NULL && GetCodecContext()->coded_frame->pts != (int64_t) AV_NOPTS_VALUE) packet->GetPacket()->pts = GetCodecContext()->coded_frame->pts; // send the packet to the muxer GetMuxer()->AddPacket(GetStream()->index, std::move(packet)); IncrementPacketCounter(); return true; } else { return false; } #endif } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/AudioEncoder.h000066400000000000000000000037701470053624300246500ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "BaseEncoder.h" class AudioEncoder : public BaseEncoder { private: struct SampleFormatData { QString m_name; AVSampleFormat m_format; }; private: static const std::vector SUPPORTED_SAMPLE_FORMATS; static const unsigned int DEFAULT_FRAME_SAMPLES; private: #if !SSR_USE_AVCODEC_ENCODE_AUDIO2 std::vector m_temp_buffer; #endif public: AudioEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options); ~AudioEncoder(); // Returns the required frame size, i.e. the number of samples (for each channel). unsigned int GetFrameSize(); // Returns the required sample format. AVSampleFormat GetSampleFormat(); // Returns the number of audio channels. unsigned int GetChannels(); // Returns the audio sample rate. unsigned int GetSampleRate(); public: static bool AVCodecIsSupported(const QString& codec_name); static void PrepareStream(AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options, const std::vector >& codec_options, unsigned int bit_rate, unsigned int channels, unsigned int sample_rate); private: virtual bool EncodeFrame(AVFrameWrapper* frame) override; }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/BaseEncoder.cpp000066400000000000000000000156101470053624300250100ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "BaseEncoder.h" #include "Logger.h" #include "AVWrapper.h" #include "Muxer.h" int ParseCodecOptionInt(const QString& key, const QString& value, int min, int max, int multiply) { bool parsed; int value_int = value.toInt(&parsed); if(!parsed) { Logger::LogError("[ParseCodecOptionInt] " + Logger::tr("Error: Option '%1' could not be parsed!").arg(key)); throw LibavException(); } return clamp(value_int, min, max) * multiply; } double ParseCodecOptionDouble(const QString& key, const QString& value, double min, double max, double multiply) { bool parsed; double value_double = value.toDouble(&parsed); if(!parsed) { Logger::LogError("[ParseCodecOptionDouble] " + Logger::tr("Error: Option '%1' could not be parsed!").arg(key)); throw LibavException(); } return clamp(value_double, min, max) * multiply; } BaseEncoder::BaseEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options) { m_muxer = muxer; m_stream = stream; m_codec_context = codec_context; m_codec_opened = false; // initialize shared data { SharedLock lock(&m_shared_data); lock->m_total_frames = 0; lock->m_total_packets = 0; lock->m_stats_actual_frame_rate = 0.0; lock->m_stats_previous_pts = AV_NOPTS_VALUE; lock->m_stats_previous_frames = 0; } // initialize thread signals m_should_stop = false; m_should_finish = false; m_is_done = false; m_error_occurred = false; try { Init(codec, options); } catch(...) { Free(); throw; } } BaseEncoder::~BaseEncoder() { Free(); } // Why can't this be done in the constructor/destructor? The problem is that a real destructor gets called *after* the derived class has already been destructed. // This means virtual function calls, like EncodeFrame, will fail. This is a problem since the encoder thread doesn't know that the derived class is gone. // A similar thing can happen with the constructor. To fix this, the derived class should call StartThread() and StopThread() manually. void BaseEncoder::StartThread() { // start encoder thread m_thread = std::thread(&BaseEncoder::EncoderThread, this); } void BaseEncoder::StopThread() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[BaseEncoder::~BaseEncoder] " + Logger::tr("Stopping encoder thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } double BaseEncoder::GetActualFrameRate() { SharedLock lock(&m_shared_data); return lock->m_stats_actual_frame_rate; } uint64_t BaseEncoder::GetTotalFrames() { SharedLock lock(&m_shared_data); return lock->m_total_frames; } unsigned int BaseEncoder::GetFrameLatency() { SharedLock lock(&m_shared_data); return (lock->m_total_frames > lock->m_total_packets)? lock->m_total_frames - lock->m_total_packets : 0; } unsigned int BaseEncoder::GetQueuedFrameCount() { SharedLock lock(&m_shared_data); return lock->m_frame_queue.size(); } unsigned int BaseEncoder::GetQueuedPacketCount() { return GetMuxer()->GetQueuedPacketCount(GetStream()->index); } void BaseEncoder::AddFrame(std::unique_ptr frame) { assert(frame->GetFrame()->pts != (int64_t) AV_NOPTS_VALUE); SharedLock lock(&m_shared_data); ++lock->m_total_frames; if(lock->m_stats_previous_pts == (int64_t) AV_NOPTS_VALUE) { lock->m_stats_previous_pts = frame->GetFrame()->pts; lock->m_stats_previous_frames = lock->m_total_frames; } double timedelta = (double) (frame->GetFrame()->pts - lock->m_stats_previous_pts) * ToDouble(m_codec_context->time_base); if(timedelta > 0.999999) { lock->m_stats_actual_frame_rate = (double) (lock->m_total_frames - lock->m_stats_previous_frames) / timedelta; lock->m_stats_previous_pts = frame->GetFrame()->pts; lock->m_stats_previous_frames = lock->m_total_frames; } lock->m_frame_queue.push_back(std::move(frame)); } void BaseEncoder::Finish() { m_should_finish = true; } void BaseEncoder::Stop() { m_should_stop = true; } void BaseEncoder::IncrementPacketCounter() { SharedLock lock(&m_shared_data); ++lock->m_total_packets; } void BaseEncoder::Init(AVCodec* codec, AVDictionary** options) { // open codec if(avcodec_open2(m_codec_context, codec, options) < 0) { Logger::LogError("[BaseEncoder::Init] " + Logger::tr("Error: Can't open codec!")); throw LibavException(); } m_codec_opened = true; // show a warning for every option that wasn't recognized AVDictionaryEntry *t = NULL; while((t = av_dict_get(*options, "", t, AV_DICT_IGNORE_SUFFIX)) != NULL) { Logger::LogWarning("[BaseEncoder::Init] " + Logger::tr("Warning: Codec option '%1' was not recognised!").arg(t->key)); } } void BaseEncoder::Free() { if(m_codec_opened) { #if !SSR_USE_AVCODEC_CLOSE_DEPRECATED avcodec_close(m_codec_context); #endif m_codec_opened = false; } } void BaseEncoder::EncoderThread() { try { Logger::LogInfo("[BaseEncoder::EncoderThread] " + Logger::tr("Encoder thread started.")); // normal encoding while(!m_should_stop) { // get a frame std::unique_ptr frame; { SharedLock lock(&m_shared_data); if(!lock->m_frame_queue.empty()) { frame = std::move(lock->m_frame_queue.front()); lock->m_frame_queue.pop_front(); } } if(frame == NULL) { if(m_should_finish) { break; } usleep(20000); continue; } // encode the frame EncodeFrame(frame.get()); } // flush the encoder if(!m_should_stop && (m_codec_context->codec->capabilities & AV_CODEC_CAP_DELAY)) { Logger::LogInfo("[BaseEncoder::EncoderThread] " + Logger::tr("Flushing encoder ...")); while(!m_should_stop) { if(!EncodeFrame(NULL)) { break; } } } // tell the others that we're done m_is_done = true; Logger::LogInfo("[BaseEncoder::EncoderThread] " + Logger::tr("Encoder thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[BaseEncoder::EncoderThread] " + Logger::tr("Exception '%1' in encoder thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[BaseEncoder::EncoderThread] " + Logger::tr("Unknown exception in encoder thread.")); } // always end the stream, even if there was an error, otherwise the muxer will wait forever m_muxer->EndStream(m_stream->index); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/BaseEncoder.h000066400000000000000000000101461470053624300244540ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "AVWrapper.h" #include "MutexDataPair.h" int ParseCodecOptionInt(const QString& key, const QString& value, int min, int max, int multiply = 1); double ParseCodecOptionDouble(const QString& key, const QString& value, double min, double max, double multiply = 1.0); class Muxer; class BaseEncoder { private: struct SharedData { std::deque > m_frame_queue; uint64_t m_total_frames, m_total_packets; double m_stats_actual_frame_rate; int64_t m_stats_previous_pts; uint64_t m_stats_previous_frames; }; typedef MutexDataPair::Lock SharedLock; private: Muxer *m_muxer; AVStream *m_stream; AVCodecContext *m_codec_context; bool m_codec_opened; std::thread m_thread; MutexDataPair m_shared_data; std::atomic m_should_stop, m_should_finish, m_is_done, m_error_occurred; protected: BaseEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options); public: virtual ~BaseEncoder(); // encoders will be deleted by Muxer, don't delete them yourself! protected: void StartThread(); void StopThread(); // important: call this in the destructor of the derived class public: // Returns the frame rate of the output stream. // This function is thread-safe. double GetActualFrameRate(); // Returns the total number of added frames. // This function is thread-safe. uint64_t GetTotalFrames(); // Returns the current input-to-output latency of the encoder (in frames). // This function is thread-safe. unsigned int GetFrameLatency(); // Returns the total number of frames in the queue. // This function is thread-safe. unsigned int GetQueuedFrameCount(); unsigned int GetQueuedPacketCount(); public: // internal // Adds a frame to the frame queue. Called by the synchronizer. // This function is thread-safe. void AddFrame(std::unique_ptr frame); // Tells the encoder to stop. It can still take some time before the encoder is actually done. Called by the muxer. // After calling this function, the muxer will wait until either IsDone or HasErrorOccurred returns true. // This function is thread-safe. void Finish(); // Same as finish, except that queued frames will be dropped and the encoder won't be flushed. Called by the muxer. // This function is thread-safe. void Stop(); // Returns whether the encoding is done. If this returns true, the object can be deleted. Called by the muxer. // Note: If an error occurred during encoding, this function will return false. // This function is thread-safe and lock-free. inline bool IsDone() { return m_is_done; } // Returns whether an error has occurred in the input thread. Called by the muxer. // This function is thread-safe and lock-free. inline bool HasErrorOccurred() { return m_error_occurred; } inline Muxer* GetMuxer() { return m_muxer; } inline AVStream* GetStream() { return m_stream; } inline AVCodecContext* GetCodecContext() { return m_codec_context; } protected: // Called by the encoder thread to encode a single frame. Frame can be NULL if the encoder uses delayed packets. // Returns whether a packet was created. virtual bool EncodeFrame(AVFrameWrapper* frame) = 0; // Called to increment the packet counter. void IncrementPacketCounter(); private: void Init(AVCodec* codec, AVDictionary** options); void Free(); void EncoderThread(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/Muxer.cpp000066400000000000000000000345451470053624300237460ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "Muxer.h" #include "Logger.h" #include "AVWrapper.h" #include "BaseEncoder.h" #include "VideoEncoder.h" #include "AudioEncoder.h" Muxer::Muxer(const QString& container_name, const QString& output_file) { m_container_name = container_name; m_output_file = output_file; m_format_context = NULL; m_started = false; // initialize stream data for(int i = 0; i < MUXER_MAX_STREAMS; ++i) { StreamLock lock(&m_stream_data[i]); lock->m_is_done = false; m_encoders[i] = NULL; } // initialize shared data { SharedLock lock(&m_shared_data); lock->m_total_bytes = 0; lock->m_stats_actual_bit_rate = 0.0; lock->m_stats_previous_time = NOPTS_DOUBLE; lock->m_stats_previous_bytes = 0; } // initialize thread signals m_is_done = false; m_error_occurred = false; try { Init(); } catch(...) { Free(); throw; } } Muxer::~Muxer() { if(m_started) { // stop the encoders Logger::LogInfo("[Muxer::~Muxer] " + Logger::tr("Stopping encoders ...")); for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { m_encoders[i]->Stop(); // no deadlock: nothing in Muxer is locked in this thread (and BaseEncoder::Stop is lock-free, but that could change) } // wait for the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[Muxer::~Muxer] " + Logger::tr("Waiting for muxer thread to stop ...")); m_thread.join(); } } // free everything Free(); } VideoEncoder* Muxer::AddVideoEncoder(const QString& codec_name, const std::vector >& codec_options, unsigned int bit_rate, unsigned int width, unsigned int height, unsigned int frame_rate) { AVCodec *codec = FindCodec(codec_name); AVCodecContext *codec_context = NULL; AVStream *stream = AddStream(codec, &codec_context); VideoEncoder *encoder; AVDictionary *options = NULL; try { VideoEncoder::PrepareStream(stream, codec_context, codec, &options, codec_options, bit_rate, width, height, frame_rate); m_encoders[stream->index] = encoder = new VideoEncoder(this, stream, codec_context, codec, &options); #if SSR_USE_AVSTREAM_CODECPAR if(avcodec_parameters_from_context(stream->codecpar, codec_context) < 0) { Logger::LogError("[Muxer::AddVideoEncoder] " + Logger::tr("Error: Can't copy parameters to stream!")); throw LibavException(); } #endif av_dict_free(&options); } catch(...) { av_dict_free(&options); throw; } return encoder; } AudioEncoder* Muxer::AddAudioEncoder(const QString& codec_name, const std::vector >& codec_options, unsigned int bit_rate, unsigned int channels, unsigned int sample_rate) { AVCodec *codec = FindCodec(codec_name); AVCodecContext *codec_context = NULL; AVStream *stream = AddStream(codec, &codec_context); AudioEncoder *encoder; AVDictionary *options = NULL; try { AudioEncoder::PrepareStream(stream, codec_context, codec, &options, codec_options, bit_rate, channels, sample_rate); m_encoders[stream->index] = encoder = new AudioEncoder(this, stream, codec_context, codec, &options); #if SSR_USE_AVSTREAM_CODECPAR if(avcodec_parameters_from_context(stream->codecpar, codec_context) < 0) { Logger::LogError("[Muxer::AddAudioEncoder] " + Logger::tr("Error: Can't copy parameters to stream!")); throw LibavException(); } #endif av_dict_free(&options); } catch(...) { av_dict_free(&options); throw; } return encoder; } void Muxer::Start() { assert(!m_started); // make sure all encoders were created successfully for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { assert(m_encoders[i] != NULL); } // write header if(avformat_write_header(m_format_context, NULL) != 0) { Logger::LogError("[Muxer::Start] " + Logger::tr("Error: Can't write header!", "Don't translate 'header'")); throw LibavException(); } m_started = true; m_thread = std::thread(&Muxer::MuxerThread, this); } void Muxer::Finish() { assert(m_started); Logger::LogInfo("[Muxer::Finish] " + Logger::tr("Finishing encoders ...")); for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { assert(m_encoders[i] != NULL); m_encoders[i]->Finish(); // no deadlock: nothing in Muxer is locked in this thread (and BaseEncoder::Finish is lock-free, but that could change) } } double Muxer::GetActualBitRate() { SharedLock lock(&m_shared_data); return lock->m_stats_actual_bit_rate; } uint64_t Muxer::GetTotalBytes() { SharedLock lock(&m_shared_data); return lock->m_total_bytes; } void Muxer::EndStream(unsigned int stream_index) { assert(stream_index < m_format_context->nb_streams); StreamLock lock(&m_stream_data[stream_index]); lock->m_is_done = true; } void Muxer::AddPacket(unsigned int stream_index, std::unique_ptr packet) { assert(m_started); assert(stream_index < m_format_context->nb_streams); StreamLock lock(&m_stream_data[stream_index]); lock->m_packet_queue.push_back(std::move(packet)); } unsigned int Muxer::GetQueuedPacketCount(unsigned int stream_index) { assert(m_started); assert(stream_index < m_format_context->nb_streams); StreamLock lock(&m_stream_data[stream_index]); return lock->m_packet_queue.size(); } void Muxer::Init() { // get the format we want (this is just a pointer, we don't have to free this) // we have to break const correctness for compatibility with older ffmpeg versions AVOutputFormat *format = (AVOutputFormat*) av_guess_format(m_container_name.toUtf8().constData(), NULL, NULL); if(format == NULL) { Logger::LogError("[Muxer::Init] " + Logger::tr("Error: Can't find chosen output format!")); throw LibavException(); } Logger::LogInfo("[Muxer::Init] " + Logger::tr("Using format %1 (%2).").arg(format->name).arg(format->long_name)); // allocate format context // ffmpeg probably wants us to use avformat_alloc_output_context2 instead, but libav doesn't have it and I can't figure out why it's needed anyway m_format_context = avformat_alloc_context(); if(m_format_context == NULL) { Logger::LogError("[Muxer::Init] " + Logger::tr("Error: Can't allocate format context!")); throw LibavException(); } m_format_context->oformat = format; // open file if(avio_open(&m_format_context->pb, QFile::encodeName(m_output_file).constData(), AVIO_FLAG_WRITE) < 0) { Logger::LogError("[Muxer::Init] " + Logger::tr("Error: Can't open output file!")); throw LibavException(); } } void Muxer::Free() { if(m_format_context != NULL) { // write trailer (needed to free private muxer data) if(m_started) { if(av_write_trailer(m_format_context) != 0) { // we can't throw exceptions here because this is called from the destructor Logger::LogError("[Muxer::Free] " + Logger::tr("Error: Can't write trailer, continuing anyway.", "Don't translate 'trailer'")); } m_started = false; } // destroy the encoders for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { if(m_encoders[i] != NULL) { delete m_encoders[i]; // no deadlock: nothing in Muxer is locked in this thread m_encoders[i] = NULL; } } // close file if(m_format_context->pb != NULL) { avio_close(m_format_context->pb); m_format_context->pb = NULL; } // free everything #if SSR_USE_AVFORMAT_FREE_CONTEXT avformat_free_context(m_format_context); m_format_context = NULL; #else for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { av_freep(&m_format_context->streams[i]->codec); av_freep(&m_format_context->streams[i]); } av_free(m_format_context); m_format_context = NULL; #endif } } AVCodec* Muxer::FindCodec(const QString& codec_name) { // we have to break const correctness for compatibility with older ffmpeg versions AVCodec *codec = (AVCodec*) avcodec_find_encoder_by_name(codec_name.toUtf8().constData()); if(codec == NULL) { Logger::LogError("[Muxer::FindCodec] " + Logger::tr("Error: Can't find codec!")); throw LibavException(); } return codec; } AVStream* Muxer::AddStream(AVCodec* codec, AVCodecContext** codec_context) { assert(!m_started); assert(m_format_context->nb_streams < MUXER_MAX_STREAMS); Logger::LogInfo("[Muxer::AddStream] " + Logger::tr("Using codec %1 (%2).").arg(codec->name).arg(codec->long_name)); // create a new stream #if SSR_USE_AVSTREAM_CODECPAR AVStream *stream = avformat_new_stream(m_format_context, NULL); #elif SSR_USE_AVFORMAT_NEW_STREAM AVStream *stream = avformat_new_stream(m_format_context, codec); #else AVStream *stream = av_new_stream(m_format_context, m_format_context->nb_streams); #endif if(stream == NULL) { Logger::LogError("[Muxer::AddStream] " + Logger::tr("Error: Can't create new stream!")); throw LibavException(); } assert(stream->index == (int) m_format_context->nb_streams - 1); #if SSR_USE_AVSTREAM_CODECPAR *codec_context = avcodec_alloc_context3(codec); if(*codec_context == NULL) { Logger::LogError("[Muxer::AddStream] " + Logger::tr("Error: Can't create new codec context!")); throw LibavException(); } #else assert(stream->codec != NULL); *codec_context = stream->codec; #endif //stream->id = m_format_context->nb_streams - 1; #if !SSR_USE_AVFORMAT_NEW_STREAM // initialize the codec context (only needed for old API) if(avcodec_get_context_defaults3(*codec_context, codec) < 0) { Logger::LogError("[Muxer::AddStream] " + Logger::tr("Error: Can't get codec context defaults!")); throw LibavException(); } (*codec_context)->codec_id = codec->id; (*codec_context)->codec_type = codec->type; #endif // not sure why this is needed, but it's in the example code and it doesn't work without this if(m_format_context->oformat->flags & AVFMT_GLOBALHEADER) (*codec_context)->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // if the codec is experimental, allow it if(codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) { Logger::LogWarning("[Muxer::AddStream] " + Logger::tr("Warning: This codec is considered experimental by libav/ffmpeg.")); (*codec_context)->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; } #if SSR_USE_SIDE_DATA_ONLY_PACKETS && !SSR_USE_SIDE_DATA_ONLY_PACKETS_DEPRECATED // this option was added with the intent to deprecate it again in the next version, // because the ffmpeg/libav devs like deprecating things :) (*codec_context)->side_data_only_packets = 1; #endif return stream; } void Muxer::MuxerThread() { try { Logger::LogInfo("[Muxer::MuxerThread] " + Logger::tr("Muxer thread started.")); double total_time = 0.0; // start muxing for( ; ; ) { // get a packet from a stream that isn't done yet std::unique_ptr packet; unsigned int current_stream = 0, streams_done = 0; for(unsigned int i = 0; i < m_format_context->nb_streams; ++i) { StreamLock lock(&m_stream_data[i]); if(lock->m_packet_queue.empty()) { if(lock->m_is_done) ++streams_done; } else { current_stream = i; packet = std::move(lock->m_packet_queue.front()); lock->m_packet_queue.pop_front(); break; } } // if all streams are done, we can stop if(streams_done == m_format_context->nb_streams) { break; } // if there is no packet, wait and try again later if(packet == NULL) { usleep(20000); continue; } // try to figure out the time (the exact value is not critical, it's only used for bitrate statistics) AVStream *stream = m_encoders[current_stream]->GetStream(); AVCodecContext *codec_context = m_encoders[current_stream]->GetCodecContext(); double packet_time = 0.0; if(packet->GetPacket()->dts != (int64_t) AV_NOPTS_VALUE) packet_time = (double) packet->GetPacket()->dts * ToDouble(codec_context->time_base); else if(packet->GetPacket()->pts != (int64_t) AV_NOPTS_VALUE) packet_time = (double) packet->GetPacket()->pts * ToDouble(codec_context->time_base); if(packet_time > total_time) total_time = packet_time; // prepare packet packet->GetPacket()->stream_index = current_stream; #if SSR_USE_AV_PACKET_RESCALE_TS av_packet_rescale_ts(packet->GetPacket(), codec_context->time_base, stream->time_base); #else if(packet->GetPacket()->pts != (int64_t) AV_NOPTS_VALUE) { packet->GetPacket()->pts = av_rescale_q(packet->GetPacket()->pts, codec_context->time_base, stream->time_base); } if(packet->GetPacket()->dts != (int64_t) AV_NOPTS_VALUE) { packet->GetPacket()->dts = av_rescale_q(packet->GetPacket()->dts, codec_context->time_base, stream->time_base); } #endif // write the packet (again, why does libav/ffmpeg call this a frame?) if(av_interleaved_write_frame(m_format_context, packet->GetPacket()) != 0) { Logger::LogError("[Muxer::MuxerThread] " + Logger::tr("Error: Can't write frame to muxer!")); throw LibavException(); } // the data is now owned by libav/ffmpeg, so don't free it packet->SetFreeOnDestruct(false); // update the byte counter { SharedLock lock(&m_shared_data); lock->m_total_bytes = m_format_context->pb->pos + (m_format_context->pb->buf_ptr - m_format_context->pb->buffer); if(lock->m_stats_previous_time == NOPTS_DOUBLE) { lock->m_stats_previous_time = total_time; lock->m_stats_previous_bytes = lock->m_total_bytes; } double timedelta = total_time - lock->m_stats_previous_time; if(timedelta > 0.999999) { lock->m_stats_actual_bit_rate = (double) ((lock->m_total_bytes - lock->m_stats_previous_bytes) * 8) / timedelta; lock->m_stats_previous_time = total_time; lock->m_stats_previous_bytes = lock->m_total_bytes; } } } // tell the others that we're done m_is_done = true; Logger::LogInfo("[Muxer::MuxerThread] " + Logger::tr("Muxer thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[Muxer::MuxerThread] " + Logger::tr("Exception '%1' in muxer thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[Muxer::MuxerThread] " + Logger::tr("Unknown exception in muxer thread.")); } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/Muxer.h000066400000000000000000000077521470053624300234130ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "MutexDataPair.h" #define MUXER_MAX_STREAMS 2 class AVPacketWrapper; class BaseEncoder; class VideoEncoder; class AudioEncoder; class Muxer { private: struct StreamData { std::deque > m_packet_queue; bool m_is_done; }; typedef MutexDataPair::Lock StreamLock; struct SharedData { uint64_t m_total_bytes; double m_stats_actual_bit_rate; double m_stats_previous_time; uint64_t m_stats_previous_bytes; }; typedef MutexDataPair::Lock SharedLock; static constexpr unsigned int INVALID_STREAM = std::numeric_limits::max(); static constexpr double NOPTS_DOUBLE = -std::numeric_limits::max(); private: QString m_container_name, m_output_file; AVFormatContext *m_format_context; bool m_started; BaseEncoder *m_encoders[MUXER_MAX_STREAMS]; std::thread m_thread; MutexDataPair m_stream_data[MUXER_MAX_STREAMS]; MutexDataPair m_shared_data; std::atomic m_is_done, m_error_occurred; public: Muxer(const QString& container_name, const QString& output_file); ~Muxer(); // Adds a video or audio encoder. VideoEncoder* AddVideoEncoder(const QString& codec_name, const std::vector >& codec_options, unsigned int bit_rate, unsigned int width, unsigned int height, unsigned int frame_rate); AudioEncoder* AddAudioEncoder(const QString& codec_name, const std::vector >& codec_options, unsigned int bit_rate, unsigned int channels, unsigned int sample_rate); // Starts the muxer. You can't create new encoders after calling this function. void Start(); // Tells the muxer to stop. It can take some time before the muxer really stops. void Finish(); // Returns the bit rate of the output stream. // This function is thread-safe. double GetActualBitRate(); // Returns the total number of bytes written to the output file. // This function is thread-safe. uint64_t GetTotalBytes(); // Returns whether the muxing is done. If this returns true, the object can be deleted. // Note: If an error occurred in the mixing thread, this function will return false. // This function is thread-safe and lock-free. inline bool IsDone() { return m_is_done; } // Returns whether an error has occurred in the input thread. // This function is thread-safe and lock-free. inline bool HasErrorOccurred() { return m_error_occurred; } public: inline QString GetOutputFile() { return m_output_file; } public: // internal // Ends the stream (i.e. tells the muxer that it shouldn't wait for more packets). Called by the encoder. // This function is thread-safe. void EndStream(unsigned int stream_index); // Adds a packet to the packet queue of a stream. Called by the encoder. // This function is thread-safe. void AddPacket(unsigned int stream_index, std::unique_ptr packet); // Returns the total number of packets in the queue of a stream. Called by the encoder. // This function is thread-safe. unsigned int GetQueuedPacketCount(unsigned int stream_index); private: void Init(); void Free(); AVCodec* FindCodec(const QString& codec_name); AVStream* AddStream(AVCodec* codec, AVCodecContext** codec_context); void MuxerThread(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/OutputManager.cpp000066400000000000000000000306331470053624300254330ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "OutputManager.h" #include "Logger.h" const size_t OutputManager::THROTTLE_THRESHOLD_FRAMES = 20; const size_t OutputManager::THROTTLE_THRESHOLD_PACKETS = 100; static QString GetNewFragmentFile(const QString& file, unsigned int fragment_number) { QFileInfo fi(file); QString newfile; newfile = fi.path() + "/" + fi.completeBaseName() + QString("-%1").arg(fragment_number, 8, 10, QLatin1Char('0')); if(!fi.suffix().isEmpty()) newfile += "." + fi.suffix(); return newfile; } OutputManager::OutputManager(const OutputSettings& output_settings) { m_output_settings = output_settings; m_fragmented = false; m_fragment_length = 5; // initialize shared data { SharedLock lock(&m_shared_data); lock->m_fragment_number = 0; lock->m_video_encoder = NULL; lock->m_audio_encoder = NULL; } // initialize thread signals m_should_stop = false; m_should_finish = false; m_is_done = false; m_error_occurred = false; try { Init(); } catch(...) { Free(); throw; } } OutputManager::~OutputManager() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[OutputManager::~OutputManager] " + Logger::tr("Stopping fragment thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } void OutputManager::Finish() { // stop the synchronizer if(m_synchronizer != NULL) { m_synchronizer->NewSegment(); // needed to make sure that all data is sent to the encoders m_synchronizer.reset(); } // after this, we still have to wait until IsFinished() returns true or else the file will be corrupted. if(m_fragmented) { m_should_finish = true; } else { SharedLock lock(&m_shared_data); assert(lock->m_muxer != NULL); lock->m_muxer->Finish(); } } bool OutputManager::IsFinished() { SharedLock lock(&m_shared_data); if(m_fragmented) { return (m_is_done || m_error_occurred); } else { assert(lock->m_muxer != NULL); return (lock->m_muxer->IsDone() || lock->m_muxer->HasErrorOccurred()); } } void OutputManager::AddVideoFrame(std::unique_ptr frame) { assert(frame->GetFrame()->pts != (int64_t) AV_NOPTS_VALUE); SharedLock lock(&m_shared_data); if(m_fragmented) { int64_t fragment_begin = m_fragment_length * m_output_format.m_video_frame_rate * (lock->m_fragment_number - 1); int64_t fragment_end = m_fragment_length * m_output_format.m_video_frame_rate * lock->m_fragment_number; if(frame->GetFrame()->pts < fragment_end) { frame->GetFrame()->pts -= fragment_begin; assert(lock->m_video_encoder != NULL); lock->m_video_encoder->AddFrame(std::move(frame)); } else { lock->m_video_frame_queue.push_back(std::move(frame)); } } else { assert(lock->m_video_encoder != NULL); lock->m_video_encoder->AddFrame(std::move(frame)); } } void OutputManager::AddAudioFrame(std::unique_ptr frame) { assert(frame->GetFrame()->pts != (int64_t) AV_NOPTS_VALUE); SharedLock lock(&m_shared_data); if(m_fragmented) { int64_t fragment_begin = m_fragment_length * m_output_format.m_audio_sample_rate * (lock->m_fragment_number - 1); int64_t fragment_end = m_fragment_length * m_output_format.m_audio_sample_rate * lock->m_fragment_number; if(frame->GetFrame()->pts < fragment_end) { frame->GetFrame()->pts -= fragment_begin; assert(lock->m_audio_encoder != NULL); lock->m_audio_encoder->AddFrame(std::move(frame)); } else { lock->m_audio_frame_queue.push_back(std::move(frame)); } } else { assert(lock->m_audio_encoder != NULL); lock->m_audio_encoder->AddFrame(std::move(frame)); } } int64_t OutputManager::GetVideoFrameDelay() { unsigned int frames = 0, packets = 0; { SharedLock lock(&m_shared_data); frames += lock->m_video_frame_queue.size(); if(lock->m_video_encoder != NULL) { frames += lock->m_video_encoder->GetQueuedFrameCount(); packets += lock->m_video_encoder->GetQueuedPacketCount(); } } int64_t interval = 0; if(frames > THROTTLE_THRESHOLD_FRAMES) { int64_t n = (frames - THROTTLE_THRESHOLD_FRAMES) * 200 / THROTTLE_THRESHOLD_FRAMES; interval += n * n; } if(packets > THROTTLE_THRESHOLD_PACKETS) { int64_t n = (packets - THROTTLE_THRESHOLD_PACKETS) * 200 / THROTTLE_THRESHOLD_PACKETS; interval += n * n; } if(interval > 1000000) interval = 1000000; return interval; } unsigned int OutputManager::GetTotalQueuedFrameCount() { SharedLock lock(&m_shared_data); unsigned int frames = lock->m_video_frame_queue.size(); if(lock->m_video_encoder != NULL) frames += lock->m_video_encoder->GetQueuedFrameCount() + lock->m_video_encoder->GetFrameLatency(); return frames; } double OutputManager::GetActualFrameRate() { SharedLock lock(&m_shared_data); if(lock->m_video_encoder == NULL) return 0.0; return lock->m_video_encoder->GetActualFrameRate(); } double OutputManager::GetActualBitRate() { SharedLock lock(&m_shared_data); if(lock->m_muxer == NULL) return 0.0; return lock->m_muxer->GetActualBitRate(); } uint64_t OutputManager::GetTotalBytes() { SharedLock lock(&m_shared_data); if(lock->m_muxer == NULL) return 0; return lock->m_muxer->GetTotalBytes(); } void OutputManager::Init() { // start muxer and encoders StartFragment(); // save output format for synchronizer (we assume that this will always be the same) { SharedLock lock(&m_shared_data); if(lock->m_video_encoder != NULL) { m_output_format.m_video_enabled = true; m_output_format.m_video_width = lock->m_video_encoder->GetWidth(); m_output_format.m_video_height = lock->m_video_encoder->GetHeight(); m_output_format.m_video_frame_rate = lock->m_video_encoder->GetFrameRate(); m_output_format.m_video_pixel_format = lock->m_video_encoder->GetPixelFormat(); m_output_format.m_video_colorspace = lock->m_video_encoder->GetColorSpace(); } else { m_output_format.m_video_enabled = false; } if(lock->m_audio_encoder != NULL) { m_output_format.m_audio_enabled = true; m_output_format.m_audio_channels = lock->m_audio_encoder->GetChannels(); //TODO// never larger than AV_NUM_DATA_POINTERS assert(m_output_format.m_audio_channels <= AV_NUM_DATA_POINTERS); m_output_format.m_audio_sample_rate = lock->m_audio_encoder->GetSampleRate(); m_output_format.m_audio_frame_size = lock->m_audio_encoder->GetFrameSize(); m_output_format.m_audio_sample_format = lock->m_audio_encoder->GetSampleFormat(); } else { m_output_format.m_audio_enabled = false; } } // start synchronizer m_synchronizer.reset(new Synchronizer(this)); // start fragment thread if(m_fragmented) { m_thread = std::thread(&OutputManager::FragmentThread, this); } } void OutputManager::Free() { // stop the synchronizer m_synchronizer.reset(); // stop the encoders and muxers { SharedLock lock(&m_shared_data); lock->m_video_encoder = NULL; // deleted by muxer lock->m_audio_encoder = NULL; // deleted by muxer lock->m_muxer.reset(); } } void OutputManager::StartFragment() { // get fragment number unsigned int fragment_number = 0; if(m_fragmented) { SharedLock lock(&m_shared_data); fragment_number = lock->m_fragment_number; } // create new muxer and encoders // we can't hold the lock while doing this because this could take some time QString filename; if(m_fragmented) { filename = GetNewFragmentFile(m_output_settings.file, fragment_number); } else { filename = m_output_settings.file; } std::unique_ptr muxer(new Muxer(m_output_settings.container_avname, filename)); VideoEncoder *video_encoder = NULL; AudioEncoder *audio_encoder = NULL; if(!m_output_settings.video_codec_avname.isEmpty()) video_encoder = muxer->AddVideoEncoder(m_output_settings.video_codec_avname, m_output_settings.video_options, m_output_settings.video_kbit_rate * 1000, m_output_settings.video_width, m_output_settings.video_height, m_output_settings.video_frame_rate); if(!m_output_settings.audio_codec_avname.isEmpty()) audio_encoder = muxer->AddAudioEncoder(m_output_settings.audio_codec_avname, m_output_settings.audio_options, m_output_settings.audio_kbit_rate * 1000, m_output_settings.audio_channels, m_output_settings.audio_sample_rate); muxer->Start(); // acquire lock and share the muxer and encoders SharedLock lock(&m_shared_data); lock->m_muxer = std::move(muxer); lock->m_video_encoder = video_encoder; lock->m_audio_encoder = audio_encoder; // increment fragment number // It's important that this is done here (i.e. after the encoders have been set up), because the fragment number // acts as a signal to AddVideoFrame/AddAudioFrame that they can pass frames to the encoders. ++lock->m_fragment_number; // push queued frames to the new encoders if(m_fragmented) { while(!lock->m_video_frame_queue.empty()) { int64_t fragment_begin = m_fragment_length * m_output_format.m_video_frame_rate * (lock->m_fragment_number - 1); int64_t fragment_end = m_fragment_length * m_output_format.m_video_frame_rate * lock->m_fragment_number; if(lock->m_video_frame_queue.front()->GetFrame()->pts >= fragment_end) break; std::unique_ptr frame = std::move(lock->m_video_frame_queue.front()); lock->m_video_frame_queue.pop_front(); frame->GetFrame()->pts -= fragment_begin; video_encoder->AddFrame(std::move(frame)); } while(!lock->m_audio_frame_queue.empty()) { int64_t fragment_begin = m_fragment_length * m_output_format.m_audio_sample_rate * (lock->m_fragment_number - 1); int64_t fragment_end = m_fragment_length * m_output_format.m_audio_sample_rate * lock->m_fragment_number; if(lock->m_audio_frame_queue.front()->GetFrame()->pts >= fragment_end) break; std::unique_ptr frame = std::move(lock->m_audio_frame_queue.front()); lock->m_audio_frame_queue.pop_front(); frame->GetFrame()->pts -= fragment_begin; audio_encoder->AddFrame(std::move(frame)); } } } void OutputManager::StopFragment() { // acquire lock and steal the muxer std::unique_ptr muxer; { SharedLock lock(&m_shared_data); muxer = std::move(lock->m_muxer); lock->m_video_encoder = NULL; // deleted by muxer lock->m_audio_encoder = NULL; // deleted by muxer } // wait until the muxer is finished // we can't hold the lock while doing this because this could take some time assert(muxer != NULL); muxer->Finish(); while(!muxer->IsDone() && !muxer->HasErrorOccurred()) { usleep(200000); } // delete everything muxer.reset(); } void OutputManager::FragmentThread() { try { Logger::LogInfo("[OutputManager::FragmentThread] " + Logger::tr("Fragment thread started.")); while(!m_should_stop) { // should we start a new fragment? bool finishing = m_should_finish, next_fragment = false; { SharedLock lock(&m_shared_data); //TODO// this delays the creation of a new fragment by one frame, this could be improved if(finishing) { next_fragment = (!lock->m_video_frame_queue.empty() || !lock->m_audio_frame_queue.empty()); } else { next_fragment = (!lock->m_video_frame_queue.empty() && !lock->m_audio_frame_queue.empty()); } } // do what needs to be done if(next_fragment) { Logger::LogInfo("[OutputManager::FragmentThread] " + Logger::tr("Next fragment ...")); StopFragment(); StartFragment(); } else if(finishing) { Logger::LogInfo("[OutputManager::FragmentThread] " + Logger::tr("Finishing ...")); StopFragment(); break; } else { usleep(200000); } } // tell the others that we're done m_is_done = true; Logger::LogInfo("[OutputManager::FragmentThread] " + Logger::tr("Fragment thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[OutputManager::FragmentThread] " + Logger::tr("Exception '%1' in fragment thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[OutputManager::FragmentThread] " + Logger::tr("Unknown exception in fragment thread.")); } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/OutputManager.h000066400000000000000000000067251470053624300251050ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "Muxer.h" #include "VideoEncoder.h" #include "AudioEncoder.h" #include "Synchronizer.h" #include "OutputSettings.h" class OutputManager { private: struct SharedData { // frame queue used while switching between fragments std::deque > m_video_frame_queue; std::deque > m_audio_frame_queue; unsigned int m_fragment_number; // muxer and encoders std::unique_ptr m_muxer; VideoEncoder *m_video_encoder; AudioEncoder *m_audio_encoder; }; typedef MutexDataPair::Lock SharedLock; private: static const size_t THROTTLE_THRESHOLD_FRAMES, THROTTLE_THRESHOLD_PACKETS; private: OutputSettings m_output_settings; OutputFormat m_output_format; bool m_fragmented; int64_t m_fragment_length; std::unique_ptr m_synchronizer; std::thread m_thread; MutexDataPair m_shared_data; std::atomic m_should_stop, m_should_finish, m_is_done, m_error_occurred; public: OutputManager(const OutputSettings& output_settings); ~OutputManager(); // Tells the encoders and muxer to finish. After calling this function, you should wait until // IsFinished returns true before deleting the output manager. void Finish(); // Returns whether the encoders and muxer have finished. bool IsFinished(); // Adds a video frame to the frame queue. Called by the synchronizer. // This function is thread-safe. void AddVideoFrame(std::unique_ptr frame); // Adds an audio frame to the frame queue. Called by the synchronizer. // This function is thread-safe. void AddAudioFrame(std::unique_ptr frame); // Returns an additional delay (in us) between frames, based on the queue size, to avoid memory problems. // As long as the queues are relatively small, this function will just return 0. // This function is thread-safe. int64_t GetVideoFrameDelay(); // Returns the total number of frames in the queue. // This function is thread-safe. unsigned int GetTotalQueuedFrameCount(); // Returns the frame rate of the output stream. // This function is thread-safe. double GetActualFrameRate(); // Returns the bit rate of the output stream. // This function is thread-safe. double GetActualBitRate(); // Returns the total number of bytes written to the output file. // This function is thread-safe. uint64_t GetTotalBytes(); private: void Init(); void Free(); void StartFragment(); void StopFragment(); void FragmentThread(); public: inline const OutputSettings* GetOutputSettings() { return &m_output_settings; } inline const OutputFormat* GetOutputFormat() { return &m_output_format; } inline Synchronizer* GetSynchronizer() { return m_synchronizer.get(); } }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/OutputSettings.h000066400000000000000000000030731470053624300253240ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" struct OutputSettings { QString file; QString container_avname; QString video_codec_avname; unsigned int video_kbit_rate; std::vector > video_options; unsigned int video_width, video_height; unsigned int video_frame_rate; bool video_allow_frame_skipping; QString audio_codec_avname; unsigned int audio_kbit_rate; std::vector > audio_options; unsigned int audio_channels, audio_sample_rate; }; struct OutputFormat { bool m_video_enabled; unsigned int m_video_width, m_video_height; unsigned int m_video_frame_rate; AVPixelFormat m_video_pixel_format; int m_video_colorspace; bool m_audio_enabled; unsigned int m_audio_channels, m_audio_sample_rate; unsigned int m_audio_frame_size; AVSampleFormat m_audio_sample_format; }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/SyncDiagram.cpp000066400000000000000000000121551470053624300250400ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "SyncDiagram.h" #include "MainWindow.h" constexpr int SyncDiagram::CHANNEL_HEIGHT, SyncDiagram::CHANNEL_SPACING, SyncDiagram::MARGIN_RIGHT; constexpr double SyncDiagram::PIXELS_PER_SECOND; inline double floormod(double x, double y) { return x - floor(x / y) * y; } SyncDiagram::SyncDiagram(size_t channels) { assert(channels > 0); { SharedLock lock(&m_shared_data); lock->m_time_channels.resize(channels); for(auto &c : lock->m_time_channels) { c.m_name = ""; c.m_current_time = 0.0; c.m_time_shift = std::numeric_limits::max() * 0.5; c.m_time_shift_v = 0.0; } } m_height = CHANNEL_SPACING + (CHANNEL_HEIGHT + CHANNEL_SPACING) * channels; m_font = QFont("Sans"); m_font.setPixelSize(12); setWindowTitle(tr("Synchronization Diagram") + " - " + MainWindow::WINDOW_CAPTION); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_update_timer = new QTimer(this); m_update_timer->setSingleShot(true); connect(this, SIGNAL(NeedsUpdate()), this, SLOT(DelayedUpdate()), Qt::QueuedConnection); connect(m_update_timer, SIGNAL(timeout()), this, SLOT(update())); setMinimumSize(minimumSizeHint()); // workaround for Qt bug } SyncDiagram::~SyncDiagram() { } void SyncDiagram::SetChannelName(size_t channel, const QString& name) { SharedLock lock(&m_shared_data); assert(channel < lock->m_time_channels.size()); lock->m_time_channels[channel].m_name = name; } void SyncDiagram::SetCurrentTime(size_t channel, double current_time) { SharedLock lock(&m_shared_data); assert(channel < lock->m_time_channels.size()); lock->m_time_channels[channel].m_current_time = current_time; double time_min = current_time - (double) (width() - MARGIN_RIGHT) / PIXELS_PER_SECOND; auto &blocks = lock->m_time_channels[channel].m_time_blocks; while(!blocks.empty() && blocks.front().m_time_end < time_min) { blocks.pop_front(); } } void SyncDiagram::AddBlock(size_t channel, double time_begin, double time_end, const QColor& color) { SharedLock lock(&m_shared_data); assert(channel < lock->m_time_channels.size()); lock->m_time_channels[channel].m_time_blocks.push_back(TimeBlock{time_begin, time_end, color}); } void SyncDiagram::Update() { emit NeedsUpdate(); } void SyncDiagram::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); painter.fillRect(rect(), QColor(255, 255, 255)); SharedLock lock(&m_shared_data); int y = CHANNEL_SPACING; for(auto &c : lock->m_time_channels) { // calculate time limits double time_min = c.m_current_time - (double) (width() - MARGIN_RIGHT) / PIXELS_PER_SECOND; double time_max = c.m_current_time + (double) MARGIN_RIGHT / PIXELS_PER_SECOND; double ts = lock->m_time_channels[0].m_current_time - c.m_current_time; if(fabs(ts - c.m_time_shift) > 2.0) { c.m_time_shift = ts; c.m_time_shift_v = 0.0; } else { c.m_time_shift_v += 0.4 * (0.4 * (ts - c.m_time_shift) - 2.0 * c.m_time_shift_v); c.m_time_shift += c.m_time_shift_v; } double pixel_wrap = (double) width(); // draw blocks for(auto &b : c.m_time_blocks) { QColor edge = b.m_color.darker(200), fill = b.m_color; edge.setAlpha(128); fill.setAlpha(64); painter.setPen(edge); painter.setBrush(fill); double x1 = floormod((b.m_time_begin + c.m_time_shift) * (double) PIXELS_PER_SECOND, pixel_wrap); double x2 = x1 + (b.m_time_end - b.m_time_begin) * (double) PIXELS_PER_SECOND; painter.drawRect(QRectF(QPointF(x1, y), QPointF(x2, y + CHANNEL_HEIGHT))); } // draw grid lines painter.setPen(QColor(0, 0, 0)); for(double t = ceil(time_min); t <= time_max; t += 1.0) { double x = floormod((t + c.m_time_shift) * (double) PIXELS_PER_SECOND, pixel_wrap); painter.drawLine(QPointF(x, y - CHANNEL_SPACING / 2), QPointF(x, y + CHANNEL_HEIGHT + CHANNEL_SPACING / 2)); } // draw the current time { double x = floormod((c.m_current_time + c.m_time_shift) * (double) PIXELS_PER_SECOND, pixel_wrap); painter.setPen(QColor(255, 0, 0)); painter.drawLine(QPointF(x, y - CHANNEL_SPACING / 2), QPointF(x, y + CHANNEL_HEIGHT + CHANNEL_SPACING / 2)); } // draw channel name painter.setPen(QColor(0, 0, 0)); painter.setFont(m_font); painter.drawText(10, y, width() - 10, CHANNEL_HEIGHT, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, c.m_name); y += CHANNEL_HEIGHT + CHANNEL_SPACING; } } void SyncDiagram::DelayedUpdate() { if(!m_update_timer->isActive()) m_update_timer->start(50); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/SyncDiagram.h000066400000000000000000000040471470053624300245060ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "MutexDataPair.h" class SyncDiagram : public QWidget { Q_OBJECT private: struct TimeBlock { double m_time_begin, m_time_end; QColor m_color; }; struct TimeChannel { QString m_name; double m_current_time, m_time_shift, m_time_shift_v; std::deque m_time_blocks; }; struct SharedData { std::vector m_time_channels; }; typedef MutexDataPair::Lock SharedLock; private: static constexpr int CHANNEL_HEIGHT = 20, CHANNEL_SPACING = 10, MARGIN_RIGHT = 50; static constexpr double PIXELS_PER_SECOND = 400.0; private: MutexDataPair m_shared_data; int m_height; QFont m_font; QTimer *m_update_timer; public: SyncDiagram(size_t channels); ~SyncDiagram(); // These functions are thread-safe. void SetChannelName(size_t channel, const QString& name); void SetCurrentTime(size_t channel, double current_time); void AddBlock(size_t channel, double time_begin, double time_end, const QColor& color); void Update(); public: virtual QSize minimumSizeHint() const override { return QSize(200, m_height); } virtual QSize sizeHint() const override { return QSize(800, m_height); } protected: virtual void paintEvent(QPaintEvent* event) override; signals: void NeedsUpdate(); private slots: void DelayedUpdate(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/Synchronizer.cpp000066400000000000000000001160771470053624300253440ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "Synchronizer.h" #include "Logger.h" #include "CommandLineOptions.h" #include "OutputManager.h" #include "OutputSettings.h" #include "VideoEncoder.h" #include "AudioEncoder.h" #include "SampleCast.h" #include "SyncDiagram.h" #include // The amount of filtering applied to audio timestamps to reduce noise. Higher values reduce timestamp noise (and associated drift correction), // but if the value is too high, it will take more time to detect gaps. const int64_t Synchronizer::AUDIO_TIMESTAMP_FILTER = 20; // These values change how fast the synchronizer does drift correction. // If this value is too low, the error will not be corrected fast enough. But if the value is too high, the audio // may get weird speed fluctuations caused by the limited accuracy of the recording timestamps. // The difference between sample length and time length has a lot of noise and can't be used directly, // so it is averaged out using exponential smoothing. However, since the difference tends to increase gradually over time, // exponential smoothing would constantly lag behind, so instead of simple proportional feedback, I use a PI controller. // For critical damping, choose I = P*P/4. const double Synchronizer::DRIFT_CORRECTION_P = 0.3; const double Synchronizer::DRIFT_CORRECTION_I = 0.3 * 0.3 / 4.0; // The maximum audio/video desynchronization allowed, in seconds. If the error is greater than this value, the synchronizer will insert zeros // rather than relying on normal drift correction. This is something that should be avoided since it will result in noticeable interruptions, // so it should only be triggered when something is really wrong. If the error is smaller, the synchronizer will do nothing and the // drift correction system will take care of it (eventually). const double Synchronizer::DRIFT_ERROR_THRESHOLD = 0.05; // The maximum block size for drift correction, in seconds. This is needed to avoid numerical problems in the feedback system. const double Synchronizer::DRIFT_MAX_BLOCK = 0.5; // The maximum number of video frames and audio samples that will be buffered. This should be enough to cope with the fact that video and // audio don't arrive at the same time, but not too high because that would cause memory problems if one of the inputs fails. // The limit for audio can be set very high, because audio uses almost no memory. const size_t Synchronizer::MAX_VIDEO_FRAMES_BUFFERED = 30; const size_t Synchronizer::MAX_AUDIO_SAMPLES_BUFFERED = 1000000; // The maximum delay between video frames, in microseconds. If the delay is longer, duplicates will be inserted. // This is needed because some video codecs/players can't handle long delays. const int64_t Synchronizer::MAX_FRAME_DELAY = 200000; static std::unique_ptr CreateVideoFrame(unsigned int width, unsigned int height, AVPixelFormat pixel_format, const std::shared_ptr& reuse_data) { // get required planes unsigned int planes = 0; size_t linesize[3] = {0}, planesize[3] = {0}; switch(pixel_format) { case AV_PIX_FMT_YUV444P: { // Y/U/V = 1 byte per pixel planes = 3; linesize[0] = grow_align16(width); planesize[0] = linesize[0] * height; linesize[1] = grow_align16(width); planesize[1] = linesize[1] * height; linesize[2] = grow_align16(width); planesize[2] = linesize[2] * height; break; } case AV_PIX_FMT_YUV422P: { // Y = 1 byte per pixel, U/V = 1 byte per 2x1 pixels assert(width % 2 == 0); planes = 3; linesize[0] = grow_align16(width ); planesize[0] = linesize[0] * height; linesize[1] = grow_align16(width / 2); planesize[1] = linesize[1] * height; linesize[2] = grow_align16(width / 2); planesize[2] = linesize[2] * height; break; } case AV_PIX_FMT_YUV420P: { // Y = 1 byte per pixel, U/V = 1 byte per 2x2 pixels assert(width % 2 == 0); assert(height % 2 == 0); planes = 3; linesize[0] = grow_align16(width ); planesize[0] = linesize[0] * height ; linesize[1] = grow_align16(width / 2); planesize[1] = linesize[1] * height / 2; linesize[2] = grow_align16(width / 2); planesize[2] = linesize[2] * height / 2; break; } case AV_PIX_FMT_NV12: { assert(width % 2 == 0); assert(height % 2 == 0); // planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved // Y = 1 byte per pixel, U/V = 1 byte per 2x2 pixels planes = 2; linesize[0] = grow_align16(width); planesize[0] = linesize[0] * height ; linesize[1] = grow_align16(width); planesize[1] = linesize[1] * height / 2; break; } case AV_PIX_FMT_BGRA: { // BGRA = 4 bytes per pixel planes = 1; linesize[0] = grow_align16(width * 4); planesize[0] = linesize[0] * height; break; } case AV_PIX_FMT_BGR24: case AV_PIX_FMT_RGB24: { // BGR/RGB = 3 bytes per pixel planes = 1; linesize[0] = grow_align16(width * 3); planesize[0] = linesize[0] * height; break; } default: assert(false); break; } // create the frame size_t totalsize = 0; for(unsigned int p = 0; p < planes; ++p) { totalsize += planesize[p]; } std::shared_ptr frame_data = (reuse_data == NULL)? std::make_shared(totalsize) : reuse_data; std::unique_ptr frame(new AVFrameWrapper(frame_data)); uint8_t *data = frame->GetRawData(); for(unsigned int p = 0; p < planes; ++p) { frame->GetFrame()->data[p] = data; frame->GetFrame()->linesize[p] = linesize[p]; data += planesize[p]; } #if SSR_USE_AVFRAME_WIDTH_HEIGHT frame->GetFrame()->width = width; frame->GetFrame()->height = height; #endif #if SSR_USE_AVFRAME_FORMAT frame->GetFrame()->format = pixel_format; #endif #if SSR_USE_AVFRAME_SAR frame->GetFrame()->sample_aspect_ratio.num = 1; frame->GetFrame()->sample_aspect_ratio.den = 1; #endif return frame; } static std::unique_ptr CreateAudioFrame(unsigned int channels, unsigned int sample_rate, unsigned int samples, unsigned int planes, AVSampleFormat sample_format) { // get required sample size // note: sample_size = sizeof(sampletype) * channels unsigned int sample_size = 0; // to keep GCC happy switch(sample_format) { case AV_SAMPLE_FMT_S16: #if SSR_USE_AVUTIL_PLANAR_SAMPLE_FMT case AV_SAMPLE_FMT_S16P: #endif sample_size = channels * sizeof(int16_t); break; case AV_SAMPLE_FMT_FLT: #if SSR_USE_AVUTIL_PLANAR_SAMPLE_FMT case AV_SAMPLE_FMT_FLTP: #endif sample_size = channels * sizeof(float); break; default: assert(false); break; } // create the frame size_t plane_size = grow_align16(samples * sample_size / planes); std::shared_ptr frame_data = std::make_shared(plane_size * planes); std::unique_ptr frame(new AVFrameWrapper(frame_data)); for(unsigned int p = 0; p < planes; ++p) { frame->GetFrame()->data[p] = frame->GetRawData() + plane_size * p; frame->GetFrame()->linesize[p] = samples * sample_size / planes; } #if SSR_USE_AVFRAME_NB_SAMPLES frame->GetFrame()->nb_samples = samples; #endif #if SSR_USE_AVFRAME_CHANNELS #if SSR_USE_AV_CHANNEL_LAYOUT av_channel_layout_default(&frame->GetFrame()->ch_layout, channels); #else frame->GetFrame()->channels = channels; #endif #endif #if SSR_USE_AVFRAME_SAMPLE_RATE frame->GetFrame()->sample_rate = sample_rate; #endif #if SSR_USE_AVFRAME_FORMAT frame->GetFrame()->format = sample_format; #endif return frame; } Synchronizer::Synchronizer(OutputManager *output_manager) { m_output_manager = output_manager; m_output_settings = m_output_manager->GetOutputSettings(); m_output_format = m_output_manager->GetOutputFormat(); assert(m_output_format->m_video_enabled || m_output_format->m_audio_enabled); try { Init(); } catch(...) { Free(); throw; } } Synchronizer::~Synchronizer() { // disconnect ConnectVideoSource(NULL); ConnectAudioSource(NULL); // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[Synchronizer::~Synchronizer] " + Logger::tr("Stopping synchronizer thread ...")); m_should_stop = true; m_thread.join(); } // flush one more time { SharedLock lock(&m_shared_data); FlushBuffers(lock.get()); } // free everything Free(); } void Synchronizer::Init() { // initialize video if(m_output_format->m_video_enabled) { m_max_frames_skipped = (m_output_settings->video_allow_frame_skipping)? (MAX_FRAME_DELAY * m_output_format->m_video_frame_rate + 500000) / 1000000 : 0; VideoLock videolock(&m_video_data); videolock->m_last_timestamp = std::numeric_limits::min(); videolock->m_next_timestamp = SINK_TIMESTAMP_ASAP; } // initialize audio if(m_output_format->m_audio_enabled) { AudioLock audiolock(&m_audio_data); audiolock->m_fast_resampler.reset(new FastResampler(m_output_format->m_audio_channels, 0.9f)); InitAudioSegment(audiolock.get()); audiolock->m_warn_desync = true; } // create sync diagram if(CommandLineOptions::GetSyncDiagram()) { m_sync_diagram.reset(new SyncDiagram(4)); m_sync_diagram->SetChannelName(0, SyncDiagram::tr("Video in")); m_sync_diagram->SetChannelName(1, SyncDiagram::tr("Audio in")); m_sync_diagram->SetChannelName(2, SyncDiagram::tr("Video out")); m_sync_diagram->SetChannelName(3, SyncDiagram::tr("Audio out")); m_sync_diagram->show(); } // initialize shared data { SharedLock lock(&m_shared_data); if(m_output_format->m_audio_enabled) { lock->m_partial_audio_frame.Alloc(m_output_format->m_audio_frame_size * m_output_format->m_audio_channels); lock->m_partial_audio_frame_samples = 0; } lock->m_video_pts = 0; lock->m_audio_samples = 0; lock->m_time_offset = 0; InitSegment(lock.get()); lock->m_warn_drop_video = true; } // start synchronizer thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&Synchronizer::SynchronizerThread, this); } void Synchronizer::Free() { } void Synchronizer::NewSegment() { if(m_output_format->m_audio_enabled) { AudioLock audiolock(&m_audio_data); InitAudioSegment(audiolock.get()); } SharedLock lock(&m_shared_data); NewSegment(lock.get()); } int64_t Synchronizer::GetTotalTime() { SharedLock lock(&m_shared_data); return GetTotalTime(lock.get()); } int64_t Synchronizer::GetNextVideoTimestamp() { assert(m_output_format->m_video_enabled); VideoLock videolock(&m_video_data); return videolock->m_next_timestamp; } void Synchronizer::ReadVideoFrame(unsigned int width, unsigned int height, const uint8_t* const* data, const int* stride, AVPixelFormat format, int colorspace, int64_t timestamp) { assert(m_output_format->m_video_enabled); // add new block to sync diagram if(m_sync_diagram != NULL) m_sync_diagram->AddBlock(0, (double) timestamp * 1.0e-6, (double) timestamp * 1.0e-6 + 1.0 / (double) m_output_format->m_video_frame_rate, QColor(255, 0, 0)); VideoLock videolock(&m_video_data); // check the timestamp if(timestamp < videolock->m_last_timestamp) { if(timestamp < videolock->m_last_timestamp - 10000) Logger::LogWarning("[Synchronizer::ReadVideoFrame] " + Logger::tr("Warning: Received video frame with non-monotonic timestamp.")); timestamp = videolock->m_last_timestamp; } // drop the frame if it is too early (before converting it) if(videolock->m_next_timestamp != SINK_TIMESTAMP_ASAP && timestamp < videolock->m_next_timestamp - (int64_t) (1000000 / m_output_format->m_video_frame_rate)) return; // update the timestamps videolock->m_last_timestamp = timestamp; videolock->m_next_timestamp = std::max(videolock->m_next_timestamp + (int64_t) (1000000 / m_output_format->m_video_frame_rate), timestamp); // create the converted frame std::unique_ptr converted_frame = CreateVideoFrame(m_output_format->m_video_width, m_output_format->m_video_height, m_output_format->m_video_pixel_format, NULL); // scale and convert the frame to the right format videolock->m_fast_scaler.Scale(width, height, format, colorspace, data, stride, m_output_format->m_video_width, m_output_format->m_video_height, m_output_format->m_video_pixel_format, m_output_format->m_video_colorspace, converted_frame->GetFrame()->data, converted_frame->GetFrame()->linesize); SharedLock lock(&m_shared_data); // avoid memory problems by limiting the video buffer size if(lock->m_video_buffer.size() >= MAX_VIDEO_FRAMES_BUFFERED) { if(lock->m_segment_audio_started) { if(lock->m_warn_drop_video) { lock->m_warn_drop_video = false; Logger::LogWarning("[Synchronizer::ReadVideoFrame] " + Logger::tr("Warning: Video buffer overflow, some frames will be lost. The audio input seems to be too slow.")); } return; } else { // if the audio hasn't started yet, it makes more sense to drop the oldest frames lock->m_video_buffer.pop_front(); assert(lock->m_video_buffer.size() > 0); lock->m_segment_video_start_time = lock->m_video_buffer.front()->GetFrame()->pts; } } // start video if(!lock->m_segment_video_started) { lock->m_segment_video_started = true; lock->m_segment_video_start_time = timestamp; lock->m_segment_video_stop_time = timestamp; } // store the frame converted_frame->GetFrame()->pts = timestamp; lock->m_video_buffer.push_back(std::move(converted_frame)); // increase the segment stop time lock->m_segment_video_stop_time = timestamp + (int64_t) (1000000 / m_output_format->m_video_frame_rate); } void Synchronizer::ReadVideoPing(int64_t timestamp) { assert(m_output_format->m_video_enabled); SharedLock lock(&m_shared_data); // if the video has not been started, ignore it if(!lock->m_segment_video_started) return; // increase the segment stop time lock->m_segment_video_stop_time = std::max(lock->m_segment_video_stop_time, timestamp + (int64_t) (1000000 / m_output_format->m_video_frame_rate)); } void Synchronizer::ReadAudioSamples(unsigned int channels, unsigned int sample_rate, AVSampleFormat format, unsigned int sample_count, const uint8_t* data, int64_t timestamp) { assert(m_output_format->m_audio_enabled); // sanity check if(sample_count == 0) return; // add new block to sync diagram if(m_sync_diagram != NULL) m_sync_diagram->AddBlock(1, (double) timestamp * 1.0e-6, (double) timestamp * 1.0e-6 + (double) sample_count / (double) sample_rate, QColor(0, 255, 0)); AudioLock audiolock(&m_audio_data); // check the timestamp if(timestamp < audiolock->m_last_timestamp) { if(timestamp < audiolock->m_last_timestamp - 10000) Logger::LogWarning("[Synchronizer::ReadAudioSamples] " + Logger::tr("Warning: Received audio samples with non-monotonic timestamp.")); timestamp = audiolock->m_last_timestamp; } // update the timestamps int64_t previous_timestamp; if(audiolock->m_first_timestamp == (int64_t) AV_NOPTS_VALUE) { audiolock->m_filtered_timestamp = timestamp; audiolock->m_first_timestamp = timestamp; previous_timestamp = timestamp; } else { previous_timestamp = audiolock->m_last_timestamp; } audiolock->m_last_timestamp = timestamp; // filter the timestamp int64_t timestamp_delta = (int64_t) sample_count * (int64_t) 1000000 / (int64_t) sample_rate; audiolock->m_filtered_timestamp += (timestamp - audiolock->m_filtered_timestamp) / AUDIO_TIMESTAMP_FILTER; // calculate drift double current_drift = GetAudioDrift(audiolock.get()); // if there are too many audio samples, drop some of them (unlikely unless you use PulseAudio) if(current_drift > DRIFT_ERROR_THRESHOLD && !audiolock->m_drop_samples) { audiolock->m_drop_samples = true; Logger::LogWarning("[Synchronizer::ReadAudioSamples] " + Logger::tr("Warning: Too many audio samples, dropping samples to keep the audio in sync with the video.")); } // if there are not enough audio samples, insert zeros if(current_drift < -DRIFT_ERROR_THRESHOLD && !audiolock->m_insert_samples) { audiolock->m_insert_samples = true; Logger::LogWarning("[Synchronizer::ReadAudioSamples] " + Logger::tr("Warning: Not enough audio samples, inserting silence to keep the audio in sync with the video.")); } // reset filter and recalculate drift if necessary if(audiolock->m_drop_samples || audiolock->m_insert_samples) { audiolock->m_filtered_timestamp = timestamp; current_drift = GetAudioDrift(audiolock.get()); } // drop samples if(audiolock->m_drop_samples) { audiolock->m_drop_samples = false; // drop samples int n = (int) round(current_drift * (double) sample_rate); if(n > 0) { if(n >= (int) sample_count) { audiolock->m_drop_samples = true; return; // drop all samples } if(format == AV_SAMPLE_FMT_FLT) { data += n * channels * sizeof(float); } else if(format == AV_SAMPLE_FMT_S16) { data += n * channels * sizeof(int16_t); } else if(format == AV_SAMPLE_FMT_S32) { data += n * channels * sizeof(int32_t); } else { assert(false); } sample_count -= n; } } // insert zeros unsigned int sample_count_out = 0; if(audiolock->m_insert_samples) { audiolock->m_insert_samples = false; // how many samples should be inserted? int n = (int) round(-current_drift * (double) sample_rate); if(n > 0) { // insert zeros audiolock->m_temp_input_buffer.Alloc(n * m_output_format->m_audio_channels); std::fill_n(audiolock->m_temp_input_buffer.GetData(), n * m_output_format->m_audio_channels, 0.0f); sample_count_out = audiolock->m_fast_resampler->Resample((double) sample_rate / (double) m_output_format->m_audio_sample_rate, 1.0, audiolock->m_temp_input_buffer.GetData(), n, &audiolock->m_temp_output_buffer, sample_count_out); // recalculate drift current_drift = GetAudioDrift(audiolock.get(), sample_count_out); } } // increase filtered timestamp audiolock->m_filtered_timestamp += timestamp_delta; // do drift correction // The point of drift correction is to keep video and audio in sync even when the clocks are not running at exactly the same speed. // This can happen because the sample rate of the sound card is not always 100% accurate. Even a 0.1% error will result in audio that is // seconds too early or too late at the end of a one hour video. This problem doesn't occur on all computers though (I'm not sure why). // Another cause of desynchronization is problems/glitches with PulseAudio (e.g. jumps in time when switching between sources). double drift_correction_dt = fmin((double) (timestamp - previous_timestamp) * 1.0e-6, DRIFT_MAX_BLOCK); audiolock->m_average_drift = clamp(audiolock->m_average_drift + DRIFT_CORRECTION_I * current_drift * drift_correction_dt, -0.5, 0.5); if(audiolock->m_average_drift < -0.02 && audiolock->m_warn_desync) { audiolock->m_warn_desync = false; Logger::LogWarning("[Synchronizer::ReadAudioSamples] " + Logger::tr("Warning: Audio input is more than 2% too slow!")); } if(audiolock->m_average_drift > 0.02 && audiolock->m_warn_desync) { audiolock->m_warn_desync = false; Logger::LogWarning("[Synchronizer::ReadAudioSamples] " + Logger::tr("Warning: Audio input is more than 2% too fast!")); } double length = (double) sample_count / (double) sample_rate; double drift_correction = clamp(DRIFT_CORRECTION_P * current_drift + audiolock->m_average_drift, -0.5, 0.5) * fmin(1.0, DRIFT_MAX_BLOCK / length); //qDebug() << "current_drift" << current_drift << "average_drift" << audiolock->m_average_drift << "drift_correction" << drift_correction; // convert the samples const float *data_float = NULL; // to keep GCC happy if(format == AV_SAMPLE_FMT_FLT) { if(channels == m_output_format->m_audio_channels) { data_float = (const float*) data; } else { audiolock->m_temp_input_buffer.Alloc(sample_count * m_output_format->m_audio_channels); data_float = audiolock->m_temp_input_buffer.GetData(); SampleChannelRemap(sample_count, (const float*) data, channels, audiolock->m_temp_input_buffer.GetData(), m_output_format->m_audio_channels); } } else if(format == AV_SAMPLE_FMT_S16) { audiolock->m_temp_input_buffer.Alloc(sample_count * m_output_format->m_audio_channels); data_float = audiolock->m_temp_input_buffer.GetData(); SampleChannelRemap(sample_count, (const int16_t*) data, channels, audiolock->m_temp_input_buffer.GetData(), m_output_format->m_audio_channels); } else if(format == AV_SAMPLE_FMT_S32) { audiolock->m_temp_input_buffer.Alloc(sample_count * m_output_format->m_audio_channels); data_float = audiolock->m_temp_input_buffer.GetData(); SampleChannelRemap(sample_count, (const int32_t*) data, channels, audiolock->m_temp_input_buffer.GetData(), m_output_format->m_audio_channels); } else { assert(false); } // resample sample_count_out = audiolock->m_fast_resampler->Resample((double) sample_rate / (double) m_output_format->m_audio_sample_rate, 1.0 / (1.0 - drift_correction), data_float, sample_count, &audiolock->m_temp_output_buffer, sample_count_out); audiolock->m_samples_written += sample_count_out; SharedLock lock(&m_shared_data); // avoid memory problems by limiting the audio buffer size if(lock->m_audio_buffer.GetSize() / m_output_format->m_audio_channels >= MAX_AUDIO_SAMPLES_BUFFERED) { if(lock->m_segment_video_started) { Logger::LogWarning("[Synchronizer::ReadAudioSamples] " + Logger::tr("Warning: Audio buffer overflow, starting new segment to keep the audio in sync with the video " "(some video and/or audio may be lost). The video input seems to be too slow.")); NewSegment(lock.get()); } else { // If the video hasn't started yet, it makes more sense to drop the oldest samples. // Shifting the start time like this isn't completely accurate, but this shouldn't happen often anyway. // The number of samples dropped is calculated so that the buffer will be 90% full after this. size_t n = lock->m_audio_buffer.GetSize() / m_output_format->m_audio_channels - MAX_AUDIO_SAMPLES_BUFFERED * 9 / 10; lock->m_audio_buffer.Pop(n * m_output_format->m_audio_channels); lock->m_segment_audio_start_time += (int64_t) round((double) n / (double) m_output_format->m_audio_sample_rate * 1.0e6); } } // start audio if(!lock->m_segment_audio_started) { lock->m_segment_audio_started = true; lock->m_segment_audio_start_time = timestamp; lock->m_segment_audio_stop_time = timestamp; } // store the samples lock->m_audio_buffer.Push(audiolock->m_temp_output_buffer.GetData(), sample_count_out * m_output_format->m_audio_channels); // increase segment stop time double new_sample_length = (double) (lock->m_segment_audio_samples_read + lock->m_audio_buffer.GetSize() / m_output_format->m_audio_channels) / (double) m_output_format->m_audio_sample_rate; lock->m_segment_audio_stop_time = lock->m_segment_audio_start_time + (int64_t) round(new_sample_length * 1.0e6); } void Synchronizer::ReadAudioHole() { assert(m_output_format->m_audio_enabled); AudioLock audiolock(&m_audio_data); if(audiolock->m_first_timestamp != (int64_t) AV_NOPTS_VALUE) { audiolock->m_average_drift = 0.0; if(!audiolock->m_drop_samples || !audiolock->m_insert_samples) { Logger::LogWarning("[Synchronizer::ReadAudioHole] " + Logger::tr("Warning: Received hole in audio stream, inserting silence to keep the audio in sync with the video.")); audiolock->m_drop_samples = true; // because PulseAudio is weird audiolock->m_insert_samples = true; } } } void Synchronizer::InitAudioSegment(AudioData* audiolock) { audiolock->m_last_timestamp = std::numeric_limits::min(); audiolock->m_first_timestamp = AV_NOPTS_VALUE; audiolock->m_samples_written = 0; audiolock->m_average_drift = 0.0; audiolock->m_drop_samples = false; audiolock->m_insert_samples = false; } double Synchronizer::GetAudioDrift(AudioData* audiolock, unsigned int extra_samples) { double sample_length = ((double) (audiolock->m_samples_written + extra_samples) + audiolock->m_fast_resampler->GetOutputLatency()) / (double) m_output_format->m_audio_sample_rate; double time_length = (double) (audiolock->m_filtered_timestamp - audiolock->m_first_timestamp) * 1.0e-6; return sample_length - time_length; } void Synchronizer::NewSegment(SharedData* lock) { FlushBuffers(lock); if(lock->m_segment_video_started && lock->m_segment_audio_started) { int64_t segment_start_time, segment_stop_time; GetSegmentStartStop(lock, &segment_start_time, &segment_stop_time); lock->m_time_offset += std::max((int64_t) 0, segment_stop_time - segment_start_time); } lock->m_video_buffer.clear(); lock->m_audio_buffer.Clear(); InitSegment(lock); } void Synchronizer::InitSegment(SharedData* lock) { lock->m_segment_video_started = !m_output_format->m_video_enabled; lock->m_segment_audio_started = !m_output_format->m_audio_enabled; lock->m_segment_video_start_time = AV_NOPTS_VALUE; lock->m_segment_audio_start_time = AV_NOPTS_VALUE; lock->m_segment_video_stop_time = AV_NOPTS_VALUE; lock->m_segment_audio_stop_time = AV_NOPTS_VALUE; lock->m_segment_audio_can_drop = true; lock->m_segment_audio_samples_read = 0; lock->m_segment_video_accumulated_delay = 0; } int64_t Synchronizer::GetTotalTime(Synchronizer::SharedData* lock) { if(lock->m_segment_video_started && lock->m_segment_audio_started) { int64_t segment_start_time, segment_stop_time; GetSegmentStartStop(lock, &segment_start_time, &segment_stop_time); return lock->m_time_offset + std::max((int64_t) 0, segment_stop_time - segment_start_time); } else { return lock->m_time_offset; } } void Synchronizer::GetSegmentStartStop(SharedData* lock, int64_t* segment_start_time, int64_t* segment_stop_time) { if(!m_output_format->m_audio_enabled) { *segment_start_time = lock->m_segment_video_start_time; *segment_stop_time = lock->m_segment_video_stop_time; } else if(!m_output_format->m_video_enabled) { *segment_start_time = lock->m_segment_audio_start_time; *segment_stop_time = lock->m_segment_audio_stop_time; } else { *segment_start_time = std::max(lock->m_segment_video_start_time, lock->m_segment_audio_start_time); *segment_stop_time = std::min(lock->m_segment_video_stop_time, lock->m_segment_audio_stop_time); } } void Synchronizer::FlushBuffers(SharedData* lock) { if(!lock->m_segment_video_started || !lock->m_segment_audio_started) return; int64_t segment_start_time, segment_stop_time; GetSegmentStartStop(lock, &segment_start_time, &segment_stop_time); // flush video if(m_output_format->m_video_enabled) FlushVideoBuffer(lock, segment_start_time, segment_stop_time); // flush audio if(m_output_format->m_audio_enabled) FlushAudioBuffer(lock, segment_start_time, segment_stop_time); } void Synchronizer::FlushVideoBuffer(Synchronizer::SharedData* lock, int64_t segment_start_time, int64_t segment_stop_time) { // Sometimes long delays between video frames can occur, e.g. when a game is showing a loading screen. // Not all codecs/players can handle that. It's also a problem for streaming. To fix this, long delays should be avoided by // duplicating the previous frame a few times when needed. Whenever a video frame is sent to the encoder, it is also copied, // with reference counting for the actual image to minimize overhead. When there is a gap, duplicate frames are inserted. // Duplicate frames are always inserted with a timestamp in the past, because we don't want to drop a real frame if it is captured // right after the duplicate was inserted. MAX_INPUT_LATENCY simulates the latency from the capturing of a frame to the synchronizer, // i.e. any new frame is assumed to have a timestamp higher than the current time minus MAX_INPUT_LATENCY. The duplicate // frame will have a timestamp that's one frame earlier than that time, so it will never interfere with the real frame. // There are two situations where duplicate frames can be inserted: // (1) The queue is not empty, but there is a gap between frames that is too large. // (2) The queue is empty and the last timestamp is too long ago (relative to the end of the video segment). // It is perfectly possible that *both* happen, each possibly multiple times, in just one function call. int64_t segment_stop_video_pts = (lock->m_time_offset + (segment_stop_time - segment_start_time)) * (int64_t) m_output_format->m_video_frame_rate / (int64_t) 1000000; int64_t delay_time_per_frame = 1000000 / m_output_format->m_video_frame_rate; for( ; ; ) { // get/predict the timestamp of the next frame int64_t next_timestamp = (lock->m_video_buffer.empty())? lock->m_segment_video_stop_time - (int64_t) (1000000 / m_output_format->m_video_frame_rate) : lock->m_video_buffer.front()->GetFrame()->pts; int64_t next_pts = (lock->m_time_offset + (next_timestamp - segment_start_time)) * (int64_t) m_output_format->m_video_frame_rate / (int64_t) 1000000; // if the frame is too late, decrease the pts by one to avoid gaps if(next_pts > lock->m_video_pts) --next_pts; // insert delays if needed, up to the segment end while(lock->m_segment_video_accumulated_delay >= delay_time_per_frame && lock->m_video_pts < segment_stop_video_pts) { lock->m_segment_video_accumulated_delay -= delay_time_per_frame; lock->m_video_pts += 1; //Logger::LogInfo("[Synchronizer::FlushVideoBuffer] Delay [" + QString::number(lock->m_video_pts - 1) + "] acc " + QString::number(lock->m_segment_video_accumulated_delay) + "."); } // insert duplicate frames if needed, up to either the next frame or the segment end if(lock->m_last_video_frame_data != NULL) { while(lock->m_video_pts + m_max_frames_skipped < std::min(next_pts, segment_stop_video_pts)) { // create duplicate frame std::unique_ptr duplicate_frame = CreateVideoFrame(m_output_format->m_video_width, m_output_format->m_video_height, m_output_format->m_video_pixel_format, lock->m_last_video_frame_data); duplicate_frame->GetFrame()->pts = lock->m_video_pts + m_max_frames_skipped; // add new block to sync diagram if(m_sync_diagram != NULL) { double t = (double) duplicate_frame->GetFrame()->pts / (double) m_output_format->m_video_frame_rate; m_sync_diagram->AddBlock(2, t, t + 1.0 / (double) m_output_format->m_video_frame_rate, QColor(255, 196, 0)); } // send the frame to the encoder lock->m_segment_video_accumulated_delay = std::max((int64_t) 0, lock->m_segment_video_accumulated_delay - m_max_frames_skipped * delay_time_per_frame); lock->m_video_pts = duplicate_frame->GetFrame()->pts + 1; //Logger::LogInfo("[Synchronizer::FlushVideoBuffer] Encoded video frame [" + QString::number(duplicate_frame->GetFrame()->pts) + "] (duplicate) acc " + QString::number(lock->m_segment_video_accumulated_delay) + "."); m_output_manager->AddVideoFrame(std::move(duplicate_frame)); lock->m_segment_video_accumulated_delay += m_output_manager->GetVideoFrameDelay(); } } // if there are no frames, or they are beyond the segment end, stop if(lock->m_video_buffer.empty() || next_pts >= segment_stop_video_pts) break; // get the frame std::unique_ptr frame = std::move(lock->m_video_buffer.front()); lock->m_video_buffer.pop_front(); frame->GetFrame()->pts = next_pts; lock->m_last_video_frame_data = frame->GetFrameData(); // if the frame is too early, drop it if(frame->GetFrame()->pts < lock->m_video_pts) { //Logger::LogInfo("[Synchronizer::FlushVideoBuffer] Dropped video frame [" + QString::number(frame->GetFrame()->pts) + "] acc " + QString::number(lock->m_segment_video_accumulated_delay) + "."); continue; } // if this is the first video frame, always set the pts to zero if(lock->m_video_pts == 0) frame->GetFrame()->pts = 0; // add new block to sync diagram if(m_sync_diagram != NULL) { double t = (double) frame->GetFrame()->pts / (double) m_output_format->m_video_frame_rate; m_sync_diagram->AddBlock(2, t, t + 1.0 / (double) m_output_format->m_video_frame_rate, QColor(255, 0, 0)); } // send the frame to the encoder lock->m_segment_video_accumulated_delay = std::max((int64_t) 0, lock->m_segment_video_accumulated_delay - (frame->GetFrame()->pts - lock->m_video_pts) * delay_time_per_frame); lock->m_video_pts = frame->GetFrame()->pts + 1; //Logger::LogInfo("[Synchronizer::FlushBuffers] Encoded video frame [" + QString::number(frame->GetFrame()->pts) + "]."); m_output_manager->AddVideoFrame(std::move(frame)); lock->m_segment_video_accumulated_delay += m_output_manager->GetVideoFrameDelay(); } } void Synchronizer::FlushAudioBuffer(Synchronizer::SharedData* lock, int64_t segment_start_time, int64_t segment_stop_time) { double sample_length = (double) (segment_stop_time - lock->m_segment_audio_start_time) * 1.0e-6; int64_t samples_max = (int64_t) ceil(sample_length * (double) m_output_format->m_audio_sample_rate) - lock->m_segment_audio_samples_read; if(lock->m_audio_buffer.GetSize() > 0) { // Normally, the correct way to calculate the position of the first sample would be: // int64_t timestamp = lock->m_segment_audio_start_time + (int64_t) round((double) lock->m_segment_audio_samples_read / (double) m_audio_sample_rate * 1.0e6); // int64_t pos = (int64_t) round((double) (lock->m_time_offset + (timestamp - segment_start_time)) * 1.0e-6 * (double) m_audio_sample_rate); // Simplified: // int64_t pos = (int64_t) round((double) (lock->m_time_offset + (lock->m_segment_audio_start_time - segment_start_time)) * 1.0e-6 * (double) m_audio_sample_rate) // + lock->m_segment_audio_samples_read; // The first part of the expression is constant, so it only has to be calculated at the start of the segment. After that the increase in position is always // equal to the number of samples written. Samples are only dropped at the start of the segment, so actually // the position doesn't have to be calculated anymore after that, since it is assumed to be equal to lock->m_audio_samples. if(lock->m_segment_audio_can_drop) { // calculate the offset of the first sample int64_t pos = (int64_t) round((double) (lock->m_time_offset + (lock->m_segment_audio_start_time - segment_start_time)) * 1.0e-6 * (double) m_output_format->m_audio_sample_rate) + lock->m_segment_audio_samples_read; // drop samples that are too early if(pos < lock->m_audio_samples) { int64_t n = std::min(lock->m_audio_samples - pos, (int64_t) lock->m_audio_buffer.GetSize() / m_output_format->m_audio_channels); lock->m_audio_buffer.Pop(n * m_output_format->m_audio_channels); lock->m_segment_audio_samples_read += n; } } int64_t samples_left = std::min(samples_max, (int64_t) lock->m_audio_buffer.GetSize() / m_output_format->m_audio_channels); // add new block to sync diagram if(m_sync_diagram != NULL && samples_left > 0) { double t = (double) lock->m_audio_samples / (double) m_output_format->m_audio_sample_rate; m_sync_diagram->AddBlock(3, t, t + (double) samples_left / (double) m_output_format->m_audio_sample_rate, QColor(0, 255, 0)); } // send the samples to the encoder while(samples_left > 0) { lock->m_segment_audio_can_drop = false; // copy samples until either the partial frame is full or there are no samples left //TODO// do direct copy/conversion to new audio frame? int64_t n = std::min((int64_t) (m_output_format->m_audio_frame_size - lock->m_partial_audio_frame_samples), samples_left); lock->m_audio_buffer.Pop(lock->m_partial_audio_frame.GetData() + lock->m_partial_audio_frame_samples * m_output_format->m_audio_channels, n * m_output_format->m_audio_channels); lock->m_segment_audio_samples_read += n; lock->m_partial_audio_frame_samples += n; lock->m_audio_samples += n; samples_left -= n; // is the partial frame full? if(lock->m_partial_audio_frame_samples == m_output_format->m_audio_frame_size) { // allocate a frame #if SSR_USE_AVUTIL_PLANAR_SAMPLE_FMT unsigned int planes = (m_output_format->m_audio_sample_format == AV_SAMPLE_FMT_S16P || m_output_format->m_audio_sample_format == AV_SAMPLE_FMT_FLTP)? m_output_format->m_audio_channels : 1; #else unsigned int planes = 1; #endif std::unique_ptr audio_frame = CreateAudioFrame(m_output_format->m_audio_channels, m_output_format->m_audio_sample_rate, m_output_format->m_audio_frame_size, planes, m_output_format->m_audio_sample_format); audio_frame->GetFrame()->pts = lock->m_audio_samples; // copy/convert the samples switch(m_output_format->m_audio_sample_format) { case AV_SAMPLE_FMT_S16: { float *data_in = (float*) lock->m_partial_audio_frame.GetData(); int16_t *data_out = (int16_t*) audio_frame->GetFrame()->data[0]; SampleCopy(m_output_format->m_audio_frame_size * m_output_format->m_audio_channels, data_in, 1, data_out, 1); break; } case AV_SAMPLE_FMT_FLT: { float *data_in = (float*) lock->m_partial_audio_frame.GetData(); float *data_out = (float*) audio_frame->GetFrame()->data[0]; memcpy(data_out, data_in, m_output_format->m_audio_frame_size * m_output_format->m_audio_channels * sizeof(float)); break; } #if SSR_USE_AVUTIL_PLANAR_SAMPLE_FMT case AV_SAMPLE_FMT_S16P: { for(unsigned int p = 0; p < planes; ++p) { float *data_in = (float*) lock->m_partial_audio_frame.GetData() + p; int16_t *data_out = (int16_t*) audio_frame->GetFrame()->data[p]; SampleCopy(m_output_format->m_audio_frame_size, data_in, planes, data_out, 1); } break; } case AV_SAMPLE_FMT_FLTP: { for(unsigned int p = 0; p < planes; ++p) { float *data_in = (float*) lock->m_partial_audio_frame.GetData() + p; float *data_out = (float*) audio_frame->GetFrame()->data[p]; SampleCopy(m_output_format->m_audio_frame_size, data_in, planes, data_out, 1); } break; } #endif default: { assert(false); break; } } lock->m_partial_audio_frame_samples = 0; //Logger::LogInfo("[Synchronizer::FlushAudioBuffer] Encoded audio frame [" + QString::number(lock->m_partial_audio_frame->pts) + "]."); m_output_manager->AddAudioFrame(std::move(audio_frame)); } } } } void Synchronizer::SynchronizerThread() { try { Logger::LogInfo("[Synchronizer::SynchronizerThread] " + Logger::tr("Synchronizer thread started.")); while(!m_should_stop) { { SharedLock lock(&m_shared_data); FlushBuffers(lock.get()); if(m_sync_diagram != NULL) { double time_in = (double) hrt_time_micro() * 1.0e-6; double time_out = (double) GetTotalTime(lock.get()) * 1.0e-6; m_sync_diagram->SetCurrentTime(0, time_in); m_sync_diagram->SetCurrentTime(1, time_in); m_sync_diagram->SetCurrentTime(2, time_out); m_sync_diagram->SetCurrentTime(3, time_out); m_sync_diagram->Update(); } } usleep(20000); } Logger::LogInfo("[Synchronizer::SynchronizerThread] " + Logger::tr("Synchronizer thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[Synchronizer::SynchronizerThread] " + Logger::tr("Exception '%1' in synchronizer thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[Synchronizer::SynchronizerThread] " + Logger::tr("Unknown exception in synchronizer thread.")); } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/Synchronizer.h000066400000000000000000000147051470053624300250040ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "SourceSink.h" #include "MutexDataPair.h" #include "FastScaler.h" #include "FastResampler.h" #include "QueueBuffer.h" #include "TempBuffer.h" #include "AVWrapper.h" class OutputManager; class OutputSettings; class OutputFormat; class SyncDiagram; class Synchronizer : public VideoSink, public AudioSink { private: struct VideoData { FastScaler m_fast_scaler; int64_t m_last_timestamp; // the timestamp of the last received video frame (for gap detection) int64_t m_next_timestamp; // the preferred timestamp of the next frame (for rate control) }; struct AudioData { std::unique_ptr m_fast_resampler; TempBuffer m_temp_input_buffer; TempBuffer m_temp_output_buffer; int64_t m_filtered_timestamp; // the timestamp after noise filtering int64_t m_last_timestamp; // the timestamp of the last received audio frame (to detect non-monotonic timestamps) int64_t m_first_timestamp; // the timestamp of the first audio frame in the current segment (for drift correction) int64_t m_samples_written; // total number of samples written to the queue in the current segment (for drift correction) double m_average_drift; // drift averaged over time (for drift correction) bool m_drop_samples, m_insert_samples; bool m_warn_desync; }; struct SharedData { TempBuffer m_partial_audio_frame; unsigned int m_partial_audio_frame_samples; std::deque > m_video_buffer; QueueBuffer m_audio_buffer; int64_t m_video_pts, m_audio_samples; // video and audio position in the final stream (encoded frames and samples, including the partial audio frame) int64_t m_time_offset; // the length of all previous segments combined (in microseconds) bool m_segment_video_started, m_segment_audio_started; // whether video and audio have started (always true if the corresponding stream is disabled) int64_t m_segment_video_start_time, m_segment_audio_start_time; // the start time of video and audio (real-time, in microseconds) int64_t m_segment_video_stop_time, m_segment_audio_stop_time; // the stop time of video and audio (real-time, in microseconds) bool m_segment_audio_can_drop; // whether audio samples can still be dropped (i.e. no samples have been sent to the encoder yet) int64_t m_segment_audio_samples_read; // the number of samples that have been read from the audio buffer (including dropped samples) int64_t m_segment_video_accumulated_delay; // sum of all video frame delays that were applied so far std::shared_ptr m_last_video_frame_data; bool m_warn_drop_video; }; typedef MutexDataPair::Lock VideoLock; typedef MutexDataPair::Lock AudioLock; typedef MutexDataPair::Lock SharedLock; private: static const int64_t AUDIO_TIMESTAMP_FILTER; static const double DRIFT_CORRECTION_P, DRIFT_CORRECTION_I; static const double DRIFT_ERROR_THRESHOLD, DRIFT_MAX_BLOCK; static const size_t MAX_VIDEO_FRAMES_BUFFERED, MAX_AUDIO_SAMPLES_BUFFERED; static const int64_t MAX_FRAME_DELAY; private: OutputManager *m_output_manager; const OutputSettings *m_output_settings; const OutputFormat *m_output_format; int64_t m_max_frames_skipped; std::unique_ptr m_sync_diagram; std::thread m_thread; MutexDataPair m_video_data; MutexDataPair m_audio_data; MutexDataPair m_shared_data; std::atomic m_should_stop, m_error_occurred; public: // The arguments 'video_encoder' and 'audio_encoder' can be NULL to disable video or audio. Synchronizer(OutputManager* output_manager); ~Synchronizer(); private: void Init(); void Free(); public: // This function tells the synchronizer to end the current segment and reset the synchronization system // in preparation for a new segment. This is required for pausing and continuing a recording. // This function has no effect if there are no frames in the current segment, so it is safe to call this multiple times. // This function is thread-safe, but for best results you should still make sure that no input is running // while this function is called, because otherwise frames may end up in the wrong segment. void NewSegment(); // Returns the total recording time (in microseconds). // This function is thread-safe. int64_t GetTotalTime(); // Returns whether an error has occurred in the synchronizer thread. // This function is thread-safe. inline bool HasErrorOccurred() { return m_error_occurred; } //inline VideoEncoder* GetVideoEncoder() { return m_video_encoder; } //inline AudioEncoder* GetAudioEncoder() { return m_audio_encoder; } public: // internal virtual int64_t GetNextVideoTimestamp() override; virtual void ReadVideoFrame(unsigned int width, unsigned int height, const uint8_t* const* data, const int* stride, AVPixelFormat format, int colorspace, int64_t timestamp) override; virtual void ReadVideoPing(int64_t timestamp) override; virtual void ReadAudioSamples(unsigned int channels, unsigned int sample_rate, AVSampleFormat format, unsigned int sample_count, const uint8_t* data, int64_t timestamp) override; virtual void ReadAudioHole() override; private: void InitAudioSegment(AudioData* audiolock); double GetAudioDrift(AudioData* audiolock, unsigned int extra_samples = 0); void NewSegment(SharedData* lock); void InitSegment(SharedData* lock); int64_t GetTotalTime(SharedData* lock); void GetSegmentStartStop(SharedData* lock, int64_t* segment_start_time, int64_t* segment_stop_time); void FlushBuffers(SharedData* lock); void FlushVideoBuffer(SharedData* lock, int64_t segment_start_time, int64_t segment_stop_time); void FlushAudioBuffer(SharedData* lock, int64_t segment_start_time, int64_t segment_stop_time); private: void SynchronizerThread(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/VideoEncoder.cpp000066400000000000000000000262101470053624300252020ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "VideoEncoder.h" #include "Logger.h" #include "AVWrapper.h" #include "Muxer.h" #include "X264Presets.h" const std::vector VideoEncoder::SUPPORTED_PIXEL_FORMATS = { {"nv12", AV_PIX_FMT_NV12, true}, {"yuv420", AV_PIX_FMT_YUV420P, true}, {"yuv422", AV_PIX_FMT_YUV422P, true}, {"yuv444", AV_PIX_FMT_YUV444P, true}, {"bgra", AV_PIX_FMT_BGRA, false}, {"bgr", AV_PIX_FMT_BGR24, false}, {"rgb", AV_PIX_FMT_RGB24, false}, }; VideoEncoder::VideoEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options) : BaseEncoder(muxer, stream, codec_context, codec, options) { #if !SSR_USE_AVCODEC_ENCODE_VIDEO2 // allocate a temporary buffer // Apparently libav/ffmpeg completely ignores the size of the buffer, and if it's too small it just crashes. // Originally it was 256k, which is large enough for about 99.9% of the packets, but it still occasionally crashes. // So now I'm using a buffer that's always at least large enough to hold a 256k header and *two* completely uncompressed frames. // (one YUV frame takes w * h * 1.5 bytes) // Newer versions of libav/ffmpeg have deprecated avcodec_encode_video and added a new function which does the allocation // automatically, just like avcodec_encode_audio2, but that function isn't available in Ubuntu 12.04/12.10 yet. m_temp_buffer.resize(std::max(FF_MIN_BUFFER_SIZE, 256 * 1024 + GetCodecContext()->width * GetCodecContext()->height * 3)); #endif StartThread(); } VideoEncoder::~VideoEncoder() { StopThread(); } AVPixelFormat VideoEncoder::GetPixelFormat() { return GetCodecContext()->pix_fmt; } int VideoEncoder::GetColorSpace() { switch(GetCodecContext()->colorspace) { case AVCOL_SPC_BT709: return SWS_CS_ITU709; case AVCOL_SPC_FCC: return SWS_CS_FCC; case AVCOL_SPC_BT470BG: return SWS_CS_ITU601; case AVCOL_SPC_SMPTE170M: return SWS_CS_SMPTE170M; case AVCOL_SPC_SMPTE240M: return SWS_CS_SMPTE240M; #ifdef SWS_CS_BT2020 case AVCOL_SPC_BT2020_NCL: case AVCOL_SPC_BT2020_CL: return SWS_CS_BT2020; #endif default: return SWS_CS_DEFAULT; } } unsigned int VideoEncoder::GetWidth() { return GetCodecContext()->width; } unsigned int VideoEncoder::GetHeight() { return GetCodecContext()->height; } unsigned int VideoEncoder::GetFrameRate() { assert(GetCodecContext()->time_base.num == 1); return GetCodecContext()->time_base.den; } bool VideoEncoder::AVCodecIsSupported(const QString& codec_name) { // we have to break const correctness for compatibility with older ffmpeg versions AVCodec *codec = (AVCodec*) avcodec_find_encoder_by_name(codec_name.toUtf8().constData()); if(codec == NULL) return false; if(!av_codec_is_encoder(codec)) return false; if(codec->type != AVMEDIA_TYPE_VIDEO) return false; for(unsigned int i = 0; i < SUPPORTED_PIXEL_FORMATS.size(); ++i) { if(AVCodecSupportsPixelFormat(codec, SUPPORTED_PIXEL_FORMATS[i].m_format)) { //qDebug() << codec_name << "supported by" << SUPPORTED_PIXEL_FORMATS[i].m_name; return true; } } return false; } void VideoEncoder::PrepareStream(AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options, const std::vector >& codec_options, unsigned int bit_rate, unsigned int width, unsigned int height, unsigned int frame_rate) { if(width == 0 || height == 0) { Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: Width or height is zero!")); throw LibavException(); } if(width > SSR_MAX_IMAGE_SIZE || height > SSR_MAX_IMAGE_SIZE) { Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: Width or height is too large, the maximum width and height is %1!").arg(SSR_MAX_IMAGE_SIZE)); throw LibavException(); } if(width % 2 != 0 || height % 2 != 0) { Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: Width or height is not an even number!")); throw LibavException(); } if(frame_rate == 0) { Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: Frame rate is zero!")); throw LibavException(); } // initialize codec context codec_context->bit_rate = bit_rate; codec_context->width = width; codec_context->height = height; codec_context->time_base.num = 1; codec_context->time_base.den = frame_rate; #if SSR_USE_AVSTREAM_TIME_BASE stream->time_base = codec_context->time_base; #endif codec_context->sample_aspect_ratio.num = 1; codec_context->sample_aspect_ratio.den = 1; stream->sample_aspect_ratio = codec_context->sample_aspect_ratio; codec_context->thread_count = std::max(1, (int) std::thread::hardware_concurrency()); // parse options QString pixel_format_name; for(unsigned int i = 0; i < codec_options.size(); ++i) { const QString &key = codec_options[i].first, &value = codec_options[i].second; if(key == "threads") { codec_context->thread_count = ParseCodecOptionInt(key, value, 1, 100); } else if(key == "qscale") { codec_context->flags |= AV_CODEC_FLAG_QSCALE; codec_context->global_quality = lrint(ParseCodecOptionDouble(key, value, -1.0e6, 1.0e6, FF_QP2LAMBDA)); } else if(key == "minrate") { codec_context->rc_min_rate = ParseCodecOptionInt(key, value, 1, 1000000, 1000); // kbit/s } else if(key == "maxrate") { codec_context->rc_max_rate = ParseCodecOptionInt(key, value, 1, 1000000, 1000); // kbit/s } else if(key == "bufsize") { codec_context->rc_buffer_size = ParseCodecOptionInt(key, value, 1, 1000000, 1000); // kbit/s } else if(key == "keyint") { codec_context->gop_size = ParseCodecOptionInt(key, value, 1, 1000000); } else if(key == "pixelformat") { pixel_format_name = value; #if !SSR_USE_AVCODEC_PRIVATE_PRESET } else if(key == "crf") { codec_context->crf = ParseCodecOptionInt(key, value, 0, 51); #endif #if !SSR_USE_AVCODEC_PRIVATE_PRESET } else if(key == "preset") { X264Preset(codec_context, value.toUtf8().constData()); #endif } else { av_dict_set(options, key.toUtf8().constData(), value.toUtf8().constData(), 0); } } // choose the pixel format codec_context->pix_fmt = AV_PIX_FMT_NONE; for(unsigned int i = 0; i < SUPPORTED_PIXEL_FORMATS.size(); ++i) { if(!pixel_format_name.isEmpty() && pixel_format_name != SUPPORTED_PIXEL_FORMATS[i].m_name) continue; if(!AVCodecSupportsPixelFormat(codec, SUPPORTED_PIXEL_FORMATS[i].m_format)) continue; Logger::LogInfo("[VideoEncoder::PrepareStream] " + Logger::tr("Using pixel format %1.").arg(SUPPORTED_PIXEL_FORMATS[i].m_name)); codec_context->pix_fmt = SUPPORTED_PIXEL_FORMATS[i].m_format; if(SUPPORTED_PIXEL_FORMATS[i].m_is_yuv) { codec_context->color_primaries = AVCOL_PRI_BT709; codec_context->color_trc = AVCOL_TRC_BT709; codec_context->colorspace = AVCOL_SPC_BT709; codec_context->color_range = AVCOL_RANGE_MPEG; codec_context->chroma_sample_location = AVCHROMA_LOC_CENTER; } else { codec_context->colorspace = AVCOL_SPC_RGB; } break; } if(codec_context->pix_fmt == AV_PIX_FMT_NONE) { Logger::LogError("[VideoEncoder::PrepareStream] " + Logger::tr("Error: The pixel format is not supported by the codec!")); throw LibavException(); } } bool VideoEncoder::EncodeFrame(AVFrameWrapper* frame) { if(frame != NULL) { #if SSR_USE_AVFRAME_WIDTH_HEIGHT assert(frame->GetFrame()->width == GetCodecContext()->width); assert(frame->GetFrame()->height == GetCodecContext()->height); #endif #if SSR_USE_AVFRAME_FORMAT assert(frame->GetFrame()->format == GetCodecContext()->pix_fmt); #endif #if SSR_USE_AVFRAME_SAR assert(frame->GetFrame()->sample_aspect_ratio.num == GetCodecContext()->sample_aspect_ratio.num); assert(frame->GetFrame()->sample_aspect_ratio.den == GetCodecContext()->sample_aspect_ratio.den); #endif } #if SSR_USE_AVCODEC_SEND_RECEIVE // send a frame AVFrame *avframe = (frame == NULL)? NULL : frame->Release(); try { if(avcodec_send_frame(GetCodecContext(), avframe) < 0) { Logger::LogError("[VideoEncoder::EncodeFrame] " + Logger::tr("Error: Sending of video frame failed!")); throw LibavException(); } } catch(...) { av_frame_free(&avframe); throw; } av_frame_free(&avframe); // try to receive a packet for( ; ; ) { std::unique_ptr packet(new AVPacketWrapper()); int res = avcodec_receive_packet(GetCodecContext(), packet->GetPacket()); if(res == 0) { // we have a packet, send the packet to the muxer GetMuxer()->AddPacket(GetStream()->index, std::move(packet)); IncrementPacketCounter(); } else if(res == AVERROR(EAGAIN)) { // we have no packet return true; } else if(res == AVERROR_EOF) { // this is the end of the stream return false; } else { Logger::LogError("[VideoEncoder::EncodeFrame] " + Logger::tr("Error: Receiving of video packet failed!")); throw LibavException(); } } #elif SSR_USE_AVCODEC_ENCODE_VIDEO2 // allocate a packet std::unique_ptr packet(new AVPacketWrapper()); // encode the frame int got_packet; if(avcodec_encode_video2(GetCodecContext(), packet->GetPacket(), (frame == NULL)? NULL : frame->GetFrame(), &got_packet) < 0) { Logger::LogError("[VideoEncoder::EncodeFrame] " + Logger::tr("Error: Encoding of video frame failed!")); throw LibavException(); } // do we have a packet? if(got_packet) { // send the packet to the muxer GetMuxer()->AddPacket(GetStream()->index, std::move(packet)); IncrementPacketCounter(); return true; } else { return false; } #else // encode the frame int bytes_encoded = avcodec_encode_video(GetCodecContext(), m_temp_buffer.data(), m_temp_buffer.size(), (frame == NULL)? NULL : frame->GetFrame()); if(bytes_encoded < 0) { Logger::LogError("[VideoEncoder::EncodeFrame] " + Logger::tr("Error: Encoding of video frame failed!")); throw LibavException(); } // do we have a packet? if(bytes_encoded > 0) { // allocate a packet std::unique_ptr packet(new AVPacketWrapper(bytes_encoded)); // copy the data memcpy(packet->GetPacket()->data, m_temp_buffer.data(), bytes_encoded); // set the timestamp // note: pts will be rescaled and stream_index will be set by Muxer if(GetCodecContext()->coded_frame != NULL && GetCodecContext()->coded_frame->pts != (int64_t) AV_NOPTS_VALUE) packet->GetPacket()->pts = GetCodecContext()->coded_frame->pts; // set the keyframe flag if(GetCodecContext()->coded_frame->key_frame) packet->GetPacket()->flags |= AV_PKT_FLAG_KEY; // send the packet to the muxer GetMuxer()->AddPacket(GetStream()->index, std::move(packet)); IncrementPacketCounter(); return true; } else { return false; } #endif } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/VideoEncoder.h000066400000000000000000000035651470053624300246570ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "BaseEncoder.h" class VideoEncoder : public BaseEncoder { private: struct PixelFormatData { QString m_name; AVPixelFormat m_format; bool m_is_yuv; }; private: static const std::vector SUPPORTED_PIXEL_FORMATS; private: #if !SSR_USE_AVCODEC_ENCODE_VIDEO2 std::vector m_temp_buffer; #endif public: VideoEncoder(Muxer* muxer, AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options); ~VideoEncoder(); // Returns the required pixel format. AVPixelFormat GetPixelFormat(); // Returns the required color space. int GetColorSpace(); unsigned int GetWidth(); unsigned int GetHeight(); unsigned int GetFrameRate(); public: static bool AVCodecIsSupported(const QString& codec_name); static void PrepareStream(AVStream* stream, AVCodecContext* codec_context, AVCodec* codec, AVDictionary** options, const std::vector >& codec_options, unsigned int bit_rate, unsigned int width, unsigned int height, unsigned int frame_rate); private: virtual bool EncodeFrame(AVFrameWrapper* frame) override; }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/X264Presets.cpp000066400000000000000000000232651470053624300246540ustar00rootroot00000000000000#include "X264Presets.h" // This file was generated by 'x264-preset-translator.php', don't edit it. #if !SSR_USE_AVCODEC_PRIVATE_PRESET void X264Preset(AVCodecContext* cc, const char* preset) { if(strcmp(preset, "ultrafast") == 0) X264Preset_ultrafast(cc); if(strcmp(preset, "superfast") == 0) X264Preset_superfast(cc); if(strcmp(preset, "veryfast") == 0) X264Preset_veryfast(cc); if(strcmp(preset, "faster") == 0) X264Preset_faster(cc); if(strcmp(preset, "fast") == 0) X264Preset_fast(cc); if(strcmp(preset, "medium") == 0) X264Preset_medium(cc); if(strcmp(preset, "slow") == 0) X264Preset_slow(cc); if(strcmp(preset, "slower") == 0) X264Preset_slower(cc); if(strcmp(preset, "veryslow") == 0) X264Preset_veryslow(cc); if(strcmp(preset, "placebo") == 0) X264Preset_placebo(cc); } void X264Preset_ultrafast(AVCodecContext* cc) { cc->coder_type = 0; cc->flags &= ~CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions &= ~X264_PART_I8X8; cc->partitions &= ~X264_PART_I4X4; cc->partitions &= ~X264_PART_P8X8; cc->partitions &= ~X264_PART_B8X8; cc->me_method = ME_EPZS; cc->me_subpel_quality = 0; cc->me_range = 16; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 0; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 0; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 0; cc->refs = 1; cc->directpred = 1; cc->trellis = 0; cc->flags2 &= ~CODEC_FLAG2_BPYRAMID; cc->flags2 &= ~CODEC_FLAG2_MIXED_REFS; cc->flags2 &= ~CODEC_FLAG2_WPRED; cc->flags2 &= ~CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->flags2 &= ~CODEC_FLAG2_MBTREE; cc->weighted_p_pred = 0; cc->aq_mode = 0; cc->rc_lookahead = 0; } void X264Preset_superfast(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions &= ~X264_PART_P8X8; cc->partitions &= ~X264_PART_B8X8; cc->me_method = ME_EPZS; cc->me_subpel_quality = 1; cc->me_range = 16; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 1; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 3; cc->refs = 1; cc->directpred = 1; cc->trellis = 0; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 &= ~CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->flags2 &= ~CODEC_FLAG2_MBTREE; cc->weighted_p_pred = 0; cc->rc_lookahead = 0; } void X264Preset_veryfast(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions |= X264_PART_P8X8; cc->partitions |= X264_PART_B8X8; cc->me_method = ME_HEX; cc->me_subpel_quality = 2; cc->me_range = 16; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 1; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 3; cc->refs = 1; cc->directpred = 1; cc->trellis = 0; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 &= ~CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->weighted_p_pred = 0; cc->rc_lookahead = 10; } void X264Preset_faster(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions |= X264_PART_P8X8; cc->partitions |= X264_PART_B8X8; cc->me_method = ME_HEX; cc->me_subpel_quality = 4; cc->me_range = 16; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 1; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 3; cc->refs = 2; cc->directpred = 1; cc->trellis = 1; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 &= ~CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->weighted_p_pred = 1; cc->rc_lookahead = 20; } void X264Preset_fast(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions |= X264_PART_P8X8; cc->partitions |= X264_PART_B8X8; cc->me_method = ME_HEX; cc->me_subpel_quality = 6; cc->me_range = 16; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 1; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 3; cc->refs = 2; cc->directpred = 1; cc->trellis = 1; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 |= CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->weighted_p_pred = 2; cc->rc_lookahead = 30; } void X264Preset_medium(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions |= X264_PART_P8X8; cc->partitions |= X264_PART_B8X8; cc->me_method = ME_HEX; cc->me_subpel_quality = 7; cc->me_range = 16; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 1; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 3; cc->refs = 3; cc->directpred = 1; cc->trellis = 1; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 |= CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->weighted_p_pred = 2; } void X264Preset_slow(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions |= X264_PART_P8X8; cc->partitions |= X264_PART_B8X8; cc->me_method = ME_UMH; cc->me_subpel_quality = 8; cc->me_range = 16; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 2; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 3; cc->refs = 5; cc->directpred = 3; cc->trellis = 1; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 |= CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->weighted_p_pred = 2; cc->rc_lookahead = 50; } void X264Preset_slower(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions |= X264_PART_P8X8; cc->partitions |= X264_PART_P4X4; cc->partitions |= X264_PART_B8X8; cc->me_method = ME_UMH; cc->me_subpel_quality = 9; cc->me_range = 16; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 2; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 3; cc->refs = 8; cc->directpred = 3; cc->trellis = 2; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 |= CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->weighted_p_pred = 2; cc->rc_lookahead = 60; } void X264Preset_veryslow(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions |= X264_PART_P8X8; cc->partitions |= X264_PART_P4X4; cc->partitions |= X264_PART_B8X8; cc->me_method = ME_UMH; cc->me_subpel_quality = 10; cc->me_range = 24; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 2; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 8; cc->refs = 16; cc->directpred = 3; cc->trellis = 2; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 |= CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 |= CODEC_FLAG2_FASTPSKIP; cc->weighted_p_pred = 2; cc->rc_lookahead = 60; } void X264Preset_placebo(AVCodecContext* cc) { cc->coder_type = 1; cc->flags |= CODEC_FLAG_LOOP_FILTER; cc->flags |= CODEC_FLAG_CLOSED_GOP; cc->me_cmp |= 1; cc->partitions |= X264_PART_I8X8; cc->partitions |= X264_PART_I4X4; cc->partitions |= X264_PART_P8X8; cc->partitions |= X264_PART_P4X4; cc->partitions |= X264_PART_B8X8; cc->me_method = ME_TESA; cc->me_subpel_quality = 10; cc->me_range = 24; cc->gop_size = 250; cc->keyint_min = 25; cc->scenechange_threshold = 40; cc->i_quant_factor = 0.71; cc->b_frame_strategy = 2; cc->qcompress = 0.6; cc->qmin = 0; cc->qmax = 69; cc->max_qdiff = 4; cc->max_b_frames = 16; cc->refs = 16; cc->directpred = 3; cc->trellis = 2; cc->flags2 |= CODEC_FLAG2_BPYRAMID; cc->flags2 |= CODEC_FLAG2_MIXED_REFS; cc->flags2 |= CODEC_FLAG2_WPRED; cc->flags2 |= CODEC_FLAG2_8X8DCT; cc->flags2 &= ~CODEC_FLAG2_FASTPSKIP; cc->weighted_p_pred = 2; cc->rc_lookahead = 60; } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/X264Presets.h000066400000000000000000000012241470053624300243100ustar00rootroot00000000000000#pragma once #include "Global.h" // This file was generated by 'x264-preset-translator.php', don't edit it. #if !SSR_USE_AVCODEC_PRIVATE_PRESET void X264Preset(AVCodecContext* cc, const char* preset); void X264Preset_ultrafast(AVCodecContext* cc); void X264Preset_superfast(AVCodecContext* cc); void X264Preset_veryfast(AVCodecContext* cc); void X264Preset_faster(AVCodecContext* cc); void X264Preset_fast(AVCodecContext* cc); void X264Preset_medium(AVCodecContext* cc); void X264Preset_slow(AVCodecContext* cc); void X264Preset_slower(AVCodecContext* cc); void X264Preset_veryslow(AVCodecContext* cc); void X264Preset_placebo(AVCodecContext* cc); #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/Output/x264-preset-translator.php000066400000000000000000000124161470053624300270360ustar00rootroot00000000000000 This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ // This script converts old ffmpeg preset files for x264 into C++ code that sets the corresponding AVCodecContext member variables. $presets = array( "ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow", "placebo", ); $translator = array( "coder" => "coder_type", "subq" => "me_subpel_quality", "me_range" => "me_range", "g" => "gop_size", "keyint_min" => "keyint_min", "sc_threshold" => "scenechange_threshold", "i_qfactor" => "i_quant_factor", "b_strategy" => "b_frame_strategy", "qcomp" => "qcompress", "qmin" => "qmin", "qmax" => "qmax", "qdiff" => "max_qdiff", "bf" => "max_b_frames", "refs" => "refs", "directpred" => "directpred", "trellis" => "trellis", "wpredp" => "weighted_p_pred", "aq_mode" => "aq_mode", "rc_lookahead" => "rc_lookahead", "me_method" => "me_method", // enum "flags" => "flags", // bitset "flags2" => "flags2", // bitset "cmp" => "me_cmp", // bitset "partitions" => "partitions", // bitset ); $translator_enum = array( "me_method" => array( "dia" => "ME_EPZS", "hex" => "ME_HEX", "umh" => "ME_UMH", "esa" => "ME_FULL", "tesa" => "ME_TESA", ), ); $translator_bitset = array( "flags" => array( "loop" => "CODEC_FLAG_LOOP_FILTER", "cgop" => "CODEC_FLAG_CLOSED_GOP", ), "flags2" => array( "bpyramid" => "CODEC_FLAG2_BPYRAMID", "mixed_refs" => "CODEC_FLAG2_MIXED_REFS", "wpred" => "CODEC_FLAG2_WPRED", "dct8x8" => "CODEC_FLAG2_8X8DCT", "fastpskip" => "CODEC_FLAG2_FASTPSKIP", "mbtree" => "CODEC_FLAG2_MBTREE", ), "cmp" => array( "chroma" => "1", ), "partitions" => array( "parti4x4" => "X264_PART_I4X4", "parti8x8" => "X264_PART_I8X8", "partp8x8" => "X264_PART_P8X8", "partp4x4" => "X264_PART_P4X4", "partb8x8" => "X264_PART_B8X8", ), ); $out_header = ""; $out_header .= "#pragma once\n"; $out_header .= "#include \"Global.h\"\n\n"; $out_header .= "// This file was generated by 'x264-preset-translator.php', don't edit it.\n\n"; $out_header .= "#if !SSR_USE_AVCODEC_PRIVATE_PRESET\n\n"; $out_source = ""; $out_source .= "#include \"X264Presets.h\"\n\n"; $out_source .= "// This file was generated by 'x264-preset-translator.php', don't edit it.\n\n"; $out_source .= "#if !SSR_USE_AVCODEC_PRIVATE_PRESET\n\n"; $out_header .= "void X264Preset(AVCodecContext* cc, const char* preset);\n\n"; $out_source .= "void X264Preset(AVCodecContext* cc, const char* preset) {\n"; foreach($presets as $preset) { $out_source .= "\tif(strcmp(preset, \"" . $preset . "\") == 0)\n"; $out_source .= "\t\tX264Preset_" . $preset . "(cc);\n"; } $out_source .= "}\n\n"; foreach($presets as $preset) { $data = file_get_contents("/usr/local/share/ffmpeg/libx264-" . $preset . ".ffpreset"); if($data === FALSE) die("Can't find preset '" . $preset . "'!\n"); $lines = explode("\n", str_replace("\r", "", $data)); $out_header .= "void X264Preset_" . $preset . "(AVCodecContext* cc);\n"; $out_source .= "void X264Preset_" . $preset . "(AVCodecContext* cc) {\n"; foreach($lines as $n => $line) { if($line == "") continue; $parts = explode("=", $line); if(count($parts) != 2) die("Syntax error at line " . $n . " of file " . $preset . ": " . $line . "\n"); if(isset($translator[$parts[0]])) { if(isset($translator_enum[$parts[0]])) { $t = $translator_enum[$parts[0]]; if(isset($t[$parts[1]])) { $out_source .= "\tcc->" . $translator[$parts[0]] . " = " . $t[$parts[1]] . ";\n"; } else { die("Unknown enum value at line " . $n . " of file " . $preset . ": " . $line . "\n"); } } else if(isset($translator_bitset[$parts[0]])) { $t = $translator_bitset[$parts[0]]; $flags = preg_split("/(\+|\-)/", $parts[1], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE); foreach($flags as $flag) { if(isset($t[$flag[0]])) { if($parts[1][$flag[1] - 1] == "+") { $out_source .= "\tcc->" . $translator[$parts[0]] . " |= " . $t[$flag[0]] . ";\n"; } else { $out_source .= "\tcc->" . $translator[$parts[0]] . " &= ~" . $t[$flag[0]] . ";\n"; } } else { die("Can't translate line " . $n . " of file " . $preset . ": " . $line . "\n" . "Unknown flag '" . $flag[0] . "'.\n"); } } } else { $out_source .= "\tcc->" . $translator[$parts[0]] . " = " . $parts[1] . ";\n"; } } else { die("Can't translate line " . $n . " of file " . $preset . ": " . $line . "\n"); } } $out_source .= "}\n\n"; } $out_header .= "\n"; $out_header .= "#endif\n"; $out_source .= "#endif\n"; file_put_contents("X264Presets.h", $out_header); file_put_contents("X264Presets.cpp", $out_source); ?> ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/SampleCast.h000066400000000000000000000071261470053624300230420ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" template OUT SampleCast(IN x); template<> inline int16_t SampleCast(int16_t x) { return x; } template<> inline int16_t SampleCast(int32_t x) { return (x + (1 << 15)) >> 16; } template<> inline int16_t SampleCast(float x) { return lrint(fmin(fmax(x * 32768.0f, -32768.0f), 32767.0f)); } template<> inline float SampleCast(int16_t x) { return (float) x * (1.0f / 32768.0f); } template<> inline float SampleCast(int32_t x) { return (float) x * (1.0f / 2147483648.0f); } template<> inline float SampleCast(float x) { return x; } template IN SampleMix(IN a, IN b); template<> inline int16_t SampleMix(int16_t a, int16_t b) { return (a + b) >> 1; } template<> inline float SampleMix(float a, float b) { return (a + b) * 0.5f; } // Simple sample format conversion. // The in_step and out_step parameters are useful for converting between planar and interleaved audio. template inline void SampleCopy(unsigned int sample_count, const IN* in_data, int in_step, OUT* out_data, int out_step) { for(unsigned int i = 0; i < sample_count; ++i) { *out_data = SampleCast(*in_data); in_data += in_step; out_data += out_step; } } // Sample format conversion and channel remapping in one step. // This function only supports interleaved audio. template inline void SampleChannelRemap(unsigned int sample_count, const IN* in_data, unsigned int in_channels, OUT* out_data, unsigned int out_channels) { if(in_channels == out_channels) { // no remapping needed for(unsigned int i = 0; i < sample_count * in_channels; ++i) { *(out_data++) = SampleCast(*(in_data++)); } } else if(in_channels == 1 && out_channels == 2) { // mono to stereo for(unsigned int i = 0; i < sample_count; ++i) { OUT val = SampleCast(*(in_data++)); *(out_data++) = val; *(out_data++) = val; } } else if(in_channels == 2 && out_channels == 1) { // stereo to mono for(unsigned int i = 0; i < sample_count; ++i) { OUT val1 = SampleCast(*(in_data++)); OUT val2 = SampleCast(*(in_data++)); *(out_data++) = SampleMix(val1, val2); } } else if(in_channels < out_channels) { // fill additional channels with silence for(unsigned int i = 0; i < sample_count; ++i) { for(unsigned int j = 0; j < in_channels; ++j) *(out_data++) = SampleCast(*(in_data++)); for(unsigned int j = 0; j < out_channels - in_channels; ++j) *(out_data++) = (OUT) 0; } } else { // drop redundant channels for(unsigned int i = 0; i < sample_count; ++i) { for(unsigned int j = 0; j < out_channels; ++j) *(out_data++) = SampleCast(*(in_data++)); in_data += in_channels - out_channels; } } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/SimpleSynth.cpp000066400000000000000000000232031470053624300236120ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "SimpleSynth.h" #if SSR_USE_ALSA #include "Logger.h" #include "SampleCast.h" #include #include #include static void MakeThreadHighPriority() { #ifdef __linux__ rlimit limit; // try to get real-time priority getrlimit(RLIMIT_RTPRIO, &limit); int rt_limit = (limit.rlim_cur == RLIM_INFINITY)? std::numeric_limits::max() : limit.rlim_cur; if(rt_limit > 0) { int priority = std::min(10, rt_limit); if(sched_setscheduler(0, SCHED_FIFO, (sched_param*) &priority) == 0) { Logger::LogInfo("[MakeThreadHighPriority] " + Logger::tr("Using real-time priority.")); return; } } // if that fails, use a low nice value instead getrlimit(RLIMIT_NICE, &limit); int nice_limit = (limit.rlim_cur == RLIM_INFINITY)? std::numeric_limits::min() : 20 - limit.rlim_cur; // see man page if(nice_limit < 0) { if(setpriority(PRIO_PROCESS, 0, std::max(-10, nice_limit)) == 0) { Logger::LogInfo("[MakeThreadHighPriority] " + Logger::tr("Using nice priority.", "Don't translate 'nice', it's a UNIX parameter that defines the priority of a process.")); return; } } Logger::LogWarning("[MakeThreadHighPriority] " + Logger::tr("Warning: Can't increase the thread priority.")); #endif } static void ALSARecoverAfterUnderrun(snd_pcm_t* pcm) { // this is not really a problem so don't show the warning //Logger::LogWarning("[ALSARecoverAfterUnderrun] " + Logger::tr("Warning: An underrun has occurred, some samples were too late.", "Don't translate 'underrun'")); if(snd_pcm_prepare(pcm) < 0) { Logger::LogError("[ALSARecoverAfterUnderrun] " + Logger::tr("Error: Can't recover device after underrun!", "Don't translate 'underrun'")); throw ALSAException(); } } SimpleSynth::SimpleSynth(const QString& sink_name, unsigned int sample_rate) { m_sink_name = sink_name; m_sample_rate = sample_rate; m_period_size = 1024; // number of samples per period m_buffer_size = m_period_size * 2; // number of samples in the buffer m_alsa_pcm = NULL; try { Init(); } catch(...) { Free(); throw; } } SimpleSynth::~SimpleSynth() { // tell the thread to stop if(m_thread.joinable()) { Logger::LogInfo("[SimpleSynth::~SimpleSynth] " + Logger::tr("Stopping synth thread ...")); m_should_stop = true; m_thread.join(); } // free everything Free(); } void SimpleSynth::PlaySequence(const Note* notes, unsigned int note_count) { if(m_error_occurred) return; SharedLock lock(&m_shared_data); for(unsigned int i = 0; i < note_count; ++i) { lock->m_notes.push_back(notes[i]); lock->m_notes.back().m_time += lock->m_current_time; } } void SimpleSynth::Init() { snd_pcm_hw_params_t *alsa_hw_params = NULL; try { // allocate parameter structure if(snd_pcm_hw_params_malloc(&alsa_hw_params) < 0) { throw std::bad_alloc(); } // open PCM device if(snd_pcm_open(&m_alsa_pcm, m_sink_name.toUtf8().constData(), SND_PCM_STREAM_PLAYBACK, 0) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't open PCM device!")); throw ALSAException(); } if(snd_pcm_hw_params_any(m_alsa_pcm, alsa_hw_params) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't get PCM hardware parameters!")); throw ALSAException(); } // set access type if(snd_pcm_hw_params_set_access(m_alsa_pcm, alsa_hw_params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't set access type!")); throw ALSAException(); } // set sample format if(snd_pcm_hw_params_set_format(m_alsa_pcm, alsa_hw_params, SND_PCM_FORMAT_S16_LE) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't set sample format!")); throw ALSAException(); } // set sample rate unsigned int sample_rate = m_sample_rate; if(snd_pcm_hw_params_set_rate_near(m_alsa_pcm, alsa_hw_params, &sample_rate, NULL) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't set sample rate!")); throw ALSAException(); } if(sample_rate != m_sample_rate) { Logger::LogWarning("[SimpleSynth::Init] " + Logger::tr("Warning: Sample rate %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_sample_rate).arg(sample_rate)); m_sample_rate = sample_rate; } // set channel count if(snd_pcm_hw_params_set_channels(m_alsa_pcm, alsa_hw_params, 1) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't set channel count!")); throw ALSAException(); } // set period size snd_pcm_uframes_t period_size = m_period_size; if(snd_pcm_hw_params_set_period_size_near(m_alsa_pcm, alsa_hw_params, &period_size, NULL) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't set period size!")); throw ALSAException(); } if(period_size != m_period_size) { Logger::LogWarning("[SimpleSynth::Init] " + Logger::tr("Warning: Period size %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_period_size).arg(period_size)); m_period_size = period_size; } // set buffer size snd_pcm_uframes_t buffer_size = m_buffer_size; if(snd_pcm_hw_params_set_buffer_size_near(m_alsa_pcm, alsa_hw_params, &buffer_size) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't set buffer size!")); throw ALSAException(); } if(buffer_size != m_buffer_size) { Logger::LogWarning("[SimpleSynth::Init] " + Logger::tr("Warning: Buffer size %1 is not supported, using %2 instead. " "This is not a problem.") .arg(m_buffer_size).arg(buffer_size)); m_buffer_size = buffer_size; } // apply parameters if(snd_pcm_hw_params(m_alsa_pcm, alsa_hw_params) < 0) { Logger::LogError("[SimpleSynth::Init] " + Logger::tr("Error: Can't apply PCM hardware parameters!")); throw ALSAException(); } // free parameter structure snd_pcm_hw_params_free(alsa_hw_params); alsa_hw_params = NULL; } catch(...) { if(alsa_hw_params != NULL) { snd_pcm_hw_params_free(alsa_hw_params); alsa_hw_params = NULL; } throw; } // start input thread m_should_stop = false; m_error_occurred = false; m_thread = std::thread(&SimpleSynth::SynthThread, this); } void SimpleSynth::Free() { if(m_alsa_pcm != NULL) { snd_pcm_close(m_alsa_pcm); m_alsa_pcm = NULL; } } void SimpleSynth::SynthThread() { try { Logger::LogInfo("[SimpleSynth::SynthThread] " + Logger::tr("Synth thread started.")); MakeThreadHighPriority(); std::vector buffer_float(m_period_size); std::vector buffer_int16(m_period_size); while(!m_should_stop) { // generate the samples { SharedLock lock(&m_shared_data); if(lock->m_notes.empty()) { // faster version memset(buffer_int16.data(), 0, m_period_size * sizeof(int16_t)); } else { memset(buffer_float.data(), 0, m_period_size * sizeof(float)); for(unsigned int i = 0; i < lock->m_notes.size(); ) { Note &n = lock->m_notes[i]; int trel = n.m_time - lock->m_current_time; int t1 = clamp(trel, 0, (int) m_period_size); int t2 = clamp(trel + (int) n.m_duration_in, 0, (int) m_period_size); int t3 = clamp(trel + (int) n.m_duration_in + (int) n.m_duration_out, 0, (int) m_period_size); if(t1 == (int) m_period_size) { // beep not started yet ++i; continue; } if(t3 == 0) { // beep done if(i != lock->m_notes.size() - 1) n = lock->m_notes.back(); lock->m_notes.pop_back(); continue; } float k = 2.0f * M_PI * n.m_frequency / (float) m_sample_rate; float amp_low = log2(n.m_amplitude * 0.001f), amp_high = log2(n.m_amplitude); for(int t = t1; t < t2; ++t) { buffer_float[t] += sin((float) (t - trel) * k) * exp2(amp_low + (amp_high - amp_low) * (float) (t - trel) / (float) n.m_duration_in); } for(int t = t2; t < t3; ++t) { buffer_float[t] += sin((float) (t - trel) * k) * exp2(amp_high + (amp_low - amp_high) * (float) (t - trel - (int) n.m_duration_in) / (float) n.m_duration_out); } ++i; } lock->m_current_time += m_period_size; SampleCopy(m_period_size, buffer_float.data(), 1, buffer_int16.data(), 1); } } // write the samples snd_pcm_sframes_t samples_written = snd_pcm_writei(m_alsa_pcm, buffer_int16.data(), m_period_size); if(samples_written < 0) { if(samples_written == -EPIPE) { ALSARecoverAfterUnderrun(m_alsa_pcm); } else { Logger::LogError("[SimpleSynth::SynthThread] " + Logger::tr("Error: Can't write samples!")); throw ALSAException(); } continue; } } Logger::LogInfo("[SimpleSynth::SynthThread] " + Logger::tr("Synth thread stopped.")); } catch(const std::exception& e) { m_error_occurred = true; Logger::LogError("[SimpleSynth::SynthThread] " + Logger::tr("Exception '%1' in synth thread.").arg(e.what())); } catch(...) { m_error_occurred = true; Logger::LogError("[SimpleSynth::SynthThread] " + Logger::tr("Unknown exception in synth thread.")); } } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/SimpleSynth.h000066400000000000000000000031721470053624300232620ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #if SSR_USE_ALSA #include "MutexDataPair.h" #include class SimpleSynth { public: struct Note { unsigned int m_time, m_duration_in, m_duration_out; float m_frequency, m_amplitude; }; private: struct SharedData { std::vector m_notes; unsigned int m_current_time; }; typedef MutexDataPair::Lock SharedLock; private: QString m_sink_name; unsigned int m_sample_rate; snd_pcm_t *m_alsa_pcm; unsigned int m_period_size, m_buffer_size; std::thread m_thread; MutexDataPair m_shared_data; std::atomic m_should_stop, m_error_occurred; public: SimpleSynth(const QString& sink_name, unsigned int sample_rate); ~SimpleSynth(); // This function is thread-safe. void PlaySequence(const Note* notes, unsigned int note_count); private: void Init(); void Free(); void SynthThread(); }; #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/SourceSink.cpp000066400000000000000000000062551470053624300234300ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "SourceSink.h" BaseSource::BaseSource() { } BaseSource::~BaseSource() { SharedLock lock(&m_shared_data); for(SinkData &s : lock->m_sinks) { s.sink->m_source = NULL; } } BaseSink::BaseSink() { m_source = NULL; m_priority = 0; } BaseSink::~BaseSink() { // Classes that inherit a sink should disconnect themselves in the destructor before doing anything else, // otherwise inputs may try to send data to partially destructed sinks. assert(m_source == NULL); } void BaseSink::ConnectBaseSource(BaseSource* source, int priority) { if(m_source == source && m_priority == priority) return; if(m_source != NULL) { BaseSource::SharedLock lock(&m_source->m_shared_data); for(auto it = lock->m_sinks.begin(); it != lock->m_sinks.end(); ++it) { if(it->sink == this) { lock->m_sinks.erase(it); break; } } } m_source = source; if(m_source != NULL) { BaseSource::SharedLock lock(&m_source->m_shared_data); BaseSource::SinkData data(this, priority); auto it = std::upper_bound(lock->m_sinks.begin(), lock->m_sinks.end(), data); lock->m_sinks.insert(it, data); } } int64_t VideoSource::CalculateNextVideoTimestamp() { SharedLock lock(&m_shared_data); for(SinkData &s : lock->m_sinks) { int64_t timestamp = static_cast(s.sink)->GetNextVideoTimestamp(); if(timestamp != SINK_TIMESTAMP_NONE) return timestamp; } return SINK_TIMESTAMP_NONE; } void VideoSource::PushVideoFrame(unsigned int width, unsigned int height, const uint8_t* const* data, const int* stride, AVPixelFormat format, int colorspace, int64_t timestamp) { SharedLock lock(&m_shared_data); for(SinkData &s : lock->m_sinks) { static_cast(s.sink)->ReadVideoFrame(width, height, data, stride, format, colorspace, timestamp); } } void VideoSource::PushVideoPing(int64_t timestamp) { SharedLock lock(&m_shared_data); for(SinkData &s : lock->m_sinks) { static_cast(s.sink)->ReadVideoPing(timestamp); } } void AudioSource::PushAudioSamples(unsigned int channels, unsigned int sample_rate, AVSampleFormat format, unsigned int sample_count, const uint8_t* data, int64_t timestamp) { SharedLock lock(&m_shared_data); for(SinkData &s : lock->m_sinks) { static_cast(s.sink)->ReadAudioSamples(channels, sample_rate, format, sample_count, data, timestamp); } } void AudioSource::PushAudioHole() { SharedLock lock(&m_shared_data); for(SinkData &s : lock->m_sinks) { static_cast(s.sink)->ReadAudioHole(); } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/AV/SourceSink.h000066400000000000000000000073371470053624300230770ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "AVWrapper.h" #include "MutexDataPair.h" // The video source/sink system keeps track of connections between video inputs and outputs. // It decides where new frames should be sent. Using these connections is thread-safe, // however only ONE thread should ever create or destroy connections (this includes destroying sources or sinks). #define SINK_TIMESTAMP_NONE ((int64_t) 0x8000000000000000ull) // the sink doesn't want any new frames at the moment #define SINK_TIMESTAMP_ASAP ((int64_t) 0x8000000000000001ull) // the sink wants a new frame as soon as possible class BaseSource; class BaseSink; class BaseSource { public: struct SinkData { BaseSink *sink; int priority; inline SinkData() {} inline SinkData(BaseSink *sink, int priority) : sink(sink), priority(priority) {} inline bool operator<(const SinkData& other) const { return (priority > other.priority); // sort in reverse order (high priority first) } }; struct SharedData { std::vector m_sinks; }; typedef MutexDataPair::Lock SharedLock; public: MutexDataPair m_shared_data; public: BaseSource(); virtual ~BaseSource(); }; class BaseSink { public: // variables are not protected by a lock because they should only be read when connections change BaseSource *m_source; int m_priority; public: BaseSink(); virtual ~BaseSink(); void ConnectBaseSource(BaseSource* source, int priority); }; class VideoSource : private BaseSource { friend class VideoSink; protected: VideoSource() {} int64_t CalculateNextVideoTimestamp(); void PushVideoFrame(unsigned int width, unsigned int height, const uint8_t* const* data, const int* stride, AVPixelFormat format, int colorspace, int64_t timestamp); void PushVideoPing(int64_t timestamp); }; class VideoSink : private BaseSink { friend class VideoSource; protected: VideoSink() {} public: inline void ConnectVideoSource(VideoSource* source, int priority = 0) { ConnectBaseSource(source, priority); } public: virtual int64_t GetNextVideoTimestamp() { return SINK_TIMESTAMP_NONE; } virtual void ReadVideoFrame(unsigned int width, unsigned int height, const uint8_t* const* data, const int* stride, AVPixelFormat format, int colorspace, int64_t timestamp) = 0; virtual void ReadVideoPing(int64_t timestamp) {} }; class AudioSource : private BaseSource { friend class AudioSink; protected: AudioSource() {} void PushAudioSamples(unsigned int channels, unsigned int sample_rate, AVSampleFormat format, unsigned int sample_count, const uint8_t* data, int64_t timestamp); void PushAudioHole(); }; class AudioSink : private BaseSink { friend class AudioSource; protected: AudioSink() {} public: inline void ConnectAudioSource(AudioSource* source, int priority = 0) { ConnectBaseSource(source, priority); } public: virtual void ReadAudioSamples(unsigned int channels, unsigned int sample_rate, AVSampleFormat format, unsigned int sample_count, const uint8_t* data, int64_t timestamp) = 0; virtual void ReadAudioHole() {} }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/Benchmark.cpp000066400000000000000000000267541470053624300227350ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "Benchmark.h" #include "AVWrapper.h" #include "CPUFeatures.h" #include "FastScaler_Convert.h" #include "FastScaler_Scale.h" #include "Logger.h" #include "TempBuffer.h" #include struct ImageGeneric { TempBuffer m_buffer; std::vector m_data; std::vector m_stride; ImageGeneric(const std::vector& stride, const std::vector& rows, std::mt19937& rng) { // count planes assert(stride.size() == rows.size()); unsigned int planes = stride.size(); m_data.resize(planes); m_stride.resize(planes); // calculate stride and total size size_t totalsize = 0; for(unsigned int p = 0; p < planes; ++p) { m_stride[p] = grow_align16(stride[p]); totalsize += m_stride[p] * rows[p]; } // allocate buffer m_buffer.Alloc(totalsize); for(unsigned int i = 0; i < totalsize; ++i) { m_buffer[i] = rng(); } // set data uint8_t *data = m_buffer.GetData(); for(unsigned int p = 0; p < planes; ++p) { m_data[p] = data; data += m_stride[p] * rows[p]; } } }; std::unique_ptr NewImageBGRA(unsigned int w, unsigned int h, std::mt19937& rng) { return std::unique_ptr(new ImageGeneric({w * 4}, {h}, rng)); } std::unique_ptr NewImageYUV444(unsigned int w, unsigned int h, std::mt19937& rng) { return std::unique_ptr(new ImageGeneric({w, w, w}, {h, h, h}, rng)); } std::unique_ptr NewImageYUV422(unsigned int w, unsigned int h, std::mt19937& rng) { return std::unique_ptr(new ImageGeneric({w, w / 2, w / 2}, {h, h, h}, rng)); } std::unique_ptr NewImageYUV420(unsigned int w, unsigned int h, std::mt19937& rng) { return std::unique_ptr(new ImageGeneric({w, w / 2, w / 2}, {h, h / 2, h / 2}, rng)); } std::unique_ptr NewImageNV12(unsigned int w, unsigned int h, std::mt19937& rng) { return std::unique_ptr(new ImageGeneric({w, w}, {h, h / 2}, rng)); } std::unique_ptr NewImageBGR(unsigned int w, unsigned int h, std::mt19937& rng) { return std::unique_ptr(new ImageGeneric({w * 3}, {h}, rng)); } typedef std::unique_ptr (*NewImageFunc)(unsigned int, unsigned int, std::mt19937&); typedef void (*ConvertFunc)(unsigned int, unsigned int, const uint8_t*, int, uint8_t* const*, const int*); template void PlaneWrapper(unsigned int w, unsigned int h, const uint8_t* in_data, int in_stride, uint8_t* const* out_data, const int* out_stride) { T(w, h, in_data, in_stride, out_data[0], out_stride[0]); } void BenchmarkScale(unsigned int in_w, unsigned int in_h, unsigned int out_w, unsigned int out_h) { std::mt19937 rng(12345); #if SSR_USE_X86_ASM bool use_ssse3 = (CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2() && CPUFeatures::HasSSE3() && CPUFeatures::HasSSSE3()); #endif // the queue needs to use enough memory to make sure that the CPU cache is flushed unsigned int pixels = std::max(in_w * in_h, out_w * out_h); unsigned int queue_size = 1 + 20000000 / pixels; unsigned int run_size = queue_size * 20; // create queue std::vector > queue_in(queue_size); std::vector > queue_out(queue_size); for(unsigned int i = 0; i < queue_size; ++i) { queue_in[i] = NewImageBGRA(in_w, in_h, rng); queue_out[i] = NewImageBGRA(out_w, out_h, rng); } // run test unsigned int time_swscale = 0, time_fallback = 0, time_ssse3 = 0; { SwsContext *sws = sws_getCachedContext(NULL, in_w, in_h, AV_PIX_FMT_BGRA, out_w, out_h, AV_PIX_FMT_BGRA, SWS_BILINEAR, NULL, NULL, NULL); if(sws == NULL) { Logger::LogError("[BenchmarkScale] " + Logger::tr("Error: Can't get swscale context!", "Don't translate 'swscale'")); throw LibavException(); } sws_setColorspaceDetails(sws, sws_getCoefficients(SWS_CS_ITU709), 0, sws_getCoefficients(SWS_CS_DEFAULT), 0, 0, 1 << 16, 1 << 16); int64_t t1 = hrt_time_micro(); for(unsigned int i = 0; i < run_size / 2; ++i) { unsigned int ii = i % queue_size; sws_scale(sws, queue_in[ii]->m_data.data(), queue_in[ii]->m_stride.data(), 0, in_h, queue_out[ii]->m_data.data(), queue_out[ii]->m_stride.data()); } int64_t t2 = hrt_time_micro(); time_swscale = (t2 - t1) / (run_size / 2); } { int64_t t1 = hrt_time_micro(); for(unsigned int i = 0; i < run_size; ++i) { unsigned int ii = i % queue_size; Scale_BGRA_Fallback(in_w, in_h, queue_in[ii]->m_data[0], queue_in[ii]->m_stride[0], out_w, out_h, queue_out[ii]->m_data[0], queue_out[ii]->m_stride[0]); } int64_t t2 = hrt_time_micro(); time_fallback = (t2 - t1) / run_size; } #if SSR_USE_X86_ASM if(use_ssse3) { int64_t t1 = hrt_time_micro(); for(unsigned int i = 0; i < run_size; ++i) { unsigned int ii = i % queue_size; Scale_BGRA_SSSE3(in_w, in_h, queue_in[ii]->m_data[0], queue_in[ii]->m_stride[0], out_w, out_h, queue_out[ii]->m_data[0], queue_out[ii]->m_stride[0]); } int64_t t2 = hrt_time_micro(); time_ssse3 = (t2 - t1) / run_size; } #endif // print result QString in_size = QString("%1x%2").arg(in_w).arg(in_h); QString out_size = QString("%1x%2").arg(out_w).arg(out_h); Logger::LogInfo("[BenchmarkScale] " + Logger::tr("BGRA %1 to BGRA %2 | SWScale %3 us | Fallback %4 us (%5%) | SSSE3 %6 us (%7%)") .arg(in_size, 9).arg(out_size, 9) .arg(time_swscale, 6) .arg(time_fallback, 6).arg(100 * time_fallback / time_swscale, 3) .arg(time_ssse3, 6).arg(100 * time_ssse3 / time_fallback, 3)); } void BenchmarkConvert(unsigned int w, unsigned int h, AVPixelFormat in_format, AVPixelFormat out_format, const QString& in_format_name, const QString& out_format_name, NewImageFunc in_image, NewImageFunc out_image, ConvertFunc fallback #if SSR_USE_X86_ASM , ConvertFunc ssse3 #endif ) { std::mt19937 rng(12345); #if SSR_USE_X86_ASM bool use_ssse3 = (CPUFeatures::HasMMX() && CPUFeatures::HasSSE() && CPUFeatures::HasSSE2() && CPUFeatures::HasSSE3() && CPUFeatures::HasSSSE3()); #endif // the queue needs to use enough memory to make sure that the CPU cache is flushed unsigned int pixels = w * h; unsigned int queue_size = 1 + 20000000 / pixels; unsigned int run_size = queue_size * 20; // create queue std::vector > queue_in(queue_size); std::vector > queue_out(queue_size); for(unsigned int i = 0; i < queue_size; ++i) { queue_in[i] = in_image(w, h, rng); queue_out[i] = out_image(w, h, rng); } // run test unsigned int time_swscale = 0, time_fallback = 0, time_ssse3 = 0; { SwsContext *sws = sws_getCachedContext(NULL, w, h, in_format, w, h, out_format, SWS_BILINEAR, NULL, NULL, NULL); if(sws == NULL) { Logger::LogError("[BenchmarkScale] " + Logger::tr("Error: Can't get swscale context!", "Don't translate 'swscale'")); throw LibavException(); } sws_setColorspaceDetails(sws, sws_getCoefficients(SWS_CS_ITU709), 0, sws_getCoefficients(SWS_CS_DEFAULT), 0, 0, 1 << 16, 1 << 16); int64_t t1 = hrt_time_micro(); for(unsigned int i = 0; i < run_size / 2; ++i) { unsigned int ii = i % queue_size; sws_scale(sws, queue_in[ii]->m_data.data(), queue_in[ii]->m_stride.data(), 0, h, queue_out[ii]->m_data.data(), queue_out[ii]->m_stride.data()); } int64_t t2 = hrt_time_micro(); time_swscale = (t2 - t1) / (run_size / 2); } { int64_t t1 = hrt_time_micro(); for(unsigned int i = 0; i < run_size; ++i) { unsigned int ii = i % queue_size; fallback(w, h, queue_in[ii]->m_data[0], queue_in[ii]->m_stride[0], queue_out[ii]->m_data.data(), queue_out[ii]->m_stride.data()); } int64_t t2 = hrt_time_micro(); time_fallback = (t2 - t1) / run_size; } #if SSR_USE_X86_ASM if(use_ssse3) { int64_t t1 = hrt_time_micro(); for(unsigned int i = 0; i < run_size; ++i) { unsigned int ii = i % queue_size; ssse3(w, h, queue_in[ii]->m_data[0], queue_in[ii]->m_stride[0], queue_out[ii]->m_data.data(), queue_out[ii]->m_stride.data()); } int64_t t2 = hrt_time_micro(); time_ssse3 = (t2 - t1) / run_size; } #endif // print result QString size = QString("%1x%2").arg(w).arg(h); Logger::LogInfo("[BenchmarkConvert] " + Logger::tr("%1 %2 to %3 %4 | SWScale %5 us | Fallback %6 us (%7%) | SSSE3 %8 us (%9%)") .arg(in_format_name).arg(size, 9).arg(out_format_name).arg(size, 9) .arg(time_swscale, 6) .arg(time_fallback, 6).arg(100 * time_fallback / time_swscale, 3) .arg(time_ssse3, 6).arg(100 * time_ssse3 / time_fallback, 3)); } void Benchmark() { Logger::LogInfo("[Benchmark] " + Logger::tr("Starting scaler benchmark ...")); BenchmarkScale(1920, 1080, 1920, 1080); // direct copy BenchmarkScale(1280, 720, 1920, 1080); // upscaling BenchmarkScale(1920, 1080, 1280, 720); // downscaling BenchmarkScale(1920, 1080, 960, 540); // pure mipmap BenchmarkScale(1920, 1080, 640, 360); // mipmap + downscaling Logger::LogInfo("[Benchmark] " + Logger::tr("Starting converter benchmark ...")); #if SSR_USE_X86_ASM BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_YUV444P, "BGRA", "YUV444", NewImageBGRA, NewImageYUV444, Convert_BGRA_YUV444_Fallback , Convert_BGRA_YUV444_SSSE3 ); BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_YUV422P, "BGRA", "YUV422", NewImageBGRA, NewImageYUV422, Convert_BGRA_YUV422_Fallback , Convert_BGRA_YUV422_SSSE3 ); BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_YUV420P, "BGRA", "YUV420", NewImageBGRA, NewImageYUV420, Convert_BGRA_YUV420_Fallback , Convert_BGRA_YUV420_SSSE3 ); BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_NV12 , "BGRA", "NV12 ", NewImageBGRA, NewImageNV12 , Convert_BGRA_NV12_Fallback , Convert_BGRA_NV12_SSSE3 ); BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_BGR24 , "BGRA", "BGR ", NewImageBGRA, NewImageBGR , PlaneWrapper, PlaneWrapper); #else BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_YUV444P, "BGRA", "YUV444", NewImageBGRA, NewImageYUV444, Convert_BGRA_YUV444_Fallback ); BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_YUV422P, "BGRA", "YUV422", NewImageBGRA, NewImageYUV422, Convert_BGRA_YUV422_Fallback ); BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_YUV420P, "BGRA", "YUV420", NewImageBGRA, NewImageYUV420, Convert_BGRA_YUV420_Fallback ); BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_NV12 , "BGRA", "NV12 ", NewImageBGRA, NewImageNV12 , Convert_BGRA_NV12_Fallback ); BenchmarkConvert(1920, 1080, AV_PIX_FMT_BGRA, AV_PIX_FMT_BGR24 , "BGRA", "BGR ", NewImageBGRA, NewImageBGR , PlaneWrapper); #endif } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/Benchmark.h000066400000000000000000000014461470053624300223710ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" void Benchmark(); ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/CMakeLists.txt000066400000000000000000000135501470053624300230650ustar00rootroot00000000000000# 'simplescreenrecorder' executable set(CMAKE_AUTOMOC ON) find_package(Threads REQUIRED) find_package(AVFormat REQUIRED) find_package(AVCodec REQUIRED) find_package(AVUtil REQUIRED) find_package(SWScale REQUIRED) find_package(X11 REQUIRED) if(WITH_V4L2) find_package(V4L2 REQUIRED) endif() if(WITH_PIPEWIRE) find_package(PipeWire REQUIRED) find_package(SPA REQUIRED) endif() if(WITH_ALSA) find_package(ALSA REQUIRED) endif() if(WITH_PULSEAUDIO) find_package(PulseAudio REQUIRED) endif() if(WITH_JACK) find_package(Jack REQUIRED) endif() if(WITH_QT5) find_package(Qt5 5.7 COMPONENTS Core Gui Widgets X11Extras REQUIRED) else() find_package(Qt4 4.8 COMPONENTS QtGui REQUIRED) endif() set(sources AV/Input/ALSAInput.cpp AV/Input/ALSAInput.h AV/Input/GLInjectInput.cpp AV/Input/GLInjectInput.h AV/Input/JACKInput.cpp AV/Input/JACKInput.h AV/Input/PipeWireInput.cpp AV/Input/PipeWireInput.h AV/Input/PulseAudioInput.cpp AV/Input/PulseAudioInput.h AV/Input/SSRVideoStream.h AV/Input/SSRVideoStreamReader.cpp AV/Input/SSRVideoStreamReader.h AV/Input/SSRVideoStreamWatcher.cpp AV/Input/SSRVideoStreamWatcher.h AV/Input/V4L2Input.cpp AV/Input/V4L2Input.h AV/Input/X11Input.cpp AV/Input/X11Input.h AV/Output/AudioEncoder.cpp AV/Output/AudioEncoder.h AV/Output/BaseEncoder.cpp AV/Output/BaseEncoder.h AV/Output/Muxer.cpp AV/Output/Muxer.h AV/Output/OutputManager.cpp AV/Output/OutputManager.h AV/Output/OutputSettings.h AV/Output/SyncDiagram.cpp AV/Output/SyncDiagram.h AV/Output/Synchronizer.cpp AV/Output/Synchronizer.h AV/Output/VideoEncoder.cpp AV/Output/VideoEncoder.h AV/Output/X264Presets.cpp AV/Output/X264Presets.h AV/AVWrapper.cpp AV/AVWrapper.h AV/FastResampler.cpp AV/FastResampler.h AV/FastResampler_FirFilter.h AV/FastResampler_FirFilter_Fallback.cpp AV/FastScaler.cpp AV/FastScaler.h AV/FastScaler_Convert.h AV/FastScaler_Convert_Fallback.cpp AV/FastScaler_Scale.h AV/FastScaler_Scale_Fallback.cpp AV/FastScaler_Scale_Generic.cpp AV/FastScaler_Scale_Generic.h AV/SampleCast.h AV/SimpleSynth.cpp AV/SimpleSynth.h AV/SourceSink.cpp AV/SourceSink.h common/CommandLineOptions.cpp common/CommandLineOptions.h common/CPUFeatures.cpp common/CPUFeatures.h common/Dialogs.cpp common/Dialogs.h common/EnumStrings.h common/LockFreeMessageQueue.h common/Logger.cpp common/Logger.h common/MutexDataPair.h common/NVidia.cpp common/NVidia.h common/QueueBuffer.h common/ScreenScaling.cpp common/ScreenScaling.h common/TempBuffer.h GUI/AudioPreviewer.cpp GUI/AudioPreviewer.h GUI/DialogGLInject.cpp GUI/DialogGLInject.h GUI/DialogRecordSchedule.cpp GUI/DialogRecordSchedule.h GUI/ElidedLabel.cpp GUI/ElidedLabel.h GUI/HiddenScrollArea.cpp GUI/HiddenScrollArea.h GUI/HotkeyListener.h GUI/HotkeyListener.cpp GUI/Icons.cpp GUI/Icons.h GUI/MainWindow.cpp GUI/MainWindow.h GUI/PageDone.cpp GUI/PageDone.h GUI/PageInput.cpp GUI/PageInput.h GUI/PageOutput.cpp GUI/PageOutput.h GUI/PageRecord.cpp GUI/PageRecord.h GUI/PageWelcome.cpp GUI/PageWelcome.h GUI/ProfileBox.cpp GUI/ProfileBox.h GUI/VideoPreviewer.cpp GUI/VideoPreviewer.h GUI/WidgetRack.cpp GUI/WidgetRack.h Benchmark.cpp Benchmark.h Global.h Main.cpp ) if(ENABLE_X86_ASM) list(APPEND sources AV/FastResampler_FirFilter_SSE2.cpp AV/FastScaler_Convert_SSSE3.cpp AV/FastScaler_Scale_SSSE3.cpp ) set_source_files_properties( AV/FastResampler_FirFilter_SSE2.cpp PROPERTIES COMPILE_FLAGS -msse2 ) set_source_files_properties( AV/FastScaler_Convert_SSSE3.cpp AV/FastScaler_Scale_SSSE3.cpp PROPERTIES COMPILE_FLAGS -mssse3 ) endif() set(res_input ../data/resources/resources.qrc ) if(WITH_QT5) qt5_add_resources(res_output ${res_input}) set(QT_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::X11Extras ) else() qt4_add_resources(res_output ${res_input}) set(QT_LIBS Qt4::QtGui ) endif() set_property(SOURCE ${res_output} PROPERTY SKIP_AUTOMOC ON) add_executable(simplescreenrecorder ${sources} ${res_output} ) target_include_directories(simplescreenrecorder PRIVATE ${AVFORMAT_INCLUDE_DIRS} ${AVCODEC_INCLUDE_DIRS} ${AVUTIL_INCLUDE_DIRS} ${SWSCALE_INCLUDE_DIRS} ${X11_X11_INCLUDE_PATH} ${X11_Xext_INCLUDE_PATH} ${X11_Xfixes_INCLUDE_PATH} ${X11_Xi_INCLUDE_PATH} ${X11_Xinerama_INCLUDE_PATH} $<$:${V4L2_INCLUDE_DIRS}> $<$:${PIPEWIRE_INCLUDE_DIRS}> $<$:${SPA_INCLUDE_DIRS}> $<$:${ALSA_INCLUDE_DIRS}> $<$:${PULSEAUDIO_INCLUDE_DIRS}> $<$:${JACK_INCLUDE_DIRS}> ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/AV ${CMAKE_CURRENT_SOURCE_DIR}/AV/Input ${CMAKE_CURRENT_SOURCE_DIR}/AV/Output ${CMAKE_CURRENT_SOURCE_DIR}/common ${CMAKE_CURRENT_SOURCE_DIR}/GUI ) target_link_libraries(simplescreenrecorder PRIVATE ${AVFORMAT_LIBRARIES} ${AVCODEC_LIBRARIES} ${AVUTIL_LIBRARIES} ${SWSCALE_LIBRARIES} ${QT_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${X11_X11_LIB} ${X11_Xext_LIB} ${X11_Xfixes_LIB} ${X11_Xi_LIB} ${X11_Xinerama_LIB} $<$:${V4L2_LIBRARIES}> $<$:${PIPEWIRE_LIBRARIES}> $<$:${ALSA_LIBRARIES}> $<$:${PULSEAUDIO_LIBRARIES}> $<$:${JACK_LIBRARIES}> ) target_compile_definitions(simplescreenrecorder PRIVATE -DSSR_USE_X86_ASM=$ -DSSR_USE_FFMPEG_VERSIONS=$ -DSSR_USE_JACK_METADATA=$ -DSSR_USE_OPENGL_RECORDING=$ -DSSR_USE_V4L2=$ -DSSR_USE_PIPEWIRE=$ -DSSR_USE_ALSA=$ -DSSR_USE_PULSEAUDIO=$ -DSSR_USE_JACK=$ -DSSR_SYSTEM_DIR="${CMAKE_INSTALL_FULL_DATADIR}/simplescreenrecorder" -DSSR_VERSION="${PROJECT_VERSION}" ) install(TARGETS simplescreenrecorder RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/000077500000000000000000000000001470053624300207455ustar00rootroot00000000000000ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/AudioPreviewer.cpp000066400000000000000000000120351470053624300244040ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "AudioPreviewer.h" #include "SampleCast.h" #include "Logger.h" AudioPreviewer::AudioPreviewer(QWidget* parent) : QWidget(parent) { { SharedLock lock(&m_shared_data); lock->m_channel_data.resize(1); lock->m_next_samples = 0; lock->m_next_frame_time = hrt_time_micro(); lock->m_frame_rate = 20; } setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); connect(this, SIGNAL(NeedsUpdate()), this, SLOT(update()), Qt::QueuedConnection); } AudioPreviewer::~AudioPreviewer() { // disconnect ConnectAudioSource(NULL); } void AudioPreviewer::Reset() { SharedLock lock(&m_shared_data); lock->m_channel_data.clear(); lock->m_channel_data.resize(1); lock->m_next_samples = 0; emit NeedsUpdate(); } void AudioPreviewer::SetFrameRate(unsigned int frame_rate) { SharedLock lock(&m_shared_data); lock->m_frame_rate = std::max(1u, frame_rate); } void AudioPreviewer::ReadAudioSamples(unsigned int channels, unsigned int sample_rate, AVSampleFormat format, unsigned int sample_count, const uint8_t* data, int64_t timestamp) { Q_UNUSED(sample_rate); Q_UNUSED(timestamp); SharedLock lock(&m_shared_data); if(sample_count == 0) return; // save the samples lock->m_channel_data.resize(channels); switch(format) { case AV_SAMPLE_FMT_S16: { const int16_t *data_in = (const int16_t*) data; for(size_t i = 0; i < sample_count; ++i) { for(unsigned int c = 0; c < channels; ++c) { lock->m_channel_data[c].Analyze(*(data_in++)); } } break; } case AV_SAMPLE_FMT_S32: { const int32_t *data_in = (const int32_t*) data; for(size_t i = 0; i < sample_count; ++i) { for(unsigned int c = 0; c < channels; ++c) { lock->m_channel_data[c].Analyze(*(data_in++)); } } break; } case AV_SAMPLE_FMT_FLT: { const float *data_in = (const float*) data; for(size_t i = 0; i < sample_count; ++i) { for(unsigned int c = 0; c < channels; ++c) { lock->m_channel_data[c].Analyze(*(data_in++)); } } break; } default: { assert(false); // unsupported sample format break; } } lock->m_next_samples += sample_count; // check the time int64_t time = hrt_time_micro(); if(time < lock->m_next_frame_time) return; lock->m_next_frame_time = std::max(lock->m_next_frame_time + 1000000 / lock->m_frame_rate, time); // move the low/high values from 'next' to 'current' for(unsigned int c = 0; c < channels; ++c) { lock->m_channel_data[c].m_current_peak = lock->m_channel_data[c].m_next_peak; lock->m_channel_data[c].m_current_rms = sqrt(lock->m_channel_data[c].m_next_rms / (float) lock->m_next_samples); lock->m_channel_data[c].m_next_peak = 0.0f; lock->m_channel_data[c].m_next_rms = 0.0f; } lock->m_next_samples = 0; emit NeedsUpdate(); } void AudioPreviewer::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); std::vector channel_data; { SharedLock lock(&m_shared_data); channel_data = lock->m_channel_data; } painter.fillRect(rect(), QColor(200, 200, 200)); int w = width() - 1, h = height() - 1; QLinearGradient grad1(0.0, 0.0, (double) width(), 0.0); grad1.setColorAt(0.0, QColor(0, 255, 0)); grad1.setColorAt(0.5, QColor(255, 255, 0)); grad1.setColorAt(1.0, QColor(255, 0, 0)); QLinearGradient grad2(0.0, 0.0, (double) width(), 0.0); grad2.setColorAt(0.0, QColor(0, 150, 0)); grad2.setColorAt(0.5, QColor(150, 150, 0)); grad2.setColorAt(1.0, QColor(150, 0, 0)); painter.setPen(Qt::NoPen); unsigned int n = channel_data.size(); for(unsigned int c = 0; c < n; ++c) { // the scale goes down to 80dB which corresponds to 1.0e-4 (for sound pressure, 20dB = 10x) float val_peak = log10(fmax(1.0e-4f, channel_data[c].m_current_peak)) / 4.0f + 1.0f; float val_rms = log10(fmax(1.0e-4f, channel_data[c].m_current_rms)) / 4.0f + 1.0f; int x1 = 0, x2 = (int) round((float) w * val_rms), x3 = (int) round((float) w * val_peak); int y1 = h * c / n, y2 = h * (c + 1) / n; painter.setBrush(grad1); painter.drawRect(x1, y1, x2 - x1, y2 - y1); painter.setBrush(grad2); painter.drawRect(x2, y1, x3 - x2, y2 - y1); } painter.setPen(QColor(0, 0, 0)); painter.setBrush(Qt::NoBrush); painter.setRenderHint(QPainter::Antialiasing); for(unsigned int c = 0; c < n; ++c) { int y1 = h * c / n, y2 = h * (c + 1) / n; painter.drawRect(QRectF(0.5, (qreal) y1 + 0.5, (qreal) w, (qreal) (y2 - y1))); } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/AudioPreviewer.h000066400000000000000000000044441470053624300240560ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "SampleCast.h" #include "SourceSink.h" #include "MutexDataPair.h" class AudioPreviewer : public QWidget, public AudioSink { Q_OBJECT private: struct ChannelData { float m_current_peak, m_current_rms; float m_next_peak, m_next_rms; inline ChannelData() { m_current_peak = m_current_rms = m_next_peak = m_next_rms = 0.0f; } template inline void Analyze(IN sample) { float val = fabs(SampleCast(sample)); m_next_peak = fmax(m_next_peak, val); m_next_rms += val * val; } }; struct SharedData { std::vector m_channel_data; unsigned int m_next_samples; int64_t m_next_frame_time; unsigned int m_frame_rate; }; typedef MutexDataPair::Lock SharedLock; private: MutexDataPair m_shared_data; public: AudioPreviewer(QWidget* parent); ~AudioPreviewer(); // Resets the preview. // This function is thread-safe. void Reset(); // Changes the preview frame rate. // This function is thread-safe. void SetFrameRate(unsigned int frame_rate); // Reads audio samples from the audio source. // This function is thread-safe. virtual void ReadAudioSamples(unsigned int channels, unsigned int sample_rate, AVSampleFormat format, unsigned int sample_count, const uint8_t* data, int64_t timestamp) override; virtual QSize minimumSizeHint() const override { return QSize(100, 21); } virtual QSize sizeHint() const override { return QSize(100, 21); } protected: virtual void paintEvent(QPaintEvent* event) override; signals: void NeedsUpdate(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/DialogGLInject.cpp000066400000000000000000000174321470053624300242370ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "DialogGLInject.h" #if SSR_USE_OPENGL_RECORDING #include "Logger.h" #include "MainWindow.h" #include "PageInput.h" #include "GLInjectInput.h" DialogGLInject::DialogGLInject(PageInput* parent) : QDialog(parent) { m_parent = parent; setWindowTitle(tr("OpenGL Settings")); QLabel *label_info = new QLabel(this); label_info->setText(tr("

Warning: OpenGL recording works by injecting a library into the program that will be recorded. " "This library will override some system functions in order to capture the frames before they are " "displayed on the screen. If you are trying to record a game that tries to detect hacking attempts " "on the client side, it's (theoretically) possible that the game will consider this a hack. This " "might even get you banned, so it's a good idea to make sure that the program you want to record " "won't ban you, *before* you try to record it. You've been warned :).

\n\n" "

Another warning: OpenGL recording is experimental, it may not work or even crash the program you " "are recording. If you are worried about losing program data, make a backup first!

\n\n" "
")); label_info->setWordWrap(true); label_info->setTextFormat(Qt::RichText); label_info->setTextInteractionFlags(Qt::TextBrowserInteraction); label_info->setOpenExternalLinks(true); QGroupBox *groupbox_channel = new QGroupBox(tr("Choose channel"), this); { QLabel *label_channel = new QLabel(tr("Channel name:"), groupbox_channel); m_lineedit_channel = new QLineEdit(m_parent->GetGLInjectChannel(), groupbox_channel); m_lineedit_channel->setToolTip(tr("Channel names are used to identify applications. You only need to use this if you want to record multiple applications at the same time.\n" "If you leave this empty, the default name 'channel-YOURUSERNAME' will be used.")); m_checkbox_relax_permissions = new QCheckBox(tr("Relax shared memory permissions (insecure)"), groupbox_channel); m_checkbox_relax_permissions->setToolTip(tr("If checked, other users on the same machine will be able to attach to the shared memory that's used for communication with the OpenGL program.\n" "This means other users can (theoretically) see what you are recording, modify the frames, inject their own frames, or simply disrupt the communication.\n" "This even applies to users that are logged in remotely (ssh). You should only enable this if you need to record a program that runs as a different user.")); m_checkbox_relax_permissions->setChecked(m_parent->GetGLInjectRelaxPermissions()); QVBoxLayout *layout = new QVBoxLayout(groupbox_channel); { QGridLayout *layout2 = new QGridLayout(); layout->addLayout(layout2); layout2->addWidget(label_channel, 0, 0); layout2->addWidget(m_lineedit_channel, 0, 1); } layout->addWidget(m_checkbox_relax_permissions); } QGroupBox *groupbox_launch = new QGroupBox(tr("Launch application"), this); { QLabel *label_command = new QLabel(tr("Command:"), groupbox_launch); m_lineedit_command = new QLineEdit(m_parent->GetGLInjectCommand(), groupbox_launch); m_lineedit_command->setToolTip(tr("This command will be executed to start the program that should be recorded.")); m_lineedit_command->setMinimumWidth(300); QLabel *label_working_directory = new QLabel(tr("Working directory:"), groupbox_launch); m_lineedit_working_directory = new QLineEdit(m_parent->GetGLInjectWorkingDirectory(), groupbox_launch); m_lineedit_working_directory->setToolTip(tr("The command will be executed in this directory. If you leave this empty, the working directory won't be changed.")); m_lineedit_working_directory->setMinimumWidth(300); m_checkbox_auto_launch = new QCheckBox(tr("Launch automatically"), this); m_checkbox_auto_launch->setToolTip(tr("If checked, the application will be launched automatically once you go to the recording page. If not checked, you have to start it manually.")); m_checkbox_auto_launch->setChecked(m_parent->GetGLInjectAutoLaunch()); QPushButton *pushbutton_launch = new QPushButton(tr("Launch now"), this); connect(pushbutton_launch, SIGNAL(clicked()), this, SLOT(OnLaunchNow())); QVBoxLayout *layout = new QVBoxLayout(groupbox_launch); { QGridLayout *layout2 = new QGridLayout(); layout->addLayout(layout2); layout2->addWidget(label_command, 0, 0); layout2->addWidget(m_lineedit_command, 0, 1); layout2->addWidget(label_working_directory, 1, 0); layout2->addWidget(m_lineedit_working_directory, 1, 1); } { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(m_checkbox_auto_launch); layout2->addWidget(pushbutton_launch); } } QGroupBox *groupbox_stream = new QGroupBox(tr("Stream settings"), this); { m_checkbox_limit_fps = new QCheckBox(tr("Limit application frame rate"), this); m_checkbox_limit_fps->setToolTip(tr("If checked, the injected library will slow down the application so the frame rate doesn't become higher than the recording frame rate.\n" "This stops the application from wasting CPU time for frames that won't be recorded, and sometimes results in smoother video\n" "(this depends on the application).")); m_checkbox_limit_fps->setChecked(m_parent->GetGLInjectLimitFPS()); QVBoxLayout *layout = new QVBoxLayout(groupbox_stream); layout->addWidget(m_checkbox_limit_fps); } QPushButton *pushbutton_close = new QPushButton(tr("Close"), this); pushbutton_close->setDefault(true); connect(pushbutton_close, SIGNAL(clicked()), this, SLOT(accept())); connect(this, SIGNAL(accepted()), this, SLOT(OnWriteBack())); connect(this, SIGNAL(rejected()), this, SLOT(OnWriteBack())); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(label_info); layout->addWidget(groupbox_channel); layout->addWidget(groupbox_launch); layout->addWidget(groupbox_stream); layout->addStretch(); { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addStretch(); layout2->addWidget(pushbutton_close); layout2->addStretch(); } setMinimumSize(minimumSizeHint()); // workaround for Qt bug } DialogGLInject::~DialogGLInject() { } void DialogGLInject::OnWriteBack() { m_parent->SetGLInjectChannel(m_lineedit_channel->text()); m_parent->SetGLInjectRelaxPermissions(m_checkbox_relax_permissions->isChecked()); m_parent->SetGLInjectCommand(m_lineedit_command->text()); m_parent->SetGLInjectWorkingDirectory(m_lineedit_working_directory->text()); m_parent->SetGLInjectAutoLaunch(m_checkbox_auto_launch->isChecked()); m_parent->SetGLInjectLimitFPS(m_checkbox_limit_fps->isChecked()); } void DialogGLInject::OnLaunchNow() { if(!GLInjectInput::LaunchApplication(m_lineedit_channel->text(), m_checkbox_relax_permissions->isChecked(), m_lineedit_command->text(), m_lineedit_working_directory->text())) { QMessageBox::critical(NULL, MainWindow::WINDOW_CAPTION, Logger::tr("The application could not be launched."), QMessageBox::Ok); } } #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/DialogGLInject.h000066400000000000000000000023571470053624300237040ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #if SSR_USE_OPENGL_RECORDING class PageInput; class DialogGLInject : public QDialog { Q_OBJECT private: PageInput *m_parent; QLineEdit *m_lineedit_channel; QCheckBox *m_checkbox_relax_permissions; QLineEdit *m_lineedit_command, *m_lineedit_working_directory; QCheckBox *m_checkbox_auto_launch; QCheckBox *m_checkbox_limit_fps; public: DialogGLInject(PageInput* parent); ~DialogGLInject(); private slots: void OnWriteBack(); void OnLaunchNow(); }; #endif ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/DialogRecordSchedule.cpp000066400000000000000000000215251470053624300254710ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "DialogRecordSchedule.h" #include "EnumStrings.h" #include "PageRecord.h" ENUMSTRINGS(enum_schedule_time_zone) = { {SCHEDULE_TIME_ZONE_LOCAL, "local"}, {SCHEDULE_TIME_ZONE_UTC, "utc"}, }; ENUMSTRINGS(enum_schedule_action) = { {SCHEDULE_ACTION_START, "start"}, {SCHEDULE_ACTION_PAUSE, "pause"}, }; const Qt::TimeSpec SCHEDULE_TIME_ZONE_TIMESPECS[SCHEDULE_TIME_ZONE_COUNT] = { Qt::LocalTime, Qt::UTC, }; const QString SCHEDULE_ACTION_TEXT[SCHEDULE_ACTION_COUNT] { RecordScheduleEntryWidget::tr("Start"), RecordScheduleEntryWidget::tr("Pause"), }; RecordScheduleEntryWidget::RecordScheduleEntryWidget(QWidget* parent) : QWidget(parent) { QLabel *label_time = new QLabel(tr("Time:"), this); m_datetimeedit_time = new QDateTimeEdit(this); m_datetimeedit_time->setDisplayFormat("yyyy-MM-dd hh:mm:ss"); m_datetimeedit_time->setCalendarPopup(true); QLabel *label_action = new QLabel(tr("Action:"), this); m_combobox_action = new QComboBox(this); for(unsigned int i = 0; i < SCHEDULE_ACTION_COUNT; ++i) { m_combobox_action->addItem(SCHEDULE_ACTION_TEXT[i]); } QHBoxLayout *layout = new QHBoxLayout(this); layout->setMargin(5); layout->addWidget(label_time); layout->addWidget(m_datetimeedit_time); layout->addSpacing(10); layout->addWidget(label_action); layout->addWidget(m_combobox_action); } RecordScheduleEntryWidget::~RecordScheduleEntryWidget() { // nothing } DialogRecordSchedule::DialogRecordSchedule(PageRecord* parent) : QDialog(parent) { m_parent = parent; m_clock_time = QDateTime(); setWindowTitle(tr("Recording schedule")); QLabel *label_info = new QLabel(tr("Time zone:"), this); m_combobox_timezone = new QComboBox(this); m_combobox_timezone->addItem(tr("Local time")); m_combobox_timezone->addItem(tr("UTC")); m_combobox_timezone->setCurrentIndex(m_parent->GetScheduleTimeZone()); QLabel *label_time = new QLabel(tr("Current time:"), this); m_label_time = new QLabel(this); m_label_time->setMinimumWidth(GetTextWidth(m_label_time->fontMetrics(), "0000-00-00 00:00:00 XXXXX") + 10); m_label_time->setAlignment((m_label_time->alignment() & ~Qt::AlignHorizontal_Mask) | Qt::AlignRight); m_widgetrack_schedule = new WidgetRack(this); m_widgetrack_schedule->setMinimumSize(500, 300); { std::vector schedule = m_parent->GetScheduleEntries(); for(unsigned int i = 0; i < schedule.size(); ++i) { RecordScheduleEntryWidget *widget = new RecordScheduleEntryWidget(m_widgetrack_schedule->viewport()); widget->SetTime(schedule[i].time); widget->SetAction(schedule[i].action); m_widgetrack_schedule->AddWidget(i, widget); } } QPushButton *pushbutton_add = new QPushButton(QIcon::fromTheme("list-add"), tr("Add"), this); // TODO icons QPushButton *pushbutton_remove = new QPushButton(QIcon::fromTheme("list-remove"), tr("Remove"), this); QPushButton *pushbutton_move_up = new QPushButton(QIcon::fromTheme("go-up"), tr("Move up"), this); QPushButton *pushbutton_move_down = new QPushButton(QIcon::fromTheme("go-down"), tr("Move down"), this); QPushButton *pushbutton_close = new QPushButton(tr("Close"), this); pushbutton_close->setDefault(true); connect(m_combobox_timezone, SIGNAL(activated(int)), this, SLOT(OnTimeZoneChanged())); connect(pushbutton_add, SIGNAL(clicked()), this, SLOT(OnAdd())); connect(pushbutton_remove, SIGNAL(clicked()), this, SLOT(OnRemove())); connect(pushbutton_move_up, SIGNAL(clicked()), this, SLOT(OnMoveUp())); connect(pushbutton_move_down, SIGNAL(clicked()), this, SLOT(OnMoveDown())); connect(pushbutton_close, SIGNAL(clicked()), this, SLOT(accept())); connect(this, SIGNAL(accepted()), this, SLOT(OnWriteBack())); connect(this, SIGNAL(rejected()), this, SLOT(OnWriteBack())); QVBoxLayout *layout = new QVBoxLayout(this); { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(label_info); layout2->addWidget(m_combobox_timezone); layout2->addSpacing(10); layout2->addStretch(); layout2->addWidget(label_time); layout2->addWidget(m_label_time); } layout->addWidget(m_widgetrack_schedule); { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(pushbutton_add); layout2->addWidget(pushbutton_remove); layout2->addWidget(pushbutton_move_up); layout2->addWidget(pushbutton_move_down); } { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addStretch(); layout2->addWidget(pushbutton_close); layout2->addStretch(); } m_timer_clock = new QTimer(this); m_timer_clock->setSingleShot(true); connect(m_timer_clock, SIGNAL(timeout()), this, SLOT(OnUpdateTime())); OnTimeZoneChanged(); OnUpdateTime(); setMinimumSize(minimumSizeHint()); // workaround for Qt bug } DialogRecordSchedule::~DialogRecordSchedule() { } void DialogRecordSchedule::OnTimeZoneChanged() { m_clock_time = QDateTime(); Qt::TimeSpec spec = SCHEDULE_TIME_ZONE_TIMESPECS[m_combobox_timezone->currentIndex()]; for(unsigned int i = 0; i < m_widgetrack_schedule->GetWidgetCount(); ++i) { RecordScheduleEntryWidget *widget = static_cast(m_widgetrack_schedule->GetWidget(i)); widget->SetTimeSpec(spec); } OnUpdateTime(); } void DialogRecordSchedule::OnUpdateTime() { QDateTime now = QDateTime::currentDateTimeUtc(); if(m_clock_time.isNull() || now > m_clock_time || now.msecsTo(m_clock_time) > 100) m_clock_time = now.addMSecs(-now.time().msec()); Qt::TimeSpec spec = SCHEDULE_TIME_ZONE_TIMESPECS[m_combobox_timezone->currentIndex()]; m_label_time->setText(m_clock_time.toTimeSpec(spec).toString("yyyy-MM-dd hh:mm:ss t")); m_clock_time = m_clock_time.addSecs(1); m_timer_clock->start(now.msecsTo(m_clock_time)); } void DialogRecordSchedule::OnAdd() { unsigned int selected = m_widgetrack_schedule->GetSelected(); unsigned int index = (selected == WidgetRack::NO_SELECTION)? m_widgetrack_schedule->GetWidgetCount() : selected + 1; RecordScheduleEntryWidget *widget = new RecordScheduleEntryWidget(m_widgetrack_schedule->viewport()); Qt::TimeSpec spec = SCHEDULE_TIME_ZONE_TIMESPECS[m_combobox_timezone->currentIndex()]; if(selected == WidgetRack::NO_SELECTION) { QDateTime time = QDateTime::currentDateTimeUtc().toTimeSpec(spec).addSecs(60); time.setTime(QTime(time.time().hour(), 0, 0, 0)); widget->SetTime(time.addSecs(60 * 60)); } else { widget->SetTime(static_cast(m_widgetrack_schedule->GetWidget(selected))->GetTime()); } m_widgetrack_schedule->AddWidget(index, widget); m_widgetrack_schedule->SetSelected(index); m_widgetrack_schedule->MakeVisible(widget); } void DialogRecordSchedule::OnRemove() { unsigned int selected = m_widgetrack_schedule->GetSelected(); if(selected != WidgetRack::NO_SELECTION) { m_widgetrack_schedule->RemoveWidget(selected); if(selected < m_widgetrack_schedule->GetWidgetCount()) { m_widgetrack_schedule->SetSelected(selected); } else if(m_widgetrack_schedule->GetWidgetCount() != 0) { m_widgetrack_schedule->SetSelected(m_widgetrack_schedule->GetWidgetCount() - 1); } } } void DialogRecordSchedule::OnMoveUp() { unsigned int selected = m_widgetrack_schedule->GetSelected(); if(selected != WidgetRack::NO_SELECTION && selected > 0) { m_widgetrack_schedule->MoveWidget(selected, selected - 1); m_widgetrack_schedule->MakeVisible(m_widgetrack_schedule->GetWidget(selected - 1)); } } void DialogRecordSchedule::OnMoveDown() { unsigned int selected = m_widgetrack_schedule->GetSelected(); if(selected != WidgetRack::NO_SELECTION && selected < m_widgetrack_schedule->GetWidgetCount() - 1) { m_widgetrack_schedule->MoveWidget(selected, selected + 1); m_widgetrack_schedule->MakeVisible(m_widgetrack_schedule->GetWidget(selected + 1)); } } void DialogRecordSchedule::OnWriteBack() { m_parent->SetScheduleTimeZone((enum_schedule_time_zone) m_combobox_timezone->currentIndex()); std::vector schedule(m_widgetrack_schedule->GetWidgetCount()); for(unsigned int i = 0; i < m_widgetrack_schedule->GetWidgetCount(); ++i) { RecordScheduleEntryWidget *widget = static_cast(m_widgetrack_schedule->GetWidget(i)); schedule[i].time = widget->GetTime(); schedule[i].action = widget->GetAction(); } m_parent->SetScheduleEntries(schedule); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/DialogRecordSchedule.h000066400000000000000000000056171470053624300251420ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" // TODO: #include "PageRecord.h" // TODO: #include "RecordSettings.h" #include "WidgetRack.h" class PageRecord; enum enum_schedule_time_zone { SCHEDULE_TIME_ZONE_LOCAL, SCHEDULE_TIME_ZONE_UTC, SCHEDULE_TIME_ZONE_COUNT // must be last }; enum enum_schedule_action { SCHEDULE_ACTION_START, SCHEDULE_ACTION_PAUSE, SCHEDULE_ACTION_COUNT // must be last }; struct ScheduleEntry { QDateTime time; enum_schedule_action action; }; extern const Qt::TimeSpec SCHEDULE_TIME_ZONE_TIMESPECS[SCHEDULE_TIME_ZONE_COUNT]; extern const QString SCHEDULE_ACTION_TEXT[SCHEDULE_ACTION_COUNT]; class RecordScheduleEntryWidget : public QWidget { Q_OBJECT private: //QComboBox *m_combobox_timing; //QTimeEdit *m_timeedit_time; QDateTimeEdit *m_datetimeedit_time; QComboBox *m_combobox_action; public: RecordScheduleEntryWidget(QWidget* parent = NULL); ~RecordScheduleEntryWidget(); inline void Print(const QString& str) { qDebug() << "timespec" << str << m_datetimeedit_time->dateTime() << m_datetimeedit_time->timeSpec(); } inline void SetTimeSpec(Qt::TimeSpec spec) { m_datetimeedit_time->setTimeSpec(spec); m_datetimeedit_time->setDateTime(m_datetimeedit_time->dateTime()); } inline void SetTime(const QDateTime& time) { m_datetimeedit_time->setTimeSpec(time.timeSpec()); m_datetimeedit_time->setDateTime(time); } inline void SetAction(enum_schedule_action action) { m_combobox_action->setCurrentIndex(clamp((int) action, 0, (int) SCHEDULE_ACTION_COUNT - 1)); } inline QDateTime GetTime() { return m_datetimeedit_time->dateTime(); } inline enum_schedule_action GetAction() { return (enum_schedule_action) clamp(m_combobox_action->currentIndex(), 0, (int) SCHEDULE_ACTION_COUNT - 1); } }; class DialogRecordSchedule : public QDialog { Q_OBJECT private: PageRecord *m_parent; QComboBox *m_combobox_timezone; QLabel *m_label_time; WidgetRack *m_widgetrack_schedule; QTimer *m_timer_clock; QDateTime m_clock_time; public: DialogRecordSchedule(PageRecord* parent); ~DialogRecordSchedule(); private slots: void OnTimeZoneChanged(); void OnUpdateTime(); void OnAdd(); void OnRemove(); void OnMoveUp(); void OnMoveDown(); void OnWriteBack(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/ElidedLabel.cpp000066400000000000000000000046151470053624300236050ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Original code written by Jason Gedge and placed in the public domain. http://gedgedev.blogspot.be/2010/12/elided-labels-in-qt.html This is a slightly modified version. This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "ElidedLabel.h" #include #include ElidedLabel::ElidedLabel(QWidget* parent, Qt::WindowFlags f) : QLabel(parent, f), m_elide_mode(Qt::ElideRight) { setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); } ElidedLabel::ElidedLabel(const QString& txt, QWidget* parent, Qt::WindowFlags f) : QLabel(txt, parent, f), m_elide_mode(Qt::ElideRight) { setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); } ElidedLabel::ElidedLabel(const QString& txt, Qt::TextElideMode elideMode, QWidget* parent, Qt::WindowFlags f) : QLabel(txt, parent, f), m_elide_mode(elideMode) { setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); } void ElidedLabel::setText(const QString& txt) { QLabel::setText(txt); cacheElidedText(geometry().width()); setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); } void ElidedLabel::cacheElidedText(int w) { m_cached_elided_text = fontMetrics().elidedText(text(), m_elide_mode, w, (buddy() == NULL)? 0 : Qt::TextShowMnemonic); } void ElidedLabel::resizeEvent(QResizeEvent* e) { QLabel::resizeEvent(e); cacheElidedText(e->size().width()); } void ElidedLabel::paintEvent(QPaintEvent* e) { if(m_elide_mode == Qt::ElideNone) { QLabel::paintEvent(e); } else { QPainter p(this); p.drawText(0, 0, geometry().width(), geometry().height(), QStyle::visualAlignment(text().isRightToLeft()? Qt::RightToLeft : Qt::LeftToRight, alignment()) | ((buddy() == NULL)? 0 : Qt::TextShowMnemonic), m_cached_elided_text); } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/ElidedLabel.h000066400000000000000000000042071470053624300232470ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert Original code written by Jason Gedge and placed in the public domain. http://gedgedev.blogspot.be/2010/12/elided-labels-in-qt.html This is a slightly modified version. This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" // A label that elides its text when not enough geometry is available to show all of the text. // Currently only capable of one-line. class ElidedLabel : public QLabel { Q_OBJECT private: Qt::TextElideMode m_elide_mode; QString m_cached_elided_text; public: ElidedLabel(QWidget* parent = NULL, Qt::WindowFlags f = Qt::WindowFlags()); ElidedLabel(const QString& txt, QWidget* parent = NULL, Qt::WindowFlags f = Qt::WindowFlags()); ElidedLabel(const QString& txt, Qt::TextElideMode elideMode = Qt::ElideRight, QWidget* parent = NULL, Qt::WindowFlags f = Qt::WindowFlags()); public: // Set the elide mode used for displaying text. inline void setElideMode(Qt::TextElideMode elideMode) { m_elide_mode = elideMode; updateGeometry(); } // Get the elide mode currently used to display text. inline Qt::TextElideMode elideMode() const { return m_elide_mode; } public: // QLabel overrides void setText(const QString&); // note: not virtual so no polymorphism ... protected: // QLabel overrides virtual void paintEvent(QPaintEvent*) override; virtual void resizeEvent(QResizeEvent*) override; protected: // Cache the elided text so as to not recompute it every paint event void cacheElidedText(int w); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/HiddenScrollArea.cpp000066400000000000000000000021701470053624300246140ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "HiddenScrollArea.h" HiddenScrollArea::HiddenScrollArea(QWidget* parent) : QScrollArea(parent) { setWidgetResizable(true); setFrameShape(QFrame::NoFrame); } HiddenScrollArea::~HiddenScrollArea() { // nothing } QSize HiddenScrollArea::sizeHint() const { return widget()->sizeHint(); } QSize HiddenScrollArea::minimumSizeHint() const { return widget()->minimumSizeHint(); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/HiddenScrollArea.h000066400000000000000000000023201470053624300242560ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" // A scroll area that tries to stay invisible if possible. The goal is to keep SSR functional even when the screen // resolution is too small to show the full GUI, but without affecting the behaviour under normal conditions. class HiddenScrollArea : public QScrollArea { Q_OBJECT public: HiddenScrollArea(QWidget* parent); ~HiddenScrollArea(); virtual QSize sizeHint() const override; virtual QSize minimumSizeHint() const override; }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/HotkeyListener.cpp000066400000000000000000000277641470053624300244420ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "HotkeyListener.h" #include "Logger.h" /* In theory, global hotkeys are pretty easy to implement since X11 has functions specifically for this purpose (XGrabKey/XUngrabKey). Unfortunately, some programs (particularly games that use SDL < 2.0) abuse the keyboard grab function in X11: They simply grab the entire keyboard all the time, which breaks all global hotkeys (not just SSR, also Alt+Tab, volume keys, ...). There is no way to avoid this: 'active' grabs (= grab the entire keyboard) always get priority over 'passive' grabs (= grab one key). The Mumble devs apparently found a solution, but it's ugly and relies on XInput2, so I still keep the original code as a fallback. XInput2 has two types of keyboards: there is a 'master' keyboard that is normally used by applications, and one or more 'slave' keyboards that correspond to actual physical keyboards. A normal keyboard grab only affects the master keyboard, not the slaves attached to that master. So even when a misbehaving application grabs the entire keyboard, it is still possible to monitor hotkeys simply by monitoring all slaves (in case you were wondering: no, monitoring the master keyboard doesn't work, I tried that). So now there are two possible solutions: (1) Create passive grabs for the hotkeys on all slave keyboards. Disadvantages: Requires hotplug detection because slaves can change, could be problematic if the user picks a really stupid hotkey (like Esc) and some application does a full grab with that hotkey as the only way out, creates problems with modifiers (example for Ctrl+R hotkey: press Ctrl, press R, release Ctrl, release R -> Ctrl is now stuck until it is pressed again). (2) Create passive grabs for the hotkeys on all master keyboards, and use raw input to monitor all slaves. Disadvantages: Modifiers must be emulated, serials must be stored to avoid double triggering of the same hotkey, misbehaving clients will still receive key presses, key repeat becomes impossible, wastes some CPU since it monitors all keys all the time. I decided to go with (2) because it sounds more reliable. It's also closer to what Mumble does. XInput2 makes it easy to detect key repeat, so I decided to ignore those for consistency (it didn't really serve a purpose anyway). This code is partially based on the global hotkey code from Mumble: https://github.com/mumble-voip/mumble/blob/master/src/mumble/GlobalShortcut_unix.cpp This is a good source of information about Xlib cookie events and XInput2 (check the other pages as well): http://who-t.blogspot.be/2009/07/xlib-cookie-events.html */ HotkeyListener *HotkeyListener::s_instance = NULL; HotkeyCallback::HotkeyCallback() { m_is_bound = false; } HotkeyCallback::~HotkeyCallback() { if(m_is_bound) HotkeyListener::GetInstance()->UnbindCallback(m_iterator); } void HotkeyCallback::Bind(unsigned int keysym, unsigned int modifiers) { Unbind(); m_iterator = HotkeyListener::GetInstance()->BindCallback(keysym, modifiers, this); m_is_bound = true; } void HotkeyCallback::Unbind() { if(m_is_bound) { HotkeyListener::GetInstance()->UnbindCallback(m_iterator); m_is_bound = false; } } void HotkeyCallback::Trigger() { emit Triggered(); } HotkeyListener::HotkeyListener() { assert(s_instance == NULL); m_x11_display = NULL; m_x11_screen = 0; m_has_xinput2 = false; try { Init(); } catch(...) { Free(); throw; } s_instance = this; } HotkeyListener::~HotkeyListener() { assert(s_instance == this); assert(m_callbacks.empty()); Free(); s_instance = NULL; } void HotkeyListener::Init() { // open X11 display m_x11_display = XOpenDisplay(NULL); if(m_x11_display == NULL) { Logger::LogError("[HotkeyListener::Init] " + Logger::tr("Error: Can't open X display!", "Don't translate 'display'")); throw X11Exception(); } m_x11_screen = DefaultScreen(m_x11_display); m_x11_root = RootWindow(m_x11_display, m_x11_screen); // try to use XInput2 int first_event, first_error; if(XQueryExtension(m_x11_display, "XInputExtension", &m_xinput2_opcode, &first_event, &first_error)) { int major = 2, minor = 0; if(XIQueryVersion(m_x11_display, &major, &minor) == Success) { // initialize variables m_has_xinput2 = true; m_xinput2_raw_modifiers = 0; m_xinput2_ignore_serial = (unsigned long) -1; // choose the events that we want to listen to unsigned char mask[(XI_LASTEVENT + 7) / 8] = {0}; XISetMask(mask, XI_RawKeyPress); XISetMask(mask, XI_RawKeyRelease); // create event mask XIEventMask evmask; evmask.deviceid = XIAllDevices; evmask.mask_len = sizeof(mask); evmask.mask = mask; // apply it XISelectEvents(m_x11_display, m_x11_root, &evmask, 1); // get all master keyboards (luckily hotplug support is not really needed here) m_xinput2_master_keyboards.clear(); int device_count; XIDeviceInfo *device_info = XIQueryDevice(m_x11_display, XIAllMasterDevices, &device_count); if(device_info != NULL) { for(int i = 0; i < device_count; ++i) { if(device_info[i].use == XIMasterKeyboard) m_xinput2_master_keyboards.insert(device_info[i].deviceid); } XIFreeDeviceInfo(device_info); } } } if(!m_has_xinput2) Logger::LogWarning("[HotkeyListener::Init] " + Logger::tr("Warning: XInput2 is not supported by X server, hotkeys may not work in some applications.", "Don't translate 'XInput2'")); // send everything (normally this is done by XPending/XNextEvent, but those won't be called until there is some data) XFlush(m_x11_display); connect(new QSocketNotifier(ConnectionNumber(m_x11_display), QSocketNotifier::Read, this), SIGNAL(activated(int)), this, SLOT(ProcessEvents())); } void HotkeyListener::Free() { if(m_x11_display != NULL) { XCloseDisplay(m_x11_display); m_x11_display = NULL; } } void HotkeyListener::GrabHotkey(const Hotkey& hotkey, bool enable) { // we need to grab multiple modifiers combinations to ignore the state of caps lock (LockMask) and num lock (Mod2Mask) unsigned int masks[] = {0, LockMask, Mod2Mask, LockMask | Mod2Mask}; // do we have XInput2? if(m_has_xinput2) { for(int device_id : m_xinput2_master_keyboards) { // choose the events that we want to listen to unsigned char mask[(XI_LASTEVENT + 7) / 8] = {0}; XISetMask(mask, XI_KeyPress); XISetMask(mask, XI_KeyRelease); // create event mask XIEventMask evmask; evmask.deviceid = device_id; evmask.mask_len = sizeof(mask); evmask.mask = mask; // create grab modifiers XIGrabModifiers modifiers[4]; for(unsigned int i = 0; i < sizeof(masks) / sizeof(masks[0]); ++i) { modifiers[i].modifiers = hotkey.m_modifiers | masks[i]; modifiers[i].status = 0; } // grab/ungrab if(enable) XIGrabKeycode(m_x11_display, device_id, hotkey.m_keycode, m_x11_root, GrabModeAsync, GrabModeAsync, false, &evmask, sizeof(modifiers) / sizeof(modifiers[0]), modifiers); else XIUngrabKeycode(m_x11_display, device_id, hotkey.m_keycode, m_x11_root, sizeof(modifiers) / sizeof(modifiers[0]), modifiers); } } else { for(unsigned int i = 0; i < sizeof(masks) / sizeof(masks[0]); ++i) { if(enable) XGrabKey(m_x11_display, hotkey.m_keycode, hotkey.m_modifiers | masks[i], m_x11_root, false, GrabModeAsync, GrabModeAsync); else XUngrabKey(m_x11_display, hotkey.m_keycode, hotkey.m_modifiers | masks[i], m_x11_root); } } // send everything (normally this is done by XPending/XNextEvent, but those won't be called until there is some data) XFlush(m_x11_display); } void HotkeyListener::ProcessHotkey(const Hotkey& hotkey) { auto range = m_callbacks.equal_range(hotkey); if(range.first != range.second) { for(auto it = range.first; it != range.second; ++it) { it->second->Trigger(); } } } HotkeyIterator HotkeyListener::BindCallback(unsigned int keysym, unsigned int modifiers, HotkeyCallback* callback) { Hotkey hotkey; hotkey.m_keycode = XKeysymToKeycode(m_x11_display, keysym); hotkey.m_modifiers = modifiers; if(m_callbacks.count(hotkey) == 0) GrabHotkey(hotkey, true); return m_callbacks.insert(std::make_pair(hotkey, callback)); } void HotkeyListener::UnbindCallback(HotkeyIterator it) { Hotkey hotkey = it->first; m_callbacks.erase(it); if(m_callbacks.count(hotkey) == 0) GrabHotkey(hotkey, false); } void HotkeyListener::ProcessEvents() { XEvent event; while(XPending(m_x11_display) > 0) { XNextEvent(m_x11_display, &event); if(XGetEventData(m_x11_display, &event.xcookie)) { if(m_has_xinput2 && event.xcookie.extension == m_xinput2_opcode) { if(event.xcookie.evtype == XI_RawKeyPress) { XIDeviceEvent *xide = (XIDeviceEvent*) event.xcookie.data; if(!m_xinput2_master_keyboards.count(xide->deviceid)) { // emulate modifier keys int keysym_count; KeySym *keysym = XGetKeyboardMapping(m_x11_display, xide->detail, 1, &keysym_count); if(keysym_count > 0) { switch(keysym[0]) { case XK_Control_L: case XK_Control_R: m_xinput2_raw_modifiers |= ControlMask; break; case XK_Shift_L: case XK_Shift_R: m_xinput2_raw_modifiers |= ShiftMask; break; case XK_Alt_L: case XK_Alt_R: case XK_Meta_L: case XK_Meta_R: m_xinput2_raw_modifiers |= Mod1Mask; break; case XK_Super_L: case XK_Super_R: case XK_Hyper_L: case XK_Hyper_R: m_xinput2_raw_modifiers |= Mod4Mask; break; } } if(keysym != NULL) XFree(keysym); // process hotkey if(event.xcookie.serial != m_xinput2_ignore_serial) { Hotkey hotkey; hotkey.m_keycode = xide->detail; hotkey.m_modifiers = m_xinput2_raw_modifiers; ProcessHotkey(hotkey); m_xinput2_ignore_serial = event.xcookie.serial; } } } if(event.xcookie.evtype == XI_RawKeyRelease) { XIDeviceEvent *xide = (XIDeviceEvent*) event.xcookie.data; if(!m_xinput2_master_keyboards.count(xide->deviceid)) { // emulate modifier keys int keysym_count; KeySym *keysym = XGetKeyboardMapping(m_x11_display, xide->detail, 1, &keysym_count); if(keysym_count > 0) { switch(keysym[0]) { case XK_Control_L: case XK_Control_R: m_xinput2_raw_modifiers &= ~ControlMask; break; case XK_Shift_L: case XK_Shift_R: m_xinput2_raw_modifiers &= ~ShiftMask; break; case XK_Alt_L: case XK_Alt_R: case XK_Meta_L: case XK_Meta_R: m_xinput2_raw_modifiers &= ~Mod1Mask; break; case XK_Super_L: case XK_Super_R: case XK_Hyper_L: case XK_Hyper_R: m_xinput2_raw_modifiers &= ~Mod4Mask; break; } } if(keysym != NULL) XFree(keysym); } } if(event.xcookie.evtype == XI_KeyPress) { XIDeviceEvent *xide = (XIDeviceEvent*) event.xcookie.data; if(!(xide->flags & XIKeyRepeat)) { // process hotkey if(event.xcookie.serial != m_xinput2_ignore_serial) { Hotkey hotkey; hotkey.m_keycode = xide->detail; hotkey.m_modifiers = xide->mods.effective & ~LockMask & ~Mod2Mask; ProcessHotkey(hotkey); m_xinput2_ignore_serial = event.xcookie.serial; } } } } XFreeEventData(m_x11_display, &event.xcookie); } else { if(event.type == KeyPress) { Hotkey hotkey; hotkey.m_keycode = event.xkey.keycode; hotkey.m_modifiers = event.xkey.state & ~LockMask & ~Mod2Mask; ProcessHotkey(hotkey); } if(event.type == MappingNotify) { if(event.xmapping.request == MappingKeyboard || event.xmapping.request == MappingModifier) XRefreshKeyboardMapping(&event.xmapping); break; } } } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/HotkeyListener.h000066400000000000000000000050351470053624300240720ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" struct Hotkey { unsigned int m_keycode, m_modifiers; inline bool operator==(const Hotkey& other) const { return (m_keycode == other.m_keycode && m_modifiers == other.m_modifiers); } inline bool operator<(const Hotkey& other) const { return (m_keycode < other.m_keycode || (m_keycode == other.m_keycode && m_modifiers < other.m_modifiers)); } }; class HotkeyCallback; typedef std::multimap::iterator HotkeyIterator; class HotkeyCallback : public QObject { Q_OBJECT private: bool m_is_bound; HotkeyIterator m_iterator; public: HotkeyCallback(); ~HotkeyCallback(); // X11 modifiers: // - Ctrl = ControlMask // - Shift = ShiftMask // - Alt = Mod1Mask // - Super = Mod4Mask void Bind(unsigned int keysym, unsigned int modifiers); void Unbind(); public: // internal void Trigger(); signals: void Triggered(); // important: always use a queued connection for consistent results }; class HotkeyListener : public QObject { Q_OBJECT private: std::multimap m_callbacks; Display *m_x11_display; int m_x11_screen; Window m_x11_root; bool m_has_xinput2; int m_xinput2_opcode; unsigned int m_xinput2_raw_modifiers; unsigned long m_xinput2_ignore_serial; std::set m_xinput2_master_keyboards; static HotkeyListener *s_instance; public: HotkeyListener(); ~HotkeyListener(); private: void Init(); void Free(); void GrabHotkey(const Hotkey& hotkey, bool enable); void ProcessHotkey(const Hotkey& hotkey); public: inline static HotkeyListener* GetInstance() { assert(s_instance != NULL); return s_instance; } public: // internal HotkeyIterator BindCallback(unsigned int keysym, unsigned int modifiers, HotkeyCallback* callback); void UnbindCallback(HotkeyIterator it); private slots: void ProcessEvents(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/Icons.cpp000066400000000000000000000041161470053624300225260ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "Icons.h" QIcon g_icon_ssr, g_icon_ssr_idle, g_icon_ssr_error, g_icon_ssr_paused, g_icon_ssr_recording; QIcon g_icon_go_home, g_icon_go_previous, g_icon_go_next, g_icon_folder_open; QIcon g_icon_pause, g_icon_record; QIcon g_icon_cancel, g_icon_save, g_icon_quit; QIcon g_icon_microphone; void LoadIcons() { g_icon_ssr = QIcon::fromTheme("simplescreenrecorder"); g_icon_ssr_idle = QIcon::fromTheme("simplescreenrecorder-idle"); g_icon_ssr_error = QIcon::fromTheme("simplescreenrecorder-error"); g_icon_ssr_paused = QIcon::fromTheme("simplescreenrecorder-paused"); g_icon_ssr_recording = QIcon::fromTheme("simplescreenrecorder-recording"); g_icon_go_home = QIcon::fromTheme("go-home"); if(QApplication::layoutDirection() == Qt::RightToLeft) { g_icon_go_previous = QIcon::fromTheme("go-next"); g_icon_go_next = QIcon::fromTheme("go-previous"); } else { g_icon_go_previous = QIcon::fromTheme("go-previous"); g_icon_go_next = QIcon::fromTheme("go-next"); } g_icon_folder_open = QIcon::fromTheme("folder-open"); g_icon_pause = QIcon::fromTheme("media-playback-pause"); g_icon_record = QIcon::fromTheme("media-record"); g_icon_cancel = QIcon::fromTheme("process-stop"); g_icon_save = QIcon::fromTheme("document-save"); g_icon_quit = QIcon::fromTheme("application-exit"); g_icon_microphone = QIcon::fromTheme("audio-input-microphone"); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/Icons.h000066400000000000000000000021411470053624300221670ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" extern QIcon g_icon_ssr, g_icon_ssr_idle, g_icon_ssr_error, g_icon_ssr_paused, g_icon_ssr_recording; extern QIcon g_icon_go_home, g_icon_go_previous, g_icon_go_next, g_icon_folder_open; extern QIcon g_icon_pause, g_icon_record; extern QIcon g_icon_cancel, g_icon_save, g_icon_quit; extern QIcon g_icon_microphone; void LoadIcons(); ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/MainWindow.cpp000066400000000000000000000177671470053624300235470ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "MainWindow.h" #include "Logger.h" #include "CommandLineOptions.h" #include "Icons.h" #include "Dialogs.h" #include "EnumStrings.h" #include "NVidia.h" #include "PageWelcome.h" #include "PageInput.h" #include "PageOutput.h" #include "PageRecord.h" #include "PageDone.h" ENUMSTRINGS(MainWindow::enum_nvidia_disable_flipping) = { {MainWindow::NVIDIA_DISABLE_FLIPPING_ASK, "ask"}, {MainWindow::NVIDIA_DISABLE_FLIPPING_YES, "yes"}, {MainWindow::NVIDIA_DISABLE_FLIPPING_NO, "no"}, }; const QString MainWindow::WINDOW_CAPTION = "SimpleScreenRecorder"; MainWindow::MainWindow() : QMainWindow() { m_nvidia_reenable_flipping = false; m_old_geometry = QRect(); setWindowTitle(WINDOW_CAPTION); setWindowIcon(g_icon_ssr); QWidget *centralwidget = new QWidget(this); setCentralWidget(centralwidget); m_page_welcome = new PageWelcome(this); m_page_input = new PageInput(this); m_page_output = new PageOutput(this); m_page_record = new PageRecord(this); m_page_done = new PageDone(this); m_stacked_layout = new QStackedLayout(centralwidget); m_stacked_layout->addWidget(m_page_welcome); m_stacked_layout->addWidget(m_page_input); m_stacked_layout->addWidget(m_page_output); m_stacked_layout->addWidget(m_page_record); m_stacked_layout->addWidget(m_page_done); LoadSettings(); GoPageStart(); // warning for non-X11 window systems (e.g. Wayland) if(!IsPlatformX11()) { MessageBox(QMessageBox::Warning, NULL, MainWindow::WINDOW_CAPTION, MainWindow::tr("You are using a non-X11 window system (e.g. Wayland) which is only partially supported by SimpleScreenRecorder. " "Several features will most likely not work properly, consider choosing a X11/Xorg session at the login screen if you experience issues. " "SimpleScreenRecorder is able to record Wayland sessions using the PipeWire backend, provided that your Wayland compositor supports it."), BUTTON_OK, BUTTON_OK); } // warning for glitch with proprietary NVIDIA drivers if(GetNVidiaDisableFlipping() == NVIDIA_DISABLE_FLIPPING_ASK || GetNVidiaDisableFlipping() == NVIDIA_DISABLE_FLIPPING_YES) { if(NVidiaGetFlipping()) { bool disable; if(GetNVidiaDisableFlipping() == NVIDIA_DISABLE_FLIPPING_ASK) { enum_button button = MessageBox(QMessageBox::Warning, NULL, MainWindow::WINDOW_CAPTION, MainWindow::tr("SimpleScreenRecorder has detected that you are using the proprietary NVIDIA driver with flipping enabled. " "This is known to cause glitches during recording. It is recommended to disable flipping. Do you want me to do this for you?\n\n" "You can also change this option manually in the NVIDIA control panel.", "Don't translate 'flipping' unless NVIDIA does the same"), BUTTON_YES | BUTTON_YES_ALWAYS | BUTTON_NO | BUTTON_NO_NEVER, BUTTON_YES); if(button == BUTTON_YES_ALWAYS) SetNVidiaDisableFlipping(NVIDIA_DISABLE_FLIPPING_YES); if(button == BUTTON_NO_NEVER) SetNVidiaDisableFlipping(NVIDIA_DISABLE_FLIPPING_NO); disable = (button == BUTTON_YES || button == BUTTON_YES_ALWAYS); } else { disable = true; } if(disable) { if(NVidiaSetFlipping(false)) { m_nvidia_reenable_flipping = true; } else { SetNVidiaDisableFlipping(NVIDIA_DISABLE_FLIPPING_ASK); MessageBox(QMessageBox::Warning, NULL, MainWindow::WINDOW_CAPTION, MainWindow::tr("I couldn't disable flipping for some reason - sorry! Try disabling it in the NVIDIA control panel.", "Don't translate 'flipping' unless NVIDIA does the same"), BUTTON_OK, BUTTON_OK); } } } } // change minimum size based on screen resolution QSize preferred_size = minimumSizeHint() + QSize(style()->pixelMetric(QStyle::PM_ScrollBarExtent), 0); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) QSize available_size(0, 0); for(QScreen *screen : QApplication::screens()) { QSize size = screen->availableGeometry().size() - QSize(80, 80); available_size = (available_size.isNull())? size : available_size.boundedTo(size); } #else QSize available_size = QApplication::desktop()->availableGeometry().size() - QSize(80, 80); #endif //qDebug() << preferred_size << available_size; if(!available_size.isNull()) setMinimumSize(preferred_size.boundedTo(available_size)); // show the window if needed if(!CommandLineOptions::GetStartHidden()) { show(); } m_page_record->UpdateShowHide(); // start recording and/or activate schedule if needed if(CommandLineOptions::GetStartRecording()) { m_page_record->OnRecordStart(); } if(CommandLineOptions::GetActivateSchedule()) { m_page_record->OnScheduleActivate(); } } MainWindow::~MainWindow() { // nothing } void MainWindow::LoadSettings() { QSettings settings(CommandLineOptions::GetSettingsFile(), QSettings::IniFormat); SetNVidiaDisableFlipping(StringToEnum(settings.value("global/nvidia_disable_flipping", QString()).toString(), NVIDIA_DISABLE_FLIPPING_ASK)); m_page_welcome->LoadSettings(&settings); m_page_input->LoadSettings(&settings); m_page_output->LoadSettings(&settings); m_page_record->LoadSettings(&settings); } void MainWindow::SaveSettings() { QSettings settings(CommandLineOptions::GetSettingsFile(), QSettings::IniFormat); settings.clear(); settings.setValue("global/nvidia_disable_flipping", EnumToString(GetNVidiaDisableFlipping())); m_page_welcome->SaveSettings(&settings); m_page_input->SaveSettings(&settings); m_page_output->SaveSettings(&settings); m_page_record->SaveSettings(&settings); } bool MainWindow::IsBusy() { return (QApplication::activeModalWidget() != NULL || QApplication::activePopupWidget() != NULL); } bool MainWindow::Validate() { if(!m_page_input->Validate()) return false; if(!m_page_output->Validate()) return false; return true; } void MainWindow::Quit() { SaveSettings(); if(m_nvidia_reenable_flipping) { NVidiaSetFlipping(true); } QApplication::quit(); } void MainWindow::closeEvent(QCloseEvent* event) { if(m_page_record->ShouldBlockClose()) { event->ignore(); return; } event->accept(); Quit(); } void MainWindow::GoPageStart() { if(m_page_welcome->GetSkipPage()) { m_stacked_layout->setCurrentWidget(m_page_input); } else { m_stacked_layout->setCurrentWidget(m_page_welcome); } } void MainWindow::GoPageWelcome() { m_stacked_layout->setCurrentWidget(m_page_welcome); } void MainWindow::GoPageInput() { m_stacked_layout->setCurrentWidget(m_page_input); } void MainWindow::GoPageOutput() { m_stacked_layout->setCurrentWidget(m_page_output); m_page_output->StartPage(); } void MainWindow::GoPageRecord() { m_stacked_layout->setCurrentWidget(m_page_record); m_page_record->StartPage(); } void MainWindow::GoPageDone() { m_stacked_layout->setCurrentWidget(m_page_done); } void MainWindow::OnShow() { if(IsBusy()) return; if(isVisible()) return; show(); if(!m_old_geometry.isNull()) { setGeometry(m_old_geometry); m_old_geometry = QRect(); } m_page_record->UpdateShowHide(); } void MainWindow::OnHide() { if(IsBusy()) return; if(!isVisible()) return; m_old_geometry = geometry(); hide(); m_page_record->UpdateShowHide(); } void MainWindow::OnShowHide() { if(isVisible()) { OnHide(); } else { OnShow(); } } void MainWindow::OnSysTrayActivated(QSystemTrayIcon::ActivationReason reason) { if(reason == QSystemTrayIcon::Trigger || reason == QSystemTrayIcon::DoubleClick) { OnShowHide(); } } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/MainWindow.h000066400000000000000000000045271470053624300232020ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" class PageWelcome; class PageInput; class PageOutput; class PageRecord; class PageDone; class MainWindow : public QMainWindow { Q_OBJECT public: enum enum_nvidia_disable_flipping { NVIDIA_DISABLE_FLIPPING_ASK, NVIDIA_DISABLE_FLIPPING_YES, NVIDIA_DISABLE_FLIPPING_NO, NVIDIA_DISABLE_FLIPPING_COUNT // must be last }; public: static const QString WINDOW_CAPTION; private: enum_nvidia_disable_flipping m_nvidia_disable_flipping; bool m_nvidia_reenable_flipping; QRect m_old_geometry; QStackedLayout *m_stacked_layout; PageWelcome *m_page_welcome; PageInput *m_page_input; PageOutput *m_page_output; PageRecord *m_page_record; PageDone *m_page_done; public: MainWindow(); ~MainWindow(); void LoadSettings(); void SaveSettings(); bool IsBusy(); bool Validate(); void Quit(); protected: virtual void closeEvent(QCloseEvent* event) override; public: inline PageInput* GetPageInput() { return m_page_input; } inline PageOutput* GetPageOutput() { return m_page_output; } inline enum_nvidia_disable_flipping GetNVidiaDisableFlipping() { return m_nvidia_disable_flipping; } inline void SetNVidiaDisableFlipping(enum_nvidia_disable_flipping flipping) { m_nvidia_disable_flipping = (enum_nvidia_disable_flipping) clamp((unsigned int) flipping, 0u, (unsigned int) NVIDIA_DISABLE_FLIPPING_COUNT - 1); } public slots: void GoPageStart(); void GoPageWelcome(); void GoPageInput(); void GoPageOutput(); void GoPageRecord(); void GoPageDone(); void OnShow(); void OnHide(); void OnShowHide(); void OnSysTrayActivated(QSystemTrayIcon::ActivationReason reason); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/PageDone.cpp000066400000000000000000000040041470053624300231310ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "PageDone.h" #include "Icons.h" #include "MainWindow.h" #include "PageOutput.h" PageDone::PageDone(MainWindow* main_window) : QWidget(main_window->centralWidget()) { m_main_window = main_window; QLabel *label_done = new QLabel(tr("The recording has been saved. You can edit the video now, or re-encode it with better settings to " "make the file smaller (the default settings are optimized for quality and speed, not file size)."), this); label_done->setWordWrap(true); QPushButton *button_open_folder = new QPushButton(g_icon_folder_open, tr("Open folder"), this); connect(button_open_folder, SIGNAL(clicked()), this, SLOT(OnOpenFolder())); QPushButton *button_back = new QPushButton(g_icon_go_home, tr("Back to the start screen"), this); connect(button_back, SIGNAL(clicked()), m_main_window, SLOT(GoPageStart())); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(label_done); { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(button_open_folder); layout2->addStretch(); } layout->addStretch(); layout->addWidget(button_back); } void PageDone::OnOpenFolder() { QFileInfo fi(m_main_window->GetPageOutput()->GetFile()); QDesktopServices::openUrl(QUrl::fromLocalFile(fi.absolutePath())); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/PageDone.h000066400000000000000000000017171470053624300226060ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" class MainWindow; class PageDone : public QWidget { Q_OBJECT private: MainWindow *m_main_window; public: PageDone(MainWindow* main_window); public slots: void OnOpenFolder(); }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/PageInput.cpp000066400000000000000000001530101470053624300233450ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "PageInput.h" #include "DialogGLInject.h" #include "Dialogs.h" #include "EnumStrings.h" #include "Global.h" #include "HiddenScrollArea.h" #include "Icons.h" #include "MainWindow.h" ENUMSTRINGS(PageInput::enum_video_backend) = { {PageInput::VIDEO_BACKEND_X11, "x11"}, #if SSR_USE_OPENGL_RECORDING {PageInput::VIDEO_BACKEND_GLINJECT, "glinject"}, #endif #if SSR_USE_V4L2 {PageInput::VIDEO_BACKEND_V4L2, "v4l2"}, #endif #if SSR_USE_PIPEWIRE {PageInput::VIDEO_BACKEND_PIPEWIRE, "pipewire"}, #endif }; ENUMSTRINGS(PageInput::enum_video_x11_area) = { {PageInput::VIDEO_X11_AREA_SCREEN, "screen"}, {PageInput::VIDEO_X11_AREA_FIXED, "fixed"}, {PageInput::VIDEO_X11_AREA_CURSOR, "cursor"}, }; ENUMSTRINGS(PageInput::enum_audio_backend) = { #if SSR_USE_ALSA {PageInput::AUDIO_BACKEND_ALSA, "alsa"}, #endif #if SSR_USE_PULSEAUDIO {PageInput::AUDIO_BACKEND_PULSEAUDIO, "pulseaudio"}, #endif #if SSR_USE_JACK {PageInput::AUDIO_BACKEND_JACK, "jack"}, #endif }; static std::vector GetScreenGeometries() { std::vector screen_geometries; #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) for(QScreen *screen : QApplication::screens()) { QRect geometry = screen->geometry(); qreal ratio = screen->devicePixelRatio(); screen_geometries.emplace_back(geometry.x(), geometry.y(), lrint((qreal) geometry.width() * ratio), lrint((qreal) geometry.height() * ratio)); } #else for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { screen_geometries.push_back(QApplication::desktop()->screenGeometry(i)); } #endif return screen_geometries; } static QRect CombineScreenGeometries(const std::vector& screen_geometries) { QRect combined_geometry; for(const QRect &geometry : screen_geometries) { combined_geometry |= geometry; } return combined_geometry; } static QPoint GetMousePhysicalCoordinates() { if(IsPlatformX11()) { Window root, child; int root_x, root_y; int win_x, win_y; unsigned int mask_return; XQueryPointer(QX11Info::display(), QX11Info::appRootWindow(), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask_return); return QPoint(root_x, root_y); } else { return QPoint(0, 0); // TODO: implement for wayland } } static QRect MapToLogicalCoordinates(const QRect& rect) { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) for(QScreen *screen : QApplication::screens()) { QRect geometry = screen->geometry(); qreal ratio = screen->devicePixelRatio(); QRect physical_geometry(geometry.x(), geometry.y(), lrint((qreal) geometry.width() * ratio), lrint((qreal) geometry.height() * ratio)); if(physical_geometry.contains(rect.center())) { return QRect( geometry.x() + lrint((qreal) (rect.x() - physical_geometry.x()) / ratio - 0.4999), geometry.y() + lrint((qreal) (rect.y() - physical_geometry.y()) / ratio - 0.4999), lrint((qreal) rect.width() / ratio - 0.4999), lrint((qreal) rect.height() / ratio - 0.4999)); } } #endif return rect; }; // This does some sanity checking on the rubber band rectangle before creating it. // Rubber bands with width or height zero or extremely large appear to cause problems. static QRect ValidateRubberBandRectangle(QRect rect) { std::vector screen_geometries = GetScreenGeometries(); QRect combined_geometry = CombineScreenGeometries(screen_geometries); return rect.normalized() & combined_geometry.adjusted(-10, -10, 10, 10); } QComboBoxWithSignal::QComboBoxWithSignal(QWidget* parent) : QComboBox(parent) {} void QComboBoxWithSignal::showPopup() { emit popupShown(); QComboBox::showPopup(); } void QComboBoxWithSignal::hidePopup() { emit popupHidden(); QComboBox::hidePopup(); } QSpinBoxWithSignal::QSpinBoxWithSignal(QWidget* parent) : QSpinBox(parent) {} void QSpinBoxWithSignal::focusInEvent(QFocusEvent* event) { emit focusIn(); QSpinBox::focusInEvent(event); } void QSpinBoxWithSignal::focusOutEvent(QFocusEvent* event) { emit focusOut(); QSpinBox::focusOutEvent(event); } #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #define TRANSPARENT_WINDOW_FLAGS (Qt::Window | Qt::BypassWindowManagerHint | Qt::FramelessWindowHint | \ Qt::WindowStaysOnTopHint | Qt::WindowTransparentForInput | Qt::WindowDoesNotAcceptFocus) #define TRANSPARENT_WINDOW_ATTRIBUTES() {\ } #else #define TRANSPARENT_WINDOW_FLAGS (Qt::Window | Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint | \ Qt::WindowStaysOnTopHint) // Replacement for Qt::WindowTransparentForInput based on X11 'shape' extension as described here: // http://shallowsky.com/blog/programming/translucent-window-click-thru.html #define TRANSPARENT_WINDOW_ATTRIBUTES() {\ setAttribute(Qt::WA_X11DoNotAcceptFocus); \ int shape_event_base, shape_error_base; \ if(IsPlatformX11()) { \ if(XShapeQueryExtension(QX11Info::display(), &shape_event_base, &shape_error_base)) { \ Region region = XCreateRegion(); \ XShapeCombineRegion(QX11Info::display(), winId(), ShapeInput, 0, 0, region, ShapeSet); \ XDestroyRegion(region); \ } \ } \ } #endif ScreenLabelWindow::ScreenLabelWindow(QWidget* parent, const QString &text) : QWidget(parent, TRANSPARENT_WINDOW_FLAGS) { TRANSPARENT_WINDOW_ATTRIBUTES(); m_text = text; m_font = QFont("Sans", 18, QFont::Bold); QFontMetrics fm(m_font); setFixedSize(fm.size(Qt::TextSingleLine, m_text) + QSize(60, 40)); setWindowOpacity(0.75); } void ScreenLabelWindow::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); painter.setPen(QColor(0, 0, 0)); painter.setBrush(QColor(255, 192, 128)); painter.drawRect(QRectF(0.5, 0.5, (qreal) width() - 1.0, (qreal) height() - 1.0)); painter.setFont(m_font); painter.drawText(0, 0, width(), height(), Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine, m_text); } RecordingFrameWindow::RecordingFrameWindow(QWidget* parent, bool outside) : QWidget(parent, TRANSPARENT_WINDOW_FLAGS) { TRANSPARENT_WINDOW_ATTRIBUTES(); m_outside = outside; QImage image(16, 16, QImage::Format_RGB32); for(size_t j = 0; j < (size_t) image.height(); ++j) { uint32_t *row = (uint32_t*) image.scanLine(j); for(size_t i = 0; i < (size_t) image.width(); ++i) { //row[i] = ((i + j) % 16 < 8)? 0xffbfbfff : 0xff9f9fdf; row[i] = ((i + j) % 16 < 8)? 0xffff8080 : 0xff8080ff; } } m_texture = QPixmap::fromImage(image); UpdateMask(); } void RecordingFrameWindow::SetRectangle(const QRect& r) { QRect rect = MapToLogicalCoordinates(ValidateRubberBandRectangle(r)); if(m_outside) rect.adjust(-RecordingFrameWindow::BORDER_WIDTH, -RecordingFrameWindow::BORDER_WIDTH, RecordingFrameWindow::BORDER_WIDTH, RecordingFrameWindow::BORDER_WIDTH); if(rect.isEmpty()) { hide(); } else { setGeometry(rect); show(); } } void RecordingFrameWindow::UpdateMask() { if(m_outside) { setMask(QRegion(0, 0, width(), height()).subtracted(QRegion(BORDER_WIDTH, BORDER_WIDTH, width() - 2 * BORDER_WIDTH, height() - 2 * BORDER_WIDTH))); setWindowOpacity(0.5); } else { if(QX11Info::isCompositingManagerRunning()) { clearMask(); setWindowOpacity(0.25); } else { setMask(QRegion(0, 0, width(), height()).subtracted(QRegion(BORDER_WIDTH, BORDER_WIDTH, width() - 2 * BORDER_WIDTH, height() - 2 * BORDER_WIDTH))); setWindowOpacity(1.0); } } } void RecordingFrameWindow::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); UpdateMask(); } void RecordingFrameWindow::paintEvent(QPaintEvent* event) { Q_UNUSED(event); QPainter painter(this); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) m_texture.setDevicePixelRatio(devicePixelRatioF()); #endif painter.setPen(QColor(0, 0, 0, 128)); painter.setBrush(Qt::NoBrush); painter.drawTiledPixmap(0, 0, width(), height(), m_texture); if(m_outside) { painter.drawRect(QRectF((qreal) BORDER_WIDTH - 0.5, (qreal) BORDER_WIDTH - 0.5, (qreal) (width() - 2 * BORDER_WIDTH) + 1.0, (qreal) (height() - 2 * BORDER_WIDTH) + 1.0)); } else { painter.drawRect(QRectF(0.5, 0.5, (qreal) width() - 1.0, (qreal) height() - 1.0)); } } PageInput::PageInput(MainWindow* main_window) : QWidget(main_window->centralWidget()) { m_main_window = main_window; m_grabbing = false; m_selecting_window = false; HiddenScrollArea *scrollarea = new HiddenScrollArea(this); QWidget *scrollarea_contents = new QWidget(scrollarea); scrollarea->setWidget(scrollarea_contents); { m_profile_box = new ProfileBox(tr("Input profile"), scrollarea_contents, "input-profiles", &LoadProfileSettingsCallback, &SaveProfileSettingsCallback, this); QGroupBox *groupbox_video = new QGroupBox(tr("Video input"), scrollarea_contents); { QLabel *label_video_backend = new QLabel(tr("Backend:"), groupbox_video); m_combobox_video_backend = new QComboBox(groupbox_video); m_combobox_video_backend->addItem("X11"); #if SSR_USE_OPENGL_RECORDING m_combobox_video_backend->addItem("OpenGL"); #endif #if SSR_USE_V4L2 m_combobox_video_backend->addItem("V4L2"); #endif #if SSR_USE_PIPEWIRE m_combobox_video_backend->addItem("PipeWire"); #endif m_combobox_video_backend->setToolTip(tr("The video backend that will be used for recording.")); m_buttongroup_video_x11_area = new QButtonGroup(groupbox_video); m_radio_area_screen = new QRadioButton(tr("Record the entire screen"), groupbox_video); m_radio_area_fixed = new QRadioButton(tr("Record a fixed rectangle"), groupbox_video); m_radio_area_cursor = new QRadioButton(tr("Follow the cursor"), groupbox_video); m_buttongroup_video_x11_area->addButton(m_radio_area_screen, VIDEO_X11_AREA_SCREEN); m_buttongroup_video_x11_area->addButton(m_radio_area_fixed, VIDEO_X11_AREA_FIXED); m_buttongroup_video_x11_area->addButton(m_radio_area_cursor, VIDEO_X11_AREA_CURSOR); m_combobox_x11_screens = new QComboBoxWithSignal(groupbox_video); m_combobox_x11_screens->setToolTip(tr("Select what monitor should be recorded in a multi-monitor configuration.")); m_checkbox_video_x11_follow_fullscreen = new QCheckBox(tr("Record entire screen with cursor"), groupbox_video); m_checkbox_video_x11_follow_fullscreen->setToolTip(tr("Record the entire screen on which the cursor is located, rather than following the cursor position.")); m_pushbutton_video_x11_select_rectangle = new QPushButton(tr("Select rectangle..."), groupbox_video); m_pushbutton_video_x11_select_rectangle->setToolTip(tr("Use the mouse to select the recorded rectangle.")); m_pushbutton_video_x11_select_window = new QPushButton(tr("Select window..."), groupbox_video); m_pushbutton_video_x11_select_window->setToolTip(tr("Use the mouse to select a window to record.\n" "Hint: If you click the border of a window, the entire window will be recorded (including the borders). Otherwise only\n" "the client area of the window will be recorded.")); m_label_video_x11_x = new QLabel(tr("Left:"), groupbox_video); m_spinbox_video_x11_x = new QSpinBoxWithSignal(groupbox_video); m_spinbox_video_x11_x->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_x11_x->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_x11_x->setToolTip(tr("The x coordinate of the upper-left corner of the recorded rectangle.\n" "Hint: You can also change this value with the scroll wheel or the up/down arrows.")); m_label_video_x11_y = new QLabel(tr("Top:"), groupbox_video); m_spinbox_video_x11_y = new QSpinBoxWithSignal(groupbox_video); m_spinbox_video_x11_y->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_x11_y->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_x11_y->setToolTip(tr("The y coordinate of the upper-left corner of the recorded rectangle.\n" "Hint: You can also change this value with the scroll wheel or the up/down arrows.")); m_label_video_x11_width = new QLabel(tr("Width:"), groupbox_video); m_spinbox_video_x11_width = new QSpinBoxWithSignal(groupbox_video); m_spinbox_video_x11_width->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_x11_width->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_x11_width->setToolTip(tr("The width of the recorded rectangle.\n" "Hint: You can also change this value with the scroll wheel or the up/down arrows.")); m_label_video_x11_height = new QLabel(tr("Height:"), groupbox_video); m_spinbox_video_x11_height = new QSpinBoxWithSignal(groupbox_video); m_spinbox_video_x11_height->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_x11_height->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_x11_height->setToolTip(tr("The height of the recorded rectangle.\n" "Hint: You can also change this value with the scroll wheel or the up/down arrows.")); #if SSR_USE_OPENGL_RECORDING m_pushbutton_video_opengl_settings = new QPushButton(tr("OpenGL settings..."), groupbox_video); m_pushbutton_video_opengl_settings->setToolTip(tr("Change the settings for OpenGL recording.")); #endif #if SSR_USE_V4L2 m_label_video_v4l2_device = new QLabel(tr("V4L2 device:"), groupbox_video); m_lineedit_video_v4l2_device = new QLineEdit(groupbox_video); m_lineedit_video_v4l2_device->setToolTip(tr("The V4L2 device to record (e.g. /dev/video0).")); m_label_video_v4l2_width = new QLabel(tr("Width:"), groupbox_video); m_spinbox_video_v4l2_width = new QSpinBoxWithSignal(groupbox_video); m_spinbox_video_v4l2_width->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_v4l2_width->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_v4l2_width->setToolTip(tr("The width of the video.")); m_label_video_v4l2_height = new QLabel(tr("Height:"), groupbox_video); m_spinbox_video_v4l2_height = new QSpinBoxWithSignal(groupbox_video); m_spinbox_video_v4l2_height->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_v4l2_height->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_v4l2_height->setToolTip(tr("The height of the video.")); #endif #if SSR_USE_PIPEWIRE m_label_video_pipewire_source = new QLabel(tr("Source:"), groupbox_video); m_lineedit_video_pipewire_source = new QLineEdit(groupbox_video); m_lineedit_video_pipewire_source->setToolTip(tr("The PipeWire source to record.")); // TODO m_label_video_pipewire_width = new QLabel(tr("Width:"), groupbox_video); m_spinbox_video_pipewire_width = new QSpinBoxWithSignal(groupbox_video); m_spinbox_video_pipewire_width->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_pipewire_width->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_pipewire_width->setToolTip(tr("The width of the video.")); m_label_video_pipewire_height = new QLabel(tr("Height:"), groupbox_video); m_spinbox_video_pipewire_height = new QSpinBoxWithSignal(groupbox_video); m_spinbox_video_pipewire_height->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_pipewire_height->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_pipewire_height->setToolTip(tr("The height of the video.")); #endif QLabel *label_frame_rate = new QLabel(tr("Frame rate:"), groupbox_video); m_spinbox_video_frame_rate = new QSpinBox(groupbox_video); m_spinbox_video_frame_rate->setRange(1, 1000); m_spinbox_video_frame_rate->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_spinbox_video_frame_rate->setToolTip(tr("The number of frames per second in the final video. Higher frame rates use more CPU time.")); m_checkbox_scale = new QCheckBox(tr("Scale video"), groupbox_video); m_checkbox_scale->setToolTip(tr("Enable or disable scaling. Scaling uses more CPU time, but if the scaled video is smaller, it could make the encoding faster.")); m_label_video_scaled_width = new QLabel(tr("Scaled width:"), groupbox_video); m_spinbox_video_scaled_weight = new QSpinBox(groupbox_video); m_spinbox_video_scaled_weight->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_scaled_weight->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_label_video_scaled_height = new QLabel(tr("Scaled height:"), groupbox_video); m_spinbox_video_scaled_height = new QSpinBox(groupbox_video); m_spinbox_video_scaled_height->setRange(0, SSR_MAX_IMAGE_SIZE); m_spinbox_video_scaled_height->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_checkbox_record_cursor = new QCheckBox(tr("Record cursor"), groupbox_video); connect(m_combobox_video_backend, SIGNAL(activated(int)), this, SLOT(OnUpdateVideoAreaFields())); connect(m_buttongroup_video_x11_area, SIGNAL(buttonClicked(int)), this, SLOT(OnUpdateVideoAreaFields())); connect(m_combobox_x11_screens, SIGNAL(activated(int)), this, SLOT(OnUpdateVideoAreaFields())); connect(m_combobox_x11_screens, SIGNAL(popupShown()), this, SLOT(OnIdentifyScreens())); connect(m_combobox_x11_screens, SIGNAL(popupHidden()), this, SLOT(OnStopIdentifyScreens())); connect(m_checkbox_video_x11_follow_fullscreen, SIGNAL(clicked()), this, SLOT(OnUpdateVideoAreaFields())); connect(m_spinbox_video_x11_x, SIGNAL(focusIn()), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_x, SIGNAL(focusOut()), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_x, SIGNAL(valueChanged(int)), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_y, SIGNAL(focusIn()), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_y, SIGNAL(focusOut()), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_y, SIGNAL(valueChanged(int)), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_width, SIGNAL(focusIn()), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_width, SIGNAL(focusOut()), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_width, SIGNAL(valueChanged(int)), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_height, SIGNAL(focusIn()), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_height, SIGNAL(focusOut()), this, SLOT(OnUpdateRecordingFrame())); connect(m_spinbox_video_x11_height, SIGNAL(valueChanged(int)), this, SLOT(OnUpdateRecordingFrame())); connect(m_pushbutton_video_x11_select_rectangle, SIGNAL(clicked()), this, SLOT(OnStartSelectRectangle())); connect(m_pushbutton_video_x11_select_window, SIGNAL(clicked()), this, SLOT(OnStartSelectWindow())); #if SSR_USE_OPENGL_RECORDING connect(m_pushbutton_video_opengl_settings, SIGNAL(clicked()), this, SLOT(OnGLInjectDialog())); #endif connect(m_checkbox_scale, SIGNAL(clicked()), this, SLOT(OnUpdateVideoScaleFields())); QVBoxLayout *layout = new QVBoxLayout(groupbox_video); { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(label_video_backend); layout2->addWidget(m_combobox_video_backend); } { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(m_radio_area_screen); layout2->addWidget(m_combobox_x11_screens); } layout->addWidget(m_radio_area_fixed); { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(m_radio_area_cursor); layout2->addWidget(m_checkbox_video_x11_follow_fullscreen); } { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(m_pushbutton_video_x11_select_rectangle); layout2->addWidget(m_pushbutton_video_x11_select_window); #if SSR_USE_OPENGL_RECORDING layout2->addWidget(m_pushbutton_video_opengl_settings); #endif layout2->addStretch(); } { QGridLayout *layout2 = new QGridLayout(); layout->addLayout(layout2); layout2->addWidget(m_label_video_x11_x, 0, 0); layout2->addWidget(m_spinbox_video_x11_x, 0, 1); layout2->addWidget(m_label_video_x11_y, 0, 2); layout2->addWidget(m_spinbox_video_x11_y, 0, 3); layout2->addWidget(m_label_video_x11_width, 1, 0); layout2->addWidget(m_spinbox_video_x11_width, 1, 1); layout2->addWidget(m_label_video_x11_height, 1, 2); layout2->addWidget(m_spinbox_video_x11_height, 1, 3); } #if SSR_USE_V4L2 { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(m_label_video_v4l2_device); layout2->addWidget(m_lineedit_video_v4l2_device); } { QGridLayout *layout2 = new QGridLayout(); layout->addLayout(layout2); layout2->addWidget(m_label_video_v4l2_width, 1, 0); layout2->addWidget(m_spinbox_video_v4l2_width, 1, 1); layout2->addWidget(m_label_video_v4l2_height, 1, 2); layout2->addWidget(m_spinbox_video_v4l2_height, 1, 3); } #endif #if SSR_USE_PIPEWIRE { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(m_label_video_pipewire_source); layout2->addWidget(m_lineedit_video_pipewire_source); } { QGridLayout *layout2 = new QGridLayout(); layout->addLayout(layout2); layout2->addWidget(m_label_video_pipewire_width, 1, 0); layout2->addWidget(m_spinbox_video_pipewire_width, 1, 1); layout2->addWidget(m_label_video_pipewire_height, 1, 2); layout2->addWidget(m_spinbox_video_pipewire_height, 1, 3); } #endif { QGridLayout *layout2 = new QGridLayout(); layout->addLayout(layout2); layout2->addWidget(label_frame_rate, 0, 0); layout2->addWidget(m_spinbox_video_frame_rate, 0, 1); } layout->addWidget(m_checkbox_scale); { QGridLayout *layout2 = new QGridLayout(); layout->addLayout(layout2); layout2->addWidget(m_label_video_scaled_width, 0, 0); layout2->addWidget(m_spinbox_video_scaled_weight, 0, 1); layout2->addWidget(m_label_video_scaled_height, 0, 2); layout2->addWidget(m_spinbox_video_scaled_height, 0, 3); } layout->addWidget(m_checkbox_record_cursor); } QGroupBox *groupbox_audio = new QGroupBox(tr("Audio input"), scrollarea_contents); { m_checkbox_audio_enable = new QCheckBox(tr("Record audio"), groupbox_audio); m_label_audio_backend = new QLabel(tr("Backend:"), groupbox_audio); m_combobox_audio_backend = new QComboBox(groupbox_audio); #if SSR_USE_ALSA m_combobox_audio_backend->addItem("ALSA"); #endif #if SSR_USE_PULSEAUDIO m_combobox_audio_backend->addItem("PulseAudio"); #endif #if SSR_USE_JACK m_combobox_audio_backend->addItem("JACK"); #endif #if SSR_USE_ALSA && SSR_USE_PULSEAUDIO m_combobox_audio_backend->setToolTip(tr("The audio backend that will be used for recording.\n" "The ALSA backend will also work on systems that use PulseAudio, but it is better to use the PulseAudio backend directly.")); #else m_combobox_audio_backend->setToolTip(tr("The audio backend that will be used for recording.")); #endif #if SSR_USE_ALSA m_label_alsa_source = new QLabel(tr("Source:"), groupbox_audio); m_combobox_alsa_source = new QComboBox(groupbox_audio); m_combobox_alsa_source->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_combobox_alsa_source->setToolTip(tr("The ALSA source that will be used for recording.\n" "The default is usually fine. The 'shared' sources allow multiple programs to record at the same time, but they may be less reliable.")); m_pushbutton_alsa_refresh = new QPushButton(tr("Refresh"), groupbox_audio); m_pushbutton_alsa_refresh->setToolTip(tr("Refreshes the list of ALSA sources.")); #endif #if SSR_USE_PULSEAUDIO m_label_pulseaudio_source = new QLabel(tr("Source:"), groupbox_audio); m_combobox_pulseaudio_source = new QComboBox(groupbox_audio); m_combobox_pulseaudio_source->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_combobox_pulseaudio_source->setToolTip(tr("The PulseAudio source that will be used for recording.\n" "A 'monitor' is a source that records the audio played by other applications.", "Don't translate 'monitor' unless PulseAudio does this as well")); m_pushbutton_pulseaudio_refresh = new QPushButton(tr("Refresh"), groupbox_audio); m_pushbutton_pulseaudio_refresh->setToolTip(tr("Refreshes the list of PulseAudio sources.")); #endif #if SSR_USE_JACK m_checkbox_jack_connect_system_capture = new QCheckBox(tr("Record system microphone")); m_checkbox_jack_connect_system_capture->setToolTip(tr("If checked, the ports will be automatically connected to the system capture ports.")); m_checkbox_jack_connect_system_playback = new QCheckBox(tr("Record system speakers")); m_checkbox_jack_connect_system_playback->setToolTip(tr("If checked, the ports will be automatically connected to anything that connects to the system playback ports.")); #endif connect(m_checkbox_audio_enable, SIGNAL(clicked()), this, SLOT(OnUpdateAudioFields())); connect(m_combobox_audio_backend, SIGNAL(activated(int)), this, SLOT(OnUpdateAudioFields())); #if SSR_USE_ALSA connect(m_pushbutton_alsa_refresh, SIGNAL(clicked()), this, SLOT(OnUpdateALSASources())); #endif #if SSR_USE_PULSEAUDIO connect(m_pushbutton_pulseaudio_refresh, SIGNAL(clicked()), this, SLOT(OnUpdatePulseAudioSources())); #endif QVBoxLayout *layout = new QVBoxLayout(groupbox_audio); layout->addWidget(m_checkbox_audio_enable); { QGridLayout *layout2 = new QGridLayout(); layout->addLayout(layout2); layout2->addWidget(m_label_audio_backend, 0, 0); layout2->addWidget(m_combobox_audio_backend, 0, 1, 1, 2); #if SSR_USE_ALSA layout2->addWidget(m_label_alsa_source, 1, 0); layout2->addWidget(m_combobox_alsa_source, 1, 1); layout2->addWidget(m_pushbutton_alsa_refresh, 1, 2); #endif #if SSR_USE_PULSEAUDIO layout2->addWidget(m_label_pulseaudio_source, 2, 0); layout2->addWidget(m_combobox_pulseaudio_source, 2, 1); layout2->addWidget(m_pushbutton_pulseaudio_refresh, 2, 2); #endif } #if SSR_USE_JACK { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addWidget(m_checkbox_jack_connect_system_capture); layout2->addWidget(m_checkbox_jack_connect_system_playback); } #endif } QVBoxLayout *layout = new QVBoxLayout(scrollarea_contents); layout->addWidget(m_profile_box); layout->addWidget(groupbox_video); layout->addWidget(groupbox_audio); layout->addStretch(); } QPushButton *button_back = new QPushButton(g_icon_go_previous, tr("Back"), this); QPushButton *button_continue = new QPushButton(g_icon_go_next, tr("Continue"), this); connect(button_back, SIGNAL(clicked()), m_main_window, SLOT(GoPageWelcome())); connect(button_continue, SIGNAL(clicked()), this, SLOT(OnContinue())); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(scrollarea); { QHBoxLayout *layout2 = new QHBoxLayout(); layout->addLayout(layout2); layout2->addSpacing(style()->pixelMetric(QStyle::PM_LayoutLeftMargin)); layout2->addWidget(button_back); layout2->addWidget(button_continue); layout2->addSpacing(style()->pixelMetric(QStyle::PM_LayoutRightMargin)); } layout->addSpacing(style()->pixelMetric(QStyle::PM_LayoutBottomMargin)); connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(OnFocusChange(QWidget*, QWidget*))); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) connect(qApp, SIGNAL(screenAdded(QScreen*)), this, SLOT(OnScreenAdded(QScreen*))); connect(qApp, SIGNAL(screenRemoved(QScreen*)), this, SLOT(OnUpdateScreenConfiguration())); #else connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), this, SLOT(OnUpdateScreenConfiguration())); connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(OnUpdateScreenConfiguration())); #endif LoadScreenConfigurations(); #if SSR_USE_ALSA LoadALSASources(); #endif #if SSR_USE_PULSEAUDIO LoadPulseAudioSources(); #endif // temporary settings to calculate the worst-case size SetAudioEnabled(true); #if SSR_USE_ALSA SetAudioBackend(AUDIO_BACKEND_ALSA); #else #if SSR_USE_PULSEAUDIO SetAudioBackend(AUDIO_BACKEND_PULSEAUDIO); #else #if SSR_USE_JACK SetAudioBackend(AUDIO_BACKEND_JACK); #else #error "At least one audio backend must be enabled!" #endif #endif #endif OnUpdateVideoAreaFields(); OnUpdateVideoScaleFields(); OnUpdateAudioFields(); } void PageInput::LoadSettings(QSettings* settings) { SetProfile(m_profile_box->FindProfile(settings->value("input/profile", QString()).toString())); LoadProfileSettings(settings); } void PageInput::SaveSettings(QSettings* settings) { settings->setValue("input/profile", m_profile_box->GetProfileName()); SaveProfileSettings(settings); } void PageInput::LoadProfileSettingsCallback(QSettings* settings, void* userdata) { PageInput *page = (PageInput*) userdata; page->LoadProfileSettings(settings); } void PageInput::SaveProfileSettingsCallback(QSettings* settings, void* userdata) { PageInput *page = (PageInput*) userdata; page->SaveProfileSettings(settings); } void PageInput::LoadProfileSettings(QSettings* settings) { // choose default audio backend #if SSR_USE_ALSA #if SSR_USE_PULSEAUDIO enum_audio_backend default_audio_backend = (m_pulseaudio_available)? AUDIO_BACKEND_PULSEAUDIO : AUDIO_BACKEND_ALSA; #else enum_audio_backend default_audio_backend = AUDIO_BACKEND_ALSA; #endif #elif SSR_USE_PULSEAUDIO enum_audio_backend default_audio_backend = AUDIO_BACKEND_PULSEAUDIO; #elif SSR_USE_JACK enum_audio_backend default_audio_backend = AUDIO_BACKEND_JACK; #else #error "At least one audio backend must be enabled!" #endif // load settings SetVideoBackend(StringToEnum(settings->value("input/video_backend", QString()).toString(), VIDEO_BACKEND_X11)); SetVideoX11Area(StringToEnum(settings->value("input/video_x11_area", QString()).toString(), VIDEO_X11_AREA_SCREEN)); SetVideoX11Screen(settings->value("input/video_x11_screen", 0).toUInt()); SetVideoX11FollowFullscreen(settings->value("input/video_x11_follow_fullscreen", false).toBool()); SetVideoX11X(settings->value("input/video_x11_x", 0).toUInt()); SetVideoX11Y(settings->value("input/video_x11_y", 0).toUInt()); SetVideoX11Width(settings->value("input/video_x11_width", 800).toUInt()); SetVideoX11Height(settings->value("input/video_x11_height", 600).toUInt()); #if SSR_USE_V4L2 SetVideoV4L2Device(settings->value("input/video_v4l2_device", "/dev/video0").toString()); SetVideoV4L2Width(settings->value("input/video_v4l2_width", 800).toUInt()); SetVideoV4L2Height(settings->value("input/video_v4l2_height", 600).toUInt()); #endif #if SSR_USE_PIPEWIRE SetVideoPipeWireSource(settings->value("input/video_pipewire_source", "").toString()); // TODO SetVideoPipeWireWidth(settings->value("input/video_pipewire_width", 800).toUInt()); SetVideoPipeWireHeight(settings->value("input/video_pipewire_height", 600).toUInt()); #endif SetVideoFrameRate(settings->value("input/video_frame_rate", 30).toUInt()); SetVideoScalingEnabled(settings->value("input/video_scale", false).toBool()); SetVideoScaledWidth(settings->value("input/video_scaled_width", 854).toUInt()); SetVideoScaledHeight(settings->value("input/video_scaled_height", 480).toUInt()); SetVideoRecordCursor(settings->value("input/video_record_cursor", true).toBool()); SetAudioEnabled(settings->value("input/audio_enabled", true).toBool()); SetAudioBackend(StringToEnum(settings->value("input/audio_backend", QString()).toString(), default_audio_backend)); #if SSR_USE_ALSA SetALSASource(FindALSASource(settings->value("input/audio_alsa_source", QString()).toString())); #endif #if SSR_USE_PULSEAUDIO SetPulseAudioSource(FindPulseAudioSource(settings->value("input/audio_pulseaudio_source", QString()).toString())); #endif #if SSR_USE_JACK SetJackConnectSystemCapture(settings->value("input/audio_jack_connect_system_capture", true).toBool()); SetJackConnectSystemPlayback(settings->value("input/audio_jack_connect_system_playback", false).toBool()); #endif #if SSR_USE_OPENGL_RECORDING SetGLInjectChannel(settings->value("input/glinject_channel", QString()).toString()); SetGLInjectRelaxPermissions(settings->value("input/glinject_relax_permissions", false).toBool()); SetGLInjectCommand(settings->value("input/glinject_command", "").toString()); SetGLInjectWorkingDirectory(settings->value("input/glinject_working_directory", "").toString()); SetGLInjectAutoLaunch(settings->value("input/glinject_auto_launch", false).toBool()); SetGLInjectLimitFPS(settings->value("input/glinject_limit_fps", false).toBool()); #endif // update things OnUpdateRecordingFrame(); OnUpdateVideoAreaFields(); OnUpdateVideoScaleFields(); OnUpdateAudioFields(); } void PageInput::SaveProfileSettings(QSettings* settings) { settings->setValue("input/video_backend", EnumToString(GetVideoBackend())); settings->setValue("input/video_x11_area", EnumToString(GetVideoX11Area())); settings->setValue("input/video_x11_screen", GetVideoX11Screen()); settings->setValue("input/video_x11_follow_fullscreen", GetVideoX11FollowFullscreen()); settings->setValue("input/video_x11_x", GetVideoX11X()); settings->setValue("input/video_x11_y", GetVideoX11Y()); settings->setValue("input/video_x11_width", GetVideoX11Width()); settings->setValue("input/video_x11_height", GetVideoX11Height()); #if SSR_USE_V4L2 settings->setValue("input/video_v4l2_device", GetVideoV4L2Device()); settings->setValue("input/video_v4l2_width", GetVideoV4L2Width()); settings->setValue("input/video_v4l2_height", GetVideoV4L2Height()); #endif #if SSR_USE_PIPEWIRE settings->setValue("input/video_pipewire_source", GetVideoPipeWireSource()); settings->setValue("input/video_pipewire_width", GetVideoPipeWireWidth()); settings->setValue("input/video_pipewire_height", GetVideoPipeWireHeight()); #endif settings->setValue("input/video_frame_rate", GetVideoFrameRate()); settings->setValue("input/video_scale", GetVideoScalingEnabled()); settings->setValue("input/video_scaled_width", GetVideoScaledWeight()); settings->setValue("input/video_scaled_height", GetVideoScaledHeight()); settings->setValue("input/video_record_cursor", GetVideoRecordCursor()); settings->setValue("input/audio_enabled", GetAudioEnabled()); settings->setValue("input/audio_backend", EnumToString(GetAudioBackend())); #if SSR_USE_ALSA settings->setValue("input/audio_alsa_source", GetALSASourceName()); #endif #if SSR_USE_PULSEAUDIO settings->setValue("input/audio_pulseaudio_source", GetPulseAudioSourceName()); #endif #if SSR_USE_JACK settings->setValue("input/audio_jack_connect_system_capture", GetJackConnectSystemCapture()); settings->setValue("input/audio_jack_connect_system_playback", GetJackConnectSystemPlayback()); #endif #if SSR_USE_OPENGL_RECORDING settings->setValue("input/glinject_channel", GetGLInjectChannel()); settings->setValue("input/glinject_relax_permissions", GetGLInjectRelaxPermissions()); settings->setValue("input/glinject_command", GetGLInjectCommand()); settings->setValue("input/glinject_working_directory", GetGLInjectWorkingDirectory()); settings->setValue("input/glinject_auto_launch", GetGLInjectAutoLaunch()); settings->setValue("input/glinject_limit_fps", GetGLInjectLimitFPS()); #endif } bool PageInput::Validate() { if(m_grabbing) return false; return true; } #if SSR_USE_ALSA QString PageInput::GetALSASourceName() { return QString::fromStdString(m_alsa_sources[GetALSASource()].m_name); } #endif #if SSR_USE_PULSEAUDIO QString PageInput::GetPulseAudioSourceName() { return QString::fromStdString(m_pulseaudio_sources[GetPulseAudioSource()].m_name); } #endif #if SSR_USE_ALSA unsigned int PageInput::FindALSASource(const QString& name) { for(unsigned int i = 0; i < m_alsa_sources.size(); ++i) { if(QString::fromStdString(m_alsa_sources[i].m_name) == name) return i; } return 0; } #endif #if SSR_USE_PULSEAUDIO unsigned int PageInput::FindPulseAudioSource(const QString& name) { for(unsigned int i = 0; i < m_pulseaudio_sources.size(); ++i) { if(QString::fromStdString(m_pulseaudio_sources[i].m_name) == name) return i; } return 0; } #endif // Tries to find the real window that corresponds to a top-level window (the actual window without window manager decorations). // Returns None if it can't find the window (probably because the window is not handled by the window manager). // Based on the xprop source code (http://cgit.freedesktop.org/xorg/app/xprop/tree/clientwin.c). static Window X11FindRealWindow(Display* display, Window window) { // is this the real window? Atom actual_type; int actual_format; unsigned long items, bytes_left; unsigned char *data = NULL; XGetWindowProperty(display, window, XInternAtom(display, "WM_STATE", true), 0, 0, false, AnyPropertyType, &actual_type, &actual_format, &items, &bytes_left, &data); if(data != NULL) XFree(data); if(actual_type != None) return window; // get the child windows Window root, parent, *childs; unsigned int childcount; if(!XQueryTree(display, window, &root, &parent, &childs, &childcount)) { return None; } // recursively call this function for all childs Window real_window = None; for(unsigned int i = childcount; i > 0; ) { --i; Window w = X11FindRealWindow(display, childs[i]); if(w != None) { real_window = w; break; } } // free child window list if(childs != NULL) XFree(childs); return real_window; } void PageInput::mousePressEvent(QMouseEvent* event) { if(m_grabbing) { if(event->button() == Qt::LeftButton) { if(IsPlatformX11()) { QPoint mouse_physical = GetMousePhysicalCoordinates(); if(m_selecting_window) { // As expected, Qt does not provide any functions to find the window at a specific position, so I have to use Xlib directly. // I'm not completely sure whether this is the best way to do this, but it appears to work. XQueryPointer returns the window // currently below the mouse along with the mouse position, but apparently this may not work correctly when the mouse pointer // is also grabbed (even though it works fine in my test), so I use XTranslateCoordinates instead. Originally I wanted to // show the rubber band when the mouse hovers over a window (instead of having to click it), but this doesn't work correctly // since X will simply return a handle the rubber band itself (even though it should be transparent to mouse events). Window selected_window; int x, y; if(XTranslateCoordinates(QX11Info::display(), QX11Info::appRootWindow(), QX11Info::appRootWindow(), mouse_physical.x(), mouse_physical.y(), &x, &y, &selected_window)) { XWindowAttributes attributes; if(selected_window != None && XGetWindowAttributes(QX11Info::display(), selected_window, &attributes)) { // naive outer/inner rectangle, this won't work for window decorations m_select_window_outer_rect = QRect(attributes.x, attributes.y, attributes.width + 2 * attributes.border_width, attributes.height + 2 * attributes.border_width); m_select_window_inner_rect = QRect(attributes.x + attributes.border_width, attributes.y + attributes.border_width, attributes.width, attributes.height); // try to find the real window (rather than the decorations added by the window manager) Window real_window = X11FindRealWindow(QX11Info::display(), selected_window); if(real_window != None) { Atom actual_type; int actual_format; unsigned long items, bytes_left; long *data = NULL; int result = XGetWindowProperty(QX11Info::display(), real_window, XInternAtom(QX11Info::display(), "_NET_FRAME_EXTENTS", true), 0, 4, false, AnyPropertyType, &actual_type, &actual_format, &items, &bytes_left, (unsigned char**) &data); if(result == Success) { if(items == 4 && bytes_left == 0 && actual_format == 32) { // format 32 means 'long', even if long is 64-bit ... Window child; // the attributes of the real window only store the *relative* position which is not what we need, so use XTranslateCoordinates again if(XTranslateCoordinates(QX11Info::display(), real_window, QX11Info::appRootWindow(), 0, 0, &x, &y, &child) && XGetWindowAttributes(QX11Info::display(), real_window, &attributes)) { // finally! m_select_window_inner_rect = QRect(x, y, attributes.width, attributes.height); m_select_window_outer_rect = m_select_window_inner_rect.adjusted(-data[0], -data[2], data[1], data[3]); } else { // I doubt this will ever be needed, but do it anyway m_select_window_inner_rect = m_select_window_outer_rect.adjusted(data[0], data[2], -data[1], -data[3]); } } } if(data != NULL) XFree(data); } // pick the inner rectangle if the users clicks inside the window, or the outer rectangle otherwise m_rubber_band_rect = (m_select_window_inner_rect.contains(mouse_physical))? m_select_window_inner_rect : m_select_window_outer_rect; UpdateRubberBand(); } } } else { m_rubber_band_rect = QRect(mouse_physical, mouse_physical); UpdateRubberBand(); } } } else { StopGrabbing(); } event->accept(); return; } event->ignore(); } void PageInput::mouseReleaseEvent(QMouseEvent* event) { if(m_grabbing) { if(event->button() == Qt::LeftButton) { if(m_rubber_band != NULL) { SetVideoAreaFromRubberBand(); } } StopGrabbing(); event->accept(); return; } event->ignore(); } void PageInput::mouseMoveEvent(QMouseEvent* event) { if(m_grabbing) { if(m_rubber_band != NULL && IsPlatformX11()) { QPoint mouse_physical = GetMousePhysicalCoordinates(); if(m_selecting_window) { // pick the inner rectangle if the user clicks inside the window, or the outer rectangle otherwise m_rubber_band_rect = (m_select_window_inner_rect.contains(mouse_physical))? m_select_window_inner_rect : m_select_window_outer_rect; } else { m_rubber_band_rect.setBottomRight(mouse_physical); } UpdateRubberBand(); } event->accept(); return; } event->ignore(); } void PageInput::keyPressEvent(QKeyEvent* event) { if(m_grabbing) { if(event->key() == Qt::Key_Escape) { StopGrabbing(); return; } event->accept(); return; } event->ignore(); } void PageInput::StartGrabbing() { // Grab the mouse and keyboard, and hide the window. Grabbing the keyboard isn't required, but if we don't grab it the user could still alt-tab // to other windows, switch workspaces, ... which would be very confusing. Grabbing doesn't work if the window is actually hidden or minimized, // so instead I just lower the window below all other windows (this is probably less confusing too). Grabbing stops as soon as the escape key // or any mouse button is pressed (or released in the case of the left mouse button). m_grabbing = true; if(m_selecting_window) m_pushbutton_video_x11_select_window->setDown(true); else m_pushbutton_video_x11_select_rectangle->setDown(true); m_main_window->lower(); grabMouse(Qt::CrossCursor); grabKeyboard(); setMouseTracking(true); } void PageInput::StopGrabbing() { m_rubber_band.reset(); setMouseTracking(false); releaseKeyboard(); releaseMouse(); m_main_window->raise(); m_main_window->activateWindow(); if(m_selecting_window) m_pushbutton_video_x11_select_window->setDown(false); else m_pushbutton_video_x11_select_rectangle->setDown(false); m_grabbing = false; } void PageInput::UpdateRubberBand() { if(m_rubber_band == NULL) m_rubber_band.reset(new RecordingFrameWindow(this, false)); m_rubber_band->SetRectangle(m_rubber_band_rect); } void PageInput::SetVideoAreaFromRubberBand() { QRect r = m_rubber_band_rect.normalized(); if(GetVideoX11Area() == VIDEO_X11_AREA_CURSOR) { SetVideoX11X(0); SetVideoX11Y(0); } else { SetVideoX11X(r.x()); SetVideoX11Y(r.y()); } SetVideoX11Width(r.width()); SetVideoX11Height(r.height()); } void PageInput::LoadScreenConfigurations() { std::vector screen_geometries = GetScreenGeometries(); QRect combined_geometry = CombineScreenGeometries(screen_geometries); m_combobox_x11_screens->clear(); m_combobox_x11_screens->addItem(tr("All screens: %1x%2", "This appears in the screen selection combobox") .arg(combined_geometry.width()).arg(combined_geometry.height())); for(size_t i = 0; i < screen_geometries.size(); ++i) { QRect &geometry = screen_geometries[i]; m_combobox_x11_screens->addItem(tr("Screen %1: %2x%3 at %4,%5", "This appears in the screen selection combobox") .arg(i + 1).arg(geometry.width()).arg(geometry.height()).arg(geometry.x()).arg(geometry.y())); } // update the video x/y/w/h in case the position or size of the selected screen changed OnUpdateVideoAreaFields(); } #if SSR_USE_ALSA void PageInput::LoadALSASources() { m_alsa_sources = ALSAInput::GetSourceList(); if(m_alsa_sources.empty()) { m_alsa_sources.push_back(ALSAInput::Source("", "(no sources found)")); } m_combobox_alsa_source->clear(); for(unsigned int i = 0; i < m_alsa_sources.size(); ++i) { QString elided = m_combobox_alsa_source->fontMetrics().elidedText("\u200e[" + QString::fromStdString(m_alsa_sources[i].m_name) + "] " + QString::fromStdString(m_alsa_sources[i].m_description), Qt::ElideMiddle, 400) + "\u200e"; m_combobox_alsa_source->addItem(elided); } } #endif #if SSR_USE_PULSEAUDIO void PageInput::LoadPulseAudioSources() { m_pulseaudio_sources = PulseAudioInput::GetSourceList(); if(m_pulseaudio_sources.empty()) { m_pulseaudio_available = false; m_pulseaudio_sources.push_back(PulseAudioInput::Source("", "(no sources found)")); } else { m_pulseaudio_available = true; } m_combobox_pulseaudio_source->clear(); for(unsigned int i = 0; i < m_pulseaudio_sources.size(); ++i) { QString elided = m_combobox_pulseaudio_source->fontMetrics().elidedText(QString::fromStdString(m_pulseaudio_sources[i].m_description), Qt::ElideMiddle, 400); m_combobox_pulseaudio_source->addItem("\u200e" + elided + "\u200e"); } } #endif void PageInput::OnUpdateRecordingFrame() { if(GetVideoBackend() == VIDEO_BACKEND_X11 && (m_spinbox_video_x11_x->hasFocus() || m_spinbox_video_x11_y->hasFocus() || m_spinbox_video_x11_width->hasFocus() || m_spinbox_video_x11_height->hasFocus())) { if(m_recording_frame == NULL) m_recording_frame.reset(new RecordingFrameWindow(this, false)); m_recording_frame->SetRectangle(QRect(GetVideoX11X(), GetVideoX11Y(), GetVideoX11Width(), GetVideoX11Height())); } else { m_recording_frame.reset(); } } void PageInput::OnUpdateVideoAreaFields() { enum_video_backend backend = GetVideoBackend(); MultiGroupVisible({ {{ m_radio_area_screen, m_radio_area_fixed, m_radio_area_cursor, m_combobox_x11_screens, m_checkbox_video_x11_follow_fullscreen, m_pushbutton_video_x11_select_rectangle, m_pushbutton_video_x11_select_window, m_label_video_x11_x, m_label_video_x11_y, m_label_video_x11_width, m_label_video_x11_height, m_spinbox_video_x11_x, m_spinbox_video_x11_y, m_spinbox_video_x11_width, m_spinbox_video_x11_height, }, (backend == VIDEO_BACKEND_X11)}, #if SSR_USE_OPENGL_RECORDING {{ m_pushbutton_video_opengl_settings, }, (backend == VIDEO_BACKEND_GLINJECT)}, #endif #if SSR_USE_V4L2 {{ m_label_video_v4l2_device, m_lineedit_video_v4l2_device, m_label_video_v4l2_width, m_label_video_v4l2_height, m_spinbox_video_v4l2_width, m_spinbox_video_v4l2_height, }, (backend == VIDEO_BACKEND_V4L2)}, #endif #if SSR_USE_PIPEWIRE {{ m_label_video_pipewire_source, m_lineedit_video_pipewire_source, m_label_video_pipewire_width, m_label_video_pipewire_height, m_spinbox_video_pipewire_width, m_spinbox_video_pipewire_height, }, (backend == VIDEO_BACKEND_PIPEWIRE)}, #endif }); #if SSR_USE_OPENGL_RECORDING GroupEnabled({m_checkbox_record_cursor}, (backend == VIDEO_BACKEND_X11 || backend == VIDEO_BACKEND_GLINJECT)); #else GroupEnabled({m_checkbox_record_cursor}, (backend == VIDEO_BACKEND_X11)); #endif if(GetVideoBackend() == VIDEO_BACKEND_X11) { switch(GetVideoX11Area()) { case VIDEO_X11_AREA_SCREEN: { m_combobox_x11_screens->setEnabled(true); m_checkbox_video_x11_follow_fullscreen->setEnabled(false); m_pushbutton_video_x11_select_rectangle->setEnabled(false); m_pushbutton_video_x11_select_window->setEnabled(false); GroupEnabled({m_label_video_x11_x, m_spinbox_video_x11_x, m_label_video_x11_y, m_spinbox_video_x11_y, m_label_video_x11_width, m_spinbox_video_x11_width, m_label_video_x11_height, m_spinbox_video_x11_height}, false); int sc = m_combobox_x11_screens->currentIndex(); std::vector screen_geometries = GetScreenGeometries(); QRect rect; if(sc > 0 && sc <= (int) screen_geometries.size()) { rect = screen_geometries[sc - 1]; } else { rect = CombineScreenGeometries(screen_geometries); } SetVideoX11X(rect.left()); SetVideoX11Y(rect.top()); SetVideoX11Width(rect.width()); SetVideoX11Height(rect.height()); break; } case VIDEO_X11_AREA_FIXED: { m_combobox_x11_screens->setEnabled(false); m_checkbox_video_x11_follow_fullscreen->setEnabled(false); m_pushbutton_video_x11_select_rectangle->setEnabled(true); m_pushbutton_video_x11_select_window->setEnabled(true); GroupEnabled({m_label_video_x11_x, m_spinbox_video_x11_x, m_label_video_x11_y, m_spinbox_video_x11_y, m_label_video_x11_width, m_spinbox_video_x11_width, m_label_video_x11_height, m_spinbox_video_x11_height}, true); break; } case VIDEO_X11_AREA_CURSOR: { m_combobox_x11_screens->setEnabled(false); m_checkbox_video_x11_follow_fullscreen->setEnabled(true); if(m_checkbox_video_x11_follow_fullscreen->isChecked()) { m_pushbutton_video_x11_select_rectangle->setEnabled(false); m_pushbutton_video_x11_select_window->setEnabled(false); GroupEnabled({m_label_video_x11_x, m_spinbox_video_x11_x, m_label_video_x11_y, m_spinbox_video_x11_y, m_label_video_x11_width, m_spinbox_video_x11_width, m_label_video_x11_height, m_spinbox_video_x11_height}, false); std::vector screen_geometries = GetScreenGeometries(); QRect rect = (screen_geometries.size() == 0)? QRect(0, 0, 0, 0) : screen_geometries[0]; SetVideoX11X(rect.left()); SetVideoX11Y(rect.top()); SetVideoX11Width(rect.width()); SetVideoX11Height(rect.height()); } else { m_pushbutton_video_x11_select_rectangle->setEnabled(true); m_pushbutton_video_x11_select_window->setEnabled(true); GroupEnabled({m_label_video_x11_x, m_spinbox_video_x11_x, m_label_video_x11_y, m_spinbox_video_x11_y}, false); GroupEnabled({m_label_video_x11_width, m_spinbox_video_x11_width, m_label_video_x11_height, m_spinbox_video_x11_height}, true); SetVideoX11X(0); SetVideoX11Y(0); } break; } default: break; } } } void PageInput::OnUpdateVideoScaleFields() { bool enabled = GetVideoScalingEnabled(); GroupEnabled({m_label_video_scaled_width, m_spinbox_video_scaled_weight, m_label_video_scaled_height, m_spinbox_video_scaled_height}, enabled); } void PageInput::OnUpdateAudioFields() { bool enabled = GetAudioEnabled(); enum_audio_backend backend = GetAudioBackend(); GroupEnabled({ m_label_audio_backend, m_combobox_audio_backend, #if SSR_USE_ALSA m_label_alsa_source, m_combobox_alsa_source, m_pushbutton_alsa_refresh, #endif #if SSR_USE_PULSEAUDIO m_label_pulseaudio_source, m_combobox_pulseaudio_source, m_pushbutton_pulseaudio_refresh, #endif #if SSR_USE_JACK m_checkbox_jack_connect_system_capture, m_checkbox_jack_connect_system_playback, #endif }, enabled); MultiGroupVisible({ #if SSR_USE_ALSA {{m_label_alsa_source, m_combobox_alsa_source, m_pushbutton_alsa_refresh}, (backend == AUDIO_BACKEND_ALSA)}, #endif #if SSR_USE_PULSEAUDIO {{m_label_pulseaudio_source, m_combobox_pulseaudio_source, m_pushbutton_pulseaudio_refresh}, (backend == AUDIO_BACKEND_PULSEAUDIO)}, #endif #if SSR_USE_JACK {{m_checkbox_jack_connect_system_capture, m_checkbox_jack_connect_system_playback}, (backend == AUDIO_BACKEND_JACK)}, #endif }); } void PageInput::OnFocusChange(QWidget* old, QWidget* now) { Q_UNUSED(old); if(m_grabbing && now != NULL && now->window() != window()) { // workaround to avoid a deadlock situation when a modal dialog appears StopGrabbing(); } } #if QT_VERSION_MAJOR >= 5 void PageInput::OnScreenAdded(QScreen* screen) { connect(screen, SIGNAL(geometryChanged()), this, SLOT(OnUpdateScreenConfiguration())); connect(screen, SIGNAL(physicalDotsPerInchChanged()), this, SLOT(OnUpdateScreenConfiguration())); OnUpdateScreenConfiguration(); } #endif void PageInput::OnUpdateScreenConfiguration() { unsigned int selected_screen = GetVideoX11Screen(); LoadScreenConfigurations(); SetVideoX11Screen(selected_screen); } #if SSR_USE_ALSA void PageInput::OnUpdateALSASources() { QString selected_source = GetALSASourceName(); LoadALSASources(); SetALSASource(FindALSASource(selected_source)); } #endif #if SSR_USE_PULSEAUDIO void PageInput::OnUpdatePulseAudioSources() { QString selected_source = GetPulseAudioSourceName(); LoadPulseAudioSources(); SetPulseAudioSource(FindPulseAudioSource(selected_source)); } #endif void PageInput::OnIdentifyScreens() { OnStopIdentifyScreens(); std::vector screen_geometries = GetScreenGeometries(); for(size_t i = 0; i < screen_geometries.size(); ++i) { QRect &rect = screen_geometries[i]; ScreenLabelWindow *label = new ScreenLabelWindow(this, tr("Screen %1", "This appears in the screen labels").arg(i + 1)); label->move(rect.x(), rect.y()); label->show(); m_screen_labels.push_back(label); } } void PageInput::OnStopIdentifyScreens() { for(unsigned int i = 0; i < m_screen_labels.size(); ++i) { delete m_screen_labels[i]; } m_screen_labels.clear(); } void PageInput::OnStartSelectRectangle() { m_selecting_window = false; StartGrabbing(); } void PageInput::OnStartSelectWindow() { m_selecting_window = true; StartGrabbing(); } #if SSR_USE_OPENGL_RECORDING void PageInput::OnGLInjectDialog() { DialogGLInject dialog(this); dialog.exec(); } #endif void PageInput::OnContinue() { if(!Validate()) return; m_main_window->GoPageOutput(); } ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/PageInput.h000066400000000000000000000340751470053624300230230ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #pragma once #include "Global.h" #include "ProfileBox.h" #if SSR_USE_ALSA #include "ALSAInput.h" #endif #if SSR_USE_PULSEAUDIO #include "PulseAudioInput.h" #endif class QComboBoxWithSignal : public QComboBox { Q_OBJECT public: QComboBoxWithSignal(QWidget* parent); virtual void showPopup() override; virtual void hidePopup() override; signals: void popupShown(); void popupHidden(); }; class QSpinBoxWithSignal : public QSpinBox { Q_OBJECT public: QSpinBoxWithSignal(QWidget* parent); virtual void focusInEvent(QFocusEvent* event) override; virtual void focusOutEvent(QFocusEvent* event) override; signals: void focusIn(); void focusOut(); }; class ScreenLabelWindow : public QWidget { Q_OBJECT private: QString m_text; QFont m_font; public: ScreenLabelWindow(QWidget* parent, const QString& text); protected: virtual void paintEvent(QPaintEvent* event) override; }; class RecordingFrameWindow : public QWidget { Q_OBJECT private: bool m_outside; QPixmap m_texture; public: static constexpr int BORDER_WIDTH = 4; public: RecordingFrameWindow(QWidget* parent, bool outside); void SetRectangle(const QRect& r); private: void UpdateMask(); protected: virtual void resizeEvent(QResizeEvent* event) override; virtual void paintEvent(QPaintEvent* event) override; }; class MainWindow; class PageInput : public QWidget { Q_OBJECT public: enum enum_video_backend { VIDEO_BACKEND_X11, #if SSR_USE_OPENGL_RECORDING VIDEO_BACKEND_GLINJECT, #endif #if SSR_USE_V4L2 VIDEO_BACKEND_V4L2, #endif #if SSR_USE_PIPEWIRE VIDEO_BACKEND_PIPEWIRE, #endif VIDEO_BACKEND_COUNT // must be last }; enum enum_video_x11_area { VIDEO_X11_AREA_SCREEN, VIDEO_X11_AREA_FIXED, VIDEO_X11_AREA_CURSOR, VIDEO_X11_AREA_COUNT // must be last }; enum enum_audio_backend { #if SSR_USE_ALSA AUDIO_BACKEND_ALSA, #endif #if SSR_USE_PULSEAUDIO AUDIO_BACKEND_PULSEAUDIO, #endif #if SSR_USE_JACK AUDIO_BACKEND_JACK, #endif AUDIO_BACKEND_COUNT // must be last }; private: MainWindow *m_main_window; bool m_grabbing, m_selecting_window; std::unique_ptr m_rubber_band, m_recording_frame; QRect m_rubber_band_rect, m_select_window_outer_rect, m_select_window_inner_rect; #if SSR_USE_ALSA std::vector m_alsa_sources; #endif #if SSR_USE_PULSEAUDIO bool m_pulseaudio_available; std::vector m_pulseaudio_sources; #endif #if SSR_USE_OPENGL_RECORDING QString m_glinject_channel; bool m_glinject_relax_permissions; QString m_glinject_command, m_glinject_working_directory; bool m_glinject_auto_launch; bool m_glinject_limit_fps; #endif std::vector m_screen_labels; ProfileBox *m_profile_box; QComboBox *m_combobox_video_backend; QButtonGroup *m_buttongroup_video_x11_area; QRadioButton *m_radio_area_screen, *m_radio_area_fixed, *m_radio_area_cursor; QComboBoxWithSignal *m_combobox_x11_screens; QCheckBox *m_checkbox_video_x11_follow_fullscreen; QPushButton *m_pushbutton_video_x11_select_rectangle, *m_pushbutton_video_x11_select_window; QLabel *m_label_video_x11_x, *m_label_video_x11_y, *m_label_video_x11_width, *m_label_video_x11_height; QSpinBoxWithSignal *m_spinbox_video_x11_x, *m_spinbox_video_x11_y, *m_spinbox_video_x11_width, *m_spinbox_video_x11_height; #if SSR_USE_OPENGL_RECORDING QPushButton *m_pushbutton_video_opengl_settings; #endif #if SSR_USE_V4L2 QLabel *m_label_video_v4l2_device; QLineEdit *m_lineedit_video_v4l2_device; QLabel *m_label_video_v4l2_width, *m_label_video_v4l2_height; QSpinBoxWithSignal *m_spinbox_video_v4l2_width, *m_spinbox_video_v4l2_height; #endif #if SSR_USE_PIPEWIRE QLabel *m_label_video_pipewire_source; QLineEdit *m_lineedit_video_pipewire_source; QLabel *m_label_video_pipewire_width, *m_label_video_pipewire_height; QSpinBoxWithSignal *m_spinbox_video_pipewire_width, *m_spinbox_video_pipewire_height; #endif QSpinBox *m_spinbox_video_frame_rate; QCheckBox *m_checkbox_scale; QLabel *m_label_video_scaled_width, *m_label_video_scaled_height; QSpinBox *m_spinbox_video_scaled_weight, *m_spinbox_video_scaled_height; QCheckBox *m_checkbox_record_cursor; QCheckBox *m_checkbox_audio_enable; QLabel *m_label_audio_backend; QComboBox *m_combobox_audio_backend; #if SSR_USE_ALSA QLabel *m_label_alsa_source; QComboBox *m_combobox_alsa_source; QPushButton *m_pushbutton_alsa_refresh; #endif #if SSR_USE_PULSEAUDIO QLabel *m_label_pulseaudio_source; QComboBox *m_combobox_pulseaudio_source; QPushButton *m_pushbutton_pulseaudio_refresh; #endif #if SSR_USE_JACK QCheckBox *m_checkbox_jack_connect_system_capture, *m_checkbox_jack_connect_system_playback; #endif public: PageInput(MainWindow* main_window); void LoadSettings(QSettings* settings); void SaveSettings(QSettings* settings); private: static void LoadProfileSettingsCallback(QSettings* settings, void* userdata); static void SaveProfileSettingsCallback(QSettings* settings, void* userdata); void LoadProfileSettings(QSettings* settings); void SaveProfileSettings(QSettings* settings); public: bool Validate(); #if SSR_USE_ALSA QString GetALSASourceName(); #endif #if SSR_USE_PULSEAUDIO QString GetPulseAudioSourceName(); #endif private: #if SSR_USE_ALSA unsigned int FindALSASource(const QString& name); #endif #if SSR_USE_PULSEAUDIO unsigned int FindPulseAudioSource(const QString& name); #endif protected: virtual void mousePressEvent(QMouseEvent* event) override; virtual void mouseReleaseEvent(QMouseEvent* event) override; virtual void mouseMoveEvent(QMouseEvent* event) override; virtual void keyPressEvent(QKeyEvent* event) override; private: void StartGrabbing(); void StopGrabbing(); void UpdateRubberBand(); void SetVideoAreaFromRubberBand(); void LoadScreenConfigurations(); #if SSR_USE_ALSA void LoadALSASources(); #endif #if SSR_USE_PULSEAUDIO void LoadPulseAudioSources(); #endif public slots: void OnUpdateRecordingFrame(); void OnUpdateVideoAreaFields(); void OnUpdateVideoScaleFields(); void OnUpdateAudioFields(); private slots: void OnFocusChange(QWidget* old, QWidget* now); #if QT_VERSION_MAJOR >= 5 void OnScreenAdded(QScreen* screen); #endif void OnUpdateScreenConfiguration(); #if SSR_USE_ALSA void OnUpdateALSASources(); #endif #if SSR_USE_PULSEAUDIO void OnUpdatePulseAudioSources(); #endif void OnIdentifyScreens(); void OnStopIdentifyScreens(); void OnStartSelectRectangle(); void OnStartSelectWindow(); #if SSR_USE_OPENGL_RECORDING void OnGLInjectDialog(); #endif void OnContinue(); public: inline unsigned int GetProfile() { return m_profile_box->GetProfile(); } inline enum_video_backend GetVideoBackend() { return (enum_video_backend) clamp(m_combobox_video_backend->currentIndex(), 0, VIDEO_BACKEND_COUNT - 1); } inline enum_video_x11_area GetVideoX11Area() { return (enum_video_x11_area) clamp(m_buttongroup_video_x11_area->checkedId(), 0, VIDEO_X11_AREA_COUNT - 1); } inline unsigned int GetVideoX11Screen() { return m_combobox_x11_screens->currentIndex(); } inline bool GetVideoX11FollowFullscreen() { return m_checkbox_video_x11_follow_fullscreen->isChecked(); } inline unsigned int GetVideoX11X() { return m_spinbox_video_x11_x->value(); } inline unsigned int GetVideoX11Y() { return m_spinbox_video_x11_y->value(); } inline unsigned int GetVideoX11Width() { return m_spinbox_video_x11_width->value(); } inline unsigned int GetVideoX11Height() { return m_spinbox_video_x11_height->value(); } #if SSR_USE_V4L2 inline QString GetVideoV4L2Device() { return m_lineedit_video_v4l2_device->text(); } inline unsigned int GetVideoV4L2Width() { return m_spinbox_video_v4l2_width->value(); } inline unsigned int GetVideoV4L2Height() { return m_spinbox_video_v4l2_height->value(); } #endif #if SSR_USE_V4L2 inline QString GetVideoPipeWireSource() { return m_lineedit_video_pipewire_source->text(); } inline unsigned int GetVideoPipeWireWidth() { return m_spinbox_video_pipewire_width->value(); } inline unsigned int GetVideoPipeWireHeight() { return m_spinbox_video_pipewire_height->value(); } #endif inline unsigned int GetVideoFrameRate() { return m_spinbox_video_frame_rate->value(); } inline bool GetVideoScalingEnabled() { return m_checkbox_scale->isChecked(); } inline unsigned int GetVideoScaledWeight() { return m_spinbox_video_scaled_weight->value(); } inline unsigned int GetVideoScaledHeight() { return m_spinbox_video_scaled_height->value(); } inline bool GetVideoRecordCursor() { return m_checkbox_record_cursor->isChecked(); } inline bool GetAudioEnabled() { return m_checkbox_audio_enable->isChecked(); } inline enum_audio_backend GetAudioBackend() { return (enum_audio_backend) clamp(m_combobox_audio_backend->currentIndex(), 0, AUDIO_BACKEND_COUNT - 1); } #if SSR_USE_ALSA inline unsigned int GetALSASource() { return clamp(m_combobox_alsa_source->currentIndex(), 0, (int) m_alsa_sources.size() - 1); } #endif #if SSR_USE_PULSEAUDIO inline unsigned int GetPulseAudioSource() { return clamp(m_combobox_pulseaudio_source->currentIndex(), 0, (int) m_pulseaudio_sources.size() - 1); } #endif #if SSR_USE_JACK inline bool GetJackConnectSystemCapture() { return m_checkbox_jack_connect_system_capture->isChecked(); } inline bool GetJackConnectSystemPlayback() { return m_checkbox_jack_connect_system_playback->isChecked(); } #endif #if SSR_USE_OPENGL_RECORDING inline QString GetGLInjectChannel() { return m_glinject_channel; } inline bool GetGLInjectRelaxPermissions() { return m_glinject_relax_permissions; } inline QString GetGLInjectCommand() { return m_glinject_command; } inline QString GetGLInjectWorkingDirectory() { return m_glinject_working_directory; } inline bool GetGLInjectAutoLaunch() { return m_glinject_auto_launch; } inline bool GetGLInjectLimitFPS() { return m_glinject_limit_fps; } #endif inline void SetProfile(unsigned int profile) { m_profile_box->SetProfile(profile); } inline void SetVideoBackend(enum_video_backend backend) { m_combobox_video_backend->setCurrentIndex(clamp((int) backend, 0, VIDEO_BACKEND_COUNT - 1)); } inline void SetVideoX11Area(enum_video_x11_area area) { QAbstractButton *b = m_buttongroup_video_x11_area->button(area); if(b != NULL) b->setChecked(true); } inline void SetVideoX11Screen(unsigned int screen) { m_combobox_x11_screens->setCurrentIndex(clamp(screen, 0u, (unsigned int) m_combobox_x11_screens->count() - 1)); } inline void SetVideoX11FollowFullscreen(bool follow_fulscreen) { m_checkbox_video_x11_follow_fullscreen->setChecked(follow_fulscreen); } inline void SetVideoX11X(unsigned int x) { m_spinbox_video_x11_x->setValue(x); } inline void SetVideoX11Y(unsigned int y) { m_spinbox_video_x11_y->setValue(y); } inline void SetVideoX11Width(unsigned int width) { m_spinbox_video_x11_width->setValue(width); } inline void SetVideoX11Height(unsigned int height) { m_spinbox_video_x11_height->setValue(height); } #if SSR_USE_V4L2 inline void SetVideoV4L2Device(const QString& device) { m_lineedit_video_v4l2_device->setText(device); } inline void SetVideoV4L2Width(unsigned int width) { m_spinbox_video_v4l2_width->setValue(width); } inline void SetVideoV4L2Height(unsigned int height) { m_spinbox_video_v4l2_height->setValue(height); } #endif #if SSR_USE_PIPEWIRE inline void SetVideoPipeWireSource(const QString& source) { m_lineedit_video_pipewire_source->setText(source); } inline void SetVideoPipeWireWidth(unsigned int width) { m_spinbox_video_pipewire_width->setValue(width); } inline void SetVideoPipeWireHeight(unsigned int height) { m_spinbox_video_pipewire_height->setValue(height); } #endif inline void SetVideoFrameRate(unsigned int frame_rate) { m_spinbox_video_frame_rate->setValue(frame_rate); } inline void SetVideoScalingEnabled(bool enable) { m_checkbox_scale->setChecked(enable); } inline void SetVideoScaledWidth(unsigned int scaled_w) { m_spinbox_video_scaled_weight->setValue(scaled_w); } inline void SetVideoScaledHeight(unsigned int scaled_h) { m_spinbox_video_scaled_height->setValue(scaled_h); } inline void SetVideoRecordCursor(bool show) { m_checkbox_record_cursor->setChecked(show); } inline void SetAudioEnabled(bool enable) { m_checkbox_audio_enable->setChecked(enable); } inline void SetAudioBackend(enum_audio_backend backend) { m_combobox_audio_backend->setCurrentIndex(clamp((int) backend, 0, AUDIO_BACKEND_COUNT - 1)); } #if SSR_USE_ALSA inline void SetALSASource(unsigned int source) { m_combobox_alsa_source->setCurrentIndex(clamp(source, 0u, (unsigned int) m_alsa_sources.size() - 1)); } #endif #if SSR_USE_PULSEAUDIO inline void SetPulseAudioSource(unsigned int source) { m_combobox_pulseaudio_source->setCurrentIndex(clamp(source, 0u, (unsigned int) m_pulseaudio_sources.size() - 1)); } #endif #if SSR_USE_JACK inline void SetJackConnectSystemCapture(bool connect) { m_checkbox_jack_connect_system_capture->setChecked(connect); } inline void SetJackConnectSystemPlayback(bool connect) { m_checkbox_jack_connect_system_playback->setChecked(connect); } #endif #if SSR_USE_OPENGL_RECORDING inline void SetGLInjectChannel(const QString& channel) { m_glinject_channel = channel; } inline void SetGLInjectRelaxPermissions(bool relax_permissions) { m_glinject_relax_permissions = relax_permissions; } inline void SetGLInjectCommand(const QString& command) { m_glinject_command = command; } inline void SetGLInjectWorkingDirectory(const QString& glinject_working_directory) { m_glinject_working_directory = glinject_working_directory; } inline void SetGLInjectAutoLaunch(bool auto_launch) { m_glinject_auto_launch = auto_launch; } inline void SetGLInjectLimitFPS(bool limit_fps) { m_glinject_limit_fps = limit_fps; } #endif }; ssr-d5310677bc41f6be95f7885d9d5f7ba6dcf4ec89/src/GUI/PageOutput.cpp000066400000000000000000001012311470053624300235440ustar00rootroot00000000000000/* Copyright (c) 2012-2020 Maarten Baert This file is part of SimpleScreenRecorder. SimpleScreenRecorder is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SimpleScreenRecorder is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SimpleScreenRecorder. If not, see . */ #include "PageOutput.h" #include "Dialogs.h" #include "EnumStrings.h" #include "HiddenScrollArea.h" #include "Icons.h" #include "Logger.h" #include "MainWindow.h" #include "PageInput.h" #include "AVWrapper.h" #include "VideoEncoder.h" #include "AudioEncoder.h" ENUMSTRINGS(PageOutput::enum_container) = { {PageOutput::CONTAINER_MKV, "mkv"}, {PageOutput::CONTAINER_MP4, "mp4"}, {PageOutput::CONTAINER_WEBM, "webm"}, {PageOutput::CONTAINER_OGG, "ogg"}, {PageOutput::CONTAINER_OTHER, "other"}, }; ENUMSTRINGS(PageOutput::enum_video_codec) = { {PageOutput::VIDEO_CODEC_H264, "h264"}, {PageOutput::VIDEO_CODEC_VP8, "vp8"}, {PageOutput::VIDEO_CODEC_THEORA, "theora"}, {PageOutput::VIDEO_CODEC_OTHER, "other"}, }; ENUMSTRINGS(PageOutput::enum_audio_codec) = { {PageOutput::AUDIO_CODEC_VORBIS, "vorbis"}, {PageOutput::AUDIO_CODEC_MP3, "mp3"}, {PageOutput::AUDIO_CODEC_AAC, "aac"}, {PageOutput::AUDIO_CODEC_UNCOMPRESSED, "uncompressed"}, {PageOutput::AUDIO_CODEC_OTHER, "other"}, }; ENUMSTRINGS(PageOutput::enum_h264_preset) = { {PageOutput::H264_PRESET_ULTRAFAST, "ultrafast"}, {PageOutput::H264_PRESET_SUPERFAST, "superfast"}, {PageOutput::H264_PRESET_VERYFAST, "veryfast"}, {PageOutput::H264_PRESET_FASTER, "faster"}, {PageOutput::H264_PRESET_FAST, "fast"}, {PageOutput::H264_PRESET_MEDIUM, "medium"}, {PageOutput::H264_PRESET_SLOW, "slow"}, {PageOutput::H264_PRESET_SLOWER, "slower"}, {PageOutput::H264_PRESET_VERYSLOW, "veryslow"}, {PageOutput::H264_PRESET_PLACEBO, "placebo"}, }; static bool MatchSuffix(const QString& suffix, const QStringList& suffixes) { return ((suffix.isEmpty() && suffixes.isEmpty()) || suffixes.contains(suffix, Qt::CaseInsensitive)); } PageOutput::PageOutput(MainWindow* main_window) : QWidget(main_window->centralWidget()) { m_main_window = main_window; m_old_container = (enum_container) 0; m_old_container_av = 0; // main codecs // (initializer lists should use explicit types for Clang) m_containers = { ContainerData({"Matroska (MKV)", "matroska", QStringList({"mkv"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("Matroska") + " (*.mkv)", {VIDEO_CODEC_H264, VIDEO_CODEC_VP8, VIDEO_CODEC_THEORA}, {AUDIO_CODEC_VORBIS, AUDIO_CODEC_MP3, AUDIO_CODEC_AAC, AUDIO_CODEC_UNCOMPRESSED}}), ContainerData({"MP4", "mp4", QStringList({"mp4"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("MP4") + " (*.mp4)", {VIDEO_CODEC_H264}, {AUDIO_CODEC_VORBIS, AUDIO_CODEC_MP3, AUDIO_CODEC_AAC}}), ContainerData({"WebM", "webm", QStringList({"webm"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("WebM") + " (*.webm)", {VIDEO_CODEC_VP8}, {AUDIO_CODEC_VORBIS}}), ContainerData({"OGG", "ogg", QStringList({"ogg"}), tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg("OGG") + " (*.ogg)", {VIDEO_CODEC_THEORA}, {AUDIO_CODEC_VORBIS}}), ContainerData({tr("Other..."), "other", QStringList(), "", std::set({}), std::set({})}), }; m_video_codecs = { {"H.264" , "libx264" }, {"VP8" , "libvpx" }, {"Theora" , "libtheora"}, {tr("Other..."), "other" }, }; m_audio_codecs = { {"Vorbis" , "libvorbis" }, {"MP3" , "libmp3lame" }, {"AAC" , "libvo_aacenc"}, {tr("Uncompressed"), "pcm_s16le" }, {tr("Other...") , "other" }, }; // alternative aac codec if(!AVCodecIsInstalled(m_audio_codecs[AUDIO_CODEC_AAC].avname)) { m_audio_codecs[AUDIO_CODEC_AAC].avname = "aac"; } // load AV container list m_containers_av.clear(); #if SSR_USE_AV_MUXER_ITERATE const AVOutputFormat *format; void *format_opaque = NULL; while((format = av_muxer_iterate(&format_opaque)) != NULL) { #else for(AVOutputFormat *format = av_oformat_next(NULL); format != NULL; format = av_oformat_next(format)) { #endif if(format->video_codec == AV_CODEC_ID_NONE) continue; ContainerData c; c.name = format->long_name; c.avname = format->name; c.suffixes = SplitSkipEmptyParts(format->extensions, ','); if(c.suffixes.isEmpty()) { c.filter = ""; } else { c.filter = tr("%1 files", "This appears in the file dialog, e.g. 'MP4 files'").arg(c.avname) + " (*." + c.suffixes[0]; for(int i = 1; i < c.suffixes.size(); ++i) { c.suffixes[i] = c.suffixes[i].trimmed(); // needed because libav/ffmpeg isn't very consistent when they say 'comma-separated' c.filter += " *." + c.suffixes[i]; } c.filter += ")"; } m_containers_av.push_back(c); } std::sort(m_containers_av.begin(), m_containers_av.end()); // load AV codec list m_video_codecs_av.clear(); m_audio_codecs_av.clear(); #if SSR_USE_AV_MUXER_ITERATE const AVCodec *codec; void *codec_opaque = NULL; while((codec = av_codec_iterate(&codec_opaque)) != NULL) { #else for(AVCodec *codec = av_codec_next(NULL); codec != NULL; codec = av_codec_next(codec)) { #endif if(!av_codec_is_encoder(codec)) continue; if(codec->type == AVMEDIA_TYPE_VIDEO && VideoEncoder::AVCodecIsSupported(codec->name)) { VideoCodecData c; c.name = codec->long_name; c.avname = codec->name; m_video_codecs_av.push_back(c); } if(codec->type == AVMEDIA_TYPE_AUDIO && AudioEncoder::AVCodecIsSupported(codec->name)) { AudioCodecData c; c.name = codec->long_name; c.avname = codec->name; m_audio_codecs_av.push_back(c); } } std::sort(m_video_codecs_av.begin(), m_video_codecs_av.end()); std::sort(m_audio_codecs_av.begin(), m_audio_codecs_av.end()); if(m_containers_av.empty()) { Logger::LogError("[PageOutput::PageOutput] " + tr("Error: Could not find any suitable container in libavformat!")); throw LibavException(); } if(m_video_codecs_av.empty()) { Logger::LogError("[PageOutput::PageOutput] " + tr("Error: Could not find any suitable video codec in libavcodec!")); throw LibavException(); } if(m_audio_codecs_av.empty()) { Logger::LogError("[PageOutput::PageOutput] " + tr("Error: Could not find any suitable audio codec in libavcodec!")); throw LibavException(); } HiddenScrollArea *scrollarea = new HiddenScrollArea(this); QWidget *scrollarea_contents = new QWidget(scrollarea); scrollarea->setWidget(scrollarea_contents); { m_profile_box = new ProfileBox(tr("Output profile"), scrollarea_contents, "output-profiles", &LoadProfileSettingsCallback, &SaveProfileSettingsCallback, this); QGroupBox *groupbox_file = new QGroupBox(tr("File"), scrollarea_contents); { QLabel *label_file = new QLabel(tr("Save as:"), groupbox_file); m_lineedit_file = new QLineEdit(groupbox_file); m_lineedit_file->setToolTip(tr("The recording will be saved to this location.")); QPushButton *button_browse = new QPushButton(tr("Browse..."), groupbox_file); m_checkbox_separate_files = new QCheckBox(tr("Separate file per segment"), groupbox_file); m_checkbox_separate_files->setToolTip(tr("If checked, a separate video file will be created every time you pause and resume the recording." "If unchecked, all recorded segments will be combined into a single video file.")); m_checkbox_add_timestamp = new QCheckBox(tr("Add timestamp"), groupbox_file); m_checkbox_add_timestamp->setToolTip(tr("If checked, the current date and time will be appended to the file name automatically.\n" "If the original file name is 'test.mkv', the video will be saved as 'test-YYYY-MM-DD_HH.MM.SS.mkv'.")); QLabel *label_container = new QLabel(tr("Container:"), groupbox_file); m_combobox_container = new QComboBox(groupbox_file); for(unsigned int i = 0; i < CONTAINER_COUNT; ++i) { QString name = "\u200e" + m_containers[i].name + "\u200e"; if(i != CONTAINER_OTHER && !AVFormatIsInstalled(m_containers[i].avname)) name += " \u200e" + tr("(not installed)") + "\u200e"; m_combobox_container->addItem(name); } m_combobox_container->setToolTip(tr("The container (file format) that will be used to save the recording.\n" "Note that not all codecs are supported by all containers, and that not all media players can read all file formats.\n" "- Matroska (MKV) supports all the codecs, but is less well-known.\n" "- MP4 is the most well-known format and will play on almost any modern media player, but supports only H.264 video\n" " (and many media players only support AAC audio).\n" "- WebM is intended for embedding video into websites (with the HTML5

If you want to record Steam games, read this first.

%MOREINFO%
https://www.maartenbaert.be/simplescreenrecorder/