wlmaker-0.8.1/0000755000175100017510000000000015222270710012566 5ustar runnerrunnerwlmaker-0.8.1/STYLE.md0000644000175100017510000000547715222270710014025 0ustar runnerrunner# CMake Style Guidelines This document outlines the style conventions used in the CMake configuration files for this project. ## 1. Command and Function Casing * **Lowercase commands:** All built-in CMake commands and functions are written in lowercase (e.g., `cmake_minimum_required`, `set`, `add_executable`, `target_link_libraries`, `install`). ## 2. Indentation, Control Flow, and Line Wrapping * **Indentation:** 2-space indentation is used for commands inside control blocks (like `if` / `endif`). * **Control Flow:** Closing commands (e.g., `endif()`, `endforeach()`, `endmacro()`, `endfunction()`) must not repeat the condition or arguments of the opening command (e.g., use `endif()` instead of `endif(config_DEBUG)`). * **Argument Wrapping:** * Short, simple commands are kept on a single line (e.g., `set(CMAKE_C_STANDARD 11)`). * Long commands or those with multiple arguments/keywords are wrapped, with each argument placed on a new line and indented by 2 spaces relative to the command (e.g., `add_executable`, `target_include_directories`). * The closing parenthesis `)` is either placed on the same line as the last argument or on its own line aligned with the command name (usually in block-style commands like `install` or `set_target_properties`). ## 3. Naming Conventions * **Local and private variables:** Written in uppercase in legacy project code (e.g., `PUBLIC_HEADER_FILES`, `SOURCES`, `DOXYGEN_IN`), but official guidance recommends using **lowercase** or **snake_case** (e.g., `public_header_files`, `sources`) to distinguish them from CMake's built-in variables. * **Options/Cache variables:** Formatted using either a lowercase/mixed-case prefix (e.g., `config_DEBUG`, `config_WERROR`) or full uppercase (e.g., `IWYU_MODE`). ## 4. Quoting * **Variable expansion and paths:** Double quotes are used for file paths, generator expressions, and strings containing variable expansion (e.g., `"${CMAKE_CURRENT_BINARY_DIR}/analyzer.c"`, `"${CURSES_INCLUDE_DIRS}"`). * **Identifiers and keywords:** Unquoted for target names, libraries, command keywords, and constant literals (e.g., `libbase`, `STATIC`, `PUBLIC`, `FATAL_ERROR`). ## 5. File Header and Comments * **Headers:** Every CMake file begins with a standardized Apache 2.0 license and copyright header. * **Comments:** Standard `#` comments are placed above the relevant block or line. Inline comments are generally lowercase-first or capitalized, providing short descriptions. ## 6. Target-Based Configuration * **Prefer target-based configuration:** Define compile options, compile definitions, include directories, and link libraries on specific targets using `target_*` commands (e.g., `target_include_directories`, `target_compile_options`, `target_compile_definitions`, `target_link_libraries`) rather than modifying global/directory-wide settings (e.g., `add_compile_options`). wlmaker-0.8.1/README.md0000644000175100017510000000731115222270710014047 0ustar runnerrunner# WaylandMaker - wlmaker A [Wayland](https://wayland.freedesktop.org/) compositor inspired by [Window Maker](https://www.windowmaker.org/). Key features: * Compositor for windows in stacking mode. * Supports multiple workspaces. * Appearance inspired by Window Maker, following the look and feel of NeXTSTEP. * Easy to use, lightweight, low gimmicks and fast. * Dock and clip, to be extended for dockable apps. How it looks, running in a window using the default theme: ![Screenshot of wlmaker running in a window](doc/wlmaker-default-screenshot.png) ### Current status **Early access**: Wayland Maker covers elementary compositor functionality on single-monitor output. Please report what's missing or broken! See [here](doc/FEATURES.md) for a detailed list of implemented or planned features, or the [roadmap](doc/ROADMAP.md) for what's planned for the upcoming versions. Highlights for current version ([0.8.1](https://github.com/phkaeser/wlmaker/releases/tag/v0.8.1)): * *new* Bugfixes ([#509](https://github.com/phkaeser/wlmaker/issues/509), [#508](https://github.com/phkaeser/wlmaker/issues/508), [#504](https://github.com/phkaeser/wlmaker/issues/504)) * Support to change Theme while running, shown in the root menu. * Window placement. * DockApp (`wlmbattery`) to show laptop battery and power status. * Improved Touchpad support: Configuration options, and right-click emulation. * Various bug fixes ([#449](https://github.com/phkaeser/wlmaker/issues/449), [#475](https://github.com/phkaeser/wlmaker/issues/475), [495](https://github.com/phkaeser/wlmaker/issues/495)). * Builds with [wlroots 0.18, 0.19 and 0.20](https://gitlab.freedesktop.org/wlroots/wlroots/-/tags). * Support for multiple outputs, configuable through `wlr-output-management-unstable-v1` protocol (eg. `wlr-randr`, `wdisplays`). * [Root menu](https://phkaeser.github.io/wlmaker/root_menu.html) can be generated from XDG repository ([#90](https://github.com/phkaeser/wlmaker/issues/90), eg. `wlmtool` or `wmmenugen`), and can be navigated by keyboard. * Screen saver support, through `ext-session-lock-v1` and `idle-inhibit-unstable-v1` protocols. * Hot corners with configurable actions, default to 'lock' or 'inhibit' locking. * Configurable through plist text files: [base configuration](etc/Config.plist), [style](share/Themes/Default.plist), [root menu](etc/RootMenu.plist) and [docks & workspaces](etc/State.plist). * wlr layer shell support (`wlr-layer-shell-unstable-v1`), fully implemented & tested. * Appearance matches Window Maker: Decorations, dock, clip. * Support for Wayland XDG shell (mostly complete. Bug reports welcome). * Initial support for X11 applications (positioning and specific modes are missing). Use `--start_xwayland` argument to enable XWayland, it's off by default. * DockApps (`apps/wlmclock`, `apps/wlmbattery`, `apps/wlmcpugraph`, ...). ### All the details? See http://phkaeser.github.io/wlmaker for: * How to configure wlmaker. * Startup options. * Supported Wayland protocols. ### Build & use it! * From source: Please follow the [detailed build instructions](doc/BUILD.md) for a step-by-step guide. * Once compiled, see the [these instructions](doc/RUN.md) on how to run Wayland Maker in a window or standalone, and to configure it for your needs. * Alternatively, use a pre-built package: [![Packaging status](https://repology.org/badge/vertical-allrepos/wlmaker.svg)](https://repology.org/project/wlmaker/versions) ## Contributing Contributions, help and bug reports are highly welcome! See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details, and [code of conduct](CODE_OF_CONDUCT.md) for more. ## License Apache 2.0; see [`LICENSE`](LICENSE) for details. wlmaker-0.8.1/third_party/0000755000175100017510000000000015222270710015117 5ustar runnerrunnerwlmaker-0.8.1/third_party/protocols/0000755000175100017510000000000015222270710017143 5ustar runnerrunnerwlmaker-0.8.1/third_party/protocols/wlr-layer-shell-unstable-v1.xml0000644000175100017510000004403615222270710025056 0ustar runnerrunner Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, 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. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. After creating a layer_surface object and setting it up, the client must perform an initial commit without any buffer attached. The compositor will reply with a layer_surface.configure event. The client must acknowledge it and is then allowed to attach a buffer to map the surface. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. This request indicates that the client will not use the layer_shell object any more. Objects that have been created through this instance are not affected. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Attaching a null buffer to a layer surface unmaps it. Unmapping a layer_surface means that the surface cannot be shown by the compositor until it is explicitly mapped again. The layer_surface returns to the state it had right after layer_shell.get_layer_surface. The client can re-map the surface by performing a commit without any buffer attached, waiting for a configure event and handling it as usual. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Types of keyboard interaction possible for layer shell surfaces. The rationale for this is twofold: (1) some applications are not interested in keyboard events and not allowing them to be focused can improve the desktop experience; (2) some applications will want to take exclusive keyboard focus. This value indicates that this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. This is the default value, set for newly created layer shell surfaces. This is useful for e.g. desktop widgets that display information or only have interaction with non-keyboard input devices. Request exclusive keyboard focus if this surface is above the shell surface layer. For the top and overlay layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to exclusive. If this layer contains multiple surfaces with keyboard interactivity set to exclusive, the compositor determines the one receiving keyboard events in an implementation- defined manner. In this case, no guarantee is made when this surface will receive keyboard focus (if ever). For the bottom and background layers, the compositor is allowed to use normal focus semantics. This setting is mainly intended for applications that need to ensure they receive all keyboard events, such as a lock screen or a password prompt. This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. The user should be able to unfocus this surface even regardless of the layer it is on. Typically, the compositor will want to use its normal mechanism to manage keyboard focus between layer shell surfaces with this setting and regular toplevels on the desktop layer (e.g. click to focus). Nevertheless, it is possible for a compositor to require a special interaction to focus or unfocus layer shell surfaces (e.g. requiring a click even if focus follows the mouse normally, or providing a keybinding to switch focus between layers). This setting is mainly intended for desktop shell components (e.g. panels) that allow keyboard interaction. Using this option can allow implementing a desktop shell that can be fully usable without the mouse. Set how keyboard events are delivered to this surface. By default, layer shell surfaces do not receive keyboard events; this request can be used to change this. This setting is inherited by child surfaces set by the get_popup request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Keyboard interactivity is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. wlmaker-0.8.1/third_party/protocols/CMakeLists.txt0000644000175100017510000000322115222270710021701 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) find_program( WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner REQUIRED) if(NOT WAYLAND_SCANNER_EXECUTABLE) message(FATAL_ERROR "'wayland-scanner' executable not found.") endif() pkg_get_variable(protocol_dir wayland-protocols pkgdatadir) add_custom_command( OUTPUT wlr-layer-shell-unstable-v1-protocol.h COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${CMAKE_CURRENT_SOURCE_DIR}/../protocols/wlr-layer-shell-unstable-v1.xml" "wlr-layer-shell-unstable-v1-protocol.h" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../protocols/wlr-layer-shell-unstable-v1.xml" VERBATIM) add_custom_command( OUTPUT xdg-shell-protocol.h COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${protocol_dir}/stable/xdg-shell/xdg-shell.xml" "xdg-shell-protocol.h" DEPENDS "${protocol_dir}/stable/xdg-shell/xdg-shell.xml" VERBATIM) add_library( protocol_headers OBJECT wlr-layer-shell-unstable-v1-protocol.h xdg-shell-protocol.h) set_target_properties( protocol_headers PROPERTIES LINKER_LANGUAGE C) wlmaker-0.8.1/build-cscope-index.sh0000755000175100017510000000103415222270710016601 0ustar runnerrunner#! /bin/sh # Generates Cscope index for wlmaker and all dependencies. set -o errexit SUBPATHS="\ dependencies \ include \ src \ submodules" base_path="$(readlink -f "$(dirname "${0}")")" rm -f "${base_path}/cscope.files" for p in ${SUBPATHS} ; do echo "Processing ${base_path}/${p} ..." find "${base_path}/${p}" -name "*.h" -o -name "*.c" -o -name "*.cpp" | xargs etags find "${base_path}/${p}" -name "*.h" -o -name "*.c" -o -name "*.cpp" >> cscope.files done cscope -Rbkq -i cscope.files 2>/dev/null echo "Done." exit 0 wlmaker-0.8.1/cmake/0000755000175100017510000000000015222270710013646 5ustar runnerrunnerwlmaker-0.8.1/cmake/embed_binary.h.in0000644000175100017510000000232515222270710017046 0ustar runnerrunner/* ========================================================================= */ /** * @file @output_header@ * Generated from "@binary_file@" * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __EMBEDDED_@prefix_uppercase@_H__ #define __EMBEDDED_@prefix_uppercase@_H__ #include #include /** Embedded content of "@binary_file@". */ extern const uint8_t embedded_binary_@prefix@_data[]; /** Size of "@binary_file@". */ extern const size_t embedded_binary_@prefix@_size; #endif // __EMBEDDED_@prefix_uppercase@_H__ /* == End of @output_header ================================================ */ wlmaker-0.8.1/cmake/EmbedBinary.cmake0000644000175100017510000000463515222270710017041 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) # During the INCLUDE() call, CMAKE_CURRENT_LIST_DIR is this file's path. # Store it for later, during the FUNCTION() call it'll be the caller's path. set(embed_binary_template_dir "${CMAKE_CURRENT_LIST_DIR}") # Adds a static C library to embed the binary file with the specified prefix. # # Generates a C source and header file from the contents of `binary_file`, and # adds both of these to a new library target `library_target`. # # The embedded data can then be accessed as external variables: # # #include ".h" # # * uint8_t embedded_binary__data[]; # * const size_t embedded_binary__size; # # Declared with lowercase name according to coding style. function(embedbinary_add_library library_target prefix binary_file) set(output_basename "${CMAKE_CURRENT_BINARY_DIR}/${prefix}") set(output_source "${output_basename}.c") set(output_header "${output_basename}.h") set(generated_cmake "${output_basename}.cmake") set(template_header "${embed_binary_template_dir}/embed_binary.h.in") set(template_source "${embed_binary_template_dir}/embed_binary.c.in") configure_file( "${embed_binary_template_dir}/embed.cmake.in" "${generated_cmake}" @ONLY) add_custom_command( OUTPUT "${output_source}" "${output_header}" COMMAND "${CMAKE_COMMAND}" -P "${generated_cmake}" MAIN_DEPENDENCY "${binary_file}" DEPENDS "${generated_cmake}" "${template_header}" "${template_source}" ) add_library(${library_target} STATIC) target_sources( ${library_target} PRIVATE "${output_source}" "${output_header}") target_include_directories( ${library_target} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") set_target_properties( ${library_target} PROPERTIES VERSION 1.0 PUBLIC_HEADER "${output_header}") endfunction() wlmaker-0.8.1/cmake/embed_binary.c.in0000644000175100017510000000205415222270710017040 0ustar runnerrunner/* ========================================================================= */ /** * @file @output_source@ * Generated from "@binary_file@" * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "@output_header@" const uint8_t embedded_binary_@prefix@_data[] = {@output_data@}; const size_t embedded_binary_@prefix@_size = sizeof( embedded_binary_@prefix@_data); /* == End of @output_source ================================================ */ wlmaker-0.8.1/cmake/WaylandProtocol.cmake0000644000175100017510000000704015222270710017772 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. find_program( WaylandScanner_EXECUTABLE NAMES wayland-scanner) mark_as_advanced(WaylandScanner_EXECUTABLE) find_package_handle_standard_args( WaylandScanner FOUND_VAR WaylandScanner_FOUND REQUIRED_VARS WaylandScanner_EXECUTABLE) # ----------------------------------------------------------------------------- # Builds a library for the protocol, and adds as dependency to target_var. function(waylandprotocol_add target_var) if(NOT WaylandScanner_EXECUTABLE) message(FATAL_ERROR "'wayland-scanner' executable required, not found.") endif() # Parse and verify arguments. set(one_value_args PROTOCOL_FILE BASE_NAME SIDE) cmake_parse_arguments(args "" "${one_value_args}" "" ${ARGN}) if(NOT "${args_SIDE}" STREQUAL "client" AND NOT "${args_SIDE}" STREQUAL "server") message(FATAL_ERROR "SIDE arg must be \"client\" or \"server\".") endif() if(args_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown args passed to _wayland_protocol_add: \"${args_UNPARSED_ARGUMENTS}\"") endif() get_filename_component(_protocol_file "${args_PROTOCOL_FILE}" ABSOLUTE) # Generate the client header. set(_header "${CMAKE_CURRENT_BINARY_DIR}/${args_BASE_NAME}-${args_SIDE}-protocol.h") set_source_files_properties("${_header}" GENERATED) add_custom_command( OUTPUT "${_header}" COMMAND "${WaylandScanner_EXECUTABLE}" "${args_SIDE}-header" "${_protocol_file}" "${_header}" DEPENDS "${WaylandScanner_EXECUTABLE}" "${_protocol_file}" VERBATIM) # Generate the glue code. set(_glue_code "${CMAKE_CURRENT_BINARY_DIR}/${args_BASE_NAME}-protocol.c") set_source_files_properties("${_glue_code}" GENERATED) add_custom_command( OUTPUT "${_glue_code}" COMMAND "${WaylandScanner_EXECUTABLE}" private-code "${_protocol_file}" "${_glue_code}" DEPENDS "${WaylandScanner_EXECUTABLE}" "${_protocol_file}" VERBATIM) set(lib_name "lib-${args_BASE_NAME}-${args_SIDE}") add_library("${lib_name}" STATIC) add_dependencies("${target_var}" "${lib_name}") target_sources("${lib_name}" PRIVATE "${_glue_code}" "${_header}") set_target_properties("${lib_name}" PROPERTIES VERSION 1.0 PUBLIC_HEADER "${_header}") # Add dependencies. if("${args_SIDE}" STREQUAL "client") pkg_check_modules(WAYLAND_CLIENT REQUIRED IMPORTED_TARGET wayland-client>=1.22.0) target_include_directories( "${lib_name}" PRIVATE "${WAYLAND_CLIENT_INCLUDE_DIRS}") else() pkg_check_modules(WAYLAND_SERVER REQUIRED IMPORTED_TARGET wayland-server>=1.22.0) target_include_directories( "${lib_name}" PRIVATE "${WAYLAND_SERVER_INCLUDE_DIRS}") endif() # The target may be an INTERFACE library. That needs INTERFACE linking. get_property(target_type TARGET "${target_var}" PROPERTY TYPE) if(target_type STREQUAL "INTERFACE_LIBRARY") target_link_libraries( "${target_var}" INTERFACE "${lib_name}") else() target_link_libraries( "${target_var}" "${lib_name}") endif() endfunction() wlmaker-0.8.1/cmake/embed.cmake.in0000644000175100017510000000266515222270710016342 0ustar runnerrunner# -*- cmake -*- # Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. file(READ "@binary_file@" binary_content HEX) string(REGEX MATCHALL "([A-Fa-f0-9][A-Fa-f0-9])" separated_content "${binary_content}") set(prefix "@prefix@") string(TOUPPER "@prefix@" prefix_uppercase) set(binary_file "@binary_file@") set(output_header "@output_header@") set(output_source "@output_source@") set(output_data "") set(bytes 0) foreach(token IN LISTS separated_content) if(NOT bytes EQUAL 0) string(APPEND output_data ", ") endif() math(EXPR line_pos "${bytes} & 0xF") if(line_pos EQUAL 0) string(APPEND output_data "\n ") endif() string(APPEND output_data "0x${token}") math(EXPR bytes "${bytes} + 1") endforeach() configure_file("@template_header@" "@output_header@" @ONLY) configure_file("@template_source@" "@output_source@" @ONLY) file(WRITE "@OUTPUT_BASENAME@.c" "${output}") wlmaker-0.8.1/.gitmodules0000644000175100017510000000101315222270710014736 0ustar runnerrunner[submodule "submodules/libbase"] path = submodules/libbase url = https://github.com/phkaeser/libbase.git branch = main update = rebase [submodule "examples/gtk-layer-shell"] path = examples/gtk-layer-shell url = https://github.com/wmww/gtk-layer-shell.git [submodule "dependencies"] path = dependencies url = https://github.com/phkaeser/wlmaker-dependencies.git [submodule "submodules/inih"] path = submodules/inih url = https://github.com/benhoyt/inih.git wlmaker-0.8.1/RELEASE.md0000644000175100017510000000113515222270710014170 0ustar runnerrunner# Instructions for releasing wlmaker * Update [CMakeLists.txt]: * Set a new `VERSION` in the `PROJECT` directive. * Update [doc/ROADMAP.md]: * Updates the roadmap's section title to a link to the new release tag. * Update [README.md]: * Update section with highlights for the current version. * Remove the `*new*` prefix from the earlier version's highlights. * On https://github.com/phkaeser/wlmaker/releases, *Draft a new release* * Create a new version tag, in format `v.` * Update the release title to `v. - ` Pending: Generate release notes. wlmaker-0.8.1/tool/0000755000175100017510000000000015222270710013543 5ustar runnerrunnerwlmaker-0.8.1/tool/menu.h0000644000175100017510000000365615222270710014672 0ustar runnerrunner/* ========================================================================= */ /** * @file menu.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_MENU_H__ #define __WLMAKER_MENU_H__ #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Generates a menu for the Themes files found at XDG data directories. * * @param path_ptr Optional: Path to read from. If NULL, use the XDG * data directories. * * @return A Plist array, or NULL on error. */ bspl_array_t *wlmtool_menu_generate_appearance(const char *path_ptr); /** * Generates an Applications menu from parsed `.desktop` files. * * @param path_ptr Optional: Path to read from. If NULL, use the XDG * data directories. * @param locale_ptr The locale for LC_MESSAGES, or NULL. * * @return a Plist array, or NULL on error. */ bspl_array_t *wlmtool_menu_generate_applications( const char *path_ptr, const char *locale_ptr); /** Unit tests for the menu generator. */ extern const bs_test_set_t wlmtool_menu_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // __WLMAKER_MENU_H__ /* == End of menu.h ======================================================== */ wlmaker-0.8.1/tool/item.h0000644000175100017510000000636615222270710014665 0ustar runnerrunner/* ========================================================================= */ /** * @file item.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_ITEM_H__ #define __WLMAKER_ITEM_H__ #include #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /* == Declarations ========================================================= */ struct wlmtool_item; struct wlmtool_menu; /** Destroys the item. */ void wlmtool_item_destroy(struct wlmtool_item *item_ptr); /** Returns a plist array representing the item (or menu). */ bspl_array_t *wlmtool_item_create_array(struct wlmtool_item *item_ptr); /** * Creates a menu. * * @param title_ptr * * @return The menu, or NULL on failure. To release associates, the function * @ref wlmtool_item_destroy must be called, on the menu's item * (@see wlmtool_item_from_menu). */ struct wlmtool_menu *wlmtool_menu_create(const char *title_ptr); /** @return the parent item handle for the menu. */ struct wlmtool_item *wlmtool_item_from_menu(struct wlmtool_menu *menu_ptr); /** * Adds an item to this menu. * * @param menu_ptr * @param item_ptr The item to add. The menu will take ownership. * * @return true on success. */ bool wlmtool_menu_add_item( struct wlmtool_menu *menu_ptr, struct wlmtool_item *item_ptr); /** * Obtain (or create) the submenu with the provided key within a menu. * * @param menu_ptr * @param key_ptr * * @return The submenu, or NULL on error. */ struct wlmtool_menu *wlmtool_menu_get_or_create_submenu( struct wlmtool_menu *menu_ptr, const void *key_ptr); /** * Creates a "file action" menu item. * * @param title_ptr Title to use for the menu entry. * @param action_ptr Action command to use. * @param resolved_path_ptr Fully resolved path to the file to act on. * * @return Menu item, or NULL on error. */ struct wlmtool_item *wlmtool_file_action_create( const char *title_ptr, const char *action_ptr, const char *resolved_path_ptr); /** * Adds a menu entry. * * @param key_ptr Key that is expected unique within the submenu. * @param args_ptr NULL-terminted array to build the entry from. * * @return Menu item, or NULL on error. */ struct wlmtool_item *wlmtool_entry_create( const char *key_ptr, const char **args_ptr); /** Unit tests for the menu item classes. */ extern const bs_test_set_t wlmtool_item_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // __WLMAKER_ITEM_H__ /* == End of item.h =========================================================== */ wlmaker-0.8.1/tool/wlmtool.c0000644000175100017510000001645415222270710015416 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmtool.c * * Command-line tool for Wayland Maker. * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include "menu.h" /* == Declarations ========================================================= */ static bool print_version(int argc, const char **argv); static bool print_help(int argc, const char **argv); static bool generate_applications_menu(int argc, const char **argv); static bool generate_themes_menu(int argc, const char **argv); #if !defined(WLMAKER_VERSION_MAJOR) || !defined(WLMAKER_VERSION_MINOR) || !defined(WLMAKER_VERSION_FULL) #eror "WLMAKER_VERSION_... not defined!" #else static const char *wlmaker_version_major = WLMAKER_VERSION_MAJOR; static const char *wlmaker_version_minor = WLMAKER_VERSION_MINOR; static const char *wlmaker_version_full = WLMAKER_VERSION_FULL; #endif /** Command descriptor. */ struct command_desc { /** Command that can be invoked. */ const char *command_ptr; /** Description of the command. */ const char *description_ptr; /** The operation executed by the command. */ bool (*op)(int argc, const char **argv); }; /** Locale, when specified as `--locale` argument to the commandline. */ static char *wlmtool_locale_ptr = NULL; /* == Data ================================================================= */ /** Definition of commandline arguments. */ static const bs_arg_t wlmtool_args[] = { BS_ARG_STRING( "locale", "Optional: Override the locale for generating the menu. Uses the " "environment's setting for LC_MESSAGES, if not set.", NULL, &wlmtool_locale_ptr), BS_ARG_SENTINEL() }; /** List of available commands. */ static const struct command_desc commands[] = { { .command_ptr = "genmenu", .description_ptr = "Generates the \"Applications\" menu for Wayland Maker, in .plist " "text format. DEPRECATED. Use \"GenerateApplicationsMenu\" instead.", .op = generate_applications_menu }, { .command_ptr = "GenerateApplicationsMenu", .description_ptr = "Generates the \"Applications\" menu for Wayland Maker, in .plist " "text format.", .op = generate_applications_menu }, { .command_ptr = "GenerateThemesMenu", .description_ptr = "Generates the \"Themes\" menu for Wayland Maker, in .plist text " "format.", .op = generate_themes_menu }, { .command_ptr = "--help", .description_ptr = "Prints usage information.", .op = print_help }, { .command_ptr = "--version", .description_ptr = "Prints version information.", .op = print_version }, {} // Sentinel. }; /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Prints version information. */ bool print_version(__UNUSED__ int argc, __UNUSED__ const char **argv) { fprintf(stdout, "Wayland Maker wlmtool version %s.%s (%s)\n", wlmaker_version_major, wlmaker_version_minor, wlmaker_version_full); return true; } /* ------------------------------------------------------------------------- */ /** Prints help. */ bool print_help(__UNUSED__ int argc, __UNUSED__ const char **argv) { fprintf(stdout, "Wayland Maker wlmtool version %s.%s (%s)\n", wlmaker_version_major, wlmaker_version_minor, wlmaker_version_full); fprintf(stderr, "\nAvailable commands and options:\n"); for (const struct command_desc *d = commands; d->command_ptr; ++d) { fprintf(stdout, "%s: %s\n", d->command_ptr, d->description_ptr); } bs_arg_print_usage(stdout, wlmtool_args); return true; } /* ------------------------------------------------------------------------- */ /** Generates the the "Applications" section for the root menu. */ bool generate_applications_menu(int argc, const char **argv) { const char *path_ptr = NULL; if (1 > argc) { fprintf(stderr, "Usage: wlmtool GenerateApplicationsMenu [PATH]\n"); return false; } else if (2 <= argc) { path_ptr = argv[1]; } bspl_array_t *array_ptr = wlmtool_menu_generate_applications( path_ptr, wlmtool_locale_ptr); if (NULL == array_ptr) return false; bs_dynbuf_t buf = {}; bool rv = bs_dynbuf_init(&buf, 1024, SIZE_MAX); if (rv) { rv = bspl_object_write(bspl_object_from_array(array_ptr), &buf); if (rv) fprintf(stdout, "%.*s", (int)buf.length, (char*)buf.data_ptr); } bspl_array_unref(array_ptr); bs_dynbuf_fini(&buf); return rv; } /* ------------------------------------------------------------------------- */ /** Generates the the "Appearance" menu from Themes plist files. */ bool generate_themes_menu(__UNUSED__ int argc, __UNUSED__ const char **argv) { bspl_array_t *array_ptr = NULL; if (1 >= argc) { array_ptr = wlmtool_menu_generate_appearance(NULL); } else { array_ptr = wlmtool_menu_generate_appearance(argv[1]); } if (NULL == array_ptr) return false; bs_dynbuf_t buf = {}; bool rv = bs_dynbuf_init(&buf, 1024, SIZE_MAX); if (rv) { rv = bspl_object_write(bspl_object_from_array(array_ptr), &buf); if (rv) fprintf(stdout, "%.*s", (int)buf.length, (char*)buf.data_ptr); } bspl_array_unref(array_ptr); bs_dynbuf_fini(&buf); return rv; } /* == Main program ========================================================= */ /** The main program. */ int main(int argc, const char **argv) { if (!bs_arg_parse(wlmtool_args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv)) { fprintf(stderr, "Failed to parse commandline arguments.\n"); bs_arg_print_usage(stderr, wlmtool_args); return EXIT_FAILURE; } if (1 < argc) { for (const struct command_desc *d = commands; d->command_ptr; ++d) { if (0 == strcmp(argv[1], d->command_ptr)) { return d->op(argc - 1, argv + 1) ? EXIT_SUCCESS : EXIT_FAILURE; } } fprintf(stderr, "Unknown command: %s.\n", argv[1]); } else { fprintf(stderr, "Missing command.\n"); } fprintf(stderr, "\nAvailable commands:\n"); for (const struct command_desc *d = commands; d->command_ptr; ++d) { fprintf(stderr, "%s: %s\n", d->command_ptr, d->description_ptr); } bs_arg_print_usage(stderr, wlmtool_args); return EXIT_FAILURE; } /* == End of wlmtool.c ===================================================== */ wlmaker-0.8.1/tool/item.c0000644000175100017510000003710415222270710014652 0ustar runnerrunner/* ========================================================================= */ /** * @file item.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "item.h" #include #include #include #include /* == Declarations ========================================================= */ /** Abstract definition of a menu item. Can hold a submenu, or a file item. */ struct wlmtool_item { /** To prevent duplicate entries. Node of @ref wlmtool_menu::tree_ptr. */ bs_avltree_node_t avlnode; /** Item of @ref wlmtool_menu::items. */ bs_dllist_node_t dlnode; /** Key used for @ref wlmtool_menu::tree_ptr. */ const char *key_ptr; /** Item title, used for sorting in the double-linked list. */ const char *title_ptr; /** Dtor for this item. */ void (*destroy)(struct wlmtool_item *item_ptr); /** Constructs an array from this item. */ bspl_array_t *(*create_array)(struct wlmtool_item *item_ptr); }; /** Menu item: A menu. */ struct wlmtool_menu { /** Parent class: An item. */ struct wlmtool_item menu_item; /** To disambiguate duplicate entries, and to lookup by key. */ bs_avltree_t *tree_ptr; /** Items, through @ref wlmtool_item::dlnode. Defines the order. */ bs_dllist_t items; /** The menu's title. */ bspl_string_t *title_ptr; }; /** Menu item. */ struct wlmtool_entry { /** Parent class: An item. */ struct wlmtool_item menu_item; /** Plist array describing the item: (Title, Action, ...). */ bspl_array_t *array_ptr; /** Duplicate of the lookup key to use. To take ownership.*/ char *key_ptr; }; static void _wlmtool_item_destroy_dlnode( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static int _wlmtool_item_cmp( const bs_avltree_node_t *node_ptr, const void *key_ptr); static int _wlmtool_item_dlnode_cmp( const bs_dllist_node_t *n1, const bs_dllist_node_t *n2); static bool _wlmtool_item_dlnode_add_to_array( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmtool_menu_destroy_item(struct wlmtool_item *item_ptr); static bspl_array_t *_wlmtool_menu_create_array( struct wlmtool_item *item_ptr); static void _wlmtool_entry_destroy(struct wlmtool_item *item_ptr); static bspl_array_t *_wlmtool_entry_create_array( struct wlmtool_item *item_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ void wlmtool_item_destroy(struct wlmtool_item *item_ptr) { item_ptr->destroy(item_ptr); } /* ------------------------------------------------------------------------- */ bspl_array_t *wlmtool_item_create_array(struct wlmtool_item *item_ptr) { return item_ptr->create_array(item_ptr); } /* ------------------------------------------------------------------------- */ struct wlmtool_menu *wlmtool_menu_create(const char *title_ptr) { struct wlmtool_menu *menu_ptr = logged_calloc(1, sizeof(*menu_ptr)); if (NULL == menu_ptr) return NULL; menu_ptr->menu_item.destroy = _wlmtool_menu_destroy_item; menu_ptr->menu_item.create_array = _wlmtool_menu_create_array; menu_ptr->tree_ptr = bs_avltree_create(_wlmtool_item_cmp, NULL); if (NULL == menu_ptr->tree_ptr) { wlmtool_item_destroy(&menu_ptr->menu_item); return NULL; } menu_ptr->title_ptr = bspl_string_create(title_ptr); if (NULL == menu_ptr->title_ptr) { wlmtool_item_destroy(&menu_ptr->menu_item); return NULL; } menu_ptr->menu_item.key_ptr = bspl_string_value(menu_ptr->title_ptr); menu_ptr->menu_item.title_ptr = bspl_string_value(menu_ptr->title_ptr); return menu_ptr; } /* ------------------------------------------------------------------------- */ struct wlmtool_item *wlmtool_item_from_menu(struct wlmtool_menu *menu_ptr) { return &menu_ptr->menu_item; } /* ------------------------------------------------------------------------- */ /** Adds an item to the menu. */ bool wlmtool_menu_add_item( struct wlmtool_menu *menu_ptr, struct wlmtool_item *item_ptr) { if (!bs_avltree_insert(menu_ptr->tree_ptr, item_ptr->key_ptr, &item_ptr->avlnode, false)) return false; bs_dllist_push_back(&menu_ptr->items, &item_ptr->dlnode); return true; } /* ------------------------------------------------------------------------- */ /** Returns a submenu for key, if it exists. If not, create it. */ struct wlmtool_menu *wlmtool_menu_get_or_create_submenu( struct wlmtool_menu *menu_ptr, const void *key_ptr) { bs_avltree_node_t *a_ptr = bs_avltree_lookup(menu_ptr->tree_ptr, key_ptr); if (NULL != a_ptr) { return BS_CONTAINER_OF(a_ptr, struct wlmtool_menu, menu_item.avlnode); } struct wlmtool_menu *submenu_ptr = wlmtool_menu_create(key_ptr); if (NULL == submenu_ptr) return NULL; if (!wlmtool_menu_add_item(menu_ptr, &submenu_ptr->menu_item)) { bs_log(BS_ERROR, "Failed to add category \"%s\"", (const char*)key_ptr); wlmtool_item_destroy(&submenu_ptr->menu_item); return NULL; } return submenu_ptr; } /* ------------------------------------------------------------------------- */ struct wlmtool_item *wlmtool_file_action_create( const char *title_ptr, const char *action_ptr, const char *resolved_path_ptr) { const char *args[4] = { [0] = title_ptr, [1] = action_ptr, [2] = resolved_path_ptr, [3] = NULL }; return wlmtool_entry_create(resolved_path_ptr, args); } /* ------------------------------------------------------------------------- */ struct wlmtool_item *wlmtool_entry_create( const char *key_ptr, const char **args_ptr) { if (NULL == args_ptr || NULL == *args_ptr) return NULL; struct wlmtool_entry *entry_ptr = logged_calloc(1, sizeof(*entry_ptr)); if (NULL == entry_ptr) return NULL; entry_ptr->menu_item.destroy = _wlmtool_entry_destroy; entry_ptr->menu_item.create_array = _wlmtool_entry_create_array; entry_ptr->array_ptr = bspl_array_create(); if (NULL == entry_ptr->array_ptr) goto error; for (const char **i_pp = args_ptr; NULL != *i_pp; ++i_pp) { bspl_string_t *s = bspl_string_create(*i_pp); if (NULL == s) goto error; if (!bspl_array_push_back(entry_ptr->array_ptr, bspl_object_from_string(s))) goto error; if (*i_pp == *args_ptr) { entry_ptr->menu_item.title_ptr = bspl_string_value(s); } if (key_ptr == *i_pp) { entry_ptr->menu_item.key_ptr = bspl_string_value(s); } bspl_string_unref(s); } if (NULL == entry_ptr->menu_item.key_ptr) { entry_ptr->key_ptr = logged_strdup(key_ptr); if (NULL == entry_ptr->key_ptr) goto error; entry_ptr->menu_item.key_ptr = entry_ptr->key_ptr; } return &entry_ptr->menu_item; error: _wlmtool_entry_destroy(&entry_ptr->menu_item); return NULL; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Callback for bs_dllist_each(), destroy the item at `dlnode_ptr`. */ void _wlmtool_item_destroy_dlnode( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { struct wlmtool_item *item_ptr = BS_CONTAINER_OF( dlnode_ptr, struct wlmtool_item, dlnode); item_ptr->destroy(item_ptr); } /* ------------------------------------------------------------------------- */ /** Comparator for the AVL tree. */ int _wlmtool_item_cmp( const bs_avltree_node_t *node_ptr, const void *key_ptr) { struct wlmtool_item *item_ptr = BS_CONTAINER_OF( node_ptr, struct wlmtool_item, avlnode); return strcmp(item_ptr->key_ptr, key_ptr); } /* ------------------------------------------------------------------------- */ /** Comparator for the double-linked list. */ int _wlmtool_item_dlnode_cmp( const bs_dllist_node_t *n1, const bs_dllist_node_t *n2) { struct wlmtool_item *i1 = BS_CONTAINER_OF(n1, struct wlmtool_item, dlnode); struct wlmtool_item *i2 = BS_CONTAINER_OF(n2, struct wlmtool_item, dlnode); return strcmp(i1->title_ptr, i2->title_ptr); } /* ------------------------------------------------------------------------- */ /** Creates array from item at `dlnode_ptr` and adds to array at `ud_ptr`. */ bool _wlmtool_item_dlnode_add_to_array( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { struct wlmtool_item *item_ptr = BS_CONTAINER_OF( dlnode_ptr, struct wlmtool_item, dlnode); bspl_array_t *parent_array_ptr = ud_ptr; bspl_array_t *array_ptr = item_ptr->create_array(item_ptr); if (NULL == array_ptr) return false; bspl_array_push_back(parent_array_ptr, bspl_object_from_array(array_ptr)); bspl_array_unref(array_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtool_item::destroy. */ void _wlmtool_menu_destroy_item(struct wlmtool_item *item_ptr) { struct wlmtool_menu *menu_ptr = BS_CONTAINER_OF( item_ptr, struct wlmtool_menu, menu_item); if (NULL != menu_ptr->title_ptr) { bspl_string_unref(menu_ptr->title_ptr); menu_ptr->title_ptr = NULL; } if (NULL != menu_ptr->tree_ptr) { bs_avltree_destroy(menu_ptr->tree_ptr); menu_ptr->tree_ptr = NULL; } bs_dllist_for_each(&menu_ptr->items, _wlmtool_item_destroy_dlnode, NULL); free(menu_ptr); } /* ------------------------------------------------------------------------- */ /** * Creates a plist array representing the menu and it's items. * * @note The menu's items will be sorted, meaning: will change order. * * @param item_ptr * * @return the plist array, or NULL on error. */ bspl_array_t *_wlmtool_menu_create_array(struct wlmtool_item *item_ptr) { struct wlmtool_menu *menu_ptr = BS_CONTAINER_OF( item_ptr, struct wlmtool_menu, menu_item); bspl_array_t *array_ptr = bspl_array_create(); if (NULL == array_ptr) return NULL; bspl_array_push_back( array_ptr, bspl_object_from_string(menu_ptr->title_ptr)); bs_dllist_sort(&menu_ptr->items, _wlmtool_item_dlnode_cmp); if (bs_dllist_all( &menu_ptr->items, _wlmtool_item_dlnode_add_to_array, array_ptr)) return array_ptr; bs_log(BS_ERROR, "Failed to create plist array from menu \"%s\"", bspl_string_value(menu_ptr->title_ptr)); bspl_array_unref(array_ptr); return NULL; } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtool_item::destroy. Dtor for the entry. */ void _wlmtool_entry_destroy(struct wlmtool_item *item_ptr) { struct wlmtool_entry *entry_ptr = BS_CONTAINER_OF( item_ptr, struct wlmtool_entry, menu_item); if (NULL != entry_ptr->array_ptr) { bspl_array_unref(entry_ptr->array_ptr); entry_ptr->array_ptr = NULL; } if (NULL != entry_ptr->key_ptr) { free(entry_ptr->key_ptr); entry_ptr->key_ptr = NULL; } free(entry_ptr); } /* ------------------------------------------------------------------------- */ /** @return A plist array for the entry, or NULL on error. */ bspl_array_t *_wlmtool_entry_create_array( struct wlmtool_item *item_ptr) { struct wlmtool_entry *entry_ptr = BS_CONTAINER_OF( item_ptr, struct wlmtool_entry, menu_item); return bspl_array_ref(entry_ptr->array_ptr); } /* == Unit Tests =========================================================== */ static void _wlmaker_item_test_create(bs_test_t *test_ptr); static void _wlmaker_item_test_array(bs_test_t *test_ptr); /** Test cases for the menu item classes. */ static const bs_test_case_t _wlmtool_item_test_cases[] = { { true, "create", _wlmaker_item_test_create }, { true, "array", _wlmaker_item_test_array }, BS_TEST_CASE_SENTINEL(), }; const bs_test_set_t wlmtool_item_test_set = BS_TEST_SET( true, "item", _wlmtool_item_test_cases); /* ------------------------------------------------------------------------- */ /** Tests item creation and destruction. */ void _wlmaker_item_test_create(bs_test_t *test_ptr) { struct wlmtool_menu *menu_ptr = wlmtool_menu_create("Menu"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr); struct wlmtool_menu *submenu_ptr = wlmtool_menu_get_or_create_submenu( menu_ptr, "Submenu"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, submenu_ptr); // Second "get_or_create" must return the same. BS_TEST_VERIFY_EQ( test_ptr, submenu_ptr, wlmtool_menu_get_or_create_submenu(menu_ptr, "Submenu")); struct wlmtool_item *item_ptr = wlmtool_file_action_create("a", "b", "c"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtool_menu_add_item(menu_ptr, item_ptr)); // Second addition with same "file" must fail. item_ptr = wlmtool_file_action_create("x", "y", "c"); BS_TEST_VERIFY_FALSE_OR_RETURN( test_ptr, wlmtool_menu_add_item(menu_ptr, item_ptr)); wlmtool_item_destroy(item_ptr); wlmtool_item_destroy(wlmtool_item_from_menu(menu_ptr)); } /* ------------------------------------------------------------------------- */ /** Tests plist array creation. */ void _wlmaker_item_test_array(bs_test_t *test_ptr) { struct wlmtool_menu *m = wlmtool_menu_create("Menu"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, m); struct wlmtool_item *i0 = wlmtool_file_action_create("a0", "b0", "c0"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i0); struct wlmtool_item *i1 = wlmtool_file_action_create("a1", "b1", "c1"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i1); BS_TEST_VERIFY_TRUE(test_ptr, wlmtool_menu_add_item(m, i0)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtool_menu_add_item(m, i1)); bspl_array_t *a = wlmtool_item_create_array(wlmtool_item_from_menu(m)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, a); BS_TEST_VERIFY_STREQ(test_ptr, "Menu", bspl_array_string_value_at(a, 0)); bspl_array_t *ai = bspl_array_from_object(bspl_array_at(a, 1)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ai); BS_TEST_VERIFY_STREQ(test_ptr, "a0", bspl_array_string_value_at(ai, 0)); BS_TEST_VERIFY_STREQ(test_ptr, "b0", bspl_array_string_value_at(ai, 1)); BS_TEST_VERIFY_STREQ(test_ptr, "c0", bspl_array_string_value_at(ai, 2)); ai = bspl_array_from_object(bspl_array_at(a, 2)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ai); BS_TEST_VERIFY_STREQ(test_ptr, "a1", bspl_array_string_value_at(ai, 0)); BS_TEST_VERIFY_STREQ(test_ptr, "b1", bspl_array_string_value_at(ai, 1)); BS_TEST_VERIFY_STREQ(test_ptr, "c1", bspl_array_string_value_at(ai, 2)); bspl_array_unref(a); wlmtool_item_destroy(wlmtool_item_from_menu(m)); } /* == End of item.c ======================================================== */ wlmaker-0.8.1/tool/menu.c0000644000175100017510000004507715222270710014670 0ustar runnerrunner/* ========================================================================= */ /** * @file menu.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "menu.h" #include #include #include #include #include #include #include #include #include #include #include #include "item.h" /* == Declarations ========================================================= */ struct wlmtool_menu; static bool _wlmtool_menu_add_themes_path( struct wlmtool_menu *menu_ptr, const char *path_ptr); static bool _wlmtool_menu_add_themes_file( const char *resolved_path_ptr, const FTSENT *ftsent_ptr, void *ud_ptr); static bool _wlmtool_menu_add_desktop_path( struct wlmtool_menu *menu_ptr, const char *path_ptr, const char *locale_ptr); static bool _wlmtool_menu_add_desktop_file( const char *resolved_path_ptr, const FTSENT *ftsent_ptr, void *ud_ptr); static char *_wlmtool_menu_themes_name_from_plist_file(const char *path_ptr); static const char *_wlmtool_menu_category_from_entry( const struct desktop_entry *desktop_entry_ptr); /** Lookup table for category names */ struct _category_translation { /** Category that is used in a `.desktop` file. */ const char *desktop_category_ptr; /** Category name to use in the menu. */ const char *menu_category_ptr; }; /** Arguments to @ref _wlmtool_menu_add_desktop_file */ struct _wlmtool_menu_add_desktop_file_arg { /** Parser for the `.desktop` files. */ struct desktop_parser *desktop_parser_ptr; /** Root menu. */ struct wlmtool_menu *root_menu_ptr; /** Chosen locale. */ const char *locale_ptr; }; /* == Data ================================================================= */ /** Command to load a theme file. */ static const char* _wlmtool_action_load_theme_from_file = "ThemeLoadFromFile"; /** Recognized categories. */ // TODO(kaeser@gubbe.ch): Internationalize this. static const struct _category_translation _wlmtool_menu_category_table[] = { { "AudioVideo", "Audio & Video" }, { "Video", "Video" }, { "Development", "Development" }, { "Education", "Education" }, { "Game", "Game" }, { "Graphics", "Graphics" }, { "Network", "Network" }, { "Office", "Office" }, { "Science", "Science" }, { "Settings", "Settings" }, { "System", "System" }, { "Utility", "Utility" }, // Reserved categories. { "Screensaver", "Screensaver" }, { "TrayIcon", "Tray Icon" }, { "Applet", "Applet" }, { "Shell", "Shell" }, { NULL, NULL }, }; /** Category to use for any entry that is not categorized. */ // TODO(kaeser@gubbe.ch): Internationalize. static const char *_wlmtool_menu_category_other_ptr = "Other"; /** Command to use for terminal. */ // TODO(kaeser@gubbe.ch): This should be configurable. static const char *_wlmtool_menu_terminal_command = "/usr/bin/foot"; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bspl_array_t *wlmtool_menu_generate_appearance(const char *path_ptr) { struct wlmtool_menu *root_menu_ptr = wlmtool_menu_create("Appearance"); if (NULL == root_menu_ptr) return NULL; struct wlmtool_menu *menu_ptr = NULL; menu_ptr = wlmtool_menu_get_or_create_submenu( root_menu_ptr, "System Themes"); if (NULL == menu_ptr) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } if (NULL != path_ptr) { if (!_wlmtool_menu_add_themes_path(root_menu_ptr, path_ptr)) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } } else { xdgHandle xdg_handle; if (NULL == xdgInitHandle(&xdg_handle)) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } bs_ptr_set_t *dir_set_ptr = bs_ptr_set_create(); const char* const *dirs_ptr = xdgDataDirectories(&xdg_handle); for (; NULL != dirs_ptr && NULL != *dirs_ptr; ++dirs_ptr) { bs_ptr_set_insert(dir_set_ptr, (void*)*dirs_ptr); char *p = bs_strdupf("%s/%s", *dirs_ptr, "wlmaker/Themes"); if (NULL == p) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } bool rv = _wlmtool_menu_add_themes_path(menu_ptr, p); free(p); if (!rv) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } } menu_ptr = wlmtool_menu_get_or_create_submenu( root_menu_ptr, "User Themes"); char *p = bs_strdupf("%s/%s", xdgDataHome(&xdg_handle), "wlmaker/Themes"); if (NULL == p) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } bool rv = _wlmtool_menu_add_themes_path(menu_ptr, p); free(p); if (!rv) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } menu_ptr = wlmtool_menu_get_or_create_submenu( root_menu_ptr, "User Themes"); p = bs_strdupf("%s/%s", xdgConfigHome(&xdg_handle), "wlmaker/Themes"); if (NULL == p) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } rv = _wlmtool_menu_add_themes_path(menu_ptr, p); free(p); if (!rv) { wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return NULL; } bs_ptr_set_destroy(dir_set_ptr); xdgWipeHandle(&xdg_handle); } bspl_array_t *array_ptr = wlmtool_item_create_array( wlmtool_item_from_menu(root_menu_ptr)); wlmtool_item_destroy(wlmtool_item_from_menu(root_menu_ptr)); return array_ptr; } /* ------------------------------------------------------------------------- */ bspl_array_t *wlmtool_menu_generate_applications( const char *path_ptr, const char *locale_ptr) { struct wlmtool_menu *menu_ptr = wlmtool_menu_create("Applications"); if (NULL == menu_ptr) return NULL; if (NULL != path_ptr) { if (!_wlmtool_menu_add_desktop_path(menu_ptr, path_ptr, locale_ptr)) { wlmtool_item_destroy(wlmtool_item_from_menu(menu_ptr)); return NULL; } } else { // No path provided. Iterate over XDG data directories. xdgHandle xdg_handle; if (NULL == xdgInitHandle(&xdg_handle)) return NULL; const char* const *dirs_ptr = xdgSearchableDataDirectories(&xdg_handle); for (; NULL != dirs_ptr && NULL != *dirs_ptr; ++dirs_ptr) { char *p = bs_strdupf("%s/applications", *dirs_ptr); if (NULL == p) return NULL; bool rv = _wlmtool_menu_add_desktop_path(menu_ptr, p, locale_ptr); if (!rv) { bs_log(BS_ERROR, "Failed to add desktop files from %s", p); } free(p); if (!rv) return NULL; } xdgWipeHandle(&xdg_handle); } bspl_array_t *array_ptr = wlmtool_item_create_array( wlmtool_item_from_menu(menu_ptr)); wlmtool_item_destroy(wlmtool_item_from_menu(menu_ptr)); return array_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Convenience wrapper to add themes file from `path_ptr` to `menu_ptr`. */ bool _wlmtool_menu_add_themes_path( struct wlmtool_menu *menu_ptr, const char *path_ptr) { if (bs_file_walk_tree( path_ptr, "*.plist", S_IFREG, false, _wlmtool_menu_add_themes_file, menu_ptr)) return true; bs_log(BS_ERROR, "Failed to add Themes files from %s", path_ptr); return false; } /* ------------------------------------------------------------------------- */ /** Adds a menu entry for the plist Themes file at `resolved_path_ptr`. */ bool _wlmtool_menu_add_themes_file( const char *resolved_path_ptr, __UNUSED__ const FTSENT *ftsent_ptr, void *ud_ptr) { struct wlmtool_menu *menu_ptr = ud_ptr; // Creates a "Theme" menu item from the plist file. char *n = _wlmtool_menu_themes_name_from_plist_file(resolved_path_ptr); if (NULL == n) return false; struct wlmtool_item *item_ptr = wlmtool_file_action_create( n, "ThemeLoadFromFile", resolved_path_ptr); free(n); if (NULL == item_ptr) return false; if (wlmtool_menu_add_item(menu_ptr, item_ptr)) return true; bs_log(BS_INFO, "Skipping already added file \"%s\"", resolved_path_ptr); wlmtool_item_destroy(item_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Convenience wrapper to add desktop files from `path_ptr` to `menu_ptr`. */ bool _wlmtool_menu_add_desktop_path( struct wlmtool_menu *menu_ptr, const char *path_ptr, const char *locale_ptr) { struct _wlmtool_menu_add_desktop_file_arg arg = { .desktop_parser_ptr = desktop_parser_create(locale_ptr), .root_menu_ptr = menu_ptr, .locale_ptr = locale_ptr }; if (NULL == arg.desktop_parser_ptr) return false; bool rv = bs_file_walk_tree( path_ptr, "*.desktop", S_IFREG, false, _wlmtool_menu_add_desktop_file, &arg); if (!rv) bs_log(BS_ERROR, "Failed to add desktop files from %s", path_ptr); desktop_parser_destroy(arg.desktop_parser_ptr); return rv; } /* ------------------------------------------------------------------------- */ /** Adds a menu entry for a desktop file. */ bool _wlmtool_menu_add_desktop_file( const char *resolved_path_ptr, __UNUSED__ const FTSENT *ftsent_ptr, void *ud_ptr) { struct _wlmtool_menu_add_desktop_file_arg *arg_ptr = ud_ptr; struct desktop_entry de = {}; char *cmd_ptr = NULL; int error_line = desktop_parser_file_to_entry( arg_ptr->desktop_parser_ptr, resolved_path_ptr, &de); if (0 != error_line) { bs_log(BS_WARNING, "Failed desktop_parser_file_to_entry(%p, %s, %p) at line %d", arg_ptr->desktop_parser_ptr, resolved_path_ptr, &de, error_line); return false; } if (DESKTOP_ENTRY_TYPE_APPLICATION != de.type || de.no_display || de.hidden) { desktop_parser_entry_release(&de); return true; } const char *c = _wlmtool_menu_category_from_entry(&de); if (NULL == c) { bs_log(BS_WARNING, "Failed to lookup category for %s", resolved_path_ptr); goto error; } struct wlmtool_menu *cat_menu_ptr = wlmtool_menu_get_or_create_submenu( arg_ptr->root_menu_ptr, c); if (NULL == cat_menu_ptr) goto error; if (NULL == de.try_exec_ptr && NULL == de.exec_ptr) { bs_log(BS_WARNING, "Missing 'Exec' and 'TryExec' in desktop file %s", resolved_path_ptr); goto error; } if (de.terminal) { cmd_ptr = bs_strdupf( "%s %s", _wlmtool_menu_terminal_command, de.try_exec_ptr ? de.try_exec_ptr : de.exec_ptr); } else { cmd_ptr = logged_strdup( de.try_exec_ptr ? de.try_exec_ptr : de.exec_ptr); } if (NULL == cmd_ptr) goto error; if (NULL == de.name_ptr) { bs_log(BS_WARNING, "Missing 'Name' in desktop file %s", resolved_path_ptr); goto error; } const char *a[] = { de.name_ptr, "ShellExecute", cmd_ptr, NULL }; struct wlmtool_item *item_ptr = wlmtool_entry_create(resolved_path_ptr, a); if (NULL == item_ptr) goto error; if (!wlmtool_menu_add_item(cat_menu_ptr, item_ptr)) { bs_log(BS_WARNING, "Duplicate? Failed to add entry for %s", resolved_path_ptr); goto error; } free(cmd_ptr); desktop_parser_entry_release(&de); return true; error: if (NULL != cmd_ptr) free(cmd_ptr); desktop_parser_entry_release(&de); return false; } /* ------------------------------------------------------------------------- */ /** * Returns a string holding name to show in the menu for the file action. * * This will attempt to load the file as a PList, and extract a string value * keyed "Name" from the toplevel dict. If `path_ptr` is not a plist file, or * does not hold a Plist dict as toplevel object, it returns NULL. * If there is no "Name" entry in the toplevel dict, the function returns the * basename of `path_ptr`. * * @param path_ptr * * @return An allocated string, or NULL on error. The allocated string must be * released by calling free(). */ char *_wlmtool_menu_themes_name_from_plist_file(const char *path_ptr) { // Load the plist, and lookup the value of "Name" in the toplevel dict. bspl_object_t *object_ptr = bspl_create_object_from_plist_file(path_ptr); if (NULL == object_ptr) return NULL; bspl_dict_t *dict_ptr = bspl_dict_from_object(object_ptr); if (NULL == dict_ptr) { bs_log(BS_WARNING, "Not a Plist dict in %s", path_ptr); bspl_object_unref(object_ptr); return NULL; } char *name_ptr = NULL; const char *plist_name_ptr = bspl_dict_get_string_value(dict_ptr, "Name"); if (NULL != plist_name_ptr) name_ptr = logged_strdup(plist_name_ptr); bspl_object_unref(object_ptr); if (NULL != name_ptr) return name_ptr; // No such value in the plist file. Use the file's basename. char *p = logged_strdup(path_ptr); if (NULL == p) return false; char *basename_ptr = basename(p); name_ptr = logged_strdup(basename_ptr); free(p); return name_ptr; } /* ------------------------------------------------------------------------- */ /** Looks up the category name for a desktop's file menu entry. */ const char *_wlmtool_menu_category_from_entry( const struct desktop_entry *desktop_entry_ptr) { if (NULL != desktop_entry_ptr->category_ptrs) { for (char **c = desktop_entry_ptr->category_ptrs; *c; ++c) { for (const struct _category_translation *ct = &_wlmtool_menu_category_table[0]; ct->desktop_category_ptr != NULL; ++ct) { if (0 == strcmp(*c, ct->desktop_category_ptr)) { return ct->menu_category_ptr; } } } } return _wlmtool_menu_category_other_ptr; } /* == Unit Tests =========================================================== */ static void _wlmtool_menu_test_generate_themes(bs_test_t *test_ptr); static void _wlmtool_menu_test_generate_applications(bs_test_t *test_ptr); /** Test cases for the menu generator. */ static const bs_test_case_t _wlmtool_menu_test_cases[] = { { true, "generate_themes", _wlmtool_menu_test_generate_themes }, { true, "applications", _wlmtool_menu_test_generate_applications }, BS_TEST_CASE_SENTINEL(), }; const bs_test_set_t wlmtool_menu_test_set = BS_TEST_SET( true, "menu", _wlmtool_menu_test_cases); /* ------------------------------------------------------------------------- */ /** Verifies themes menu generation from the test files. */ void _wlmtool_menu_test_generate_themes(bs_test_t *test_ptr) { struct wlmtool_menu *m = wlmtool_menu_create("Appearance"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, m); const char *p = bs_test_data_path(test_ptr, "Themes"); BS_TEST_VERIFY_TRUE(test_ptr, _wlmtool_menu_add_themes_path(m, p)); bspl_array_t *a = wlmtool_item_create_array(wlmtool_item_from_menu(m)); wlmtool_item_destroy(wlmtool_item_from_menu(m)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, a); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, a); BS_TEST_VERIFY_EQ(test_ptr, 3, bspl_array_size(a)); BS_TEST_VERIFY_STREQ(test_ptr, "Appearance", bspl_array_string_value_at(a, 0)); const char *load = _wlmtool_action_load_theme_from_file; bspl_array_t *aa = bspl_array_from_object(bspl_array_at(a, 1)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, aa); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, bspl_array_size(aa)); BS_TEST_VERIFY_STREQ(test_ptr, "ThemeA", bspl_array_string_value_at(aa, 0)); BS_TEST_VERIFY_STREQ(test_ptr, load, bspl_array_string_value_at(aa, 1)); aa = bspl_array_from_object(bspl_array_at(a, 2)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, aa); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, bspl_array_size(aa)); BS_TEST_VERIFY_STREQ(test_ptr, "ThemeB", bspl_array_string_value_at(aa, 0)); BS_TEST_VERIFY_STREQ(test_ptr, load, bspl_array_string_value_at(aa, 1)); bspl_array_unref(a); } /* ------------------------------------------------------------------------- */ /** Tests generation of the 'Applications' menu. */ void _wlmtool_menu_test_generate_applications(bs_test_t *test_ptr) { const char *p = bs_test_data_path(test_ptr, "%s", ""); bspl_array_t *a = wlmtool_menu_generate_applications(p, NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, a); BS_TEST_VERIFY_EQ(test_ptr, 2, bspl_array_size(a)); BS_TEST_VERIFY_STREQ( test_ptr, "Applications", bspl_array_string_value_at(a, 0)); bspl_array_t *aa = bspl_array_from_object(bspl_array_at(a, 1)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, aa); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 2, bspl_array_size(aa)); BS_TEST_VERIFY_STREQ( test_ptr, "System", bspl_array_string_value_at(aa, 0)); bspl_array_t *aaa = bspl_array_from_object(bspl_array_at(aa, 1)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, aaa); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, bspl_array_size(aaa)); BS_TEST_VERIFY_STREQ( test_ptr, "WaylandMaker", bspl_array_string_value_at(aaa, 0)); BS_TEST_VERIFY_STREQ( test_ptr, "ShellExecute", bspl_array_string_value_at(aaa, 1)); BS_TEST_VERIFY_STREQ( test_ptr, "/usr/bin/foot ./wlmaker", bspl_array_string_value_at(aaa, 2)); bspl_array_unref(a); } /* == End of menu.c ======================================================== */ wlmaker-0.8.1/tool/desktop-parser/0000755000175100017510000000000015222270710016506 5ustar runnerrunnerwlmaker-0.8.1/tool/desktop-parser/desktop-parser.c0000644000175100017510000010123015222270710021612 0ustar runnerrunner/* ========================================================================= */ /** * @file desktop-parser.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2025 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "desktop-parser.h" #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** State for the DFA automaton. */ enum _dfa_state { /** Normal mode. */ _DFA_STATE_NORMAL, /** Escape mode, after a \ character. */ _DFA_STATE_ESCAPED, /** Terminate. */ _DFA_STATE_HALT }; /** Character classes for the DFA automaton. */ enum _dfa_class { /** Backslash. */ _DFA_CLASS_BACKSLASH = 0, /** Semicolon. */ _DFA_CLASS_SEMICOLON = 1, /** NUL character. */ _DFA_CLASS_NUL = 2, /** Anything else. */ _DFA_CLASS_REGULAR = 3, }; /** Transition for the DFA automaton. */ struct _dfa_transition { /** Next state. */ enum _dfa_state next_state; /** * Function to apply for the current input character. * * @param dest_ptr Current position in output string. * @param dest_ptr Current input character. * * @return New position in output string. */ char *(*process)(char *dest_ptr, const char c); }; /** Encapsulates both parser and entry as argument to the inih handler. */ struct _desktop_parser_handler_arg { /** Parser. */ const struct desktop_parser *parser; /** The entry to parse into. */ struct desktop_entry *entry_ptr; }; static int _desktop_parser_handler( void *userdata_ptr, const char *section_ptr, const char *name_ptr, const char *value_ptr); static char *_create_locale_key(const char *l, const char *t, const char *m); static char *_dfa_next_strings_element(const char **word_ptr); static char *_dfa_append_char(char *dest_ptr, const char c); static char *_dfa_append_escaped(char *dest_ptr, const char c); static char *_dfa_append_nul(char *dest_ptr, const char c); static char *_dfa_append_bslash_nul(char *dest_ptr, const char c); /** Parser handle. */ struct desktop_parser { /** Describes a key entry, and extracts the optional localization key. */ regex_t key_regex; /** Lookup keys for localized strings, ordered in decreasing priority. */ char *localization_key[4]; /** Length of each of the localized strings. */ size_t localization_key_len[4]; }; static bool _desktop_parser_translate_type( const char *value_ptr, void *dest_ptr); static bool _desktop_parser_translate_boolean( const char *value_ptr, void *dest_ptr); static bool _desktop_parser_translate_string( const char *value_ptr, void *dest_ptr); static bool _desktop_parser_translate_strings( const char *value_ptr, void *dest_ptr); static bool _desktop_parser_translate_exec( const char *value_ptr, void *dest_ptr); static void _desktop_parser_destroy_string(void *dest_ptr); static void _desktop_parser_destroy_strings(void *dest_ptr); /** Descriptor for a key. */ struct key_descriptor { /** The key's name. */ const char *key; /** Length of the key's name. */ size_t len; /** * Offset of the value field, relative to the userdata. */ size_t ofs; /** * Offset of the locale priority indicator. Iff priority_ofs != ofs, the * field is treated as localized. */ size_t priority_ofs; /** Translator function. */ bool (*translate)(const char *value_ptr, void *dest_ptr); /** Dtor. */ void (*destroy)(void *dest_ptr); }; /* == Data ================================================================= */ /** Regular expression describing a (possibly localized) key. */ static const char *key_regex_str = "^" "([A-Za-z0-9-]+)" "(\\[[a-z]{2,3}[a-zA-Z0-9_@\\.-]*\\])?" "$"; /** Actual transition table for extracting one string from "string(s)". */ static const struct _dfa_transition _dfa_transition_table[3][4] = { // Normal state. { { _DFA_STATE_ESCAPED, NULL }, { _DFA_STATE_HALT, _dfa_append_nul }, { _DFA_STATE_HALT, _dfa_append_char }, { _DFA_STATE_NORMAL, _dfa_append_char }, }, // Escaped state. { { _DFA_STATE_NORMAL, _dfa_append_char }, { _DFA_STATE_NORMAL, _dfa_append_char }, { _DFA_STATE_HALT, _dfa_append_bslash_nul }, { _DFA_STATE_NORMAL, _dfa_append_escaped }, }, // Halt. Nothing to do. { { _DFA_STATE_HALT, NULL }, { _DFA_STATE_HALT, NULL }, { _DFA_STATE_HALT, NULL }, { _DFA_STATE_HALT, NULL } } }; /** Group name for the application details in a `.desktop` file. */ static const char *desktop_entry_group_name = "Desktop Entry"; /** Supported keys. */ struct key_descriptor keys[] = { { .key = "Type", .len = strlen("Type"), .ofs = offsetof(struct desktop_entry, type), .priority_ofs = offsetof(struct desktop_entry, type), .destroy = NULL, .translate = _desktop_parser_translate_type, }, { .key = "Hidden", .len = strlen("Hidden"), .ofs = offsetof(struct desktop_entry, hidden), .priority_ofs = offsetof(struct desktop_entry, hidden), .destroy = NULL, .translate = _desktop_parser_translate_boolean, }, { .key = "NoDisplay", .len = strlen("NoDisplay"), .ofs = offsetof(struct desktop_entry, no_display), .priority_ofs = offsetof(struct desktop_entry, no_display), .destroy = NULL, .translate = _desktop_parser_translate_boolean, }, { .key = "Terminal", .len = strlen("Terminal"), .ofs = offsetof(struct desktop_entry, terminal), .priority_ofs = offsetof(struct desktop_entry, terminal), .destroy = NULL, .translate = _desktop_parser_translate_boolean, }, { .key = "Name", .len = strlen("Name"), .ofs = offsetof(struct desktop_entry, name_ptr), .translate = _desktop_parser_translate_string, .destroy = _desktop_parser_destroy_string, .priority_ofs = offsetof(struct desktop_entry, name_priority), }, { .key = "Exec", .len = strlen("Exec"), .ofs = offsetof(struct desktop_entry, exec_ptr), .priority_ofs = offsetof(struct desktop_entry, exec_ptr), .destroy = _desktop_parser_destroy_string, .translate = _desktop_parser_translate_exec }, { .key = "TryExec", .len = strlen("TryExec"), .ofs = offsetof(struct desktop_entry, try_exec_ptr), .priority_ofs = offsetof(struct desktop_entry, try_exec_ptr), .destroy = _desktop_parser_destroy_string, .translate = _desktop_parser_translate_string }, { .key = "Path", .len = strlen("Path"), .ofs = offsetof(struct desktop_entry, path_ptr), .priority_ofs = offsetof(struct desktop_entry, path_ptr), .destroy = _desktop_parser_destroy_string, .translate = _desktop_parser_translate_string }, { .key = "Categories", .len = strlen("Categories"), .ofs = offsetof(struct desktop_entry, category_ptrs), .priority_ofs = offsetof(struct desktop_entry, category_ptrs), .destroy = _desktop_parser_destroy_strings, .translate = _desktop_parser_translate_strings }, { .key = NULL } }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ struct desktop_parser *desktop_parser_create(const char *locale_ptr) { struct desktop_parser *parser = calloc(1, sizeof(struct desktop_parser)); if (NULL == parser) return NULL; if (0 != regcomp(&parser->key_regex, key_regex_str, REG_EXTENDED)) { desktop_parser_destroy(parser); return NULL; } if (NULL != locale_ptr && 0 != strcmp("C", locale_ptr)) { // Splits the locale, see setlocale(3). The locale name is of the form // language[_territory][.codeset][@modifier]. char *language_ptr = strdup(locale_ptr); if (NULL == language_ptr) { desktop_parser_destroy(parser); return NULL; } language_ptr = strtok(language_ptr, "@"); char *modifier_ptr = strtok(NULL, "@"); language_ptr = strtok(language_ptr, "."); strtok(NULL, "."); // Code set. Not used for desktop file key. language_ptr = strtok(language_ptr, "_"); char *territory_ptr = strtok(NULL, "_"); // Matching order. See speficication in "Localized values for keys". parser->localization_key[3] = _create_locale_key( language_ptr, territory_ptr, modifier_ptr); parser->localization_key[2] = _create_locale_key( language_ptr, territory_ptr, NULL); parser->localization_key[1] = _create_locale_key( language_ptr, NULL, modifier_ptr); parser->localization_key[0] = _create_locale_key( language_ptr, NULL, NULL); for (int i = 0; i < 4; ++i) { char *c = parser->localization_key[i]; parser->localization_key_len[i] = c ? strlen(c) : 0; } free(language_ptr); } return parser; } /* ------------------------------------------------------------------------- */ void desktop_parser_destroy(struct desktop_parser *parser) { for (int i = 0; i < 4; ++i) { if (NULL != parser->localization_key[i]) { free(parser->localization_key[i]); } parser->localization_key[i] = NULL; parser->localization_key_len[i] = 0; } regfree(&parser->key_regex); free(parser); } /* ------------------------------------------------------------------------- */ int desktop_parser_file_to_entry( const struct desktop_parser *parser, const char *fname_ptr, struct desktop_entry *entry_ptr) { struct _desktop_parser_handler_arg arg = { .parser = parser, .entry_ptr = entry_ptr }; return ini_parse(fname_ptr, _desktop_parser_handler, &arg); } /* ------------------------------------------------------------------------- */ int desktop_parser_string_to_entry( const struct desktop_parser *parser, const char *string_ptr, struct desktop_entry *entry_ptr) { struct _desktop_parser_handler_arg arg = { .parser = parser, .entry_ptr = entry_ptr }; return ini_parse_string(string_ptr, _desktop_parser_handler, &arg); } /* ------------------------------------------------------------------------- */ void desktop_parser_entry_release(struct desktop_entry *entry_ptr) { for (const struct key_descriptor *key_ptr = &keys[0]; NULL != key_ptr->key; ++key_ptr) { if (key_ptr->destroy) { key_ptr->destroy((char*)entry_ptr + key_ptr->ofs); } } *entry_ptr = (struct desktop_entry){}; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Callback handler for the inih parse methods. Parses "Desktop Entry". * * @param userdata_ptr * @param section_ptr * @param name_ptr * @param value_ptr * * @return 0 on error, and 1 if the name/value was parsed (or skipped for * legitimate reason). */ int _desktop_parser_handler( void *userdata_ptr, const char *section_ptr, const char *name_ptr, const char *value_ptr) { struct _desktop_parser_handler_arg *arg = userdata_ptr; const struct desktop_parser *parser = arg->parser; struct desktop_entry *entry_ptr = arg->entry_ptr; // Skip groups other than the main entry. Remove type? if (0 != strcmp(section_ptr, desktop_entry_group_name)) return 1; // Verify that the key is valid, with optional locale index. regmatch_t m[3]; if (0 != regexec(&parser->key_regex, name_ptr, 3, &m[0], 0)) return 0; // If there is a LOCALE key, attempt to match it with the configured locale // and priority. 0 indicates no LOCALE key, higher value is better match. int priority = 0 < m[2].rm_so; for (; 0 < priority && priority <= 4; ++priority) { size_t len = parser->localization_key_len[priority - 1]; const char *lk = parser->localization_key[priority - 1]; if (0 < m[2].rm_eo - m[2].rm_so && len + 2 == (size_t)(m[2].rm_eo - m[2].rm_so) && 0 == memcmp(lk, name_ptr + m[2].rm_so + 1, len)) break; } // 5 means we didn't have a match: Skip this key/value pair. if (5 <= priority) return 1; // Look for a matching key descriptor, then translate. for (const struct key_descriptor *kd = &keys[0]; kd->key; ++kd) { if (0 >= m[1].rm_eo - m[1].rm_so || kd->len != (size_t)(m[1].rm_eo - m[1].rm_so) || 0 != memcmp(kd->key, name_ptr + m[1].rm_so, kd->len)) continue; // Descriptor doesn't expect a LOCALE, but the key had one: Fail. if (kd->priority_ofs == kd->ofs && 0 < priority) return 0; // Don't overwrite higher priority or earlier values for same priority. if (kd->priority_ofs != kd->ofs) { int8_t stored_priority = ((int8_t*)entry_ptr)[kd->priority_ofs]; if (stored_priority & (1 << priority)) return 0; ((int8_t*)entry_ptr)[kd->priority_ofs] |= 1 << priority; if (stored_priority > (1 << priority)) return 1; // Higher prio. } if (!kd->translate(value_ptr, (char*)entry_ptr+kd->ofs)) return 0; return 1; } // Unknown key. Skip. return 1; } /* ------------------------------------------------------------------------- */ /** * Creates a lookup key for localization. * * @param l Language ("lang"). If NULL, no string is created. * @param t Territory (Freedesktop speficication: "COUNTRY"), may be NULL. * @param m Modifier ("MODIFIER"), may be NULL. * * @return Pointer to an allocated string, holding "lang[_COUNTRY][@MODIFIER]", * or NULL on error, or NULL if `l` was NULL. */ char *_create_locale_key(const char *l, const char *t, const char *m) { if (!l) return NULL; size_t len = strlen(l)+1 + (t ? strlen(t)+1 : 0) + (m ? strlen(m)+1 : 0); char *key_ptr = malloc(len); if (NULL == key_ptr) return NULL; snprintf(key_ptr, len, "%s%s%s%s%s", l, (t ? "_" : ""), (t ? t : ""), (m ? "@" : ""), (m ? m : "")); return key_ptr; } /* ------------------------------------------------------------------------- */ /** * Translates the "Type" key into an enum deskop_entry_type. * * @param value_ptr * @param dest_ptr * * @return true on success. */ bool _desktop_parser_translate_type( const char *value_ptr, void *dest_ptr) { enum desktop_entry_type *entry_type_ptr = dest_ptr; if (0 == strcmp("Application", value_ptr)) { *entry_type_ptr = DESKTOP_ENTRY_TYPE_APPLICATION; return true; } else if (0 == strcmp("Link", value_ptr)) { *entry_type_ptr = DESKTOP_ENTRY_TYPE_LINK; return true; } else if (0 == strcmp("Directory", value_ptr)) { *entry_type_ptr = DESKTOP_ENTRY_TYPE_DIRECTORY; return true; } *entry_type_ptr = DESKTOP_ENTRY_TYPE_UNKNOWN; return false; } /* ------------------------------------------------------------------------- */ /** * Translates a boolean-typed value into a bool at *dest_ptr. * * Accepts also "True" and "False", since there are `.desktop` files with mis- * spelling. The specification restricts it to "true" and "false", though. * https://specifications.freedesktop.org/desktop-entry/latest/value-types.htm. * * @param value_ptr Must either be "false" or "true". * @param dest_ptr Pointer to a bool. * * @return true on success. */ bool _desktop_parser_translate_boolean( const char *value_ptr, void *dest_ptr) { bool *bool_ptr = dest_ptr; if (0 == strcmp("true", value_ptr) || 0 == strcmp("True", value_ptr)) { *bool_ptr = true; return true; } else if (0 == strcmp("false", value_ptr) || 0 == strcmp("False", value_ptr)) { *bool_ptr = false; return true; } *bool_ptr = false; return false; } /* ------------------------------------------------------------------------- */ /** * Translates a string, while un-escaping supported escape codes (s, n, t, r). * * @param value_ptr * @param dest_ptr * * @return true on success. */ bool _desktop_parser_translate_string( const char *value_ptr, void *dest_ptr) { char **str_ptr_ptr = dest_ptr; if (NULL != *str_ptr_ptr) { free(*str_ptr_ptr); } char *d = malloc(strlen(value_ptr) + 1); if (NULL == d) return false; *str_ptr_ptr = d; for (const char *s = value_ptr; *s != '\0'; ++s) { if (*s == '\\') { switch (*++s) { case 's': *d++ = ' '; break; case 'n': *d++ = '\n'; break; case 't': *d++ = '\t'; break; case 'r': *d++ = '\r'; break; case '\\': *d++ = '\\'; break; default: /* Escaped code not specified. Skip. */ break; } } else { *d++ = *s; } } *d = '\0'; return true; } /* ------------------------------------------------------------------------- */ /** * Translates multiple strings, separated by semicolon; as for "Categories". * * The plural version of "string", as defined at * https://specifications.freedesktop.org/desktop-entry/latest/value-types.html. * * @param value_ptr * @param dest_ptr * * @return true on success. */ bool _desktop_parser_translate_strings( const char *value_ptr, void *dest_ptr) { char **ptrs = calloc(1, sizeof(char*)); if (NULL == ptrs) return false; size_t cat_idx = 0; do { char *token_ptr = _dfa_next_strings_element(&value_ptr); if ((NULL != token_ptr) && (0 < strlen(token_ptr))) { char **new_ptrs = realloc(ptrs, (cat_idx + 2) * sizeof(char*)); if (NULL == new_ptrs) { _desktop_parser_destroy_strings(&ptrs); free(token_ptr); return false; } ptrs = new_ptrs; ptrs[cat_idx++] = token_ptr; ptrs[cat_idx] = NULL; token_ptr = NULL; } if (NULL != token_ptr) free(token_ptr); while (';' == *value_ptr) ++value_ptr; } while (*value_ptr); *(char***)dest_ptr = ptrs; return true; } /* ------------------------------------------------------------------------- */ /** * Translates an exec key value, and un-escapes the specific escape codes. * * https://specifications.freedesktop.org/desktop-entry/latest/exec-variables.html * * TODO(kaeser@gubbe.ch): This is... lossy. When un-escaping the arguments, the * result should be stored as separate strings, such as usable for execve(2). * * @param value_ptr * @param dest_ptr * * @return true on success */ bool _desktop_parser_translate_exec( const char *value_ptr, void *dest_ptr) { char **str_ptr_ptr = dest_ptr; if (NULL != *str_ptr_ptr) { free(*str_ptr_ptr); } char *d = calloc(1, (strlen(value_ptr) + 1)); if (NULL == d) return false; *str_ptr_ptr = d; bool quoted_arg = false; for (const char *s = value_ptr; *s != '\0'; ++s) { if (quoted_arg) { if (*s == '"') { quoted_arg = false; } else if (*s == '\\') { switch (*++s) { case '"': *d++ = '"'; break; case '`': *d++ = '`'; break; case '$': *d++ = '$'; break; case '\\': *d++ = '\\'; break; default: /* Invalid escape code. */ return false; } } else { *d++ = *s; } } else { if (*s == '"') { quoted_arg = true; } else if (*s == '%') { if (!strchr("fFuUdDnNickvm", *(s + 1))) return false; ++s; // For now: Skip all (valid) field codes. } else { *d++ = *s; } } } *d = '\0'; return !quoted_arg; // All quotes must be closed. } /* ------------------------------------------------------------------------- */ /** Frees the memory associated with the string. */ void _desktop_parser_destroy_string(void *dest_ptr) { char **str_ptr_ptr = dest_ptr; if (NULL == *str_ptr_ptr) return; free(*str_ptr_ptr); *str_ptr_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Frees the memory associated with the NULL-terminated string array. */ void _desktop_parser_destroy_strings(void *dest_ptr) { char **str_ptrs = *(char***)dest_ptr; if (NULL == str_ptrs) return; for (char **s = str_ptrs; NULL != *s; ++s) free(*s); free(str_ptrs); *(char***)dest_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Returns the next element of a "string(s)" string. */ char *_dfa_next_strings_element(const char **word_ptr) { char *copied_ptr = strdup(*word_ptr); if (NULL == copied_ptr) return NULL; char *dest_ptr = copied_ptr; enum _dfa_state state = _DFA_STATE_NORMAL; while (true) { enum _dfa_class class; switch (**word_ptr) { case '\\': class = _DFA_CLASS_BACKSLASH; break; case ';': class = _DFA_CLASS_SEMICOLON; break; case '\0': class = _DFA_CLASS_NUL; break; default: class = _DFA_CLASS_REGULAR; break; } const struct _dfa_transition *t = &_dfa_transition_table[state][class]; if (NULL != t->process) dest_ptr = t->process(dest_ptr, **word_ptr); state = t->next_state; if (_DFA_STATE_HALT == state) break; ++*word_ptr; } char *result_ptr = strdup(copied_ptr); free(copied_ptr); return result_ptr; } /* ------------------------------------------------------------------------- */ /** Appends the character to the output. */ char *_dfa_append_char(char *dest_ptr, const char c) { *dest_ptr = c; return ++dest_ptr; } /* ------------------------------------------------------------------------- */ /** Appends the corresponnding escaped code to the output. */ char *_dfa_append_escaped(char *dest_ptr, const char c) { switch (c) { case 'n': *dest_ptr++ = '\n'; break; case 's': *dest_ptr++ = ' '; break; case 't': *dest_ptr++ = '\t'; break; case 'r': *dest_ptr++ = '\r'; break; case '\\': *dest_ptr++ = '\\'; break; default: *dest_ptr++ = c; } return dest_ptr; } /* ------------------------------------------------------------------------- */ /** Appends a NUL to the output. */ char *_dfa_append_nul(char *dest_ptr, __UNUSED__ const char c) { *dest_ptr = '\0'; return ++dest_ptr; } /* ------------------------------------------------------------------------- */ /** Appends a backlsash followed by NUL to the output. */ char *_dfa_append_bslash_nul(char *dest_ptr, __UNUSED__ const char c) { *dest_ptr++ = '\\'; *dest_ptr = '\0'; return ++dest_ptr; } /* == Unit tests =========================================================== */ static void _desktop_parser_test_ini_string(bs_test_t *test_ptr); static void _desktop_parser_test_ini_file(bs_test_t *test_ptr); static void _desktop_parser_test_locale_string(bs_test_t *test_ptr); static void _desktop_parser_test_translate(bs_test_t *test_ptr); static void _desktop_parser_test_translate_strings(bs_test_t *test_ptr); /** Test cases for action items. */ static const bs_test_case_t _desktop_parser_test_cases[] = { { true, "ini_string", _desktop_parser_test_ini_string }, { true, "ini_file", _desktop_parser_test_ini_file }, { true, "locale_string", _desktop_parser_test_locale_string }, { true, "translate", _desktop_parser_test_translate }, { true, "translate_strings", _desktop_parser_test_translate_strings }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t desktop_parser_test_set = BS_TEST_SET( true, "desktop-parser", _desktop_parser_test_cases); /* ------------------------------------------------------------------------- */ /** Tests parsing INI content, but from a string. */ void _desktop_parser_test_ini_string(bs_test_t *test_ptr) { struct desktop_parser *p = desktop_parser_create("en_US.UTF-8@euro"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, p); struct desktop_entry e = {}; static const char *i = "\ [Desktop Entry]\n\ Exec=TheBinary\n\ Categories=System;Compositor\n\ Name[en]=Name1\n\ Name[en_US@euro]=Name2\n\ Name[en_US]=Name3\n\ Name[de]=DerName"; BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e)); BS_TEST_VERIFY_STREQ(test_ptr, "TheBinary", e.exec_ptr); BS_TEST_VERIFY_STREQ(test_ptr, "Name2", e.name_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 4)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, e.category_ptrs); BS_TEST_VERIFY_STREQ(test_ptr, "System", e.category_ptrs[0]); BS_TEST_VERIFY_STREQ(test_ptr, "Compositor", e.category_ptrs[1]); BS_TEST_VERIFY_EQ(test_ptr, NULL, e.category_ptrs[2]); desktop_parser_entry_release(&e); desktop_parser_destroy(p); } /* ------------------------------------------------------------------------- */ /** Tests parsing a sample .desktop file. */ void _desktop_parser_test_ini_file(bs_test_t *test_ptr) { struct desktop_parser *p = desktop_parser_create("en_US.UTF-8@euro"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, p); struct desktop_entry e = {}; const char *f = bs_test_data_path(test_ptr, "wlmaker.desktop"); BS_TEST_VERIFY_EQ_OR_RETURN( test_ptr, 0, desktop_parser_file_to_entry(p, f, &e)); BS_TEST_VERIFY_EQ(test_ptr, DESKTOP_ENTRY_TYPE_APPLICATION, e.type); BS_TEST_VERIFY_STREQ(test_ptr, "WaylandMaker", e.name_ptr); BS_TEST_VERIFY_STREQ(test_ptr, "/usr/local/bin/wlmaker", e.exec_ptr); BS_TEST_VERIFY_STREQ(test_ptr, "./wlmaker", e.try_exec_ptr); BS_TEST_VERIFY_STREQ(test_ptr, "/usr/local", e.path_ptr); BS_TEST_VERIFY_FALSE(test_ptr, e.hidden); BS_TEST_VERIFY_FALSE(test_ptr, e.no_display); BS_TEST_VERIFY_TRUE(test_ptr, e.terminal); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, e.category_ptrs); BS_TEST_VERIFY_STREQ(test_ptr, "System", e.category_ptrs[0]); BS_TEST_VERIFY_STREQ(test_ptr, "Compositor", e.category_ptrs[1]); BS_TEST_VERIFY_EQ(test_ptr, NULL, e.category_ptrs[2]); desktop_parser_entry_release(&e); desktop_parser_destroy(p); } /* ------------------------------------------------------------------------- */ /** Tests parsing localized strings. */ void _desktop_parser_test_locale_string(bs_test_t *test_ptr) { // For convenience. const char *gn = desktop_entry_group_name; int (*h)(void *, const char *, const char *, const char *) = _desktop_parser_handler; struct desktop_entry e = {}; struct _desktop_parser_handler_arg ha = { .parser = desktop_parser_create("en_US.UTF-8@euro"), .entry_ptr = &e }; BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ha.parser); // 'Exec' is not a localestring. Fail. BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Exec[en]", "x")); // Name is a localestring. Exercise increasing priority. BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name", "n0")); BS_TEST_VERIFY_STREQ(test_ptr, "n0", e.name_ptr); // Cannot set twice. BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name", "n10")); BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en]", "n1")); BS_TEST_VERIFY_STREQ(test_ptr, "n1", e.name_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 1)); BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name", "n0")); e.name_priority = 1 << 1; BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name", "n0")); BS_TEST_VERIFY_STREQ(test_ptr, "n1", e.name_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en@euro]", "n2")); BS_TEST_VERIFY_STREQ(test_ptr, "n2", e.name_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 2)); BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name[en]", "n1")); e.name_priority = 1 << 2; BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en]", "n1")); BS_TEST_VERIFY_STREQ(test_ptr, "n2", e.name_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en_US]", "n3")); BS_TEST_VERIFY_STREQ(test_ptr, "n3", e.name_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 3)); BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name[en@euro]", "n2")); BS_TEST_VERIFY_STREQ(test_ptr, "n3", e.name_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, h(&ha, gn, "Name[en_US@euro]", "n4")); BS_TEST_VERIFY_STREQ(test_ptr, "n4", e.name_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, e.name_priority & (1 << 4)); BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name[en_US@euro]", "n4")); BS_TEST_VERIFY_EQ(test_ptr, 0, h(&ha, gn, "Name[en_US]", "n3")); BS_TEST_VERIFY_STREQ(test_ptr, "n4", e.name_ptr); desktop_parser_destroy((struct desktop_parser*)ha.parser); desktop_parser_entry_release(&e); } /* ------------------------------------------------------------------------- */ /** Tests translators: Escaped string. */ void _desktop_parser_test_translate(bs_test_t *test_ptr) { struct desktop_parser *p = desktop_parser_create("en_US.UTF-8@euro"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, p); struct desktop_entry e = {}; const char *i = "[Desktop Entry]\nName=A\\sB\\nC\\tD\\rE\\\\F\\xG"; BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e)); BS_TEST_VERIFY_STREQ(test_ptr, "A B\nC\tD\rE\\FG", e.name_ptr); i = "[Desktop Entry]\nExec=a %f %U \"a \\` \\\" \\$ \\\\ \""; BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e)); BS_TEST_VERIFY_STREQ(test_ptr, "a a ` \" $ \\ ", e.exec_ptr); i = "[Desktop Entry]\nTerminal=true"; BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e)); BS_TEST_VERIFY_TRUE(test_ptr, e.terminal); i = "[Desktop Entry]\nTerminal=True"; BS_TEST_VERIFY_EQ(test_ptr, 0, desktop_parser_string_to_entry(p, i, &e)); BS_TEST_VERIFY_TRUE(test_ptr, e.terminal); desktop_parser_entry_release(&e); desktop_parser_destroy(p); } /* ------------------------------------------------------------------------- */ /** Tests translation of multi-strings. */ void _desktop_parser_test_translate_strings(bs_test_t *test_ptr) { // First: Some test cases for the escaper + tokenizer. struct _test_data { const char *input_ptr; const char *expected_ptr ; const char next_input; } test_data[] = { { "token", "token", '\0' }, { "token;", "token", ';' }, { "to;ken;", "to", ';' }, { "to\\;ken", "to;ken", '\0' }, { "token\\", "token\\", '\0' }, { "E\\nS\\sC\\tA\\rP\\\\E", "E\nS C\tA\rP\\E", '\0' }, { NULL, NULL, '\0' } }; for (const struct _test_data *t = test_data; t->input_ptr; t++) { const char *arg = t->input_ptr; char *result_ptr = _dfa_next_strings_element(&arg); BS_TEST_VERIFY_STREQ(test_ptr, t->expected_ptr, result_ptr); BS_TEST_VERIFY_EQ(test_ptr, t->next_input, *arg); free(result_ptr); } // Then: Test the actual translator, ensuring it returns the array. char **spp = NULL; const char *s = "simple"; BS_TEST_VERIFY_TRUE(test_ptr, _desktop_parser_translate_strings(s, &spp)); BS_TEST_VERIFY_STREQ(test_ptr, "simple", spp[0]); BS_TEST_VERIFY_EQ(test_ptr, NULL, spp[1]); _desktop_parser_destroy_strings(&spp); s = "semi;colon;;"; BS_TEST_VERIFY_TRUE(test_ptr, _desktop_parser_translate_strings(s, &spp)); BS_TEST_VERIFY_STREQ(test_ptr, "semi", spp[0]); BS_TEST_VERIFY_STREQ(test_ptr, "colon", spp[1]); BS_TEST_VERIFY_EQ(test_ptr, NULL, spp[2]); _desktop_parser_destroy_strings(&spp); s = "one;two;th\\;ree;;;four"; BS_TEST_VERIFY_TRUE(test_ptr, _desktop_parser_translate_strings(s, &spp)); BS_TEST_VERIFY_STREQ(test_ptr, "one", spp[0]); BS_TEST_VERIFY_STREQ(test_ptr, "two", spp[1]); BS_TEST_VERIFY_STREQ(test_ptr, "th;ree", spp[2]); BS_TEST_VERIFY_STREQ(test_ptr, "four", spp[3]); BS_TEST_VERIFY_EQ(test_ptr, NULL, spp[4]); _desktop_parser_destroy_strings(&spp); } /* == End of parse.c ======================================================= */ wlmaker-0.8.1/tool/desktop-parser/desktop-parser.h0000644000175100017510000001107415222270710021625 0ustar runnerrunner/* ========================================================================= */ /** * @file desktop-parser.h * * Simple parser for FreeDesktop `.desktop` files, to provide application- * specific information within a compositor and for constructing application * menus. Depends on `libinih-dev`. * * Reference: * * http://specifications.freedesktop.org/desktop-entry/1.5/ * * https://github.com/benhoyt/inih * * Currently built to support the necessary keys for building the root menu * for Wayland Maker. Specifically, that includes: * * [*] Type * * [*] NoDisplay * * [*] Hidden * * [*] Terminal * * [*] Exec * * [*] Name * * [*] Categories * * [*] TryExec * * [*] Path * * Further improvements: * * Handle the %f, %u, ... specifiers. * * Add support for "numeric" type. Though, it's currently unused for .desktop. * * Use the "Terminal" flag and construct a command that executes in terminal. * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2025 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_DESKTOP_PARSER_H__ #define __WLMAKER_DESKTOP_PARSER_H__ #include #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus struct desktop_parser; /** Permissible values for `Type=...`. */ enum desktop_entry_type { DESKTOP_ENTRY_TYPE_UNKNOWN = 0, DESKTOP_ENTRY_TYPE_APPLICATION = 1, DESKTOP_ENTRY_TYPE_LINK = 2, DESKTOP_ENTRY_TYPE_DIRECTORY = 3, }; /** Holds information for one desktop entry. */ struct desktop_entry { /** Type of the desktop entry. */ enum desktop_entry_type type; /** Whether this desktop entry had been deleted (to be ignored). */ bool hidden; /** Whether to exclude this entry from the menus. */ bool no_display; /** Whether the program runs in a terminal window. */ bool terminal; /** Helper for localized "Name". */ int8_t name_priority; /** Localized specific name of the application. */ char *name_ptr; /** Program to execute, possibly with arguments. */ char *exec_ptr; /** Path to executable, used to determine if the program is installed. */ char *try_exec_ptr; /** The working directory to run the program in. */ char *path_ptr; /** An array of strings, each indicating a category. NULL-terminated. */ char **category_ptrs; }; /** * Creates a desktop parser, using the provided locale. * * @param locale_ptr Locale set for `LC_MESSAGES`. See setlocale(3). * * @return Pointer to the desktop parser, or NULL on error. Must be destroyed * by calling @ref desktop_parser_destroy. */ struct desktop_parser *desktop_parser_create(const char *locale_ptr); /** * Destroys the desktop parser. * * @param parser */ void desktop_parser_destroy(struct desktop_parser *parser); /** * Parses a file into the provided entry. * * @param parser * @param fname_ptr * @param entry_ptr * * @return 0 on success, or the line number where the parser failed. */ int desktop_parser_file_to_entry( const struct desktop_parser *parser, const char *fname_ptr, struct desktop_entry *entry_ptr); /** * Parses an in-memory string into the provided entry. * * @param parser * @param string_ptr * @param entry_ptr * * @return 0 on success, or the line number where the parser failed. */ int desktop_parser_string_to_entry( const struct desktop_parser *parser, const char *string_ptr, struct desktop_entry *entry_ptr); /** * Releases the resources associated to the entry. * * @param entry_ptr */ void desktop_parser_entry_release(struct desktop_entry *entry_ptr); /** Unit test set. */ extern const bs_test_set_t desktop_parser_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // __WLMAKER_DESKTOP_PARSER_H__ /* == End of desktop-parser.h ============================================== */ wlmaker-0.8.1/tool/desktop-parser/CMakeLists.txt0000644000175100017510000000213315222270710021245 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) add_library(desktop-parser STATIC desktop-parser.c) target_link_libraries( desktop-parser PRIVATE libbase inih) target_include_directories( desktop-parser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") set_target_properties( desktop-parser PROPERTIES VERSION 1.0 PUBLIC_HEADER desktop-parser.h) if(iwyu_path_and_options) set_target_properties( desktop-parser PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8.1/tool/CMakeLists.txt0000644000175100017510000000310115222270710016276 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) add_library(libwlmtool STATIC item.c menu.c) target_include_directories( libwlmtool PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${LIBXDGBASEDIR_INCLUDE_DIRS}") target_link_libraries( libwlmtool PRIVATE libbase libbase_plist desktop-parser PkgConfig::LIBXDGBASEDIR) add_executable( wlmtool wlmtool.c) target_link_libraries( wlmtool libwlmtool libbase) target_compile_definitions( wlmtool PRIVATE "WLMAKER_VERSION_MAJOR=\"${WLMAKER_VERSION_MAJOR}\"" "WLMAKER_VERSION_MINOR=\"${WLMAKER_VERSION_MINOR}\"" "WLMAKER_VERSION_FULL=\"${WLMAKER_VERSION_FULL}\"") install( TARGETS wlmtool DESTINATION "${CMAKE_INSTALL_BINDIR}") if(iwyu_path_and_options) set_target_properties( wlmtool PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( libwlmtool PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() add_subdirectory(desktop-parser) wlmaker-0.8.1/src/0000755000175100017510000000000015222270710013355 5ustar runnerrunnerwlmaker-0.8.1/src/wlmbacktrace.c0000644000175100017510000000771215222270710016167 0ustar runnerrunner/* ========================================================================= */ /** * @file backtrace.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * Copyright (c) 2025 by Philipp Kaeser */ #include "wlmbacktrace.h" #include #include #include #include #include #if defined(WLMAKER_HAVE_LIBBACKTRACE) #include #endif // defined(WLMAKER_HAVE_LIBBACKTRACE) /* == Declarations ========================================================= */ #if defined(WLMAKER_HAVE_LIBBACKTRACE) /** State for libbacktrace. */ static struct backtrace_state *_wlmaker_bt_state_ptr; static void _backtrace_error_callback( __UNUSED__ void *data_ptr, const char *msg_ptr, int errnum); static int _backtrace_full_callback( __UNUSED__ void *data, uintptr_t pc, const char *filename_ptr, int line_num, const char *function_ptr); static void _signal_backtrace(int signum); #endif // defined(WLMAKER_HAVE_LIBBACKTRACE) /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmaker_backtrace_setup(const char *filename_ptr) { #if defined(WLMAKER_HAVE_LIBBACKTRACE) _wlmaker_bt_state_ptr = backtrace_create_state( filename_ptr, 0, _backtrace_error_callback, NULL); if (NULL == _wlmaker_bt_state_ptr) { bs_log(BS_ERROR, "Failed backtrace_create_state()"); return false; } signal(SIGABRT, _signal_backtrace); signal(SIGBUS, _signal_backtrace); signal(SIGFPE, _signal_backtrace); signal(SIGILL, _signal_backtrace); signal(SIGSEGV, _signal_backtrace); #else bs_log(BS_DEBUG, "No libbacktrace, ignoring setup for %s", filename_ptr); #endif // defined(WLMAKER_HAVE_LIBBACKTRACE) return true; } /* == Local (static) methods =============================================== */ #if defined(WLMAKER_HAVE_LIBBACKTRACE) /* ------------------------------------------------------------------------- */ /** Error callback for libbacktrace calls. */ void _backtrace_error_callback( __UNUSED__ void *data_ptr, const char *msg_ptr, int errnum) { bs_log_severity_t severity = BS_ERROR; if (0 != errnum && -1 != errnum) severity |= BS_ERRNO; bs_log(severity, "Backtrace error: %s", msg_ptr); } /* ------------------------------------------------------------------------- */ /** Full callback for printing a libbacktrace frame. */ int _backtrace_full_callback( __UNUSED__ void *data, uintptr_t program_counter, const char *filename_ptr, int line_num, const char *function_ptr) { bs_log(BS_ERROR, "%"PRIxPTR" in %s () at %s:%d", program_counter, function_ptr ? function_ptr : "(unknown)", filename_ptr ? filename_ptr : "(unknown)", line_num); return 0; } /* ------------------------------------------------------------------------- */ /** Signal handler: Prints a backtrace. */ void _signal_backtrace(int signum) { bs_log(BS_ERROR, "Caught signal %d", signum); backtrace_full( _wlmaker_bt_state_ptr, 0, _backtrace_full_callback, _backtrace_error_callback, NULL); signal(SIGABRT, SIG_DFL); abort(); } #endif // defined(WLMAKER_HAVE_LIBBACKTRACE) /* == End of backtrace.c =================================================== */ wlmaker-0.8.1/src/server.h0000644000175100017510000002100215222270710015027 0ustar runnerrunner/* ========================================================================= */ /** * @file server.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_SERVER_H__ #define __WLMAKER_SERVER_H__ #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "input/manager.h" /** A handle for a wlmaker server. */ typedef struct _wlmaker_server_t wlmaker_server_t; #include "backend/backend.h" #include "config.h" #include "corner.h" // IWYU pragma: keep #include "cursor.h" // IWYU pragma: keep #include "files.h" #include "icon_manager.h" // IWYU pragma: keep #include "input_observation.h" #include "idle.h" // IWYU pragma: keep #include "layer_shell.h" // IWYU pragma: keep #include "lock_mgr.h" // IWYU pragma: keep #include "root_menu.h" // IWYU pragma: keep #include "subprocess_monitor.h" // IWYU pragma: keep #include "toolkit/toolkit.h" #include "xdg_decoration.h" // IWYU pragma: keep #include "xdg_shell.h" // IWYU pragma: keep #include "xwl.h" // IWYU pragma: keep #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Options for the Wayland server. */ typedef struct { /** Whether to start XWayland. */ bool start_xwayland; /** Desired output width, for windowed mode. 0 for no preference. */ uint32_t width; /** Desired output height, for windowed mode. 0 for no preference. */ uint32_t height; /** Whether to include 'Logo' to modifiers. */ bool bind_with_logo; } wlmaker_server_options_t; /** State of the Wayland server. */ struct _wlmaker_server_t { /** Files module handle. */ wlmaker_files_t *files_ptr; /** Configuration dictionnary. */ bspl_dict_t *config_dict_ptr; /** Copy of the options. */ const wlmaker_server_options_t *options_ptr; /** Wayland display. */ struct wl_display *wl_display_ptr; /** Name of the socket for clients to connect. */ const char *wl_socket_name_ptr; /** Session lock manager. */ wlmaker_lock_mgr_t *lock_mgr_ptr; /** Idle monitor. */ wlmaker_idle_monitor_t *idle_monitor_ptr; /** WLR viewporter. */ struct wlr_viewporter *wlr_viewporter_ptr; /** Fractional scale manager. */ struct wlr_fractional_scale_manager_v1 *wlr_fractional_scale_manager_ptr; /** wlroots seat. */ struct wlr_seat *wlr_seat_ptr; /** The scene graph API. */ struct wlr_scene *wlr_scene_ptr; /** wlroots output layout. */ struct wlr_output_layout *wlr_output_layout_ptr; // Clipboard and selection support. /** Provides the wl_data_device protocol for clipboard (Ctrl+C/V). */ struct wlr_data_device_manager *wlr_data_device_manager_ptr; /** Listener to approve clipboard selection requests. */ struct wl_listener request_set_selection_listener; /** Provides the primary selection protocol for middle-click paste. */ struct wlr_primary_selection_v1_device_manager *wlr_primary_selection_v1_device_manager_ptr; /** Listener to approve primary selection requests. */ struct wl_listener request_set_primary_selection_listener; /** Enables clipboard managers and tools (wl-copy, wl-paste). */ struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1_ptr; /** The XDG Shell handler. */ wlmaker_xdg_shell_t *xdg_shell_ptr; /** The XDG decoration manager. */ wlmaker_xdg_decoration_manager_t *xdg_decoration_manager_ptr; /** Layer shell handler. */ wlmaker_layer_shell_t *layer_shell_ptr; /** Backend handler. */ wlmbe_backend_t *backend_ptr; /** Input device manager. */ wlmim_t *input_manager_ptr; /** Any activity from the input manager. */ struct wl_listener input_activity_listener; /** Hack: Deactivate task list, when losing 'ALT' modifier. */ struct wl_listener deactivate_task_list_listener; /** Icon manager. */ wlmaker_icon_manager_t *icon_manager_ptr; /** Input observation. */ wlmaker_input_observation_manager_t *input_observation_manager_ptr; /** * XWayland interface. Will be set only if compiled with XWayland, through * WLMAKER_HAVE_XWAYLAND defined. * And through setting @ref wlmaker_server_options_t::start_xwayland. */ wlmaker_xwl_t *xwl_ptr; /** The root element. */ wlmtk_root_t *root_ptr; /** Whether the task list is currently shown. */ bool task_list_enabled; /** Signal: When the task list is enabled. (to be shown) */ struct wl_signal task_list_enabled_event; /** Signal: When the task list is disabled. (to be hidden) */ struct wl_signal task_list_disabled_event; /** Called when the theme has changed. */ struct wl_signal theme_changed_event; /** Clients for this server. */ bs_dllist_t clients; /** Subprocess monitoring. */ wlmaker_subprocess_monitor_t *monitor_ptr; /** Montor & handler of 'hot corners'. */ wlmaker_corner_t *corner_ptr; // TODO(kaeser@gubbe.ch): Move these events into a 'registry' struct, so // it can be more easily shared throughout the code. /** Signal: Triggered whenever a window is created. */ struct wl_signal window_created_event; /** Signal: Triggered whenever a window is destroyed. */ struct wl_signal window_destroyed_event; /** Temporary: Points to the @ref wlmtk_dock_t of the clip. */ wlmtk_dock_t *clip_dock_ptr; /** Root menu, when active. NULL when not invoked. */ wlmaker_root_menu_t *root_menu_ptr; /** Parsed contents of the root menu definition, from plist. */ bspl_array_t *root_menu_array_ptr; /** Listener for `unclaimed_button_event` signal raised by `wlmtk_root`. */ struct wl_listener unclaimed_button_event_listener; /** From @ref wlmaker_background::dlnode. */ bs_dllist_t backgrounds; /** The current configuration style. */ wlmaker_config_style_t *style_ptr; }; /** * Creates the server and initializes all needed sub-modules. * * @param config_dict_ptr Configuration, as dictionary object. The server * will keep a reference on it until destroyed. * @param files_ptr Files handle. Must outlive the server. * @param style_ptr Style. Must outlive the server. * @param options_ptr Options for the server. The server expects the * pointed area to outlive the server. * * @return The server handle or NULL on failure. The handle must be freed by * calling wlmaker_server_destroy(). */ wlmaker_server_t *wlmaker_server_create( bspl_dict_t *config_dict_ptr, wlmaker_files_t *files_ptr, wlmaker_config_style_t *style_ptr, const wlmaker_server_options_t *options_ptr); /** * Destroys the server handle, as created by wlmaker_server_create(). * * @param server_ptr */ void wlmaker_server_destroy(wlmaker_server_t *server_ptr); /** * Activates the task list. * * @param server_ptr */ void wlmaker_server_activate_task_list(wlmaker_server_t *server_ptr); /** * Looks up which output serves the current cursor coordinates and returns that. * * @param server_ptr * * @return Pointer to the output at the seat's cursor position. */ struct wlr_output *wlmaker_server_get_output_at_cursor( wlmaker_server_t *server_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMAKER_SERVER_H__ */ /* == End of server.h ================================================== */ wlmaker-0.8.1/src/action_item.h0000644000175100017510000000653415222270710016031 0ustar runnerrunner/* ========================================================================= */ /** * @file action_item.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_ACTION_ITEM_H__ #define __WLMAKER_ACTION_ITEM_H__ #include #include #include /** Forward declaration: An action-triggering menu item. */ typedef struct _wlmaker_action_item_t wlmaker_action_item_t; #include "action.h" #include "server.h" #include "toolkit/toolkit.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Descriptor for creating a menu item triggering an action. */ typedef struct { /** Text for the menu item. */ const char *text_ptr; /** The action to trigger. */ wlmaker_action_t action; /** Extra argument for @ref wlmaker_action_execute. */ char *action_arg_ptr; /** * Where to store the @ref wlmaker_action_item_t, relative to the * `dest_ptr` argument of @ref wlmaker_action_item_create_from_desc. */ size_t destination_ofs; } wlmaker_action_item_desc_t; /** * Creates a menu item that triggers a @ref wlmaker_action_t. * * @param text_ptr * @param style_ref_ptr * @param action * @param action_arg_ptr Extra argument. Will be duplicated. * @param server_ptr * * @return Pointer to the menu item's handle or NULL on error. */ wlmaker_action_item_t *wlmaker_action_item_create( const char *text_ptr, wlmtk_menu_style_ref_t *style_ref_ptr, wlmaker_action_t action, const char *action_arg_ptr, wlmaker_server_t *server_ptr); /** * Creates a menu item triggering an action item from a descriptor. * * @param desc_ptr * @param dest_ptr * @param style_ref_ptr * @param server_ptr * * @return Pointer to the item's handle or NULL on error. */ wlmaker_action_item_t *wlmaker_action_item_create_from_desc( const wlmaker_action_item_desc_t *desc_ptr, void *dest_ptr, wlmtk_menu_style_ref_t *style_ref_ptr, wlmaker_server_t *server_ptr); /** @returns pointer to the superclass @ref wlmtk_menu_item_t. */ wlmtk_menu_item_t *wlmaker_action_item_menu_item( wlmaker_action_item_t *action_item_ptr); /** * Binds an action to a menu item. * * @param menu_item_ptr * @param action * @param action_arg_ptr * @param server_ptr * * @return true on success. */ bool wlmaker_menu_item_bind_action( wlmtk_menu_item_t* menu_item_ptr, wlmaker_action_t action, const char *action_arg_ptr, wlmaker_server_t *server_ptr); /** Unit test set. */ extern const bs_test_set_t wlmaker_action_item_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMAKER_ACTION_ITEM_H__ */ /* == End of action_item.h ================================================= */ wlmaker-0.8.1/src/layer_shell.c0000644000175100017510000001135615222270710016032 0ustar runnerrunner/* ========================================================================= */ /** * @file layer_shell.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "layer_shell.h" #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "layer_panel.h" #include "toolkit/toolkit.h" #include "server.h" /* == Declarations ========================================================= */ /** State of Layer Shell handler. */ struct _wlmaker_layer_shell_t { /** wlroots Layer Shell v1 handler. */ struct wlr_layer_shell_v1 *wlr_layer_shell_v1_ptr; /** Back-link to the server. */ wlmaker_server_t *server_ptr; /** Listener for the `new_surface` signal raised by `wlr_layer_shell_v1`. */ struct wl_listener new_surface_listener; /** Listener for the `destroy` signal raised by `wlr_layer_shell_v1`. */ struct wl_listener destroy_listener; }; static void handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void handle_new_surface( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_layer_shell_t *wlmaker_layer_shell_create(wlmaker_server_t *server_ptr) { wlmaker_layer_shell_t *layer_shell_ptr = logged_calloc( 1, sizeof(wlmaker_layer_shell_t)); if (NULL == layer_shell_ptr) return NULL; layer_shell_ptr->server_ptr = server_ptr; layer_shell_ptr->wlr_layer_shell_v1_ptr = wlr_layer_shell_v1_create( server_ptr->wl_display_ptr, 4 /* version */); if (NULL == layer_shell_ptr->wlr_layer_shell_v1_ptr) { bs_log(BS_ERROR, "Failed wlr_layer_shell_v1_create()"); return NULL; } wlmtk_util_connect_listener_signal( &layer_shell_ptr->wlr_layer_shell_v1_ptr->events.new_surface, &layer_shell_ptr->new_surface_listener, handle_new_surface); wlmtk_util_connect_listener_signal( &layer_shell_ptr->wlr_layer_shell_v1_ptr->events.destroy, &layer_shell_ptr->destroy_listener, handle_destroy); return layer_shell_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_layer_shell_destroy(wlmaker_layer_shell_t *layer_shell_ptr) { wlmtk_util_disconnect_listener(&layer_shell_ptr->destroy_listener); wlmtk_util_disconnect_listener(&layer_shell_ptr->new_surface_listener); free(layer_shell_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Event handler for the `destroy` signal raised by `wlr_layer_shell_v1`. * * @param listener_ptr * @param data_ptr */ void handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_layer_shell_t *layer_shell_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_layer_shell_t, destroy_listener); wlmaker_layer_shell_destroy(layer_shell_ptr); } /* ------------------------------------------------------------------------- */ /** * Event handler for the `new_surface` signal raised by `wlr_layer_shell_v1`. * * @param listener_ptr * @param data_ptr */ void handle_new_surface( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_layer_shell_t *layer_shell_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_layer_shell_t, new_surface_listener); struct wlr_layer_surface_v1 *wlr_layer_surface_v1_ptr = data_ptr; if (NULL == wlr_layer_surface_v1_ptr->output) { wlr_layer_surface_v1_ptr->output = wlmaker_server_get_output_at_cursor( layer_shell_ptr->server_ptr); } wlmaker_layer_panel_t *layer_panel_ptr = wlmaker_layer_panel_create( wlr_layer_surface_v1_ptr, layer_shell_ptr->server_ptr); if (NULL == layer_panel_ptr) { wlr_layer_surface_v1_destroy(wlr_layer_surface_v1_ptr); } } /* == End of layer_shell.c ================================================= */ wlmaker-0.8.1/src/corner.h0000644000175100017510000000405415222270710015021 0ustar runnerrunner/* ========================================================================= */ /** * @file corner.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __CORNER_H__ #define __CORNER_H__ /** Forward declaration: State of hot corner monitor. */ typedef struct _wlmaker_corner_t wlmaker_corner_t; #include #include #include "task_list.h" struct wl_event_loop; struct wl_signal; struct wlr_cursor; struct wlr_output_layout; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates the hot-corner handler. * * @param hot_corner_config_dict_ptr * @param wl_event_loop_ptr * @param wlr_output_layout_ptr * @param wlr_cursor_ptr * @param cursor_position_updated_ptr * @param server_ptr * * @return Pointer to the hot-corner monitor. */ wlmaker_corner_t *wlmaker_corner_create( bspl_dict_t *hot_corner_config_dict_ptr, struct wl_event_loop *wl_event_loop_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_cursor *wlr_cursor_ptr, struct wl_signal *cursor_position_updated_ptr, wlmaker_server_t *server_ptr); /** * Destroys the hot-corner handler. * * @param corner_ptr */ void wlmaker_corner_destroy(wlmaker_corner_t *corner_ptr); /** Unit test set. */ extern const bs_test_set_t wlmaker_corner_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __CORNER_H__ */ /* == End of corner.h ====================================================== */ wlmaker-0.8.1/src/icon_manager.h0000644000175100017510000000343615222270710016156 0ustar runnerrunner/* ========================================================================= */ /** * @file icon_manager.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __ICON_MANAGER_H__ #define __ICON_MANAGER_H__ struct wl_display; /** Forward declaration: Icon Manager handle. */ typedef struct _wlmaker_icon_manager_t wlmaker_icon_manager_t; /** Forward declaration: Toplevel icon handle. */ typedef struct _wlmaker_toplevel_icon_t wlmaker_toplevel_icon_t; #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates an icon manager. * * @param wl_display_ptr * @param server_ptr * * @return The handle of the icon manager or NULL on error. Must be destroyed * by calling @ref wlmaker_icon_manager_destroy. */ wlmaker_icon_manager_t *wlmaker_icon_manager_create( struct wl_display *wl_display_ptr, wlmaker_server_t *server_ptr); /** * Destroys the Icon Manager. * * @param icon_manager_ptr */ void wlmaker_icon_manager_destroy( wlmaker_icon_manager_t *icon_manager_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __ICON_MANAGER_H__ */ /* == End of icon_manager.h ================================================ */ wlmaker-0.8.1/src/xdg_decoration.c0000644000175100017510000005471215222270710016523 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_decoration.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "xdg_decoration.h" #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include "config.h" #include "toolkit/toolkit.h" #include "xdg_shell.h" #include "xdg_toplevel.h" /* == Declarations ========================================================= */ /** State of the XDG decoration manager. */ struct _wlmaker_xdg_decoration_manager_t { /** The wlroots XDG decoration manager. */ struct wlr_xdg_decoration_manager_v1* wlr_xdg_decoration_manager_v1_ptr; /** Injectable, for tests: wlr_xdg_toplevel_decoration_v1_set_mode(). */ uint32_t (*set_mode)( struct wlr_xdg_toplevel_decoration_v1 *decoration, enum wlr_xdg_toplevel_decoration_v1_mode mode); /** Operation mode for the decoration manager. */ wlmaker_config_decoration_t mode; /** Listener for `new_toplevel_decoration`. */ struct wl_listener new_toplevel_decoration_listener; /** Listener for `destroy` of `wlr_xdg_decoration_manager_v1`. */ struct wl_listener destroy_listener; }; /** A decoration handle. */ typedef struct { /** Points to the wlroots `wlr_xdg_toplevel_decoration_v1`. */ struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_toplevel_decoration_v1_ptr; /** Back-link to the decoration manager. */ wlmaker_xdg_decoration_manager_t *decoration_manager_ptr; /** Listener for `request_mode` of `wlr_xdg_toplevel_decoration_v1`. */ struct wl_listener request_mode_listener; /** Listener for `destroy` of `wlr_xdg_toplevel_decoration_v1.` */ struct wl_listener destroy_listener; /** Listener for `commit` of `wlr_surface::events`. */ struct wl_listener surface_commit_listener; /** Listener for `destroy` of `wlr_surface::events`. */ struct wl_listener surface_destroy_listener; } wlmaker_xdg_decoration_t; static void handle_new_toplevel_decoration( struct wl_listener *listener_ptr, void *data_ptr); static void handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static wlmaker_xdg_decoration_t *wlmaker_xdg_decoration_create( wlmaker_xdg_decoration_manager_t *decoration_manager_ptr, struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_toplevel_decoration_v1_ptr); static void wlmaker_xdg_decoration_destroy( wlmaker_xdg_decoration_t *decoration_ptr); static void handle_decoration_request_mode( struct wl_listener *listener_ptr, void *data_ptr); static void handle_decoration_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _xdg_decoration_handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr); static void _xdg_decoration_handle_surface_destroy( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Plist descriptor of decoration mode. @see wlmaker_config_decoration_t. */ static const bspl_enum_desc_t _wlmaker_config_decoration_desc[] = { BSPL_ENUM("SuggestClient", WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT), BSPL_ENUM("SuggestServer", WLMAKER_CONFIG_DECORATION_SUGGEST_SERVER), BSPL_ENUM("EnforceClient", WLMAKER_CONFIG_DECORATION_ENFORCE_CLIENT), BSPL_ENUM("EnforceServer", WLMAKER_CONFIG_DECORATION_ENFORCE_SERVER), BSPL_ENUM_SENTINEL() }; /** Plist descriptor of the 'Decoration' dict contents. */ static const bspl_desc_t _wlmaker_xdg_decoration_config_desc[] = { BSPL_DESC_ENUM("Mode", true, wlmaker_xdg_decoration_manager_t, mode, mode, WLMAKER_CONFIG_DECORATION_SUGGEST_SERVER, _wlmaker_config_decoration_desc), BSPL_DESC_SENTINEL() }; /** Name of the top-level dict holding the decoration manager's config. */ static const char *_wlmaker_xdg_decoration_dict_name = "Decoration"; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_xdg_decoration_manager_t *wlmaker_xdg_decoration_manager_create( struct wl_display *wl_display_ptr, bspl_dict_t *config_dict_ptr) { wlmaker_xdg_decoration_manager_t *decoration_manager_ptr = logged_calloc( 1, sizeof(wlmaker_xdg_decoration_manager_t)); if (NULL == decoration_manager_ptr) return NULL; decoration_manager_ptr->set_mode = wlr_xdg_toplevel_decoration_v1_set_mode; decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr = wlr_xdg_decoration_manager_v1_create(wl_display_ptr); if (NULL == decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr) { wlmaker_xdg_decoration_manager_destroy(decoration_manager_ptr); return NULL; } bspl_dict_t *decoration_dict_ptr = bspl_dict_ref( bspl_dict_get_dict(config_dict_ptr, _wlmaker_xdg_decoration_dict_name)); if (NULL == decoration_dict_ptr) { bs_log(BS_ERROR, "No '%s' dict.", _wlmaker_xdg_decoration_dict_name); wlmaker_xdg_decoration_manager_destroy(decoration_manager_ptr); return NULL; } bool decoded = bspl_decode_dict( decoration_dict_ptr, _wlmaker_xdg_decoration_config_desc, decoration_manager_ptr); bspl_dict_unref(decoration_dict_ptr); if (!decoded) { bs_log(BS_ERROR, "Failed to decode '%s' dict", _wlmaker_xdg_decoration_dict_name); wlmaker_xdg_decoration_manager_destroy(decoration_manager_ptr); return NULL; } wlmtk_util_connect_listener_signal( &decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr-> events.new_toplevel_decoration, &decoration_manager_ptr->new_toplevel_decoration_listener, handle_new_toplevel_decoration); wlmtk_util_connect_listener_signal( &decoration_manager_ptr->wlr_xdg_decoration_manager_v1_ptr-> events.destroy, &decoration_manager_ptr->destroy_listener, handle_destroy); return decoration_manager_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_xdg_decoration_manager_destroy( wlmaker_xdg_decoration_manager_t *decoration_manager_ptr) { wlmtk_util_disconnect_listener(&decoration_manager_ptr->new_toplevel_decoration_listener); wlmtk_util_disconnect_listener(&decoration_manager_ptr->destroy_listener); free(decoration_manager_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Handler for the `new_toplevel_decoration` signal of * `wlr_xdg_decoration_manager_v1`. * * @param listener_ptr * @param data_ptr Points to `wlr_xdg_toplevel_decoration_v1`. */ void handle_new_toplevel_decoration( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xdg_decoration_manager_t *decoration_manager_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_decoration_manager_t, new_toplevel_decoration_listener); struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_toplevel_decoration_v1_ptr = data_ptr; wlmaker_xdg_decoration_t *decoration_ptr = wlmaker_xdg_decoration_create( decoration_manager_ptr, wlr_xdg_toplevel_decoration_v1_ptr); if (NULL == decoration_ptr) return; handle_decoration_request_mode( &decoration_ptr->request_mode_listener, NULL); } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal of `wlr_xdg_decoration_manager_v1`. * * @param listener_ptr * @param data_ptr */ static void handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_decoration_manager_t *decoration_manager_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_decoration_manager_t, destroy_listener); free(decoration_manager_ptr); } /* ------------------------------------------------------------------------- */ /** * Creates a decoration handle. * * @param decoration_manager_ptr * @param wlr_xdg_toplevel_decoration_v1_ptr * * @returns The decoration handle or NULL on error. */ wlmaker_xdg_decoration_t *wlmaker_xdg_decoration_create( wlmaker_xdg_decoration_manager_t *decoration_manager_ptr, struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_toplevel_decoration_v1_ptr) { wlmaker_xdg_decoration_t *decoration_ptr = logged_calloc( 1, sizeof(wlmaker_xdg_decoration_t)); if (NULL == decoration_ptr) return NULL; decoration_ptr->decoration_manager_ptr = decoration_manager_ptr; decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr = wlr_xdg_toplevel_decoration_v1_ptr; wlmtk_util_connect_listener_signal( &decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->events.destroy, &decoration_ptr->destroy_listener, handle_decoration_destroy); wlmtk_util_connect_listener_signal( &decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->events.request_mode, &decoration_ptr->request_mode_listener, handle_decoration_request_mode); struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr = decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->toplevel; wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->base->surface->events.commit, &decoration_ptr->surface_commit_listener, _xdg_decoration_handle_surface_commit); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->base->surface->events.destroy, &decoration_ptr->surface_destroy_listener, _xdg_decoration_handle_surface_destroy); return decoration_ptr; } /* ------------------------------------------------------------------------- */ /** * Destroys the decoration handle. * * @param decoration_ptr */ void wlmaker_xdg_decoration_destroy(wlmaker_xdg_decoration_t *decoration_ptr) { _xdg_decoration_handle_surface_destroy( &decoration_ptr->surface_destroy_listener, NULL); wlmtk_util_disconnect_listener(&decoration_ptr->destroy_listener); wlmtk_util_disconnect_listener(&decoration_ptr->request_mode_listener); free(decoration_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `request_mode` signal of `wlr_xdg_toplevel_decoration_v1`. * * @param listener_ptr * @param data_ptr */ void handle_decoration_request_mode( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_decoration_t *decoration_ptr = wl_container_of( listener_ptr, decoration_ptr, request_mode_listener); struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr = decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->toplevel; enum wlr_xdg_toplevel_decoration_v1_mode mode = decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->requested_mode; switch (decoration_ptr->decoration_manager_ptr->mode) { case WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT: if (WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE == mode) { mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; } break; case WLMAKER_CONFIG_DECORATION_SUGGEST_SERVER: if (WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE == mode) { mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; } break; case WLMAKER_CONFIG_DECORATION_ENFORCE_CLIENT: mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; break; case WLMAKER_CONFIG_DECORATION_ENFORCE_SERVER: mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; break; default: bs_log(BS_FATAL, "Unhandled config_decoration value %d", decoration_ptr->decoration_manager_ptr->mode); BS_ABORT(); } if (wlr_xdg_toplevel_ptr->base->initialized) { decoration_ptr->decoration_manager_ptr->set_mode( decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr, mode); } struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = wlr_xdg_toplevel_ptr->base->data; if (NULL == wlmaker_xdg_toplevel_ptr) { bs_log(BS_WARNING, "Decoration request for XDG toplevel %p w/o handle?", wlr_xdg_toplevel_ptr); return; } bs_log(BS_INFO, "XDG decoration request_mode for XDG surface %p, " "XDG toplevel handle %p: Current %d, pending %d, scheduled %d, " "requested %d. Set: %d", wlr_xdg_toplevel_ptr->base->surface, wlmaker_xdg_toplevel_ptr, decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->current.mode, decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->pending.mode, decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->scheduled_mode, decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->requested_mode, mode); wlmaker_xdg_toplevel_set_server_side_decorated( wlmaker_xdg_toplevel_ptr, mode != WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal of `wlr_xdg_toplevel_decoration_v1`. * * @param listener_ptr * @param data_ptr */ void handle_decoration_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_decoration_t *decoration_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_decoration_t, destroy_listener); wlmaker_xdg_decoration_destroy(decoration_ptr); } /* ------------------------------------------------------------------------- */ /** * Handles surface commit: If initialized, set_mode and unsubscribe. * * @param listener_ptr * @param data_ptr */ void _xdg_decoration_handle_surface_commit( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_decoration_t *decoration_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_decoration_t, surface_commit_listener); struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr = decoration_ptr->wlr_xdg_toplevel_decoration_v1_ptr->toplevel; if (!wlr_xdg_toplevel_ptr->base->initialized) return; // Initialized! Unsubscribe from surface, and trigger a request_mode. _xdg_decoration_handle_surface_destroy( &decoration_ptr->surface_destroy_listener, NULL); handle_decoration_request_mode( &decoration_ptr->request_mode_listener, NULL); } /* ------------------------------------------------------------------------- */ /** * Handles surface destroy: Unsubscribe surface listeners. * * @param listener_ptr * @param data_ptr */ void _xdg_decoration_handle_surface_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_decoration_t *decoration_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_decoration_t, surface_destroy_listener); wlmtk_util_disconnect_listener(&decoration_ptr->surface_commit_listener); wlmtk_util_disconnect_listener(&decoration_ptr->surface_destroy_listener); } /* == Unit tests =========================================================== */ static void test_manager(bs_test_t *test_ptr); static void test_decoration_initialized(bs_test_t *test_ptr); static void test_decoration_uninitialized(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_xdg_decoration_test_cases[] = { { true, "manager", test_manager }, { true, "decoration_initialized", test_decoration_initialized }, { true, "decoration_uninitialized", test_decoration_uninitialized }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_xdg_decoration_test_set = BS_TEST_SET( true, "xdg_decoration", wlmaker_xdg_decoration_test_cases); /** Argument to injected set_mode. */ struct _xdg_decoration_test_arg { /** The decoration handle. */ struct wlr_xdg_toplevel_decoration_v1 decoration; /** Counter for calls to wlmaker_xdg_decoration_manager_t::set_mode. */ int set_mode_calls; /** Last `mode` arg to wlmaker_xdg_decoration_manager_t::set_mode. */ enum wlr_xdg_toplevel_decoration_v1_mode set_mode_arg; }; /** Injected method, for wlr_xdg_toplevel_decoration_v1_set_mode(). */ static uint32_t _xdg_decoration_fake_set_mode( __UNUSED__ struct wlr_xdg_toplevel_decoration_v1 *decoration_ptr, enum wlr_xdg_toplevel_decoration_v1_mode mode) { struct _xdg_decoration_test_arg *arg_ptr = BS_CONTAINER_OF( decoration_ptr, struct _xdg_decoration_test_arg, decoration); ++arg_ptr->set_mode_calls; arg_ptr->set_mode_arg = mode; return 0; } /* ------------------------------------------------------------------------- */ /** Setup and teardown of XDG decoration manager. */ void test_manager(bs_test_t *test_ptr) { static const char *c = "{ Decoration = { Mode = SuggestClient }}"; struct wl_display *wl_display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_display_ptr); bspl_object_t *o = bspl_create_object_from_plist_string(c); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); wlmaker_xdg_decoration_manager_t *d = wlmaker_xdg_decoration_manager_create( wl_display_ptr, bspl_dict_from_object(o)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, d); wlmaker_xdg_decoration_manager_destroy(d); bspl_object_unref(o); wl_display_destroy(wl_display_ptr); } /* ------------------------------------------------------------------------- */ /** Test decoration for an initialized surface. */ void test_decoration_initialized(bs_test_t *test_ptr) { wlmaker_xdg_decoration_manager_t m = { .mode = WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT, .set_mode = _xdg_decoration_fake_set_mode }; struct wlr_surface ws = {}; wl_signal_init(&ws.events.commit); wl_signal_init(&ws.events.destroy); struct wlr_xdg_surface s = { .initialized = true, .surface = &ws }; struct wlr_xdg_toplevel tl = { .base = &s }; struct _xdg_decoration_test_arg t = { .decoration = { .toplevel = &tl } }; wl_signal_init(&t.decoration.events.destroy); wl_signal_init(&t.decoration.events.request_mode); // New decoration: Set_mode right away. handle_new_toplevel_decoration( &m.new_toplevel_decoration_listener, &t.decoration); BS_TEST_VERIFY_EQ(test_ptr, 1, t.set_mode_calls); BS_TEST_VERIFY_EQ( test_ptr, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE, t.set_mode_arg); // Upon request_mode: Respond with set_mode. wl_signal_emit(&t.decoration.events.request_mode, NULL); BS_TEST_VERIFY_EQ(test_ptr, 2, t.set_mode_calls); BS_TEST_VERIFY_EQ( test_ptr, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE, t.set_mode_arg); // Client-side mode is kept. t.decoration.requested_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; wl_signal_emit(&t.decoration.events.request_mode, NULL); BS_TEST_VERIFY_EQ( test_ptr, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE, t.set_mode_arg); // Server-side mode is kept, too. t.decoration.requested_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; wl_signal_emit(&t.decoration.events.request_mode, NULL); BS_TEST_VERIFY_EQ( test_ptr, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE, t.set_mode_arg); wl_signal_emit(&t.decoration.events.destroy, NULL); } /* ------------------------------------------------------------------------- */ /** Test decoration for an uninitialized surface. */ void test_decoration_uninitialized(bs_test_t *test_ptr) { wlmaker_xdg_decoration_manager_t m = { .mode = WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT, .set_mode = _xdg_decoration_fake_set_mode }; struct wlr_surface ws = {}; wl_signal_init(&ws.events.commit); wl_signal_init(&ws.events.destroy); struct wlr_xdg_surface s = { .initialized = false, .surface = &ws }; struct wlr_xdg_toplevel tl = { .base = &s }; struct _xdg_decoration_test_arg t = { .decoration = { .toplevel = &tl } }; wl_signal_init(&t.decoration.events.destroy); wl_signal_init(&t.decoration.events.request_mode); t.decoration.requested_mode = WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; // New decoration: Do not set_mode right away. handle_new_toplevel_decoration( &m.new_toplevel_decoration_listener, &t.decoration); BS_TEST_VERIFY_EQ(test_ptr, 0, t.set_mode_calls); // A surface commit, but still not initialized: Keep. wl_signal_emit(&ws.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 0, t.set_mode_calls); // Set to initialized. A surface commit triggers set_mode. s.initialized = true; wl_signal_emit(&ws.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, t.set_mode_calls); BS_TEST_VERIFY_EQ( test_ptr, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE, t.set_mode_arg); wl_signal_emit(&t.decoration.events.destroy, NULL); // Reset surface. Not initialized. A request_mode won't set_mode. t.set_mode_calls = 0; s.initialized = false; handle_new_toplevel_decoration( &m.new_toplevel_decoration_listener, &t.decoration); wl_signal_emit(&t.decoration.events.request_mode, NULL); BS_TEST_VERIFY_EQ(test_ptr, 0, t.set_mode_calls); // A surface commit, but still not initialized: Keep. wl_signal_emit(&ws.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 0, t.set_mode_calls); // Set to initialized. A surface commit triggers set_mode. s.initialized = true; wl_signal_emit(&ws.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, t.set_mode_calls); BS_TEST_VERIFY_EQ( test_ptr, WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE, t.set_mode_arg); wl_signal_emit(&t.decoration.events.destroy, NULL); } /* == End of xdg_decoration.c ============================================== */ wlmaker-0.8.1/src/wlmaker.c0000644000175100017510000004105515222270710015170 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmaker.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /// setenv() is a POSIX extension. #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include #include #include #include /// Use non-stable features of wlroots. #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "../share/theme.h" // IWYU pragma: keep #include "action.h" #include "backend/backend.h" #include "background.h" #include "wlmbacktrace.h" #include "clip.h" #include "config.h" #include "dock.h" #include "files.h" #include "root_menu.h" #include "server.h" #include "task_list.h" #include "toolkit/toolkit.h" /** Will hold the value of --config_file. */ static char *wlmaker_arg_config_file_ptr = NULL; /** Will hold the value of --state_file. */ static char *wlmaker_arg_state_file_ptr = NULL; /** Will hold the value of --theme_file. */ static char *wlmaker_arg_theme_file_ptr = NULL; /** Will hold the value of --root_menu_file. */ static char *wlmaker_arg_root_menu_file_ptr = NULL; #if !defined(WLMAKER_VERSION_MAJOR) || !defined(WLMAKER_VERSION_MINOR) || !defined(WLMAKER_VERSION_FULL) #eror "WLMAKER_VERSION_... not defined!" #else static const char *wlmaker_version_major = WLMAKER_VERSION_MAJOR; static const char *wlmaker_version_minor = WLMAKER_VERSION_MINOR; static const char *wlmaker_version_full = WLMAKER_VERSION_FULL; #endif /** Startup options for the server. */ static wlmaker_server_options_t wlmaker_server_options = { .start_xwayland = false, .width = 0, .height = 0, .bind_with_logo = false, }; /** Log levels. */ static const bs_arg_enum_table_t wlmaker_log_levels[] = { { .name_ptr = "DEBUG", BS_DEBUG }, { .name_ptr = "INFO", BS_INFO }, { .name_ptr = "WARNING", BS_WARNING }, { .name_ptr = "ERROR", BS_ERROR }, { .name_ptr = NULL }, }; /** Definition of commandline arguments. */ static const bs_arg_t wlmaker_args[] = { #if defined(WLMAKER_HAVE_XWAYLAND) BS_ARG_BOOL( "start_xwayland", "Optional: Whether to start XWayland. Disabled by default.", false, &wlmaker_server_options.start_xwayland), #endif // defined(WLMAKER_HAVE_XWAYLAND) BS_ARG_STRING( "config_file", "Optional: Path to a configuration file. If not provided, wlmaker " "will scan default paths for a configuration file, or fall back to " "a built-in configuration.", NULL, &wlmaker_arg_config_file_ptr), BS_ARG_STRING( "state_file", "Optional: Path to a state file, with state of workspaces, dock and " "clips configured. If not provided, wlmaker will scan default paths " "for a state file, or fall back to a built-in default.", NULL, &wlmaker_arg_state_file_ptr), BS_ARG_STRING( "theme_file", "Optional: Path to a \"theme\" file, configuring the visual style for " "elements. If not provided, wlmaker will use a built-in default theme.", NULL, &wlmaker_arg_theme_file_ptr), BS_ARG_STRING( "root_menu_file", "Optional: Path to a file describing the root menu. If not provided, " "wlmaker will use a built-in definition for the root menu.", NULL, &wlmaker_arg_root_menu_file_ptr), BS_ARG_ENUM( "log_level", "Log level to apply. One of DEBUG, INFO, WARNING, ERROR.", "INFO", &wlmaker_log_levels[0], (int*)&bs_log_severity), BS_ARG_BOOL( "bind_with_logo", "Optional: Whether to add 'Logo' as modifier to each key binding. " "Useful to test key bindings in wlmaker when running as a Wayland " "or X11 client. Disabled by default.", false, &wlmaker_server_options.bind_with_logo), BS_ARG_UINT32( "height", "Desired output height. Applies when running in windowed mode, and " "only if --width is set, too. Set to 0 for using the output's " "preferred dimensions.", 0, 0, UINT32_MAX, &wlmaker_server_options.height), BS_ARG_UINT32( "width", "Desired output width. Applies when running in windowed mode, and " "only if --height is set, too. Set to 0 for using the output's " "preferred dimensions.", 0, 0, UINT32_MAX, &wlmaker_server_options.width), BS_ARG_SENTINEL() }; /** References auto-started subprocesses. */ static bs_ptr_stack_t wlmaker_subprocess_stack; /** Compiled regular expression for extracting file & line no. from wlr_log. */ static regex_t wlmaker_wlr_log_regex; /** Regular expression string for extracting file & line no. from wlr_log. */ static const char *wlmaker_wlr_log_regex_string = "^\\[([^\\:]+)\\:([0-9]+)\\]\\ "; /** Contents of the workspace style. */ typedef struct { /** Workspace name. */ char name[32]; /** Background color. */ uint32_t color; } wlmaker_workspace_style_t; /** Style descriptor for the "Workspace" dict of wlmaker-state.plist. */ static const bspl_desc_t wlmaker_workspace_style_desc[] = { BSPL_DESC_CHARBUF( "Name", true, wlmaker_workspace_style_t, name, name, 32, NULL), BSPL_DESC_ARGB32( "Color", false, wlmaker_workspace_style_t, color, color, 0), BSPL_DESC_SENTINEL() }; /* ------------------------------------------------------------------------- */ /** * Wraps the wlr_log calls on bs_log. * * @param importance * @param fmt * @param args */ static void wlr_to_bs_log( enum wlr_log_importance importance, const char *fmt, va_list args) { bs_log_severity_t severity = BS_DEBUG; switch (importance) { case WLR_SILENT: // Fall-through to DEBUG severity. case WLR_DEBUG: severity = BS_DEBUG; break; case WLR_INFO: severity = BS_INFO; break; case WLR_ERROR: severity = BS_ERROR; break; default: severity = BS_INFO; break; } if (!bs_will_log(severity)) return; // Log to buffer. Ignores overflows. char buf[BS_LOG_MAX_BUF_SIZE]; vsnprintf(buf, sizeof(buf), fmt, args); regmatch_t matches[4]; if (0 != regexec(&wlmaker_wlr_log_regex, buf, 4, &matches[0], 0) || matches[0].rm_so != 0 || !(matches[0].rm_eo >= 6) || // Minimum "[x:1] ". matches[1].rm_so != 1 || !(matches[2].rm_so > 2) || matches[3].rm_so != -1) { bs_log(severity, "%s (wlr_log unexpected format!)", buf); return; } buf[matches[1].rm_eo] = '\0'; buf[matches[2].rm_eo] = '\0'; uint64_t line_no = 0; bs_strconvert_uint64(&buf[matches[2].rm_so], &line_no, 10); line_no = BS_MIN((uint64_t)INT_MAX, line_no); bs_log_write(severity, &buf[matches[1].rm_so], (int)line_no, "%s", &buf[matches[0].rm_eo]); } /* ------------------------------------------------------------------------- */ /** Launches a sub-process, and keeps it on the subprocess stack. */ bool start_subprocess(const char *cmdline_ptr) { bs_subprocess_t *sp_ptr = bs_subprocess_create_cmdline(cmdline_ptr); if (NULL == sp_ptr) { bs_log(BS_ERROR, "Failed bs_subprocess_create_cmdline(\"%s\")", cmdline_ptr); return false; } if (!bs_subprocess_start(sp_ptr)) { bs_log(BS_ERROR, "Failed bs_subprocess_start for \"%s\"", cmdline_ptr); return false; } // Push to stack. Ignore errors: We'll keep it running untracked. bs_ptr_stack_push(&wlmaker_subprocess_stack, sp_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Creates workspaces as configured in the state dict. */ bool create_workspaces( bspl_dict_t *state_dict_ptr, wlmaker_server_t *server_ptr) { bspl_array_t *array_ptr = bspl_dict_get_array( state_dict_ptr, "Workspaces"); if (NULL == array_ptr) return false; bool rv = true; for (size_t i = 0; i < bspl_array_size(array_ptr); ++i) { bspl_dict_t *dict_ptr = bspl_dict_from_object( bspl_array_at(array_ptr, i)); if (NULL == dict_ptr) { bs_log(BS_ERROR, "Array element in \"Workspaces\" is not a dict"); rv = false; break; } wlmaker_workspace_style_t s; if (!bspl_decode_dict(dict_ptr, wlmaker_workspace_style_desc, &s)) { bs_log(BS_ERROR, "Failed to decode dict element %zu in \"Workspace\"", i); rv = false; break; } wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create( server_ptr->wlr_output_layout_ptr, s.name, &server_ptr->style_ptr->tile); if (NULL == workspace_ptr) { bs_log(BS_ERROR, "Failed wlmtk_workspace_create(\"%s\")", s.name); rv = false; break; } if (s.color == 0) { s.color = server_ptr->style_ptr->background_color; } bs_dllist_node_t *bg_dlnode_ptr = wlmaker_background_create( workspace_ptr, server_ptr->wlr_output_layout_ptr, s.color); if (NULL == bg_dlnode_ptr) { bs_log(BS_ERROR, "Failed wlmaker_background()"); rv = false; break; } bs_dllist_push_back(&server_ptr->backgrounds, bg_dlnode_ptr); wlmtk_root_add_workspace(server_ptr->root_ptr, workspace_ptr); } return rv; } /* == Main program ========================================================= */ /** The main program. */ int main(__UNUSED__ int argc, __UNUSED__ const char **argv) { wlmaker_dock_t *dock_ptr = NULL; wlmaker_clip_t *clip_ptr = NULL; wlmaker_task_list_t *task_list_ptr = NULL; int rv = EXIT_SUCCESS; if (!wlmaker_backtrace_setup(argv[0])) return EXIT_FAILURE; rv = regcomp( &wlmaker_wlr_log_regex, wlmaker_wlr_log_regex_string, REG_EXTENDED); if (0 != rv) { char err_buf[512]; regerror(rv, &wlmaker_wlr_log_regex, err_buf, sizeof(err_buf)); bs_log(BS_ERROR, "Failed compiling regular expression: %s", err_buf); return EXIT_FAILURE; } wlr_log_init(WLR_DEBUG, wlr_to_bs_log); bs_log_severity = BS_INFO; // Will be overwritten in bs_arg_parse(). BS_ASSERT(bs_ptr_stack_init(&wlmaker_subprocess_stack)); if (!bs_arg_parse(wlmaker_args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv)) { fprintf(stderr, "Failed to parse commandline arguments.\n"); bs_arg_print_usage(stderr, wlmaker_args); return EXIT_FAILURE; } for (int i = 1; i < argc; ++i) { if (0 == strcmp(argv[i], "--help")) { bs_arg_print_usage(stdout, wlmaker_args); return EXIT_SUCCESS; } else if (0 == strcmp(argv[i], "--version")) { fprintf(stdout, "wlmaker version %s.%s (%s)\n", wlmaker_version_major, wlmaker_version_minor, wlmaker_version_full); return EXIT_SUCCESS; } else { bs_log(BS_ERROR, "Unhandled extra argument \"%s\"", argv[i]); return EXIT_FAILURE; } } bs_log(BS_INFO, "Starting wlmaker %s.%s (%s)", wlmaker_version_major, wlmaker_version_minor, wlmaker_version_full); wlmaker_files_t *files_ptr = wlmaker_files_create("wlmaker"); if (NULL == files_ptr) { bs_log(BS_ERROR, "Failed wlmaker_files_create(\"wlmaker\")"); return EXIT_FAILURE; } bspl_dict_t *config_dict_ptr = wlmaker_config_load( files_ptr, wlmaker_arg_config_file_ptr); if (NULL != wlmaker_arg_config_file_ptr) free(wlmaker_arg_config_file_ptr); if (NULL == config_dict_ptr) { fprintf(stderr, "Failed to load & initialize configuration.\n"); return EXIT_FAILURE; } bspl_dict_t *state_dict_ptr = wlmaker_state_load( files_ptr, wlmaker_arg_state_file_ptr); if (NULL != wlmaker_arg_state_file_ptr) free(wlmaker_arg_state_file_ptr); if (NULL == state_dict_ptr) { fprintf(stderr, "Failed to load & initialize state.\n"); return EXIT_FAILURE; } const char *theme_file_ptr = wlmaker_arg_theme_file_ptr; if (NULL == theme_file_ptr) { theme_file_ptr = bspl_dict_get_string_value( config_dict_ptr, "ThemeFile"); } wlmaker_config_style_t style = {}; if (!wlmaker_theme_load(files_ptr, theme_file_ptr, &style)) { fprintf(stderr, "Failed to load & initialize theme.\n"); return EXIT_FAILURE; } wlmaker_server_t *server_ptr = wlmaker_server_create( config_dict_ptr, files_ptr, &style, &wlmaker_server_options); if (NULL == server_ptr) return EXIT_FAILURE; // TODO(kaeser@gubbe.ch): Uh, that's ugly... server_ptr->root_menu_ptr = wlmaker_root_menu_create( server_ptr, wlmaker_arg_root_menu_file_ptr, wlmtk_window_style_to_ref(server_ptr->style_ptr->window_style_ptr), wlmtk_menu_style_to_ref(server_ptr->style_ptr->menu_style_ptr)); if (NULL == server_ptr->root_menu_ptr) { return EXIT_FAILURE; } wlmtk_menu_set_open( wlmaker_root_menu_menu(server_ptr->root_menu_ptr), false); wlmaker_action_handle_t *action_handle_ptr = wlmaker_action_bind_keys( server_ptr, bspl_dict_get_dict(config_dict_ptr, wlmaker_action_config_dict_key), wlmaker_server_options.bind_with_logo); if (NULL == action_handle_ptr) { bs_log(BS_ERROR, "Failed to bind keys."); return EXIT_FAILURE; } if (!create_workspaces(state_dict_ptr, server_ptr)) { return EXIT_FAILURE; } rv = EXIT_SUCCESS; if (wlr_backend_start(wlmbe_backend_wlr(server_ptr->backend_ptr))) { if (0 >= wlmbe_num_outputs(server_ptr->wlr_output_layout_ptr)) { bs_log(BS_ERROR, "No outputs available!"); return EXIT_FAILURE; } bs_log(BS_INFO, "Starting Wayland compositor for server %p at %s ...", server_ptr, server_ptr->wl_socket_name_ptr); setenv("WAYLAND_DISPLAY", server_ptr->wl_socket_name_ptr, true); bspl_array_t *autostarted_ptr = bspl_dict_get_array( config_dict_ptr, "Autostart"); if (NULL != autostarted_ptr) { for (size_t i = 0; i < bspl_array_size(autostarted_ptr); ++i) { const char *cmd_ptr = bspl_array_string_value_at( autostarted_ptr, i); if (!start_subprocess(cmd_ptr)) return EXIT_FAILURE; } } dock_ptr = wlmaker_dock_create( server_ptr, state_dict_ptr, &style); clip_ptr = wlmaker_clip_create( server_ptr, state_dict_ptr, &style); task_list_ptr = wlmaker_task_list_create( server_ptr, &style.task_list); if (NULL == dock_ptr || NULL == clip_ptr || NULL == task_list_ptr) { bs_log(BS_ERROR, "Failed to create dock, clip or task list."); } else { wl_display_run(server_ptr->wl_display_ptr); } } else { bs_log(BS_ERROR, "Failed wlmaker_server_start()"); rv = EXIT_FAILURE; } if (NULL != task_list_ptr) wlmaker_task_list_destroy(task_list_ptr); if (NULL != clip_ptr) wlmaker_clip_destroy(clip_ptr); if (NULL != dock_ptr) wlmaker_dock_destroy(dock_ptr); wlmaker_action_unbind_keys(action_handle_ptr); bspl_array_unref(server_ptr->root_menu_array_ptr); wlmaker_server_destroy(server_ptr); bs_subprocess_t *sp_ptr; while (NULL != (sp_ptr = bs_ptr_stack_pop(&wlmaker_subprocess_stack))) { bs_subprocess_destroy(sp_ptr); } bs_ptr_stack_fini(&wlmaker_subprocess_stack); bspl_decoded_destroy(wlmaker_config_style_desc, &style); bspl_dict_unref(config_dict_ptr); bspl_dict_unref(state_dict_ptr); regfree(&wlmaker_wlr_log_regex); return rv; } /* == End of wlmaker.c ===================================================== */ wlmaker-0.8.1/src/idle.c0000644000175100017510000003571015222270710014444 0ustar runnerrunner/* ========================================================================= */ /** * @file idle.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "idle.h" #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "subprocess_monitor.h" #include "toolkit/toolkit.h" #include "server.h" /* == Declarations ========================================================= */ /** Forward declaration: Handle of an idle inhibitor. */ typedef struct _wlmaker_idle_inhibitor_t wlmaker_idle_inhibitor_t; /** State of the idle monitor. */ struct _wlmaker_idle_monitor_t { /** Back-link to the server. */ wlmaker_server_t *server_ptr; /** Dictionnary holding the 'ScreenLock' configuration. */ bspl_dict_t *lock_config_dict_ptr; /** Reference to the event loop. */ struct wl_event_loop *wl_event_loop_ptr; /** The timer's event source. */ struct wl_event_source *timer_event_source_ptr; /** Whether the timer expired. Reset in @ref wlmaker_idle_monitor_reset. */ bool timer_expired; /** Listener for `new_inhibitor` of wlr_idle_inhibit_manager_v1`. */ struct wl_listener new_inhibitor_listener; /** Lists registered inhibitors: @ref wlmaker_idle_inhibitor_t::dlnode. */ bs_dllist_t idle_inhibitors; /** * Counter for inhibits. Timer-triggered locks are taking effect only * when inhibits == 0. */ int inhibits; /** Listener for @ref wlmtk_root_events_t::unlock_event. */ struct wl_listener unlock_listener; /** The wlroots idle inhibit manager. */ struct wlr_idle_inhibit_manager_v1 *wlr_idle_inhibit_manager_v1_ptr; /** Whether the idle monitor is locked. Prevents timer registry. */ bool locked; }; /** State of an idle inhibitor. */ struct _wlmaker_idle_inhibitor_t { /** Back-link to the idle monitor. */ wlmaker_idle_monitor_t *idle_monitor_ptr; /** The idle inhibitor tied to this inhibitor. */ struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor_v1_ptr; /** List node, part of @ref wlmaker_idle_monitor_t::idle_inhibitors. */ bs_dllist_node_t dlnode; /** Listener for the `destroy` signal of `wlr_idle_inhibitor_v1`. */ struct wl_listener destroy_listener; }; static void _wlmaker_idle_monitor_consider_locking( wlmaker_idle_monitor_t *idle_monitor_ptr); static int _wlmaker_idle_monitor_timer(void *data_ptr); static int _wlmaker_idle_msec(wlmaker_idle_monitor_t *idle_monitor_ptr); static bool _wlmaker_idle_monitor_add_inhibitor( wlmaker_idle_monitor_t *idle_monitor_ptr, struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor_v1_ptr); static void _wlmaker_idle_monitor_handle_destroy_inhibitor( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_idle_monitor_handle_new_inhibitor( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_idle_monitor_handle_unlock( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_idle_monitor_t *wlmaker_idle_monitor_create( wlmaker_server_t *server_ptr) { wlmaker_idle_monitor_t *monitor_ptr = logged_calloc( 1, sizeof(wlmaker_idle_monitor_t)); if (NULL == monitor_ptr) return NULL; monitor_ptr->server_ptr = server_ptr; monitor_ptr->wl_event_loop_ptr = wl_display_get_event_loop( server_ptr->wl_display_ptr); monitor_ptr->lock_config_dict_ptr = bspl_dict_ref( bspl_dict_get_dict(server_ptr->config_dict_ptr, "ScreenLock")); if (NULL == monitor_ptr->lock_config_dict_ptr) { bs_log(BS_ERROR, "No 'ScreenLock' dict found in config."); wlmaker_idle_monitor_destroy(monitor_ptr); return NULL; } monitor_ptr->wlr_idle_inhibit_manager_v1_ptr = wlr_idle_inhibit_v1_create(server_ptr->wl_display_ptr); if (NULL == monitor_ptr->wlr_idle_inhibit_manager_v1_ptr) { bs_log(BS_ERROR, "Failed wlr_idle_inhibit_v1_create(%p)", server_ptr->wl_display_ptr); wlmaker_idle_monitor_destroy(monitor_ptr); return NULL; } wlmtk_util_connect_listener_signal( &monitor_ptr->wlr_idle_inhibit_manager_v1_ptr->events.new_inhibitor, &monitor_ptr->new_inhibitor_listener, _wlmaker_idle_monitor_handle_new_inhibitor); monitor_ptr->timer_event_source_ptr = wl_event_loop_add_timer( monitor_ptr->wl_event_loop_ptr, _wlmaker_idle_monitor_timer, monitor_ptr); if (NULL == monitor_ptr->timer_event_source_ptr) { bs_log(BS_ERROR, "Failed wl_event_loop_add_timer(%p, %p, %p)", monitor_ptr->wl_event_loop_ptr, _wlmaker_idle_monitor_timer, monitor_ptr); wlmaker_idle_monitor_destroy(monitor_ptr); return NULL; } if (0 != wl_event_source_timer_update( monitor_ptr->timer_event_source_ptr, _wlmaker_idle_msec(monitor_ptr))) { bs_log(BS_ERROR, "Failed wl_event_source_timer_update(%p, 1000)", monitor_ptr->timer_event_source_ptr); wlmaker_idle_monitor_destroy(monitor_ptr); return NULL; } return monitor_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_idle_monitor_destroy(wlmaker_idle_monitor_t *idle_monitor_ptr) { if (NULL != idle_monitor_ptr->unlock_listener.link.prev) { wlmtk_util_disconnect_listener(&idle_monitor_ptr->unlock_listener); } if (NULL != idle_monitor_ptr->timer_event_source_ptr) { wl_event_source_remove(idle_monitor_ptr->timer_event_source_ptr); idle_monitor_ptr->timer_event_source_ptr = NULL; } if (NULL != idle_monitor_ptr->lock_config_dict_ptr) { bspl_dict_unref(idle_monitor_ptr->lock_config_dict_ptr); idle_monitor_ptr->lock_config_dict_ptr = NULL; } // Note: The idle inhibit manager does not have a dtor. free(idle_monitor_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_idle_monitor_reset(wlmaker_idle_monitor_t *idle_monitor_ptr) { if (idle_monitor_ptr->locked) return; int rv = wl_event_source_timer_update( idle_monitor_ptr->timer_event_source_ptr, _wlmaker_idle_msec(idle_monitor_ptr)); BS_ASSERT(0 == rv); idle_monitor_ptr->timer_expired = false; } /* ------------------------------------------------------------------------- */ bool wlmaker_idle_monitor_lock(wlmaker_idle_monitor_t *idle_monitor_ptr) { const char *cmd_ptr = bspl_dict_get_string_value( idle_monitor_ptr->lock_config_dict_ptr, "Command"); if (NULL == cmd_ptr) { bs_log(BS_WARNING, "No 'Command' configured in 'ScreenLock' config."); return false; } bs_subprocess_t *subprocess_ptr = bs_subprocess_create_cmdline(cmd_ptr); if (NULL == subprocess_ptr) { bs_log(BS_WARNING, "Failed bs_subprocess_create_cmdline(\"%s\")", cmd_ptr); return false; } if (!bs_subprocess_start(subprocess_ptr)) { bs_log(BS_WARNING, "Failed bs_subprocess_start(%p) for \"%s\".", subprocess_ptr, cmd_ptr); bs_subprocess_destroy(subprocess_ptr); return false; } wlmaker_subprocess_handle_t *handle_ptr = wlmaker_subprocess_monitor_entrust( idle_monitor_ptr->server_ptr->monitor_ptr, subprocess_ptr, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (NULL != handle_ptr) { wlmaker_subprocess_monitor_cede( idle_monitor_ptr->server_ptr->monitor_ptr, handle_ptr); } return true; } /* ------------------------------------------------------------------------- */ void wlmaker_idle_monitor_inhibit(wlmaker_idle_monitor_t *idle_monitor_ptr) { ++idle_monitor_ptr->inhibits; } /* ------------------------------------------------------------------------- */ void wlmaker_idle_monitor_uninhibit(wlmaker_idle_monitor_t *idle_monitor_ptr) { BS_ASSERT(0 < idle_monitor_ptr->inhibits); --idle_monitor_ptr->inhibits; _wlmaker_idle_monitor_consider_locking(idle_monitor_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Executes a lock, if not inhibited & timer has indeed expired. */ void _wlmaker_idle_monitor_consider_locking( wlmaker_idle_monitor_t *idle_monitor_ptr) { // No locking if there's inhibitors or no expired timer. if (0 < idle_monitor_ptr->inhibits || !idle_monitor_ptr->timer_expired) return; // Lock. If there's a problem there => don't register for unlock. if (!wlmaker_idle_monitor_lock(idle_monitor_ptr)) return; wlmtk_root_t *root_ptr = idle_monitor_ptr->server_ptr->root_ptr; idle_monitor_ptr->locked = true; wlmtk_util_connect_listener_signal( &wlmtk_root_events(root_ptr)->unlock_event, &idle_monitor_ptr->unlock_listener, _wlmaker_idle_monitor_handle_unlock); } /* ------------------------------------------------------------------------- */ /** * Timer function for the wayland event loop. * * @param data_ptr Untyped pointer to @ref wlmaker_idle_monitor_t. * * @return Whether the event source is registered for re-check with * wl_event_source_check(): 0 for all done, 1 for needing a re-check. If * not registered, the return value is ignored and should be zero. */ int _wlmaker_idle_monitor_timer(void *data_ptr) { wlmaker_idle_monitor_t *idle_monitor_ptr = data_ptr; idle_monitor_ptr->timer_expired = true; _wlmaker_idle_monitor_consider_locking(idle_monitor_ptr); return 0; } /* ------------------------------------------------------------------------- */ /** * Returns the idle timeout time in milliseconds. * * @param idle_monitor_ptr * * @return The idle timeout, read from the config dictionnary. If no or a * negative value was configured, 0 is returned, indicating the timer * to NOT be armed. */ int _wlmaker_idle_msec(wlmaker_idle_monitor_t *idle_monitor_ptr) { const char *idle_seconds_ptr = bspl_dict_get_string_value( idle_monitor_ptr->lock_config_dict_ptr, "IdleSeconds"); if (NULL == idle_seconds_ptr) return 0; uint64_t seconds; if (!bs_strconvert_uint64(idle_seconds_ptr, &seconds, 10) || seconds >= (INT32_MAX / 1000)) { bs_log(BS_WARNING, "Bad value for 'IdleSeconds': %s", idle_seconds_ptr); return 0; } if (0 >= seconds) return 0; return 1000 * seconds; } /* ------------------------------------------------------------------------- */ /** * Creates and adds a new inhibitor to the monitor. * * @param idle_monitor_ptr * @param wlr_idle_inhibitor_v1_ptr * * @return true on success. */ bool _wlmaker_idle_monitor_add_inhibitor( wlmaker_idle_monitor_t *idle_monitor_ptr, struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor_v1_ptr) { wlmaker_idle_inhibitor_t *idle_inhibitor_ptr = logged_calloc( 1, sizeof(wlmaker_idle_inhibitor_t)); if (NULL == idle_inhibitor_ptr) return false; idle_inhibitor_ptr->idle_monitor_ptr = idle_monitor_ptr; idle_inhibitor_ptr->wlr_idle_inhibitor_v1_ptr = wlr_idle_inhibitor_v1_ptr; wlmtk_util_connect_listener_signal( &wlr_idle_inhibitor_v1_ptr->events.destroy, &idle_inhibitor_ptr->destroy_listener, _wlmaker_idle_monitor_handle_destroy_inhibitor); bs_dllist_push_back(&idle_monitor_ptr->idle_inhibitors, &idle_inhibitor_ptr->dlnode); wlmaker_idle_monitor_inhibit(idle_monitor_ptr); return true; } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal of the inhibitor. Destroys it. * * @param listener_ptr * @param data_ptr Unused. */ void _wlmaker_idle_monitor_handle_destroy_inhibitor( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_idle_inhibitor_t *idle_inhibitor_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_idle_inhibitor_t, destroy_listener); wlmaker_idle_monitor_uninhibit(idle_inhibitor_ptr->idle_monitor_ptr); bs_dllist_remove( &idle_inhibitor_ptr->idle_monitor_ptr->idle_inhibitors, &idle_inhibitor_ptr->dlnode); wlmtk_util_disconnect_listener(&idle_inhibitor_ptr->destroy_listener); free(idle_inhibitor_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `new_inhibitor` signal of the inhibit manager: Registers * the inhibitor. * * @param listener_ptr * @param data_ptr Pointer to struct wlr_idle_inhibitor_v1. */ static void _wlmaker_idle_monitor_handle_new_inhibitor( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_idle_monitor_t *idle_monitor_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_idle_monitor_t, new_inhibitor_listener); struct wlr_idle_inhibitor_v1 *wlr_idle_inhibitor_v1_ptr = data_ptr; if (!_wlmaker_idle_monitor_add_inhibitor( idle_monitor_ptr, wlr_idle_inhibitor_v1_ptr)) { wl_resource_post_error( wlr_idle_inhibitor_v1_ptr->resource, WL_DISPLAY_ERROR_NO_MEMORY, "Failed _wlmaker_idle_monitor_add_inhibitor(%p, %p)", idle_monitor_ptr, wlr_idle_inhibitor_v1_ptr); return; } } /* ------------------------------------------------------------------------- */ /** * Handler for @ref wlmtk_root_events_t::unlock_event. Re-arms the timer. * * @param listener_ptr * @param data_ptr unused. */ void _wlmaker_idle_monitor_handle_unlock( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_idle_monitor_t *idle_monitor_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_idle_monitor_t, unlock_listener); wlmtk_util_disconnect_listener(&idle_monitor_ptr->unlock_listener); idle_monitor_ptr->locked = false; wlmaker_idle_monitor_reset(idle_monitor_ptr); } /* == End of idle.c ======================================================== */ wlmaker-0.8.1/src/backtrace.c0000644000175100017510000000767015222270710015452 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmbacktrace.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * Copyright (c) 2025 by Philipp Kaeser */ #include "wlmbacktrace.h" #include #include #include #include #if defined(WLMAKER_HAVE_LIBBACKTRACE) #include #endif // defined(WLMAKER_HAVE_LIBBACKTRACE) /* == Declarations ========================================================= */ #if defined(WLMAKER_HAVE_LIBBACKTRACE) /** State for libbacktrace. */ static struct backtrace_state *_wlmaker_bt_state_ptr; static void _backtrace_error_callback( __UNUSED__ void *data_ptr, const char *msg_ptr, int errnum); static int _backtrace_full_callback( __UNUSED__ void *data, uintptr_t pc, const char *filename_ptr, int line_num, const char *function_ptr); static void _signal_backtrace(int signum); #endif // defined(WLMAKER_HAVE_LIBBACKTRACE) /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmaker_backtrace_setup(const char *filename_ptr) { #if defined(WLMAKER_HAVE_LIBBACKTRACE) _wlmaker_bt_state_ptr = backtrace_create_state( filename_ptr, 0, _backtrace_error_callback, NULL); if (NULL == _wlmaker_bt_state_ptr) { bs_log(BS_ERROR, "Failed backtrace_create_state()"); return false; } signal(SIGABRT, _signal_backtrace); signal(SIGBUS, _signal_backtrace); signal(SIGFPE, _signal_backtrace); signal(SIGILL, _signal_backtrace); signal(SIGSEGV, _signal_backtrace); #else bs_log(BS_DEBUG, "No libbacktrace, ignoring setup for %s", filename_ptr); #endif // defined(WLMAKER_HAVE_LIBBACKTRACE) return true; } /* == Local (static) methods =============================================== */ #if defined(WLMAKER_HAVE_LIBBACKTRACE) /* ------------------------------------------------------------------------- */ /** Error callback for libbacktrace calls. */ void _backtrace_error_callback( __UNUSED__ void *data_ptr, const char *msg_ptr, int errnum) { bs_log_severity_t severity = BS_ERROR; if (0 != errnum && -1 != errnum) severity |= BS_ERRNO; bs_log(severity, "Backtrace error: %s", msg_ptr); } /* ------------------------------------------------------------------------- */ /** Full callback for printing a libbacktrace frame. */ int _backtrace_full_callback( __UNUSED__ void *data, uintptr_t program_counter, const char *filename_ptr, int line_num, const char *function_ptr) { bs_log(BS_ERROR, "%"PRIxPTR" in %s () at %s:%d", program_counter, function_ptr ? function_ptr : "(unknown)", filename_ptr ? filename_ptr : "(unknown)", line_num); return 0; } /* ------------------------------------------------------------------------- */ /** Signal handler: Prints a backtrace. */ void _signal_backtrace(int signum) { bs_log(BS_ERROR, "Caught signal %d", signum); backtrace_full( _wlmaker_bt_state_ptr, 0, _backtrace_full_callback, _backtrace_error_callback, NULL); signal(SIGABRT, SIG_DFL); abort(); } #endif // defined(WLMAKER_HAVE_LIBBACKTRACE) /* == End of wlmbacktrace.c ================================================ */ wlmaker-0.8.1/src/files.h0000644000175100017510000000603315222270710014632 0ustar runnerrunner/* ========================================================================= */ /** * @file files.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_FILES_H__ #define __WLMAKER_FILES_H__ #include /** Handle for files module. */ typedef struct _wlmaker_files_t wlmaker_files_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Create the files module. * * @param dirname_ptr Name of a subdirectory, that will be used as a * component for each of the XDG paths. * * @return Pointer to the module handle. Must be freed by calling * @ref wlmaker_files_destroy. */ wlmaker_files_t *wlmaker_files_create(const char *dirname_ptr); /** * Destroys the file module. * * @param files_ptr */ void wlmaker_files_destroy(wlmaker_files_t *files_ptr); /** * Returns a full path name for a config file. * * This expands into ${XDG_CONFIG_HOME}/`*dirname_ptr`/`*fname_ptr`. * * @param files_ptr * @param fname_ptr * * @return Full path name, or NULL on error. */ char *wlmaker_files_xdg_config_fname( wlmaker_files_t *files_ptr, const char *fname_ptr); /** * Finds a file (or directory, ...) from XDG configuration directories. * * Joins @ref wlmaker_files_t::dirname_ptr with `fname_ptr`, and then looks for * that entity in `${XDG_CONFIG_HOME}` and `${XDG_CONFIG_DIRS}`. Returns true * if the file has `mode_type`, eg. `S_IFREG` or `S_ISDIR`. * * @return Full path name, or NULL on error. The caller must call free() to * release the associated memory. */ char *wlmaker_files_xdg_config_find( wlmaker_files_t *files_ptr, const char *fname_ptr, int mode_type); /** * Finds a file (or directory, ...) from XDG data directories. * * Joins @ref wlmaker_files_t::dirname_ptr with `fname_ptr`, and then looks for * that entity in `${XDG_DATA_HOME}` and `${XDG_DATA_DIRS}`. Returns true * if the file has `mode_type`, eg. `S_IFREG` or `S_ISDIR`. * * @return Full path name, or NULL on error. The caller must call free() to * release the associated memory. */ char *wlmaker_files_xdg_data_find( wlmaker_files_t *files_ptr, const char *fname_ptr, int mode_type); /** Unit test set for @ref wlmaker_files_t. */ extern const bs_test_set_t wlmaker_files_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMAKER_FILES_H__ */ /* == End of files.h ======================================================= */ wlmaker-0.8.1/src/task_list.c0000644000175100017510000004517215222270710015527 0ustar runnerrunner/* ========================================================================= */ /** * @file task_list.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /// for PATH_MAX. #define _POSIX_C_SOURCE 1 #include "task_list.h" #include #include #include #include #include #include #include #include #include #include /// Include unstable interfaces of wlroots. #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include "config.h" #include "server.h" #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** State of the task list. */ struct _wlmaker_task_list_t { /** Derived from a toolkit panel. */ wlmtk_panel_t super_panel; /** Buffer that shows the tasklist's content. */ wlmtk_buffer_t buffer; /** Backlink to the server. */ wlmaker_server_t *server_ptr; /** Listener for the `task_list_enabled` signal by `wlmaker_server_t`. */ struct wl_listener task_list_enabled_listener; /** Listener for the `task_list_disabled` signal by `wlmaker_server_t`. */ struct wl_listener task_list_disabled_listener; /** Listener for @ref wlmtk_root_events_t::window_mapped. */ struct wl_listener window_mapped_listener; /** Listener for @ref wlmtk_root_events_t::window_unmapped. */ struct wl_listener window_unmapped_listener; /** Listener for @ref wlmaker_server_t::theme_changed_event. */ struct wl_listener theme_changed_listener; /** Whether the task list is currently enabled (mapped). */ bool enabled; /** Visual style. */ struct wlmaker_task_list_style style; }; static bool _wlmaker_task_list_refresh( wlmaker_task_list_t *task_list_ptr, const struct wlmaker_task_list_style *style_ptr); static struct wlr_buffer *create_wlr_buffer( wlmtk_workspace_t *workspace_ptr, const struct wlmaker_task_list_style *style_ptr); static void _wlmaker_task_list_draw_into_cairo( cairo_t *cairo_ptr, const struct wlmaker_task_list_style *style_ptr, wlmtk_workspace_t *workspace_ptr); static void _wlmaker_task_list_draw_window_into_cairo( cairo_t *cairo_ptr, const wlmtk_style_font_t *font_style_ptr, uint32_t color, wlmtk_window_t *window_ptr, bool active, int pos_y); static const char *_wlmaker_task_list_window_name( wlmtk_window_t *window_ptr); static uint32_t _wlmaker_task_list_request_size( wlmtk_panel_t *panel_ptr, int width, int height); static void _wlmaker_task_list_handle_task_list_enabled( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_task_list_handle_task_list_disabled( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_task_list_handle_theme_changed( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_task_list_handle_window_mapped( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_task_list_handle_window_unmapped( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ const bspl_desc_t wlmaker_task_list_style_desc[] = { BSPL_DESC_CUSTOM( "Fill", true, struct wlmaker_task_list_style, fill, fill, wlmtk_style_decode_fill, NULL, NULL, NULL), BSPL_DESC_DICT( "Font", true, struct wlmaker_task_list_style, font, font, wlmtk_style_font_desc), BSPL_DESC_ARGB32( "TextColor", true, struct wlmaker_task_list_style, text_color, text_color, 0), BSPL_DESC_SENTINEL() }; /** Task list positioning: Fixed dimensions, at center of layer. */ static const wlmtk_panel_positioning_t _wlmaker_task_list_positioning = { .desired_width = 400, .desired_height = 200, .anchor = WLR_EDGE_BOTTOM | WLR_EDGE_TOP | WLR_EDGE_LEFT | WLR_EDGE_RIGHT }; /** Virtual method table for the task list. */ static const wlmtk_panel_vmt_t _wlmaker_task_list_vmt = { .request_size = _wlmaker_task_list_request_size }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_task_list_t *wlmaker_task_list_create( wlmaker_server_t *server_ptr, const struct wlmaker_task_list_style *style_ptr) { wlmaker_task_list_t *task_list_ptr = logged_calloc( 1, sizeof(wlmaker_task_list_t)); task_list_ptr->server_ptr = server_ptr; task_list_ptr->style = *style_ptr; if (!wlmtk_panel_init(&task_list_ptr->super_panel, &_wlmaker_task_list_positioning)) { wlmaker_task_list_destroy(task_list_ptr); return NULL; } wlmtk_panel_extend(&task_list_ptr->super_panel, &_wlmaker_task_list_vmt); wlmtk_element_set_visible( wlmtk_panel_element(&task_list_ptr->super_panel), true); if (!wlmtk_buffer_init(&task_list_ptr->buffer)) { wlmaker_task_list_destroy(task_list_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_buffer_element(&task_list_ptr->buffer), true); wlmtk_container_add_element( &task_list_ptr->super_panel.super_container, wlmtk_buffer_element(&task_list_ptr->buffer)); wlmtk_util_connect_listener_signal( &server_ptr->task_list_enabled_event, &task_list_ptr->task_list_enabled_listener, _wlmaker_task_list_handle_task_list_enabled); wlmtk_util_connect_listener_signal( &server_ptr->task_list_disabled_event, &task_list_ptr->task_list_disabled_listener, _wlmaker_task_list_handle_task_list_disabled); wlmtk_util_connect_listener_signal( &server_ptr->theme_changed_event, &task_list_ptr->theme_changed_listener, _wlmaker_task_list_handle_theme_changed); wlmtk_util_connect_listener_signal( &wlmtk_root_events(server_ptr->root_ptr)->window_mapped, &task_list_ptr->window_mapped_listener, _wlmaker_task_list_handle_window_mapped); wlmtk_util_connect_listener_signal( &wlmtk_root_events(server_ptr->root_ptr)->window_unmapped, &task_list_ptr->window_unmapped_listener, _wlmaker_task_list_handle_window_unmapped); return task_list_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_task_list_destroy(wlmaker_task_list_t *task_list_ptr) { wlmtk_util_disconnect_listener(&task_list_ptr->window_unmapped_listener); wlmtk_util_disconnect_listener(&task_list_ptr->window_mapped_listener); wlmtk_util_disconnect_listener(&task_list_ptr->theme_changed_listener); wlmtk_util_disconnect_listener(&task_list_ptr->task_list_disabled_listener); wlmtk_util_disconnect_listener(&task_list_ptr->task_list_enabled_listener); if (wlmtk_buffer_element(&task_list_ptr->buffer)->parent_container_ptr) { wlmtk_container_remove_element( &task_list_ptr->super_panel.super_container, wlmtk_buffer_element(&task_list_ptr->buffer)); } wlmtk_buffer_fini(&task_list_ptr->buffer); wlmtk_panel_fini(&task_list_ptr->super_panel); free(task_list_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Refreshes the task list. Should be done whenever a list is mapped/unmapped. * * @param task_list_ptr * @param style_ptr * * @return true on success. */ bool _wlmaker_task_list_refresh( wlmaker_task_list_t *task_list_ptr, const struct wlmaker_task_list_style *style_ptr) { wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(task_list_ptr->server_ptr->root_ptr); struct wlr_buffer *wlr_buffer_ptr = create_wlr_buffer( workspace_ptr, style_ptr); if (NULL == wlr_buffer_ptr) return false; wlmtk_buffer_set(&task_list_ptr->buffer, wlr_buffer_ptr); wlr_buffer_drop(wlr_buffer_ptr); return true; } /* ------------------------------------------------------------------------- */ /** * Creates a `struct wlr_buffer` with windows of `workspace_ptr` drawn into. * * @param workspace_ptr * @param style_ptr * * @return A pointer to the `struct wlr_buffer` with the list of windows * (tasks), or NULL on error. */ struct wlr_buffer *create_wlr_buffer( wlmtk_workspace_t *workspace_ptr, const struct wlmaker_task_list_style *style_ptr) { struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( _wlmaker_task_list_positioning.desired_width, _wlmaker_task_list_positioning.desired_height); if (NULL == wlr_buffer_ptr) return NULL; cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); return NULL; } _wlmaker_task_list_draw_into_cairo(cairo_ptr, style_ptr, workspace_ptr); cairo_destroy(cairo_ptr); return wlr_buffer_ptr; } /* ------------------------------------------------------------------------- */ /** * Draws all tasks of `workspace_ptr` into `cairo_ptr`. * * @param cairo_ptr * @param style_ptr * @param workspace_ptr */ void _wlmaker_task_list_draw_into_cairo( cairo_t *cairo_ptr, const struct wlmaker_task_list_style *style_ptr, wlmtk_workspace_t *workspace_ptr) { wlmaker_primitives_cairo_fill(cairo_ptr, &style_ptr->fill); // Not tied to a workspace? We're done, all set. if (NULL == workspace_ptr) return; const bs_dllist_t *windows_ptr = wlmtk_workspace_get_windows_dllist( workspace_ptr); // No windows at all? Done here. if (bs_dllist_empty(windows_ptr)) return; // Find node of the active window, for centering the task list. bs_dllist_node_t *centered_dlnode_ptr = windows_ptr->head_ptr; bs_dllist_node_t *active_dlnode_ptr = windows_ptr->head_ptr; while (NULL != active_dlnode_ptr && wlmtk_workspace_get_activated_window(workspace_ptr) != wlmtk_window_from_dlnode(active_dlnode_ptr)) { active_dlnode_ptr = active_dlnode_ptr->next_ptr; } if (NULL != active_dlnode_ptr) centered_dlnode_ptr = active_dlnode_ptr; int pos_y = _wlmaker_task_list_positioning.desired_height / 2 + 10; _wlmaker_task_list_draw_window_into_cairo( cairo_ptr, &style_ptr->font, style_ptr->text_color, wlmtk_window_from_dlnode(centered_dlnode_ptr), centered_dlnode_ptr == active_dlnode_ptr, pos_y); bs_dllist_node_t *dlnode_ptr = centered_dlnode_ptr->prev_ptr; for (int further_windows = 1; NULL != dlnode_ptr && further_windows <= 3; dlnode_ptr = dlnode_ptr->prev_ptr, ++further_windows) { _wlmaker_task_list_draw_window_into_cairo( cairo_ptr, &style_ptr->font, style_ptr->text_color, wlmtk_window_from_dlnode(dlnode_ptr), false, pos_y - further_windows * 26); } dlnode_ptr = centered_dlnode_ptr->next_ptr; for (int further_windows = 1; NULL != dlnode_ptr && further_windows <= 3; dlnode_ptr = dlnode_ptr->next_ptr, ++further_windows) { _wlmaker_task_list_draw_window_into_cairo( cairo_ptr, &style_ptr->font, style_ptr->text_color, wlmtk_window_from_dlnode(dlnode_ptr), false, pos_y + further_windows * 26); } } /* ------------------------------------------------------------------------- */ /** * Draws one window (task) into `cairo_ptr`. * * @param cairo_ptr * @param font_style_ptr * @param color * @param window_ptr * @param active Whether this window is currently active. * @param pos_y Y position within the `cairo_ptr`. */ void _wlmaker_task_list_draw_window_into_cairo( cairo_t *cairo_ptr, const wlmtk_style_font_t *font_style_ptr, uint32_t color, wlmtk_window_t *window_ptr, bool active, int pos_y) { cairo_set_source_argb8888(cairo_ptr, color); cairo_set_font_size(cairo_ptr, font_style_ptr->size); cairo_select_font_face( cairo_ptr, font_style_ptr->face, CAIRO_FONT_SLANT_NORMAL, active ? CAIRO_FONT_WEIGHT_BOLD : CAIRO_FONT_WEIGHT_NORMAL); cairo_move_to(cairo_ptr, 10, pos_y); cairo_show_text(cairo_ptr, _wlmaker_task_list_window_name(window_ptr)); } /* ------------------------------------------------------------------------- */ /** * Constructs a comprehensive name for the window. * * @param window_ptr * * @return Pointer to the constructed name. This is a static buffer that does * not require to be free'd, but will be re-used upon next call to * window_name. */ const char *_wlmaker_task_list_window_name(wlmtk_window_t *window_ptr) { static char name[256]; size_t pos = 0; const char *title_ptr = wlmtk_window_get_title(window_ptr); if (NULL != title_ptr) { pos = bs_strappendf(name, sizeof(name), pos, "%s", title_ptr); } const wlmtk_util_client_t *client_ptr = wlmtk_window_get_client_ptr( window_ptr); if (NULL != client_ptr && 0 != client_ptr->pid) { if (0 < pos) pos = bs_strappendf(name, sizeof(name), pos, " "); pos = bs_strappendf(name, sizeof(name), pos, "[%"PRIdMAX, (intmax_t)client_ptr->pid); char fname[PATH_MAX], cmdline[PATH_MAX]; snprintf(fname, sizeof(fname), "/proc/%"PRIdMAX"/cmdline", (intmax_t)client_ptr->pid); ssize_t read_bytes = bs_file_read_buffer( fname, cmdline, sizeof(cmdline)); if (0 < read_bytes) { pos = bs_strappendf(name, sizeof(name), pos, ": %s", cmdline); } pos = bs_strappendf(name, sizeof(name), pos, "]"); } if (0 < pos) pos = bs_strappendf(name, sizeof(name), pos, " "); pos = bs_strappendf(name, sizeof(name), pos, "(%p)", window_ptr); return &name[0]; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_panel_vmt_t::request_size. * * @param panel_ptr * @param width * @param height * * @return 0 always. */ uint32_t _wlmaker_task_list_request_size( wlmtk_panel_t *panel_ptr, __UNUSED__ int width, __UNUSED__ int height) { wlmtk_panel_commit(panel_ptr, 0, &_wlmaker_task_list_positioning); return 0; } /* ------------------------------------------------------------------------- */ /** * Handler for the `task_list_enabled_listener`. * * Enables the task listener: Creates the task list for the currently-active * workspace and enables the task list on that workspace. * * @param listener_ptr * @param data_ptr */ void _wlmaker_task_list_handle_task_list_enabled( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_task_list_t, task_list_enabled_listener); _wlmaker_task_list_refresh(task_list_ptr, &task_list_ptr->style); if (task_list_ptr->enabled) { BS_ASSERT(NULL != wlmtk_panel_get_layer(&task_list_ptr->super_panel)); return; } wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(task_list_ptr->server_ptr->root_ptr); wlmtk_layer_t *layer_ptr = wlmtk_workspace_get_layer( workspace_ptr, WLMTK_WORKSPACE_LAYER_OVERLAY); wlmtk_layer_add_panel( layer_ptr, &task_list_ptr->super_panel, wlmaker_server_get_output_at_cursor(task_list_ptr->server_ptr)); task_list_ptr->enabled = true; } /* ------------------------------------------------------------------------- */ /** * Handler for the `task_list_disabled_listener`: Hides the list. * * @param listener_ptr * @param data_ptr */ void _wlmaker_task_list_handle_task_list_disabled( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_task_list_t, task_list_disabled_listener); BS_ASSERT(NULL != wlmtk_panel_get_layer(&task_list_ptr->super_panel)); wlmtk_layer_remove_panel( wlmtk_panel_get_layer(&task_list_ptr->super_panel), &task_list_ptr->super_panel); task_list_ptr->enabled = false; } /* ------------------------------------------------------------------------- */ /** * Handler for the `theme_changed_listener`: Updates the style * * @param listener_ptr * @param data_ptr */ void _wlmaker_task_list_handle_theme_changed( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_task_list_t, theme_changed_listener); wlmaker_config_style_t *style_ptr = data_ptr; if (_wlmaker_task_list_refresh(task_list_ptr, &style_ptr->task_list)) { task_list_ptr->style = style_ptr->task_list; } } /* ------------------------------------------------------------------------- */ /** * Handler for the `window_mapped_listener`: Refreshes the list (if enabled). * * @param listener_ptr * @param data_ptr */ void _wlmaker_task_list_handle_window_mapped( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_task_list_t, window_mapped_listener); if (task_list_ptr->enabled) { _wlmaker_task_list_refresh(task_list_ptr, &task_list_ptr->style); } } /* ------------------------------------------------------------------------- */ /** * Handler for the `window_unmapped_listener`: Refreshes the list (if enabled). * * @param listener_ptr * @param data_ptr */ void _wlmaker_task_list_handle_window_unmapped( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_task_list_t *task_list_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_task_list_t, window_unmapped_listener); if (task_list_ptr->enabled) { _wlmaker_task_list_refresh(task_list_ptr, &task_list_ptr->style); } } /* == End of task_list.c =================================================== */ wlmaker-0.8.1/src/wlmbacktrace.h0000644000175100017510000000274515222270710016175 0ustar runnerrunner/* ========================================================================= */ /** * @file wlmbacktrace.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMBACKTRACE_H__ #define __WLMBACKTRACE_H__ #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Sets up signal handlers to catch issues and log a backtrace. * * @param filename_ptr path name of the executable file; if it is NULL * the library will try system-specific path names. * If not NULL, FILENAME must point to a permanent * buffer. * * @return true on success. */ bool wlmaker_backtrace_setup(const char *filename_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMBACKTRACE_H__ */ /* == End of wlmbacktrace.h ================================================ */ wlmaker-0.8.1/src/xdg_toplevel.c0000644000175100017510000016106015222270710016221 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_toplevel.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "xdg_toplevel.h" #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "config.h" #include "input/cursor.h" #include "input/manager.h" #include "server.h" #include "tl_menu.h" #include "toolkit/toolkit.h" #include "xdg_popup.h" /* == Declarations ========================================================= */ /** State of an XDG toplevel in wlmaker. */ struct wlmaker_xdg_toplevel { /** Holds surface as content, will be the window's content. */ wlmtk_base_t base; /** Back-link to server. */ wlmaker_server_t *server_ptr; /** The corresponding wlroots XDG toplevel. */ struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr; /** The toplevel's toolkit surface. */ wlmtk_surface_t *surface_ptr; /** The toplevel's window, when mapped. */ wlmtk_window_t *window_ptr; /** The toplevel's window menu. */ wlmaker_tl_menu_t *tl_menu_ptr; /** Listener for the `destroy` signal of the `wlr_xdg_toplevel::events`. */ struct wl_listener destroy_listener; /** Listener for `request_maximize` of `wlr_xdg_toplevel::events`. */ struct wl_listener request_maximize_listener; /** Listener for `request_fullscreen` of `wlr_xdg_toplevel::events`. */ struct wl_listener request_fullscreen_listener; /** Listener for `request_minimize` of `wlr_xdg_toplevel::events`. */ struct wl_listener request_minimize_listener; /** Listener for `request_move` signal of `wlr_xdg_toplevel::events`. */ struct wl_listener request_move_listener; /** Listener for `request_resize` signal of `wlr_xdg_toplevel::events`. */ struct wl_listener request_resize_listener; /** Listener for `show_window_menu` of `wlr_xdg_toplevel::events`. */ struct wl_listener request_show_window_menu_listener; /** Listener for `set_parent` of `wlr_xdg_toplevel::events`. */ struct wl_listener set_parent_listener; /** Listener for `set_title` of the `wlr_xdg_toplevel::events`. */ struct wl_listener set_title_listener; /** Listener for `set_app_id` of `wlr_xdg_toplevel::events`. */ struct wl_listener set_app_id_listener; /** Listener for the `new_popup` signal of the `wlr_xdg_surface`. */ struct wl_listener new_popup_listener; /** Listener for the `map` signal of the `wlr_surface`. */ struct wl_listener surface_map_listener; /** Listener for the `unmap` signal of the `wlr_surface`. */ struct wl_listener surface_unmap_listener; /** Listener for the `commit` signal of the `wlr_surface`. */ struct wl_listener surface_commit_listener; /** Listener for @ref wlmtk_window_events_t::request_close. */ struct wl_listener window_request_close_listener; /** Listener for @ref wlmtk_window_events_t::set_activated. */ struct wl_listener window_set_activated_listener; /** Listener for @ref wlmtk_window_events_t::request_size. */ struct wl_listener window_request_size_listener; /** Listener for @ref wlmtk_window_events_t::request_fullscreen. */ struct wl_listener window_request_fullscreen_listener; /** Listener for @ref wlmtk_window_events_t::request_maximized. */ struct wl_listener window_request_maximized_listener; /** Injected method for wlr_xdg_toplevel_set_maximized(). */ uint32_t (*_set_maximized)(struct wlr_xdg_toplevel *, bool); /** Injected method for wlr_xdg_toplevel_set_fullscreen(). */ uint32_t (*_set_fullscreen)(struct wlr_xdg_toplevel *, bool); /** Injected method for wlr_xdg_toplevel_set_size(). */ uint32_t (*_set_size)(struct wlr_xdg_toplevel *, int32_t, int32_t); /** Injected method for wlr_xdg_toplevel_set_activated(). */ uint32_t (*_set_activated)(struct wlr_xdg_toplevel *, bool); /** Injected method for wlr_surface_get_extents(). */ void (*_get_extents)(struct wlr_surface *, struct wlr_box *); /** Serial of the most recent commit() call. */ uint32_t committed_serial; /** Serial of the most recent set_size() call. */ uint32_t set_size_serial; /** Whether this toplevel is configured to be server-side decorated. */ bool server_side_decorated; /** Properties that are pending to be configured for the toplevel. */ struct { enum { /** Maximization. */ WXT_PROP_MAXIMIZED = 1 << 0, /** Fullscreen. */ WXT_PROP_FULLSCREEN = 1 << 1, /** Size. */ WXT_PROP_SIZE = 1 << 2, /** Activation. */ WXT_PROP_ACTIVATED = 1 << 3 } properties; /** Maximization status. */ bool maximized; /** Fullscreen status. */ bool fullscreen; /** Width and height. */ int32_t width, height; /** Activated. */ bool activated; } pending; /** * Whether the surface had been mapped. Actual map to the workspace may be * pending, if size was not yet non-zero. */ bool mapped; }; struct wlmaker_xdg_toplevel *_wlmaker_xdg_toplevel_create_injected( struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, wlmaker_server_t *server_ptr, uint32_t (*_set_maximized)(struct wlr_xdg_toplevel *, bool), uint32_t (*_set_fullscreen)(struct wlr_xdg_toplevel *, bool), uint32_t (*_set_size)(struct wlr_xdg_toplevel *, int32_t, int32_t), uint32_t (*_set_activated)(struct wlr_xdg_toplevel *, bool), void (*_get_extents)(struct wlr_surface *, struct wlr_box *)); static void _wlmaker_xdg_toplevel_flush_properties( struct wlmaker_xdg_toplevel *wxt_ptr); static void _wlmaker_xdg_toplevel_try_map( struct wlmaker_xdg_toplevel *wxt_ptr); static void _wlmaker_xdg_toplevel_handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_request_maximize( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_request_fullscreen( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_request_minimize( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_request_move( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_request_resize( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_request_show_window_menu( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_set_parent( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_set_title( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_set_app_id( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_new_popup( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_surface_map( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_surface_unmap( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_window_request_close( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_window_set_activated( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_window_request_size( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_window_request_fullscreen( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_toplevel_handle_window_request_maximized( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_create( struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, wlmaker_server_t *server_ptr) { return _wlmaker_xdg_toplevel_create_injected( wlr_xdg_toplevel_ptr, server_ptr, wlr_xdg_toplevel_set_maximized, wlr_xdg_toplevel_set_fullscreen, wlr_xdg_toplevel_set_size, wlr_xdg_toplevel_set_activated, #if WLR_VERSION_NUM >= (19 << 8) wlr_surface_get_extents #else wlr_surface_get_extends #endif ); } /* ------------------------------------------------------------------------- */ void wlmaker_xdg_toplevel_destroy(struct wlmaker_xdg_toplevel *wxt_ptr) { bs_log(BS_INFO, "Destroying XDG toplevel %p", wxt_ptr); wxt_ptr->wlr_xdg_toplevel_ptr->base->data = NULL; wlmtk_util_disconnect_listener(&wxt_ptr->window_request_fullscreen_listener); wlmtk_util_disconnect_listener(&wxt_ptr->window_request_size_listener); wlmtk_util_disconnect_listener(&wxt_ptr->window_set_activated_listener); wlmtk_util_disconnect_listener(&wxt_ptr->window_request_close_listener); wlmtk_util_disconnect_listener(&wxt_ptr->surface_commit_listener); wlmtk_util_disconnect_listener(&wxt_ptr->surface_unmap_listener); wlmtk_util_disconnect_listener(&wxt_ptr->surface_map_listener); wlmtk_util_disconnect_listener(&wxt_ptr->new_popup_listener); wlmtk_util_disconnect_listener(&wxt_ptr->set_app_id_listener); wlmtk_util_disconnect_listener(&wxt_ptr->set_title_listener); wlmtk_util_disconnect_listener(&wxt_ptr->set_parent_listener); wlmtk_util_disconnect_listener(&wxt_ptr->request_show_window_menu_listener); wlmtk_util_disconnect_listener(&wxt_ptr->request_resize_listener); wlmtk_util_disconnect_listener(&wxt_ptr->request_move_listener); wlmtk_util_disconnect_listener(&wxt_ptr->request_fullscreen_listener); wlmtk_util_disconnect_listener(&wxt_ptr->request_maximize_listener); wlmtk_util_disconnect_listener(&wxt_ptr->request_minimize_listener); wlmtk_util_disconnect_listener(&wxt_ptr->destroy_listener); if (NULL != wxt_ptr->tl_menu_ptr) { wlmaker_tl_menu_destroy(wxt_ptr->tl_menu_ptr); wxt_ptr->tl_menu_ptr = NULL; } if (NULL != wxt_ptr->window_ptr) { wl_signal_emit( &wxt_ptr->server_ptr->window_destroyed_event, wxt_ptr->window_ptr); wlmtk_window_destroy(wxt_ptr->window_ptr); wxt_ptr->window_ptr = NULL; } wlmtk_base_fini(&wxt_ptr->base); free(wxt_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_xdg_toplevel_set_server_side_decorated( struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr, bool server_side_decorated) { wlmaker_xdg_toplevel_ptr->server_side_decorated = server_side_decorated; wlmtk_window_set_server_side_decorated( wlmaker_xdg_toplevel_ptr->window_ptr, wlmaker_xdg_toplevel_ptr->server_side_decorated); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Creates the toplevel, permitting injected calls to set properties. */ struct wlmaker_xdg_toplevel *_wlmaker_xdg_toplevel_create_injected( struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, wlmaker_server_t *server_ptr, uint32_t (*_set_maximized)(struct wlr_xdg_toplevel *, bool), uint32_t (*_set_fullscreen)(struct wlr_xdg_toplevel *, bool), uint32_t (*_set_size)(struct wlr_xdg_toplevel *, int32_t, int32_t), uint32_t (*_set_activated)(struct wlr_xdg_toplevel *, bool), void (*_get_extents)(struct wlr_surface *, struct wlr_box *)) { // Guard clause: Must have a base. */ if (NULL == wlr_xdg_toplevel_ptr->base) { bs_log(BS_ERROR, "Missing base for wlr_xdg_toplevel at %p", wlr_xdg_toplevel_ptr); return NULL; } struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = logged_calloc( 1, sizeof(struct wlmaker_xdg_toplevel)); if (NULL == wlmaker_xdg_toplevel_ptr) return NULL; wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr = wlr_xdg_toplevel_ptr; wlmaker_xdg_toplevel_ptr->server_ptr = server_ptr; wlmaker_xdg_toplevel_ptr->_set_maximized = _set_maximized; wlmaker_xdg_toplevel_ptr->_set_fullscreen = _set_fullscreen; wlmaker_xdg_toplevel_ptr->_set_size = _set_size; wlmaker_xdg_toplevel_ptr->_set_activated = _set_activated; wlmaker_xdg_toplevel_ptr->_get_extents = _get_extents; if (!wlmtk_base_init(&wlmaker_xdg_toplevel_ptr->base, NULL)) goto error; wlmaker_xdg_toplevel_ptr->surface_ptr = wlmtk_xdg_surface_create( wlr_xdg_toplevel_ptr->base, server_ptr->wlr_seat_ptr); if (NULL == wlmaker_xdg_toplevel_ptr->surface_ptr) goto error; wlmtk_base_set_content_element( &wlmaker_xdg_toplevel_ptr->base, wlmtk_surface_element(wlmaker_xdg_toplevel_ptr->surface_ptr)); wlmaker_xdg_toplevel_ptr->window_ptr = wlmtk_window_create( wlmtk_base_element(&wlmaker_xdg_toplevel_ptr->base), wlmtk_window_style_to_ref( wlmaker_xdg_toplevel_ptr->server_ptr->style_ptr->window_style_ptr), wlmtk_menu_style_to_ref( wlmaker_xdg_toplevel_ptr->server_ptr->style_ptr->menu_style_ptr)); if (NULL == wlmaker_xdg_toplevel_ptr->window_ptr) goto error; if (NULL != server_ptr->input_manager_ptr) { wlmtk_window_set_options( wlmaker_xdg_toplevel_ptr->window_ptr, wlmim_cursor_options( server_ptr->input_manager_ptr)->move_window_modifier); } wlmtk_window_set_properties( wlmaker_xdg_toplevel_ptr->window_ptr, WLMTK_WINDOW_PROPERTY_RESIZABLE | WLMTK_WINDOW_PROPERTY_ICONIFIABLE | WLMTK_WINDOW_PROPERTY_CLOSABLE); wlmtk_util_client_t client = {}; if (NULL != wlr_xdg_toplevel_ptr->resource) { wl_client_get_credentials( wlr_xdg_toplevel_ptr->resource->client, &client.pid, &client.uid, &client.gid); } wlmtk_window_set_client(wlmaker_xdg_toplevel_ptr->window_ptr, &client); wlmaker_xdg_toplevel_ptr->tl_menu_ptr = wlmaker_tl_menu_create( wlmaker_xdg_toplevel_ptr->window_ptr, server_ptr); if (NULL == wlmaker_xdg_toplevel_ptr->tl_menu_ptr) goto error; wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.destroy, &wlmaker_xdg_toplevel_ptr->destroy_listener, _wlmaker_xdg_toplevel_handle_destroy); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.request_maximize, &wlmaker_xdg_toplevel_ptr->request_maximize_listener, _wlmaker_xdg_toplevel_handle_request_maximize); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.request_fullscreen, &wlmaker_xdg_toplevel_ptr->request_fullscreen_listener, _wlmaker_xdg_toplevel_handle_request_fullscreen); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.request_minimize, &wlmaker_xdg_toplevel_ptr->request_minimize_listener, _wlmaker_xdg_toplevel_handle_request_minimize); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.request_move, &wlmaker_xdg_toplevel_ptr->request_move_listener, _wlmaker_xdg_toplevel_handle_request_move); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.request_resize, &wlmaker_xdg_toplevel_ptr->request_resize_listener, _wlmaker_xdg_toplevel_handle_request_resize); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.request_show_window_menu, &wlmaker_xdg_toplevel_ptr->request_show_window_menu_listener, _wlmaker_xdg_toplevel_handle_request_show_window_menu); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.set_parent, &wlmaker_xdg_toplevel_ptr->set_parent_listener, _wlmaker_xdg_toplevel_handle_set_parent); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.set_title, &wlmaker_xdg_toplevel_ptr->set_title_listener, _wlmaker_xdg_toplevel_handle_set_title); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->events.set_app_id, &wlmaker_xdg_toplevel_ptr->set_app_id_listener, _wlmaker_xdg_toplevel_handle_set_app_id); wlmtk_util_connect_listener_signal( &wlr_xdg_toplevel_ptr->base->events.new_popup, &wlmaker_xdg_toplevel_ptr->new_popup_listener, _wlmaker_xdg_toplevel_handle_new_popup); wlmtk_surface_connect_map_listener_signal( wlmaker_xdg_toplevel_ptr->surface_ptr, &wlmaker_xdg_toplevel_ptr->surface_map_listener, _wlmaker_xdg_toplevel_handle_surface_map); wlmtk_surface_connect_unmap_listener_signal( wlmaker_xdg_toplevel_ptr->surface_ptr, &wlmaker_xdg_toplevel_ptr->surface_unmap_listener, _wlmaker_xdg_toplevel_handle_surface_unmap); wlmtk_util_connect_listener_signal( &wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr->base->surface->events.commit, &wlmaker_xdg_toplevel_ptr->surface_commit_listener, _wlmaker_xdg_toplevel_handle_surface_commit); wlmtk_util_connect_listener_signal( &wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->request_close, &wlmaker_xdg_toplevel_ptr->window_request_close_listener, _wlmaker_xdg_toplevel_handle_window_request_close); wlmtk_util_connect_listener_signal( &wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->set_activated, &wlmaker_xdg_toplevel_ptr->window_set_activated_listener, _wlmaker_xdg_toplevel_handle_window_set_activated); wlmtk_util_connect_listener_signal( &wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->request_size, &wlmaker_xdg_toplevel_ptr->window_request_size_listener, _wlmaker_xdg_toplevel_handle_window_request_size); wlmtk_util_connect_listener_signal( &wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->request_fullscreen, &wlmaker_xdg_toplevel_ptr->window_request_fullscreen_listener, _wlmaker_xdg_toplevel_handle_window_request_fullscreen); wlmtk_util_connect_listener_signal( &wlmtk_window_events(wlmaker_xdg_toplevel_ptr->window_ptr)->request_maximized, &wlmaker_xdg_toplevel_ptr->window_request_maximized_listener, _wlmaker_xdg_toplevel_handle_window_request_maximized); wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr->base->data = wlmaker_xdg_toplevel_ptr; wl_signal_emit( &server_ptr->window_created_event, wlmaker_xdg_toplevel_ptr->window_ptr); bs_log(BS_INFO, "Created XDG toplevel %p", wlmaker_xdg_toplevel_ptr); return wlmaker_xdg_toplevel_ptr; error: wlmaker_xdg_toplevel_destroy(wlmaker_xdg_toplevel_ptr); return NULL; } /* ------------------------------------------------------------------------- */ /** * Sends pending properties as configure() to the client, after 1st commit. * * If the first commit hasn't happened yet, the properties will not be sent. * A later call to @ref _wlmaker_xdg_toplevel_flush_properties will then flush * them. See @ref wlmaker_xdg_toplevel::pending. * * @param wxt_ptr */ void _wlmaker_xdg_toplevel_flush_properties( struct wlmaker_xdg_toplevel *wxt_ptr) { if (!wxt_ptr->wlr_xdg_toplevel_ptr->base->initialized) return; if (wxt_ptr->pending.properties & WXT_PROP_MAXIMIZED) { wxt_ptr->_set_maximized( wxt_ptr->wlr_xdg_toplevel_ptr, wxt_ptr->pending.maximized); } if (wxt_ptr->pending.properties & WXT_PROP_FULLSCREEN) { wxt_ptr->_set_fullscreen( wxt_ptr->wlr_xdg_toplevel_ptr, wxt_ptr->pending.fullscreen); } if (wxt_ptr->pending.properties & WXT_PROP_SIZE) { wxt_ptr->set_size_serial = wxt_ptr->_set_size( wxt_ptr->wlr_xdg_toplevel_ptr, wxt_ptr->pending.width, wxt_ptr->pending.height); } if (wxt_ptr->pending.properties & WXT_PROP_ACTIVATED){ wxt_ptr->_set_activated( wxt_ptr->wlr_xdg_toplevel_ptr, wxt_ptr->pending.activated); } wxt_ptr->pending.properties = 0; } /* ------------------------------------------------------------------------- */ /** * Maps the window to the workspace, if the surface is ready. * * A surface is ready if it had received the map signal (and no unmap), and * if it has non-zero width and height. * * @param wxt_ptr */ void _wlmaker_xdg_toplevel_try_map(struct wlmaker_xdg_toplevel *wxt_ptr) { if (!wxt_ptr->mapped) return; struct wlr_box committed_size = wlmtk_window_get_size(wxt_ptr->window_ptr); if (0 == committed_size.width || 0 == committed_size.height) return; if (NULL != wlmtk_window_get_workspace(wxt_ptr->window_ptr)) return; wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace( wxt_ptr->server_ptr->root_ptr); wlmtk_workspace_map_window(workspace_ptr, wxt_ptr->window_ptr); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel is destroyed: Destry the wlmaker toplevel, too. */ void _wlmaker_xdg_toplevel_handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, destroy_listener); wlmaker_xdg_toplevel_destroy(wlmaker_xdg_toplevel_ptr); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel requests to be maximized. */ void _wlmaker_xdg_toplevel_handle_request_maximize( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, request_maximize_listener); wlmtk_window_request_maximized( wxt_ptr->window_ptr, wxt_ptr->wlr_xdg_toplevel_ptr->requested.maximized); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel (client) requests to be put in fullscreen. */ void _wlmaker_xdg_toplevel_handle_request_fullscreen( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, request_fullscreen_listener); // If the request is to enable fullscreen: Set the requested output. // Otherwise clear it, to prevent lingering references. wlmtk_window_set_preferred_wlr_output( wxt_ptr->window_ptr, wxt_ptr->wlr_xdg_toplevel_ptr->requested.fullscreen ? wxt_ptr->wlr_xdg_toplevel_ptr->requested.fullscreen_output : NULL); wlmtk_window_request_fullscreen( wxt_ptr->window_ptr, wxt_ptr->wlr_xdg_toplevel_ptr->requested.fullscreen); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel requests to be minimized. */ void _wlmaker_xdg_toplevel_handle_request_minimize( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, request_minimize_listener); bs_log(BS_ERROR, "TODO: Request minimize %p", wlmaker_xdg_toplevel_ptr); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel requests to be moved. */ void _wlmaker_xdg_toplevel_handle_request_move( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, request_move_listener); wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace( wlmaker_xdg_toplevel_ptr->window_ptr); if (NULL == workspace_ptr) return; wlmtk_workspace_begin_window_move( workspace_ptr, wlmaker_xdg_toplevel_ptr->window_ptr); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel requests to be resized. */ void _wlmaker_xdg_toplevel_handle_request_resize( struct wl_listener *listener_ptr, void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, request_resize_listener); struct wlr_xdg_toplevel_resize_event *resize_event_ptr = data_ptr; wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace( wlmaker_xdg_toplevel_ptr->window_ptr); if (NULL == workspace_ptr) return; wlmtk_workspace_begin_window_resize( workspace_ptr, wlmaker_xdg_toplevel_ptr->window_ptr, resize_event_ptr->edges); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel requests to show the window menu. */ void _wlmaker_xdg_toplevel_handle_request_show_window_menu( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, request_show_window_menu_listener); wlmtk_window_menu_set_enabled( wlmaker_xdg_toplevel_ptr->window_ptr, !wlmtk_menu_is_open(wlmtk_window_menu(wlmaker_xdg_toplevel_ptr->window_ptr))); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel sets the parent. */ void _wlmaker_xdg_toplevel_handle_set_parent( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, set_parent_listener); bs_log(BS_ERROR, "TODO: set_parent %p", wlmaker_xdg_toplevel_ptr); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel sets the title. */ void _wlmaker_xdg_toplevel_handle_set_title( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, set_title_listener); wlmtk_window_set_title( wlmaker_xdg_toplevel_ptr->window_ptr, wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr->title); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel sets the app ID. */ void _wlmaker_xdg_toplevel_handle_set_app_id( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, set_app_id_listener); bs_log(BS_ERROR, "TODO: set_app_id %p", wlmaker_xdg_toplevel_ptr); } /* ------------------------------------------------------------------------- */ /** The XDG toplevel adds a new popup. */ void _wlmaker_xdg_toplevel_handle_new_popup( struct wl_listener *listener_ptr, void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, new_popup_listener); struct wlr_xdg_popup *wlr_xdg_popup_ptr = data_ptr; wlmaker_xdg_popup_t *xdg_popup_ptr = wlmaker_xdg_popup_create( wlr_xdg_popup_ptr, wlmaker_xdg_toplevel_ptr->server_ptr->wlr_seat_ptr); if (NULL == xdg_popup_ptr) { wl_resource_post_error( wlr_xdg_popup_ptr->resource, WL_DISPLAY_ERROR_NO_MEMORY, "Failed wlmtk_xdg_popup2_create"); return; } wlmtk_base_push_element( &wlmaker_xdg_toplevel_ptr->base, wlmaker_xdg_popup_element(xdg_popup_ptr)); } /* ------------------------------------------------------------------------- */ /** Handles the XDG's surface map event. */ void _wlmaker_xdg_toplevel_handle_surface_map( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, surface_map_listener); wxt_ptr->mapped = true; _wlmaker_xdg_toplevel_try_map(wxt_ptr); } /* ------------------------------------------------------------------------- */ /** Handles when the XDG surface is to be unmapped. */ void _wlmaker_xdg_toplevel_handle_surface_unmap( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, surface_unmap_listener); wxt_ptr->mapped = false; if (NULL != wlmtk_window_get_workspace(wxt_ptr->window_ptr)) { wlmtk_workspace_unmap_window( wlmtk_window_get_workspace(wxt_ptr->window_ptr), wxt_ptr->window_ptr); } } /* ------------------------------------------------------------------------- */ /** Handles XDG surface commits. */ void _wlmaker_xdg_toplevel_handle_surface_commit( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, surface_commit_listener); struct wlr_xdg_surface *wlr_xdg_surface_ptr = wxt_ptr->wlr_xdg_toplevel_ptr->base; struct wlr_box geo = wxt_ptr->wlr_xdg_toplevel_ptr->base->current.geometry; if (0 >= geo.width && 0 >= geo.height) { wxt_ptr->_get_extents( wxt_ptr->wlr_xdg_toplevel_ptr->base->surface, &geo); } wlmtk_window_commit_size( wxt_ptr->window_ptr, geo.width - geo.x, geo.height - geo.y); if (0 != wxt_ptr->pending.properties) { _wlmaker_xdg_toplevel_flush_properties(wxt_ptr); } else if (wlr_xdg_surface_ptr->initial_commit) { wlr_xdg_surface_schedule_configure(wlr_xdg_surface_ptr); } wxt_ptr->committed_serial = wxt_ptr->wlr_xdg_toplevel_ptr->base->current.configure_serial; _wlmaker_xdg_toplevel_try_map(wxt_ptr); if (wxt_ptr->wlr_xdg_toplevel_ptr->current.fullscreen != wlmtk_window_is_fullscreen(wxt_ptr->window_ptr)) { wlmtk_window_commit_fullscreen( wxt_ptr->window_ptr, wxt_ptr->wlr_xdg_toplevel_ptr->current.fullscreen); } else if (wxt_ptr->wlr_xdg_toplevel_ptr->current.maximized != wlmtk_window_is_maximized(wxt_ptr->window_ptr)) { wlmtk_window_commit_maximized( wxt_ptr->window_ptr, wxt_ptr->wlr_xdg_toplevel_ptr->current.maximized); } } /* ------------------------------------------------------------------------- */ /** Handles the window's request_close event: Forwards to the toplevel. */ void _wlmaker_xdg_toplevel_handle_window_request_close( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, window_request_close_listener); wlr_xdg_toplevel_send_close( wlmaker_xdg_toplevel_ptr->wlr_xdg_toplevel_ptr); } /* ------------------------------------------------------------------------- */ /** Handles the window's activation event: Forwards to the toplevel. */ void _wlmaker_xdg_toplevel_handle_window_set_activated( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, window_set_activated_listener); wlmaker_xdg_toplevel_ptr->pending.properties |= WXT_PROP_ACTIVATED; wlmaker_xdg_toplevel_ptr->pending.activated = wlmtk_window_is_activated(wlmaker_xdg_toplevel_ptr->window_ptr); _wlmaker_xdg_toplevel_flush_properties(wlmaker_xdg_toplevel_ptr); if (NULL != wlmaker_xdg_toplevel_ptr->surface_ptr) { wlmtk_surface_set_activated( wlmaker_xdg_toplevel_ptr->surface_ptr, wlmtk_window_is_activated(wlmaker_xdg_toplevel_ptr->window_ptr)); } } /* ------------------------------------------------------------------------- */ /** Handles the window's request for size. */ void _wlmaker_xdg_toplevel_handle_window_request_size( struct wl_listener *listener_ptr, void *data_ptr) { struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, window_request_size_listener); const struct wlr_box *box_ptr = data_ptr; // Ignore the request, if commits have not caught up yet. if (wxt_ptr->set_size_serial > wxt_ptr->committed_serial) return; wxt_ptr->pending.width = box_ptr->width; wxt_ptr->pending.height = box_ptr->height; wxt_ptr->pending.properties |= WXT_PROP_SIZE; _wlmaker_xdg_toplevel_flush_properties(wxt_ptr); } /* ------------------------------------------------------------------------- */ /** Handles the window's request for going to fullscreen. */ void _wlmaker_xdg_toplevel_handle_window_request_fullscreen( struct wl_listener *listener_ptr, void *data_ptr) { struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, window_request_fullscreen_listener); bool *fullscreen_ptr = data_ptr; wxt_ptr->pending.properties |= WXT_PROP_FULLSCREEN; wxt_ptr->pending.fullscreen = *fullscreen_ptr; if (wxt_ptr->pending.fullscreen) wxt_ptr->pending.maximized = false; _wlmaker_xdg_toplevel_flush_properties(wxt_ptr); } /* ------------------------------------------------------------------------- */ /** Handles the window's request for going to maximized. */ void _wlmaker_xdg_toplevel_handle_window_request_maximized( struct wl_listener *listener_ptr, void *data_ptr) { struct wlmaker_xdg_toplevel *wxt_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmaker_xdg_toplevel, window_request_maximized_listener); bool *maximized_ptr = data_ptr; wxt_ptr->pending.properties |= WXT_PROP_MAXIMIZED; wxt_ptr->pending.maximized = *maximized_ptr; _wlmaker_xdg_toplevel_flush_properties(wxt_ptr); } /** == Unit test helpers =================================================== */ /** Data needed for using an output layout for tests. */ struct _wlmaker_test_layout { #ifndef DOXYGEN_SHOULD_SKIP_THIS struct wl_display *wl_display_ptr; struct wlr_output_layout *wlr_output_layout_ptr; struct wlr_output wlr_output; #endif }; static bool _wlmaker_test_layout_init(struct _wlmaker_test_layout *tl_ptr); static void _wlmaker_test_layout_fini(struct _wlmaker_test_layout *tl_ptr); /** Initializes @ref _wlmaker_test_layout */ bool _wlmaker_test_layout_init(struct _wlmaker_test_layout *tl_ptr) { *tl_ptr = (struct _wlmaker_test_layout){}; tl_ptr->wl_display_ptr = wl_display_create(); if (NULL == tl_ptr->wl_display_ptr) return false; tl_ptr->wlr_output_layout_ptr = wlr_output_layout_create( tl_ptr->wl_display_ptr); if (NULL == tl_ptr->wl_display_ptr) { _wlmaker_test_layout_fini(tl_ptr); return false; } tl_ptr->wlr_output = (struct wlr_output){ .name = "Output", .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&tl_ptr->wlr_output); return true; } /** Un-initializes @ref _wlmaker_test_layout */ void _wlmaker_test_layout_fini(struct _wlmaker_test_layout *tl_ptr) { if (NULL != tl_ptr->wl_display_ptr) { wl_display_destroy(tl_ptr->wl_display_ptr); tl_ptr->wl_display_ptr = NULL; } } /** Data for testing an XDG toplevel. */ struct _xdg_toplevel_test_data { #ifndef DOXYGEN_SHOULD_SKIP_THIS struct wlr_surface wlr_surface; struct wlr_xdg_surface wlr_xdg_surface; struct wlr_xdg_toplevel wlr_xdg_toplevel; struct _wlmaker_test_layout test_layout; wlmaker_config_style_t style; wlmaker_server_t server; int32_t set_size_width; int32_t set_size_height; int set_maximized_calls; int set_fullscreen_calls; int set_size_calls; int set_activated_calls; wlmtk_workspace_t *workspace_ptr; #endif }; /** A fake for wlr_xdg_toplevel_set_maximized(). Records the call. */ uint32_t _wlmaker_xdg_toplevel_fake_set_maximized( struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, __UNUSED__ bool maximized) { struct _xdg_toplevel_test_data *td_ptr = BS_CONTAINER_OF( wlr_xdg_toplevel_ptr, struct _xdg_toplevel_test_data, wlr_xdg_toplevel); td_ptr->set_maximized_calls++; return 0; } /** A fake for wlr_xdg_toplevel_set_fullscreen(). Records the call. */ uint32_t _wlmaker_xdg_toplevel_fake_set_fullscreen( struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, __UNUSED__ bool fullscreen) { struct _xdg_toplevel_test_data *td_ptr = BS_CONTAINER_OF( wlr_xdg_toplevel_ptr, struct _xdg_toplevel_test_data, wlr_xdg_toplevel); td_ptr->set_fullscreen_calls++; return 0; } /** A fake for wlr_xdg_toplevel_set_size(). Records the call. */ uint32_t _wlmaker_xdg_toplevel_fake_set_size( struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, int32_t width, int32_t height) { struct _xdg_toplevel_test_data *td_ptr = BS_CONTAINER_OF( wlr_xdg_toplevel_ptr, struct _xdg_toplevel_test_data, wlr_xdg_toplevel); td_ptr->set_size_calls++; td_ptr->set_size_width = width; td_ptr->set_size_height = height; return 0; } /** A fake for wlr_xdg_toplevel_set_activated(). Records the call. */ uint32_t _wlmaker_xdg_toplevel_fake_set_activated( struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, __UNUSED__ bool activated) { struct _xdg_toplevel_test_data *td_ptr = BS_CONTAINER_OF( wlr_xdg_toplevel_ptr, struct _xdg_toplevel_test_data, wlr_xdg_toplevel); td_ptr->set_activated_calls++; return 0; } /** A fake for wlr_surface_get_extents(). Sets box to empty. */ void _wlmaker_xdg_toplevel_fake_get_extents_empty( __UNUSED__ struct wlr_surface *wlr_surface_ptr, struct wlr_box *box_ptr) { *box_ptr = (struct wlr_box){}; } /** A fake for wlr_surface_get_extents(). Sets box to fixed size. */ void _wlmaker_xdg_toplevel_fake_get_extents_64x32( __UNUSED__ struct wlr_surface *wlr_surface_ptr, struct wlr_box *box_ptr) { *box_ptr = (struct wlr_box){ .width = 64, .height = 32 }; } /* == Unit tests =========================================================== */ static void *_wlmaker_xdg_toplevel_test_setup(void); static void _wlmaker_xdg_toplevel_test_teardown(void *test_context_ptr); static void _wlmaker_xdg_toplevel_test_maximize(bs_test_t *test_ptr); static void _wlmaker_xdg_toplevel_test_fullscreen(bs_test_t *test_ptr); static void _wlmaker_xdg_toplevel_test_size(bs_test_t *test_ptr); static void _wlmaker_xdg_toplevel_test_activated(bs_test_t *test_ptr); static void _wlmaker_xdg_toplevel_test_map(bs_test_t *test_ptr); static void _wlmaker_xdg_toplevel_test_map_nogeo(bs_test_t *test_ptr); /** Unit test cases. */ const bs_test_case_t _test_cases[] = { { true, "maximize", _wlmaker_xdg_toplevel_test_maximize }, { true, "fullscreen", _wlmaker_xdg_toplevel_test_fullscreen }, { true, "size", _wlmaker_xdg_toplevel_test_size }, { true, "activated", _wlmaker_xdg_toplevel_test_activated }, { true, "map", _wlmaker_xdg_toplevel_test_map }, { true, "map_nogeo", _wlmaker_xdg_toplevel_test_map_nogeo }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_xdg_toplevel_test_set = BS_TEST_SET_CONTEXT( true, "xdg_toplevel", _test_cases, _wlmaker_xdg_toplevel_test_setup, _wlmaker_xdg_toplevel_test_teardown); /* ------------------------------------------------------------------------- */ /** Creates a @ref _xdg_toplevel_test_data in the test context. */ void *_wlmaker_xdg_toplevel_test_setup(void) { struct _xdg_toplevel_test_data *td_ptr = logged_calloc( 1, sizeof(struct _xdg_toplevel_test_data)); if (NULL == td_ptr) return NULL; td_ptr->wlr_surface.data = td_ptr; td_ptr->wlr_xdg_surface.surface = &td_ptr->wlr_surface; td_ptr->wlr_xdg_toplevel.base = &td_ptr->wlr_xdg_surface; td_ptr->style = (wlmaker_config_style_t){ .window_style_ptr = wlmtk_window_style_create(), .menu_style_ptr = wlmtk_menu_style_create(), }; if (NULL == td_ptr->style.window_style_ptr || NULL == td_ptr->style.menu_style_ptr) goto error; if (!_wlmaker_test_layout_init(&td_ptr->test_layout)) goto error; wlr_output_layout_add_auto( td_ptr->test_layout.wlr_output_layout_ptr, &td_ptr->test_layout.wlr_output); td_ptr->server.wlr_output_layout_ptr = td_ptr->test_layout.wlr_output_layout_ptr; td_ptr->server.style_ptr = &td_ptr->style; td_ptr->server.wlr_scene_ptr = wlr_scene_create(); if (NULL == td_ptr->server.wlr_scene_ptr) goto error; td_ptr->server.root_ptr = wlmtk_root_create( td_ptr->server.wlr_scene_ptr, td_ptr->test_layout.wlr_output_layout_ptr); if (NULL == td_ptr->server.root_ptr) goto error; wl_signal_init(&td_ptr->server.window_created_event); wl_signal_init(&td_ptr->server.window_destroyed_event); wl_signal_init(&td_ptr->wlr_surface.events.commit); wl_signal_init(&td_ptr->wlr_surface.events.map); wl_signal_init(&td_ptr->wlr_surface.events.unmap); wl_signal_init(&td_ptr->wlr_xdg_surface.events.destroy); wl_signal_init(&td_ptr->wlr_xdg_surface.events.ping_timeout); wl_signal_init(&td_ptr->wlr_xdg_surface.events.new_popup); wl_signal_init(&td_ptr->wlr_xdg_surface.events.configure); wl_signal_init(&td_ptr->wlr_xdg_surface.events.ack_configure); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.destroy); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_maximize); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_fullscreen); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_minimize); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_move); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_resize); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.request_show_window_menu); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.set_parent); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.set_title); wl_signal_init(&td_ptr->wlr_xdg_toplevel.events.set_app_id); static const struct wlmtk_tile_style ts = { .size = 64 }; td_ptr->workspace_ptr = wlmtk_workspace_create( td_ptr->test_layout.wlr_output_layout_ptr, "test", &ts); if (NULL == td_ptr->workspace_ptr) goto error; wlmtk_root_add_workspace(td_ptr->server.root_ptr, td_ptr->workspace_ptr); return td_ptr; error: _wlmaker_xdg_toplevel_test_teardown(td_ptr); return NULL; } /* ------------------------------------------------------------------------- */ /** Tear down XDG toplevel data. */ void _wlmaker_xdg_toplevel_test_teardown(void *test_context_ptr) { struct _xdg_toplevel_test_data *td_ptr = test_context_ptr; if (NULL != td_ptr->server.root_ptr) { wlmtk_root_destroy(td_ptr->server.root_ptr); td_ptr->server.root_ptr = NULL; } wlr_scene_node_destroy(&td_ptr->server.wlr_scene_ptr->tree.node); _wlmaker_test_layout_fini(&td_ptr->test_layout); if (NULL != td_ptr->style.window_style_ptr) { wlmtk_window_style_ref_release( wlmtk_window_style_to_ref(td_ptr->style.window_style_ptr)); } if (NULL != td_ptr->style.menu_style_ptr) { wlmtk_menu_style_ref_release( wlmtk_menu_style_to_ref(td_ptr->style.menu_style_ptr)); } free(td_ptr); } /* ------------------------------------------------------------------------- */ /** Tests maximize requests, from client & window. */ void _wlmaker_xdg_toplevel_test_maximize(bs_test_t *test_ptr) { struct _xdg_toplevel_test_data *td_ptr = BS_ASSERT_NOTNULL(bs_test_context(test_ptr)); wlmtk_util_test_listener_t created, destroyed; wlmtk_util_connect_test_listener( &td_ptr->server.window_created_event, &created); wlmtk_util_connect_test_listener( &td_ptr->server.window_destroyed_event, &destroyed); struct wlmaker_xdg_toplevel *wxt_ptr = _wlmaker_xdg_toplevel_create_injected( &td_ptr->wlr_xdg_toplevel, &td_ptr->server, _wlmaker_xdg_toplevel_fake_set_maximized, _wlmaker_xdg_toplevel_fake_set_fullscreen, _wlmaker_xdg_toplevel_fake_set_size, _wlmaker_xdg_toplevel_fake_set_activated, _wlmaker_xdg_toplevel_fake_get_extents_empty); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, created.calls); // No initial commit yet. No `set_maximized`. wl_signal_emit(&td_ptr->wlr_xdg_toplevel.events.request_maximize, NULL); BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_maximized_calls); // Now we have pending properties. Commit must trigger a `configure`. td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure(). td_ptr->wlr_xdg_surface.current.geometry.width = 200; td_ptr->wlr_xdg_surface.current.geometry.height = 100; wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_maximized_calls); // When initialized a `request_maximize` configures right away. td_ptr->set_maximized_calls = 0; wl_signal_emit(&td_ptr->wlr_xdg_toplevel.events.request_maximize, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_maximized_calls); // Issued from the window, ie. from the compositor. bool m = true; td_ptr->wlr_xdg_surface.initialized = false; // Ready for configure(). td_ptr->set_maximized_calls = 0; wl_signal_emit( &wlmtk_window_events(wxt_ptr->window_ptr)->request_maximized, &m); BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_maximized_calls); // Pending properties. Next commit must trigger. td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure(). wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_maximized_calls); td_ptr->set_maximized_calls = 0; wl_signal_emit( &wlmtk_window_events(wxt_ptr->window_ptr)->request_maximized, &m); BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_maximized_calls); wlmaker_xdg_toplevel_destroy(wxt_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, destroyed.calls); } /* ------------------------------------------------------------------------- */ /** Tests fullscreen requests, from client & window. */ void _wlmaker_xdg_toplevel_test_fullscreen(bs_test_t *test_ptr) { struct _xdg_toplevel_test_data *td_ptr = BS_ASSERT_NOTNULL(bs_test_context(test_ptr)); struct wlmaker_xdg_toplevel *wxt_ptr = _wlmaker_xdg_toplevel_create_injected( &td_ptr->wlr_xdg_toplevel, &td_ptr->server, _wlmaker_xdg_toplevel_fake_set_maximized, _wlmaker_xdg_toplevel_fake_set_fullscreen, _wlmaker_xdg_toplevel_fake_set_size, _wlmaker_xdg_toplevel_fake_set_activated, _wlmaker_xdg_toplevel_fake_get_extents_empty); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr); // Issued from the window, ie. from the compositor. bool f = true; wl_signal_emit( &wlmtk_window_events(wxt_ptr->window_ptr)->request_fullscreen, &f); BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_fullscreen_calls); // Pending properties. Next commit must trigger. td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure(). td_ptr->wlr_xdg_surface.current.geometry.width = 200; td_ptr->wlr_xdg_surface.current.geometry.height = 100; wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_fullscreen_calls); // Issue from the client. No commit yet. Must not configure(). td_ptr->wlr_xdg_surface.initialized = false; // Not ready for configure(). td_ptr->set_fullscreen_calls = 0; wl_signal_emit(&td_ptr->wlr_xdg_toplevel.events.request_fullscreen, NULL); BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_fullscreen_calls); // Following commit must trigger a `configure`. td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure(). wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_fullscreen_calls); wlmaker_xdg_toplevel_destroy(wxt_ptr); } /* ------------------------------------------------------------------------- */ /** Tests set_size requests. */ void _wlmaker_xdg_toplevel_test_size(bs_test_t *test_ptr) { struct _xdg_toplevel_test_data *td_ptr = BS_ASSERT_NOTNULL(bs_test_context(test_ptr)); struct wlmaker_xdg_toplevel *wxt_ptr = _wlmaker_xdg_toplevel_create_injected( &td_ptr->wlr_xdg_toplevel, &td_ptr->server, _wlmaker_xdg_toplevel_fake_set_maximized, _wlmaker_xdg_toplevel_fake_set_fullscreen, _wlmaker_xdg_toplevel_fake_set_size, _wlmaker_xdg_toplevel_fake_set_activated, _wlmaker_xdg_toplevel_fake_get_extents_empty); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr); // Issued from the window, ie. from the compositor. struct wlr_box b = { .width = 10, .height = 20 }; wl_signal_emit( &wlmtk_window_events(wxt_ptr->window_ptr)->request_size, &b); BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_size_calls); // Pending properties. Next commit must trigger. td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure(). td_ptr->wlr_xdg_surface.current.geometry.width = 200; td_ptr->wlr_xdg_surface.current.geometry.height = 100; wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_size_calls); BS_TEST_VERIFY_EQ(test_ptr, 10, td_ptr->set_size_width); BS_TEST_VERIFY_EQ(test_ptr, 20, td_ptr->set_size_height); wlmaker_xdg_toplevel_destroy(wxt_ptr); } /* ------------------------------------------------------------------------- */ /** Tests set_activated requests. */ void _wlmaker_xdg_toplevel_test_activated(bs_test_t *test_ptr) { struct _xdg_toplevel_test_data *td_ptr = BS_ASSERT_NOTNULL(bs_test_context(test_ptr)); struct wlmaker_xdg_toplevel *wxt_ptr = _wlmaker_xdg_toplevel_create_injected( &td_ptr->wlr_xdg_toplevel, &td_ptr->server, _wlmaker_xdg_toplevel_fake_set_maximized, _wlmaker_xdg_toplevel_fake_set_fullscreen, _wlmaker_xdg_toplevel_fake_set_size, _wlmaker_xdg_toplevel_fake_set_activated, _wlmaker_xdg_toplevel_fake_get_extents_empty); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr); // Issued from the window, ie. from the compositor. bool a = true; wl_signal_emit( &wlmtk_window_events(wxt_ptr->window_ptr)->set_activated, &a); BS_TEST_VERIFY_EQ(test_ptr, 0, td_ptr->set_activated_calls); // Pending properties. Next commit must trigger. td_ptr->wlr_xdg_surface.initialized = true; // Ready for configure(). td_ptr->wlr_xdg_surface.current.geometry.width = 200; td_ptr->wlr_xdg_surface.current.geometry.height = 100; wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, 1, td_ptr->set_activated_calls); wlmaker_xdg_toplevel_destroy(wxt_ptr); } /* ------------------------------------------------------------------------- */ /** Tests surface map calls. */ void _wlmaker_xdg_toplevel_test_map(bs_test_t *test_ptr) { struct _xdg_toplevel_test_data *td_ptr = BS_ASSERT_NOTNULL(bs_test_context(test_ptr)); struct wlmaker_xdg_toplevel *wxt_ptr = _wlmaker_xdg_toplevel_create_injected( &td_ptr->wlr_xdg_toplevel, &td_ptr->server, _wlmaker_xdg_toplevel_fake_set_maximized, _wlmaker_xdg_toplevel_fake_set_fullscreen, _wlmaker_xdg_toplevel_fake_set_size, _wlmaker_xdg_toplevel_fake_set_activated, _wlmaker_xdg_toplevel_fake_get_extents_empty); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr); wlmtk_window_t *w = wxt_ptr->window_ptr; // for convenience. // Override the surface element. We cannot actually map it. wlmtk_base_set_content_element(&wxt_ptr->base, NULL); wxt_ptr->surface_ptr = NULL; // Situation 1: Initial map call, but surface has size (0x0) yet. // => will not be mapped yet, but only after a commit with non-zero size. wl_signal_emit(&td_ptr->wlr_surface.events.map, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); td_ptr->wlr_xdg_surface.current.geometry.width = 200; td_ptr->wlr_xdg_surface.current.geometry.height = 100; wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 200, 100, wlmtk_window_get_size(w)); wl_signal_emit(&td_ptr->wlr_surface.events.unmap, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); // Situation 2: Size is set. Must get mapped upon surface 'map'. wl_signal_emit(&td_ptr->wlr_surface.events.map, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); wl_signal_emit(&td_ptr->wlr_surface.events.unmap, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); wlmaker_xdg_toplevel_destroy(wxt_ptr); } /* ------------------------------------------------------------------------- */ /** Tests surface map calls if no geometry is given. */ void _wlmaker_xdg_toplevel_test_map_nogeo(bs_test_t *test_ptr) { struct _xdg_toplevel_test_data *td_ptr = BS_ASSERT_NOTNULL(bs_test_context(test_ptr)); struct wlmaker_xdg_toplevel *wxt_ptr = _wlmaker_xdg_toplevel_create_injected( &td_ptr->wlr_xdg_toplevel, &td_ptr->server, _wlmaker_xdg_toplevel_fake_set_maximized, _wlmaker_xdg_toplevel_fake_set_fullscreen, _wlmaker_xdg_toplevel_fake_set_size, _wlmaker_xdg_toplevel_fake_set_activated, _wlmaker_xdg_toplevel_fake_get_extents_64x32); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wxt_ptr); wlmtk_window_t *w = wxt_ptr->window_ptr; // for convenience. // Override the surface element. We cannot actually map it. wlmtk_base_set_content_element(&wxt_ptr->base, NULL); wxt_ptr->surface_ptr = NULL; // Situation 1: Initial map call. Surface has geometry 0,0, but // the extents are 64x32. Will map, with that size. wl_signal_emit(&td_ptr->wlr_surface.events.map, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 64, 32, wlmtk_window_get_size(w)); wl_signal_emit(&td_ptr->wlr_surface.events.unmap, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); // Situation 2: Size is set. Must get mapped upon surface 'map'. wl_signal_emit(&td_ptr->wlr_surface.events.map, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); wl_signal_emit(&td_ptr->wlr_surface.events.commit, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); wl_signal_emit(&td_ptr->wlr_surface.events.unmap, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_workspace(w)); wlmaker_xdg_toplevel_destroy(wxt_ptr); } /* == End of xdg_toplevel.c ================================================ */ wlmaker-0.8.1/src/xdg_decoration.h0000644000175100017510000000354015222270710016521 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_decoration.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __XDG_DECORATION_H__ #define __XDG_DECORATION_H__ #include #include struct wl_display; /** The decoration manager handle. */ typedef struct _wlmaker_xdg_decoration_manager_t wlmaker_xdg_decoration_manager_t; #include "server.h" // IWYU pragma: keep #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a new XDG decoration manager. * * @param wl_display_ptr * @param config_dict_ptr * * @return A decoration manager handle or NULL on error. */ wlmaker_xdg_decoration_manager_t *wlmaker_xdg_decoration_manager_create( struct wl_display *wl_display_ptr, bspl_dict_t *config_dict_ptr); /** * Destroys the XDG decoration manager. * * @param decoration_manager_ptr */ void wlmaker_xdg_decoration_manager_destroy( wlmaker_xdg_decoration_manager_t *decoration_manager_ptr); /** Unit test set. */ extern const bs_test_set_t wlmaker_xdg_decoration_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __XDG_DECORATION_H__ */ /* == End of xdg_decoration.h ============================================== */ wlmaker-0.8.1/src/input_observation.c0000644000175100017510000003651115222270710017301 0ustar runnerrunner/* ========================================================================= */ /** * @file input_observation.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "input_observation.h" #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "server.h" #include "toolkit/toolkit.h" #include "ext-input-observation-v1-server-protocol.h" struct wl_client; struct wl_resource; /* == Declarations ========================================================= */ /** State of the input observation manager. */ struct _wlmaker_input_observation_manager_t { /** The global holding the input observation's interface. */ struct wl_global *wl_global_ptr; /** Link to the wlroots' implementation of wl_seat. */ struct wlr_seat *wlr_seat_ptr; /** Link to the wlroots' cursor implementation. */ struct wlr_cursor *wlr_cursor_ptr; }; /** State of a position observer. */ struct _wlmaker_input_position_observer_t { /** The corresponding resource. */ struct wl_resource *wl_resource_ptr; /** The pointer it was set up for. */ struct wl_resource *pointer_wl_resource_ptr; /** The surface it observes the position for. */ struct wlr_surface *wlr_surface_ptr; /** Link to the wlroots' cursor implementation. */ struct wlr_cursor *wlr_cursor_ptr; /** Listener for `destroy` event of the `wlr_surface_ptr`. */ struct wl_listener surface_destroy_listener; /** Listener for `frame` event of struct wlr_cursor. */ struct wl_listener cursor_frame_listener; }; static wlmaker_input_observation_manager_t *input_observation_manager_from_resource( struct wl_resource *wl_resource_ptr); static void bind_input_observation( struct wl_client *wl_client_ptr, void *data_ptr, uint32_t version, uint32_t id); static void handle_resource_destroy( struct wl_client *wl_client_ptr, struct wl_resource *wl_resource_ptr); static void input_observation_manager_handle_create_pointer_observer( struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *pointer_wl_resource_ptr, struct wl_resource *surface); static wlmaker_input_position_observer_t *wlmaker_input_position_observer_from_resource( struct wl_resource *wl_resource_ptr); static wlmaker_input_position_observer_t *wlmaker_input_position_observer_create( struct wl_client *wl_client_ptr, wlmaker_input_observation_manager_t *manager_ptr, uint32_t id, int version, struct wl_resource *pointer_wl_resource_ptr, struct wlr_surface *wlr_surface_ptr); static void wlmaker_input_position_observer_resource_destroy( struct wl_resource *wl_resource_ptr); static void wlmaker_input_position_observer_destroy( wlmaker_input_position_observer_t *position_observer_ptr); static void _wlmaker_input_position_observer_handle_surface_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_input_position_observer_handle_cursor_frame( struct wl_listener *listener_ptr, void *data_ptr); /* ========================================================================= */ /** Implementation of the position observation manager. */ static const struct ext_input_observation_manager_v1_interface input_observation_manager_v1_implementation = { .destroy = handle_resource_destroy, .create_pointer_observer = input_observation_manager_handle_create_pointer_observer, }; /** Implementation of the position (position) observer. */ static const struct ext_input_position_observer_v1_interface input_position_observer_v1_implementation = { .destroy = handle_resource_destroy, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_input_observation_manager_t *wlmaker_input_observation_manager_create( struct wl_display *wl_display_ptr, struct wlr_seat *wlr_seat_ptr, struct wlr_cursor *wlr_cursor_ptr) { wlmaker_input_observation_manager_t *manager_ptr = logged_calloc( 1, sizeof(wlmaker_input_observation_manager_t)); if (NULL == manager_ptr) return NULL; manager_ptr->wlr_seat_ptr = wlr_seat_ptr; manager_ptr->wlr_cursor_ptr = wlr_cursor_ptr; manager_ptr->wl_global_ptr = wl_global_create( wl_display_ptr, &ext_input_observation_manager_v1_interface, 1, manager_ptr, bind_input_observation); if (NULL == manager_ptr->wl_global_ptr) { bs_log(BS_ERROR, "Failed wl_global_create"); wlmaker_input_observation_manager_destroy(manager_ptr); return NULL; } return manager_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_input_observation_manager_destroy( wlmaker_input_observation_manager_t *manager_ptr) { if (NULL != manager_ptr->wl_global_ptr) { wl_global_destroy(manager_ptr->wl_global_ptr); manager_ptr->wl_global_ptr = NULL; } free(manager_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Returns the toplevel position observer from the resource, with type check. * * @param wl_resource_ptr * * @return Position to the @ref wlmaker_input_observation_manager_t. */ wlmaker_input_observation_manager_t *input_observation_manager_from_resource( struct wl_resource *wl_resource_ptr) { BS_ASSERT(wl_resource_instance_of( wl_resource_ptr, &ext_input_observation_manager_v1_interface, &input_observation_manager_v1_implementation)); return wl_resource_get_user_data(wl_resource_ptr); } /* ------------------------------------------------------------------------- */ /** * Binds the position observation manager for the client. * * @param wl_client_ptr * @param data_ptr * @param version * @param id */ void bind_input_observation( struct wl_client *wl_client_ptr, void *data_ptr, uint32_t version, uint32_t id) { struct wl_resource *wl_resource_ptr = wl_resource_create( wl_client_ptr, &ext_input_observation_manager_v1_interface, version, id); if (NULL == wl_resource_ptr) { wl_client_post_no_memory(wl_client_ptr); return; } wlmaker_input_observation_manager_t *manager_ptr = data_ptr; wl_resource_set_implementation( wl_resource_ptr, &input_observation_manager_v1_implementation, // implementation. manager_ptr, // data NULL); // dtor. We don't have an explicit one. } /* ------------------------------------------------------------------------- */ /** * Handler for the 'destroy' method: Destroys the resource. * * @param wl_client_ptr * @param wl_resource_ptr */ void handle_resource_destroy( __UNUSED__ struct wl_client *wl_client_ptr, struct wl_resource *wl_resource_ptr) { wl_resource_destroy(wl_resource_ptr); } /* ------------------------------------------------------------------------- */ /** * Creates an observer, associated with the surface. * * Requires that @ref wlmaker_input_observation_manager_t::wlr_seat_ptr is set * and has the `WL_SEAT_CAPABILITY_POINTER` capability. * * @param wl_client_ptr * @param wl_resource_ptr * @param id * @param pointer_wl_resource_ptr Resource handle of the pointer. * @param surface_wl_resource_ptr Resource handle of the surface. */ void input_observation_manager_handle_create_pointer_observer( struct wl_client *wl_client_ptr, struct wl_resource *wl_resource_ptr, uint32_t id, struct wl_resource *pointer_wl_resource_ptr, struct wl_resource *surface_wl_resource_ptr) { wlmaker_input_observation_manager_t *manager_ptr = input_observation_manager_from_resource(wl_resource_ptr); // Guard clause: We require the position capability to be (or have been) // present for the seat. if (!(manager_ptr->wlr_seat_ptr->accumulated_capabilities & WL_SEAT_CAPABILITY_POINTER)) { wl_resource_post_error( wl_resource_ptr, WL_DISPLAY_ERROR_INVALID_METHOD, "Missing pointer capability on seat"); return; } struct wlr_surface *wlr_surface_ptr = wlr_surface_from_resource(surface_wl_resource_ptr); wlmaker_input_position_observer_t *position_observer_ptr = wlmaker_input_position_observer_create( wl_client_ptr, manager_ptr, id, wl_resource_get_version(wl_resource_ptr), pointer_wl_resource_ptr, wlr_surface_ptr); if (NULL == position_observer_ptr) { wl_client_post_no_memory(wl_client_ptr); return; } } /* ------------------------------------------------------------------------- */ /** Ctor for the position observer. */ wlmaker_input_position_observer_t *wlmaker_input_position_observer_create( struct wl_client *wl_client_ptr, wlmaker_input_observation_manager_t *manager_ptr, uint32_t id, int version, struct wl_resource *pointer_wl_resource_ptr, struct wlr_surface *wlr_surface_ptr) { wlmaker_input_position_observer_t *position_observer_ptr = logged_calloc( 1, sizeof(wlmaker_input_position_observer_t)); if (NULL == position_observer_ptr) return NULL; position_observer_ptr->pointer_wl_resource_ptr = pointer_wl_resource_ptr; position_observer_ptr->wlr_surface_ptr = wlr_surface_ptr; position_observer_ptr->wlr_cursor_ptr = manager_ptr->wlr_cursor_ptr; position_observer_ptr->wl_resource_ptr = wl_resource_create( wl_client_ptr, &ext_input_position_observer_v1_interface, version, id); if (NULL == position_observer_ptr->wl_resource_ptr) { bs_log(BS_ERROR, "Failed wl_resource_create(%p, %p, %d, %"PRIu32")", wl_client_ptr, &ext_input_position_observer_v1_interface, version, id); wlmaker_input_position_observer_destroy(position_observer_ptr); return NULL; } wl_resource_set_implementation( position_observer_ptr->wl_resource_ptr, &input_position_observer_v1_implementation, position_observer_ptr, wlmaker_input_position_observer_resource_destroy); wlmtk_util_connect_listener_signal( &position_observer_ptr->wlr_surface_ptr->events.destroy, &position_observer_ptr->surface_destroy_listener, _wlmaker_input_position_observer_handle_surface_destroy); wlmtk_util_connect_listener_signal( &position_observer_ptr->wlr_cursor_ptr->events.frame, &position_observer_ptr->cursor_frame_listener, _wlmaker_input_position_observer_handle_cursor_frame); return position_observer_ptr; } /* ------------------------------------------------------------------------- */ /** Dtor, from the resource. */ void wlmaker_input_position_observer_resource_destroy( struct wl_resource *wl_resource_ptr) { wlmaker_input_position_observer_t *position_observer_ptr = wlmaker_input_position_observer_from_resource(wl_resource_ptr); wlmaker_input_position_observer_destroy(position_observer_ptr); } /* ------------------------------------------------------------------------- */ /** Dtor. */ void wlmaker_input_position_observer_destroy( wlmaker_input_position_observer_t *position_observer_ptr) { wlmtk_util_disconnect_listener(&position_observer_ptr->cursor_frame_listener); wlmtk_util_disconnect_listener(&position_observer_ptr->surface_destroy_listener); free(position_observer_ptr); } /* ------------------------------------------------------------------------- */ /** Type-safe conversion from resource to position observer. */ wlmaker_input_position_observer_t *wlmaker_input_position_observer_from_resource( struct wl_resource *wl_resource_ptr) { BS_ASSERT(wl_resource_instance_of( wl_resource_ptr, &ext_input_position_observer_v1_interface, &input_position_observer_v1_implementation)); return wl_resource_get_user_data(wl_resource_ptr); } /* ------------------------------------------------------------------------- */ /** Handles surface destruction: Destroy the position observer. */ static void _wlmaker_input_position_observer_handle_surface_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_input_position_observer_t *position_observer_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_input_position_observer_t, surface_destroy_listener); wl_resource_destroy(position_observer_ptr->wl_resource_ptr); } /* ------------------------------------------------------------------------- */ /** Handles cursor frame events: Sends the current pointer position. */ void _wlmaker_input_position_observer_handle_cursor_frame( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_input_position_observer_t *position_observer_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_input_position_observer_t, cursor_frame_listener); // Guard clause: No wlmtk surface or no scene means it's not fully mapped. wlmtk_surface_t *surface_ptr = position_observer_ptr->wlr_surface_ptr->data; if (NULL == surface_ptr || NULL == wlmtk_surface_element(surface_ptr)->wlr_scene_node_ptr) return; // TODO(kaeser@gubbe.ch): For a lower-level implementation, we should // only send if the pointer is from the given seat. // See wlr_seat_client_from_pointer_resource(). // Get coordinates. Returns false if not all parents rae enabled. int node_x, node_y; if (!wlr_scene_node_coords( wlmtk_surface_element(surface_ptr)->wlr_scene_node_ptr, &node_x, &node_y)) { return; } // Then, compute the cursor position, relative to the surface dimenions. // Note: This assumes the surface remains aligned to X/Y axes. int width, height; wlmtk_surface_get_size(surface_ptr, &width, &height); // Translate to 24:8 fixpoint and bound to range. double x = 256.0 * (double)( position_observer_ptr->wlr_cursor_ptr->x - node_x) / width; double y = 256.0 * (double)( position_observer_ptr->wlr_cursor_ptr->y - node_y) / height; ext_input_position_observer_v1_send_position( position_observer_ptr->wl_resource_ptr, position_observer_ptr->wlr_surface_ptr->resource, 0, BS_MAX(INT32_MIN, BS_MIN(INT32_MAX, x)), BS_MAX(INT32_MIN, BS_MIN(INT32_MAX, y))); } /* == End of input_observation.c =========================================== */ wlmaker-0.8.1/src/clip.h0000644000175100017510000000367115222270710014464 0ustar runnerrunner/* ========================================================================= */ /** * @file clip.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Creates the wlmaker clip. A view, with server-bound surfaces, that act * as a workspace-local dock and a workspace pager. * * Corresponding Window Maker documentation: * http://www.windowmaker.org/docs/guidedtour/clip.html */ #ifndef __CLIP_H__ #define __CLIP_H__ #include #include /** Forward definition: Clip handle. */ typedef struct _wlmaker_clip_t wlmaker_clip_t; #include "config.h" #include "server.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates the Clip. Needs the server to be up with workspaces running. * * @param server_ptr * @param state_dict_ptr * @param style_ptr * * @return Pointer to the Clip handle, or NULL on error. */ wlmaker_clip_t *wlmaker_clip_create( wlmaker_server_t *server_ptr, bspl_dict_t *state_dict_ptr, const wlmaker_config_style_t *style_ptr); /** * Destroys the Clip. * * @param clip_ptr */ void wlmaker_clip_destroy(wlmaker_clip_t *clip_ptr); /** Unit test set. */ extern const bs_test_set_t wlmaker_clip_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __CLIP_H__ */ /* == End of clip.h ======================================================== */ wlmaker-0.8.1/src/xdg_shell.h0000644000175100017510000000455215222270710015505 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_shell.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __XDG_SHELL_H__ #define __XDG_SHELL_H__ #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE /** Handle for XDG Shell server handler. */ typedef struct _wlmaker_xdg_shell_t wlmaker_xdg_shell_t; #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Handle for XDG Shell server handler. */ struct _wlmaker_xdg_shell_t { /** Back-link to the server this XDG Shell belongs to. */ wlmaker_server_t *server_ptr; /** XDG Shell handler. */ struct wlr_xdg_shell *wlr_xdg_shell_ptr; /** Listener for the `new_surface` signal raised by `wlr_xdg_shell`. */ struct wl_listener new_surface_listener; /** Listener for the `new_toplevel` signal raised by `wlr_xdg_shell`. */ struct wl_listener new_toplevel_listener; /** Listener for the `new_popup` signal raised by `wlr_xdg_shell`. */ struct wl_listener new_popup_listener; /** Listener for the `destroy` signal raised by `wlr_xdg_shell`. */ struct wl_listener destroy_listener; }; /** * Creates an XDG shell server handler. * * @param server_ptr * * @return The XDG Shell server handler or NULL on error. */ wlmaker_xdg_shell_t *wlmaker_xdg_shell_create(wlmaker_server_t *server_ptr); /** * Destroys the XDG Shell server handler. * * @param xdg_shell_ptr */ void wlmaker_xdg_shell_destroy(wlmaker_xdg_shell_t *xdg_shell_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __XDG_SHELL_H__ */ /* == End of xdg_shell.h =================================================== */ wlmaker-0.8.1/src/background.h0000644000175100017510000000357215222270710015654 0ustar runnerrunner/* ========================================================================= */ /** * @file background.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __BACKGROUND_H__ #define __BACKGROUND_H__ #include #include #include "toolkit/toolkit.h" struct wlr_output_layout; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a background, derived from a @ref wlmtk_panel_t. * * @param workspace_ptr * @param wlr_output_layout_ptr * @param color * * @return A list node acting as handle for the background, or NULL on error. */ bs_dllist_node_t *wlmaker_background_create( wlmtk_workspace_t *workspace_ptr, struct wlr_output_layout *wlr_output_layout_ptr, uint32_t color); /** * Destroys the background. * * @param dlnode_ptr * @param ud_ptr */ void wlmaker_background_dlnode_destroy( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /** * Sets the color for this workspace's background on each output. * * @param dlnode_ptr * @param ud_ptr */ void wlmaker_background_dlnode_set_color( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __BACKGROUND_H__ */ /* == End of background.h ================================================== */ wlmaker-0.8.1/src/xwl_surface.h0000644000175100017510000000367515222270710016063 0ustar runnerrunner/* ========================================================================= */ /** * @file xwl_surface.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __XWL_SURFACE_H__ #define __XWL_SURFACE_H__ #if defined(WLMAKER_HAVE_XWAYLAND) #include #include "task_list.h" #include "xwl.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration. */ struct wlr_xwayland_surface; /** XWayland window (surface) state. */ typedef struct _wlmaker_xwl_surface_t wlmaker_xwl_surface_t; /** * Creates an XWayland window. Technically, window surface. * * @param wlr_xwayland_surface_ptr * @param xwl_ptr * @param server_ptr * * @return Pointer to a @ref wlmaker_xwl_surface_t. */ wlmaker_xwl_surface_t *wlmaker_xwl_surface_create( struct wlr_xwayland_surface *wlr_xwayland_surface_ptr, wlmaker_xwl_t *xwl_ptr, wlmaker_server_t *server_ptr); /** * Destroys the XWayland window (surface). * * @param xwl_surface_ptr */ void wlmaker_xwl_surface_destroy(wlmaker_xwl_surface_t *xwl_surface_ptr); /** Unit test set for XWL surface. */ extern const bs_test_set_t wlmaker_xwl_surface_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // defined(WLMAKER_HAVE_XWAYLAND) #endif /* __XWL_SURFACE_H__ */ /* == End of xwl_surface.h ================================================= */ wlmaker-0.8.1/src/layer_shell.h0000644000175100017510000000300315222270710016025 0ustar runnerrunner/* ========================================================================= */ /** * @file layer_shell.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LAYER_SHELL_H__ #define __LAYER_SHELL_H__ /** Handle for the layer shell. */ typedef struct _wlmaker_layer_shell_t wlmaker_layer_shell_t; #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a layer shell handler. * * @param server_ptr * * @return A handle to the layer shell handler, or NULL on error. */ wlmaker_layer_shell_t *wlmaker_layer_shell_create( wlmaker_server_t *server_ptr); /** * Destroys the layer shell handler. * * @param layer_shell_ptr */ void wlmaker_layer_shell_destroy(wlmaker_layer_shell_t *layer_shell_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LAYER_SHELL_H__ */ /* == End of layer_shell.h ================================================= */ wlmaker-0.8.1/src/root_menu.c0000644000175100017510000010566215222270710015542 0ustar runnerrunner/* ========================================================================= */ /** * @file root_menu.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "root_menu.h" #include #include #include #include #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #undef WLR_USE_UNSTABLE #include "../etc/root_menu.h" #include "action.h" #include "action_item.h" #include "config.h" #include "subprocess_monitor.h" #include "server.h" /* == Declarations ========================================================= */ /** State of the root menu. */ struct _wlmaker_root_menu_t { /** Window. */ wlmtk_window_t *window_ptr; /** The root menu base instance. */ wlmtk_menu_t *menu_ptr; /** Listener for @ref wlmtk_menu_events_t::open_changed. */ struct wl_listener menu_open_changed_listener; /** Listener for @ref wlmtk_menu_events_t::request_close. */ struct wl_listener menu_request_close_listener; /** Listener for @ref wlmtk_window_events_t::request_close. */ struct wl_listener window_request_close_listener; /** Listener for @ref wlmtk_window_events_t::set_activated. */ struct wl_listener window_set_activated_listener; /** Back-link to the server. */ wlmaker_server_t *server_ptr; }; /** State of a menu generator, while waiting for subprocess to complete. */ typedef struct { /** Subprocess handle. */ wlmaker_subprocess_handle_t *subprocess_handle_ptr; /** Back-link to the server. */ wlmaker_server_t *server_ptr; /** The menu this generator is going to populate. */ wlmtk_menu_t *menu_ptr; /** Menu style reference. */ wlmtk_menu_style_ref_t *style_ref_ptr; /** Dynamic buffer to hold stdout while the process is running. */ bs_dynbuf_t *stdout_dynbuf_ptr; /** Listener for @ref wlmtk_menu_events_t::destroy. */ struct wl_listener menu_destroy_listener; } wlmaker_root_menu_generator_t; static void _wlmaker_root_menu_handle_window_request_close( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_root_menu_handle_window_set_activated( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_root_menu_handle_menu_open_changed( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_root_menu_handle_request_close( struct wl_listener *listener_ptr, void *data_ptr); static bool _wlmaker_root_menu_init_menu_from_array( wlmtk_menu_t *menu_ptr, bspl_array_t *array_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr); static bool _wlmaker_root_menu_populate_menu_items_from_array( wlmtk_menu_t *menu_ptr, bspl_array_t *array_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr); static bool _wlmaker_root_menu_populate_menu_items_from_file( wlmtk_menu_t *menu_ptr, const char *filename_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr); static bool _wlmaker_root_menu_populate_menu_items_from_generator( wlmtk_menu_t *menu_ptr, const char *command_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr); static void _wlmaker_root_menu_generator_destroy( wlmaker_root_menu_generator_t *gen_menu_ptr); static void _wlmaker_root_menu_generator_handle_menu_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_root_menu_generator_handle_terminated( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, int state, int code); static wlmtk_menu_item_t *_wlmaker_root_menu_create_item_from_array( bspl_array_t *item_array_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr); static wlmtk_menu_item_t *_wlmaker_root_menu_create_disabled_item( wlmtk_menu_style_ref_t *style_ref_ptr, const char *fmt_ptr, ...) __ARG_PRINTF__(2, 3); /* == Data ================================================================= */ /** Indicates to load the file specified in following argument. */ static const char *_wlmaker_root_menu_statement_include = "IncludePlistMenu"; /** * Indicates to generate the menu using a shell command specified in the * following argument. */ static const char *_wlmaker_root_menu_statement_generate = "GeneratePlistMenu"; /** * Unit test injector: struct wl_display that will be terminated when * subprocess terminates. Must be NULL when not for in unit tests. */ static struct wl_display *_wlmaker_root_menu_test_wl_display_ptr = NULL; /** Number of generators, used to terminate display in unit tests. */ static size_t _wlmaker_root_menu_generators = 0; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_root_menu_t *wlmaker_root_menu_create( wlmaker_server_t *server_ptr, const char *arg_root_menu_file_ptr, wlmtk_window_style_ref_t *window_style_ref_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr) { bspl_array_t *root_menu_array_ptr = bspl_array_from_object( wlmaker_config_object_load( server_ptr->files_ptr, "root_menu", arg_root_menu_file_ptr, "RootMenu.plist", embedded_binary_root_menu_data, embedded_binary_root_menu_size)); if (NULL == root_menu_array_ptr) return NULL; if (bspl_array_size(root_menu_array_ptr) <= 1) { bs_log(BS_ERROR, "Needs > 1 array element for menu definition."); return NULL; } if (BSPL_STRING != bspl_object_type( bspl_array_at(root_menu_array_ptr, 0))) { bs_log(BS_ERROR, "Array element [0] must be a string."); return NULL; } wlmaker_root_menu_t *root_menu_ptr = logged_calloc( 1, sizeof(wlmaker_root_menu_t)); if (NULL == root_menu_ptr) return NULL; root_menu_ptr->server_ptr = server_ptr; root_menu_ptr->server_ptr->root_menu_ptr = root_menu_ptr; root_menu_ptr->menu_ptr = wlmtk_menu_create(menu_style_ref_ptr); if (NULL == root_menu_ptr->menu_ptr) { wlmaker_root_menu_destroy(root_menu_ptr); bspl_array_unref(root_menu_array_ptr); return NULL; } if (!_wlmaker_root_menu_init_menu_from_array( root_menu_ptr->menu_ptr, root_menu_array_ptr, menu_style_ref_ptr, server_ptr)) { bspl_array_unref(root_menu_array_ptr); return NULL; } wlmtk_util_connect_listener_signal( &wlmtk_menu_events(root_menu_ptr->menu_ptr)->open_changed, &root_menu_ptr->menu_open_changed_listener, _wlmaker_root_menu_handle_menu_open_changed); wlmtk_util_connect_listener_signal( &wlmtk_menu_events(root_menu_ptr->menu_ptr)->request_close, &root_menu_ptr->menu_request_close_listener, _wlmaker_root_menu_handle_request_close); root_menu_ptr->window_ptr = wlmtk_window_create( wlmtk_menu_element(root_menu_ptr->menu_ptr), window_style_ref_ptr, menu_style_ref_ptr); if (NULL == root_menu_ptr->window_ptr) { wlmaker_root_menu_destroy(root_menu_ptr); bspl_array_unref(root_menu_array_ptr); return NULL; } wlmtk_util_connect_listener_signal( &wlmtk_window_events(root_menu_ptr->window_ptr)->request_close, &root_menu_ptr->window_request_close_listener, _wlmaker_root_menu_handle_window_request_close); wlmtk_util_connect_listener_signal( &wlmtk_window_events(root_menu_ptr->window_ptr)->set_activated, &root_menu_ptr->window_set_activated_listener, _wlmaker_root_menu_handle_window_set_activated); wlmtk_window_set_title( root_menu_ptr->window_ptr, bspl_array_string_value_at(root_menu_array_ptr, 0)); wlmtk_window_set_server_side_decorated(root_menu_ptr->window_ptr, true); bspl_array_unref(root_menu_array_ptr); return root_menu_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_root_menu_destroy(wlmaker_root_menu_t *root_menu_ptr) { if (NULL != root_menu_ptr->server_ptr) { BS_ASSERT(root_menu_ptr->server_ptr->root_menu_ptr == root_menu_ptr); root_menu_ptr->server_ptr->root_menu_ptr = NULL; root_menu_ptr->server_ptr = NULL;; } if (NULL != root_menu_ptr->window_ptr) { // Unmap, in case it's not unmapped yet. wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace( root_menu_ptr->window_ptr); if (NULL != workspace_ptr) { wlmtk_workspace_unmap_window(workspace_ptr, root_menu_ptr->window_ptr); } wlmtk_util_disconnect_listener( &root_menu_ptr->window_request_close_listener); wlmtk_util_disconnect_listener( &root_menu_ptr->window_set_activated_listener); wlmtk_window_destroy(root_menu_ptr->window_ptr); root_menu_ptr->window_ptr = NULL; } if (NULL != root_menu_ptr->menu_ptr) { wlmtk_util_disconnect_listener( &root_menu_ptr->menu_request_close_listener); wlmtk_util_disconnect_listener( &root_menu_ptr->menu_open_changed_listener); wlmtk_menu_destroy(root_menu_ptr->menu_ptr); root_menu_ptr->menu_ptr = NULL; } free(root_menu_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_window_t *wlmaker_root_menu_window(wlmaker_root_menu_t *root_menu_ptr) { return root_menu_ptr->window_ptr; } /* ------------------------------------------------------------------------- */ wlmtk_menu_t *wlmaker_root_menu_menu(wlmaker_root_menu_t *root_menu_ptr) { return root_menu_ptr->menu_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Handles when window close button is pressed: Hides the menu. */ void _wlmaker_root_menu_handle_window_request_close( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_root_menu_t *root_menu_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_root_menu_t, window_request_close_listener); wlmtk_menu_set_open(root_menu_ptr->menu_ptr, false); } /* ------------------------------------------------------------------------- */ /** Handles when the menu is activated: Get keyboard focus. */ void _wlmaker_root_menu_handle_window_set_activated( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_root_menu_t *root_menu_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_root_menu_t, window_set_activated_listener); wlmtk_element_t *e = wlmtk_menu_element(root_menu_ptr->menu_ptr); if (NULL != e->parent_container_ptr) { wlmtk_container_set_keyboard_focus_element( e->parent_container_ptr, e, wlmtk_window_is_activated(root_menu_ptr->window_ptr)); } } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_menu_events_t::open_changed. Unmaps window on close. */ void _wlmaker_root_menu_handle_menu_open_changed( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_root_menu_t *root_menu_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_root_menu_t, menu_open_changed_listener); if (!wlmtk_menu_is_open(root_menu_ptr->menu_ptr) && NULL != wlmtk_window_get_workspace(root_menu_ptr->window_ptr)) { wlmtk_workspace_unmap_window( wlmtk_window_get_workspace(root_menu_ptr->window_ptr), root_menu_ptr->window_ptr); } else { uint32_t properties = 0; if (WLMTK_MENU_MODE_RIGHTCLICK == wlmtk_menu_get_mode(root_menu_ptr->menu_ptr)) { properties |= WLMTK_WINDOW_PROPERTY_RIGHTCLICK; wlmtk_container_pointer_grab( wlmtk_menu_element( root_menu_ptr->menu_ptr)->parent_container_ptr, wlmtk_menu_element(root_menu_ptr->menu_ptr)); } else { properties |= WLMTK_WINDOW_PROPERTY_CLOSABLE; } wlmtk_window_set_properties(root_menu_ptr->window_ptr, properties); } } /* ------------------------------------------------------------------------- */ /** Listens to @ref wlmtk_menu_events_t::request_close. Closes the menu. */ void _wlmaker_root_menu_handle_request_close( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_root_menu_t *root_menu_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_root_menu_t, menu_request_close_listener); wlmtk_menu_set_open(root_menu_ptr->menu_ptr, false); } /* ------------------------------------------------------------------------- */ /** * Initializes the menu from the menu configuration array. * * The menu configuration is a Plist array. The first item is the menu's title, * while the second item defines the nature of the menu configuration: * * It can define a set of menu items, in form of Plist arrays: * @verbinclude tests/data/menu.plist * * Or, it is a definition to include a Plist menu: * @verbinclude tests/data/menu-include.plist * * Or, it is a definition to generate a Plist menu: * @verbinclude tests/data/menu-generate.plist * * @param menu_ptr * @param array_ptr * @param menu_style_ref_ptr * @param server_ptr * * @return true on success. */ bool _wlmaker_root_menu_init_menu_from_array( wlmtk_menu_t *menu_ptr, bspl_array_t *array_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr) { // (1) object must be array, and have >= 2 elements: title and content. if (2 > bspl_array_size(array_ptr)) { bs_log(BS_ERROR, "Plist menu definition array size must be >= 2."); return false; } bspl_object_t *content_object_ptr = bspl_array_at(array_ptr, 1); switch (bspl_object_type(content_object_ptr)) { case BSPL_ARRAY: // Indicates the first element is an item with a submenu, and there // are optionally further elements. Populate the parent menu from // that. return _wlmaker_root_menu_populate_menu_items_from_array( menu_ptr, array_ptr, menu_style_ref_ptr, server_ptr); case BSPL_STRING: if (3 > bspl_array_size(array_ptr)) { bs_log(BS_ERROR, "Must have 3 objects on \"%s\"", bspl_array_string_value_at(array_ptr, 1)); return false; } if (0 == strcmp( bspl_string_value_from_object(content_object_ptr), _wlmaker_root_menu_statement_include)) { return _wlmaker_root_menu_populate_menu_items_from_file( menu_ptr, bspl_array_string_value_at(array_ptr, 2), menu_style_ref_ptr, server_ptr); } else if (0 == strcmp( bspl_string_value_from_object(content_object_ptr), _wlmaker_root_menu_statement_generate)) { return _wlmaker_root_menu_populate_menu_items_from_generator( menu_ptr, bspl_array_string_value_at(array_ptr, 2), menu_style_ref_ptr, server_ptr); } bs_log(BS_ERROR, "Unknown menu definition \"%s\"", bspl_string_value_from_object(content_object_ptr)); return false; default: break; } bs_log(BS_ERROR, "Unhandled object type to populate menu."); return false; } /* ------------------------------------------------------------------------- */ /** * Populates the menu's items from the Plist array. This handles the case of * a menu configuration that specifies the menu items as a Plist array. * * The first item of `array_ptr` is the menu's title, and each further item * is expected to be another Plist array, defining a menu item. * * @param menu_ptr * @param array_ptr * @param menu_style_ref_ptr * @param server_ptr * * @return true on success */ bool _wlmaker_root_menu_populate_menu_items_from_array( wlmtk_menu_t *menu_ptr, bspl_array_t *array_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr) { if (bspl_array_size(array_ptr) <= 1) { bs_log(BS_ERROR, "Needs > 1 array element for menu definition."); return false; } const char *name_ptr = bspl_array_string_value_at(array_ptr, 0); if (NULL == name_ptr) { bs_log(BS_ERROR, "Array element [0] must be a string."); return false; } for (size_t i = 1; i < bspl_array_size(array_ptr); ++i) { bspl_array_t *item_array_ptr = bspl_array_from_object( bspl_array_at(array_ptr, i)); if (NULL == item_array_ptr) { bs_log(BS_ERROR, "Menu %s: Element [%zu] must be an array", name_ptr, i); return false; } if (NULL == bspl_array_string_value_at(item_array_ptr, 0)) { bs_log(BS_ERROR, "Menu %s: First element of item [%zu] must be a string", name_ptr, i); return false; } wlmtk_menu_item_t *menu_item_ptr = _wlmaker_root_menu_create_item_from_array( item_array_ptr, menu_style_ref_ptr, server_ptr); if (NULL == menu_item_ptr) return false; wlmtk_menu_add_item(menu_ptr, menu_item_ptr); } return true; } /* ------------------------------------------------------------------------- */ /** * Loads a Plist array from file and populates the menu's items from it. * * @param menu_ptr * @param filename_ptr * @param menu_style_ref_ptr * @param server_ptr * * @return true on success */ bool _wlmaker_root_menu_populate_menu_items_from_file( wlmtk_menu_t *menu_ptr, const char *filename_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr) { char *path_ptr = bs_file_resolve_path(filename_ptr, NULL); if (NULL == path_ptr) { bs_log(BS_ERROR, "Failed bs_file_resolve_path(\"%s\", NULL)", filename_ptr); return false; } bspl_object_t *object_ptr = bspl_create_object_from_plist_file(path_ptr); free(path_ptr); if (NULL == object_ptr || BSPL_ARRAY != bspl_object_type(object_ptr)) { bs_log(BS_ERROR, "Failed to load Plist ARRAY from \"%s\"", filename_ptr); if (NULL != object_ptr) bspl_object_unref(object_ptr); return false; } bool rv = _wlmaker_root_menu_populate_menu_items_from_array( menu_ptr, bspl_array_from_object(object_ptr), menu_style_ref_ptr, server_ptr); if (!rv) { bs_log(BS_ERROR, "Failed to generate menu from Plist file \"%s\"", filename_ptr); } bspl_object_unref(object_ptr); return rv; } /* ------------------------------------------------------------------------- */ /** * Launches a subprocess, to populate the menu's items. * * Uses a @ref wlmaker_root_menu_generator_t to track state of the subprocess * and to tie it with the menu's lifecycle. * * @param menu_ptr * @param command_ptr * @param menu_style_ref_ptr * @param server_ptr * * @return true on success */ bool _wlmaker_root_menu_populate_menu_items_from_generator( wlmtk_menu_t *menu_ptr, const char *command_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr) { bs_subprocess_t *subprocess_ptr = NULL; wlmaker_root_menu_generator_t *generator_ptr = logged_calloc( 1, sizeof(wlmaker_root_menu_generator_t)); if (NULL == generator_ptr) return false; generator_ptr->server_ptr = server_ptr; generator_ptr->menu_ptr = menu_ptr; generator_ptr->style_ref_ptr = menu_style_ref_ptr; generator_ptr->stdout_dynbuf_ptr = bs_dynbuf_create(1024, INT32_MAX); if (NULL == generator_ptr->stdout_dynbuf_ptr) goto error; wlmtk_util_connect_listener_signal( &wlmtk_menu_events(menu_ptr)->destroy, &generator_ptr->menu_destroy_listener, _wlmaker_root_menu_generator_handle_menu_destroy); const char *args[] = { "/bin/sh", "-c", command_ptr, NULL }; subprocess_ptr = bs_subprocess_create(args[0], args, NULL); if (NULL == subprocess_ptr) goto error; if (!bs_subprocess_start(subprocess_ptr)) goto error; bs_log(BS_INFO, "Created subprocess %p [%"PRIdMAX"] for \"/bin/sh\" \"-c\" \"%s\"", subprocess_ptr, (intmax_t)bs_subprocess_pid(subprocess_ptr), command_ptr); _wlmaker_root_menu_generators++; generator_ptr->subprocess_handle_ptr = wlmaker_subprocess_monitor_entrust( server_ptr->monitor_ptr, subprocess_ptr, _wlmaker_root_menu_generator_handle_terminated, generator_ptr, NULL, NULL, NULL, NULL, generator_ptr->stdout_dynbuf_ptr); if (NULL == generator_ptr->subprocess_handle_ptr) goto error; return true; error: if (NULL != subprocess_ptr) { bs_subprocess_destroy(subprocess_ptr); } _wlmaker_root_menu_generator_destroy(generator_ptr); return false; } /* ------------------------------------------------------------------------- */ /** Dtor for the menu generator. */ void _wlmaker_root_menu_generator_destroy( wlmaker_root_menu_generator_t *generator_ptr) { if (NULL != generator_ptr->subprocess_handle_ptr) { wlmaker_subprocess_monitor_cede( generator_ptr->server_ptr->monitor_ptr, generator_ptr->subprocess_handle_ptr); generator_ptr->subprocess_handle_ptr = NULL; } if (NULL != generator_ptr->stdout_dynbuf_ptr) { bs_dynbuf_destroy(generator_ptr->stdout_dynbuf_ptr); generator_ptr->stdout_dynbuf_ptr = NULL; } wlmtk_util_disconnect_listener(&generator_ptr->menu_destroy_listener); free(generator_ptr); } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_menu_events_t::destroy. Calls dtor. */ void _wlmaker_root_menu_generator_handle_menu_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_root_menu_generator_t *generator_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_root_menu_generator_t, menu_destroy_listener); generator_ptr->menu_ptr = NULL; _wlmaker_root_menu_generator_destroy(generator_ptr); } /* ------------------------------------------------------------------------- */ /** Handler for when the subprocess is terminated. */ void _wlmaker_root_menu_generator_handle_terminated( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, int state, int code) { wlmaker_root_menu_generator_t *generator_ptr = userdata_ptr; wlmtk_menu_item_t *menu_item_ptr = NULL; if (0 != state) { if (INT_MIN != state) { menu_item_ptr = _wlmaker_root_menu_create_disabled_item( generator_ptr->style_ref_ptr, "Failed, exit code %d", state); bs_log(BS_ERROR, "Subprocess %p failed, exit code %d", subprocess_handle_ptr, state); } else { menu_item_ptr = _wlmaker_root_menu_create_disabled_item( generator_ptr->style_ref_ptr, "Failed, signal %d", code); bs_log(BS_ERROR, "Subprocess %p failed, signal %d", subprocess_handle_ptr, code); } } else { bs_log(BS_INFO, "Subprocess %p terminated", subprocess_handle_ptr); bspl_object_t *object_ptr = bspl_create_object_from_dynbuf( generator_ptr->stdout_dynbuf_ptr); if (NULL == object_ptr || BSPL_ARRAY != bspl_object_type(object_ptr)) { menu_item_ptr = _wlmaker_root_menu_create_disabled_item( generator_ptr->style_ref_ptr, "Failed to parse Plist ARRAY from \"%.*s\"", (int)(generator_ptr->stdout_dynbuf_ptr->length), (char*)(generator_ptr->stdout_dynbuf_ptr->data_ptr)); bs_log(BS_ERROR, "Failed to parse Plist ARRAY from \"%.*s\"", (int)(generator_ptr->stdout_dynbuf_ptr->length), (char*)(generator_ptr->stdout_dynbuf_ptr->data_ptr)); } else { if (!_wlmaker_root_menu_populate_menu_items_from_array( generator_ptr->menu_ptr, bspl_array_from_object(object_ptr), generator_ptr->style_ref_ptr, generator_ptr->server_ptr)) { menu_item_ptr = _wlmaker_root_menu_create_disabled_item( generator_ptr->style_ref_ptr, "Failed to populate menu from Plist ARRAY \"%.*s\"", (int)(generator_ptr->stdout_dynbuf_ptr->length), (char*)(generator_ptr->stdout_dynbuf_ptr->data_ptr)); bs_log(BS_ERROR, "Failed to populate menu from Plist ARRAY \"%.*s\"", (int)(generator_ptr->stdout_dynbuf_ptr->length), (char*)(generator_ptr->stdout_dynbuf_ptr->data_ptr)); } } if (NULL != object_ptr) { bspl_object_unref(object_ptr); object_ptr = NULL; } } if (NULL!= menu_item_ptr) { wlmtk_menu_add_item(generator_ptr->menu_ptr, menu_item_ptr); } generator_ptr->subprocess_handle_ptr = NULL; _wlmaker_root_menu_generators--; if (NULL != _wlmaker_root_menu_test_wl_display_ptr && 0 >= _wlmaker_root_menu_generators) { wl_display_terminate(_wlmaker_root_menu_test_wl_display_ptr); _wlmaker_root_menu_test_wl_display_ptr = NULL; } } /* ------------------------------------------------------------------------- */ /** * Creates a menu item from the Plist array. * * The Plist array either defines a menu action item, where the array elements * are `(Title, ActionName, OptionalActionArg)`. Or, it defines a submenu, as * specified in @ref _wlmaker_root_menu_init_menu_from_array. * Or, it has only a title, then it will be shown as disabled menu item. * * For the list of permitted `ActionName` values, see @ref wlmaker_action_desc. * @param item_array_ptr * @param menu_style_ref_ptr * @param server_ptr * * @return The menu item, or NULL on error. */ wlmtk_menu_item_t *_wlmaker_root_menu_create_item_from_array( bspl_array_t *item_array_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr, wlmaker_server_t *server_ptr) { wlmtk_menu_item_t *menu_item_ptr = wlmtk_menu_item_create( menu_style_ref_ptr); if (NULL == menu_item_ptr) return NULL; if (!wlmtk_menu_item_set_text( menu_item_ptr, bspl_array_string_value_at(item_array_ptr, 0))) goto error; if (1 >= bspl_array_size(item_array_ptr)) { wlmtk_menu_item_set_enabled(menu_item_ptr, false); } else { // If second element is a string that translates to an action: Bind it. int action; if (bspl_enum_name_to_value( wlmaker_action_desc, bspl_array_string_value_at(item_array_ptr, 1), &action)) { wlmaker_menu_item_bind_action( menu_item_ptr, action, bspl_array_string_value_at(item_array_ptr, 2), server_ptr); return menu_item_ptr; } wlmtk_menu_t *submenu_ptr = wlmtk_menu_create(menu_style_ref_ptr); if (NULL == submenu_ptr) goto error; wlmtk_menu_item_set_submenu(menu_item_ptr, submenu_ptr); if (!_wlmaker_root_menu_init_menu_from_array( submenu_ptr, item_array_ptr, menu_style_ref_ptr, server_ptr)) { goto error; } } return menu_item_ptr; error: wlmtk_menu_item_destroy(menu_item_ptr); return NULL; } /* ------------------------------------------------------------------------- */ /** * Creates a disabled menu item as a means to display generator state. * * @param style_ref_ptr * @param fmt_ptr * * @return the disabled menu item, or NULL on error. */ wlmtk_menu_item_t *_wlmaker_root_menu_create_disabled_item( wlmtk_menu_style_ref_t *style_ref_ptr, const char *fmt_ptr, ...) { va_list ap; char buf[1024]; va_start(ap, fmt_ptr); vsnprintf(buf, sizeof(buf), fmt_ptr, ap); va_end(ap); wlmtk_menu_item_t *menu_item_ptr = wlmtk_menu_item_create(style_ref_ptr); if (NULL == menu_item_ptr) { bs_log(BS_ERROR, "Failed wlmtk_menu_item_create(%p) for \"%s\"", style_ref_ptr, buf); return NULL; } wlmtk_menu_item_set_text(menu_item_ptr, buf); wlmtk_menu_item_set_enabled(menu_item_ptr, false); return menu_item_ptr; } /* == Unit tests =========================================================== */ static void test_default_menu(bs_test_t *test_ptr); static void test_generated_menu(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_root_menu_test_cases[] = { { true, "default_menu", test_default_menu }, { true, "generated_menu", test_generated_menu }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_root_menu_test_set = BS_TEST_SET( true, "root_menu", wlmaker_root_menu_test_cases); /* ------------------------------------------------------------------------- */ /** Verifies that the compiled-in configuration translates into a menu. */ void test_default_menu(bs_test_t *test_ptr) { wlmaker_server_t server = {}; wlmtk_window_style_ref_t *wsr = wlmtk_window_style_to_ref(wlmtk_window_style_create()); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wsr); wlmtk_menu_style_ref_t *msr = wlmtk_menu_style_to_ref(wlmtk_menu_style_create()); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, msr); wlmaker_root_menu_t *root_menu_ptr = wlmaker_root_menu_create( &server, NULL, wsr, msr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, root_menu_ptr); wlmaker_root_menu_destroy(root_menu_ptr); wlmtk_window_style_ref_release(wsr); wlmtk_menu_style_ref_release(msr); } /* ------------------------------------------------------------------------- */ /** Verifies that an example menu with generator is translated. */ void test_generated_menu(bs_test_t *test_ptr) { wlmtk_window_style_ref_t *wsr = wlmtk_window_style_to_ref(wlmtk_window_style_create()); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wsr); wlmtk_menu_style_ref_t *msr = wlmtk_menu_style_to_ref(wlmtk_menu_style_create()); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, msr); wlmaker_server_t server = { .wl_display_ptr = wl_display_create(), .wlr_scene_ptr = wlr_scene_create() }; BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wlr_scene_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr); wl_signal_init(&server.window_created_event); wl_signal_init(&server.window_destroyed_event); server.wlr_output_layout_ptr = wlr_output_layout_create( server.wl_display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wlr_output_layout_ptr); server.root_ptr = wlmtk_root_create( server.wlr_scene_ptr, server.wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr); server.monitor_ptr = wlmaker_subprocess_monitor_create( &server); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.monitor_ptr); #ifndef WLMAKER_SOURCE_DIR #error "Missing definition of WLMAKER_SOURCE_DIR!" #endif BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 0, chdir(WLMAKER_SOURCE_DIR)); wlmaker_root_menu_t *root_menu_ptr; // Exercise & verify including a submenu from a file. root_menu_ptr = wlmaker_root_menu_create( &server, bs_test_data_path(test_ptr, "menu-include.plist"), wsr, msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_menu_ptr); wlmtk_menu_t *menu_ptr = wlmaker_root_menu_menu(root_menu_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, wlmtk_menu_items_size(menu_ptr)); wlmaker_root_menu_destroy(root_menu_ptr); // Exercise & verify generating a submenu from a shell command. root_menu_ptr = wlmaker_root_menu_create( &server, bs_test_data_path(test_ptr, "menu-generate.plist"), wsr, msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_menu_ptr); menu_ptr = wlmaker_root_menu_menu(root_menu_ptr); _wlmaker_root_menu_test_wl_display_ptr = server.wl_display_ptr; wl_display_run(server.wl_display_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, wlmtk_menu_items_size(menu_ptr)); wlmaker_root_menu_destroy(root_menu_ptr); // Exercise & verify that a menu can be generated from output of the // `wlmtool` command. // Note: RootMenuDebian.plist is generated, it in WLMAKER_BINARY_DIR. #ifndef WLMAKER_BINARY_DIR #error "Missing definition of WLMAKER_BINARY_DIR!" #endif root_menu_ptr = wlmaker_root_menu_create( &server, WLMAKER_BINARY_DIR "/etc/RootMenuDebian.plist", wsr, msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_menu_ptr); menu_ptr = wlmaker_root_menu_menu(root_menu_ptr); _wlmaker_root_menu_test_wl_display_ptr = server.wl_display_ptr; wl_display_run(server.wl_display_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, wlmtk_menu_items_size(menu_ptr)); wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_at(menu_ptr, 0); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item_ptr); menu_ptr = wlmtk_menu_item_get_submenu(item_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr); BS_TEST_VERIFY_NEQ(test_ptr, 0, wlmtk_menu_items_size(menu_ptr)); item_ptr = wlmtk_menu_item_at(menu_ptr, 0); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item_ptr); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(item_ptr)); wlmaker_root_menu_destroy(root_menu_ptr); wlmaker_subprocess_monitor_destroy(server.monitor_ptr); wlmtk_root_destroy(server.root_ptr); wl_display_destroy(server.wl_display_ptr); wlr_scene_node_destroy(&server.wlr_scene_ptr->tree.node); wlmtk_window_style_ref_release(wsr); wlmtk_menu_style_ref_release(msr); } /* == End of root_menu.c =================================================== */ wlmaker-0.8.1/src/lock_mgr.c0000644000175100017510000012745615222270710015335 0ustar runnerrunner/* ========================================================================= */ /** * @file lock_mgr.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "lock_mgr.h" #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "toolkit/toolkit.h" #include "server.h" /* == Declarations ========================================================= */ /** Type of wlr_session_lock_surface_v1_configure(). */ typedef uint32_t (*_wlmaker_lock_surface_configure_t)( struct wlr_session_lock_surface_v1 *lock_surface, uint32_t width, uint32_t height); /** Type of wlr_session_lock_v1_send_locked(). */ typedef void (*_wlmaker_lock_send_locked_t)( struct wlr_session_lock_v1 *lock); /** State of the session lock manager. */ struct _wlmaker_lock_mgr_t { /** The wlroots session lock manager. */ struct wlr_session_lock_manager_v1 *wlr_session_lock_manager_v1_ptr; /** Reference to the wlmaker server. */ wlmaker_server_t *server_ptr; /** Listener for the `new_lock` signal of `wlr_session_lock_manager_v1`. */ struct wl_listener new_lock_listener; /** Listener for the `destroy` signal of `wlr_session_lock_manager_v1`. */ struct wl_listener destroy_listener; }; /** Forward declaration: Lock. */ typedef struct _wlmaker_lock_t wlmaker_lock_t; /** Forward declaration: Output. */ typedef struct _wlmaker_lock_output_t wlmaker_lock_output_t; /** State of the session lock. */ struct _wlmaker_lock_t { /** The wlroots session lock. */ struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr; /** Seat for the session. */ struct wlr_seat *wlr_seat_ptr; /** The root this lock is applied for. */ wlmtk_root_t *root_ptr; /** The output layout. */ struct wlr_output_layout *wlr_output_layout_ptr; /** Injected method: Configure the lock surface. */ _wlmaker_lock_surface_configure_t injected_surface_configure; /** Injected method: Confirm session lock. */ _wlmaker_lock_send_locked_t injected_send_locked; /** Container holding the lock surfaces. */ wlmtk_container_t container; /** Listener for the `new_surface` signal of `wlr_session_lock_v1`. */ struct wl_listener new_surface_listener; /** Listener for the `unlock` signal of `wlr_session_lock_v1`. */ struct wl_listener unlock_listener; /** Listener for the `destroy` signal of `wlr_session_lock_v1`. */ struct wl_listener destroy_listener; /** Tracks all the outputs. */ wlmtk_output_tracker_t *output_tracker_ptr; /** Outputs with surface. Via @ref wlmaker_lock_output_t::dlnode. */ bs_dllist_t outputs; }; /** An active output, that should then also get locked. */ struct _wlmaker_lock_output_t { /** Element of @ref wlmaker_lock_t::outputs. */ bs_dllist_node_t dlnode; /** The wlroots session lock surface. */ struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr; /** Toolkit surface for the associated wl_surface. */ wlmtk_surface_t *wlmtk_surface_ptr; /** Back-link to the lock. */ wlmaker_lock_t *lock_ptr; /** Whether this lock surface got committed, ie. is ready to lock. */ bool committed; /** Serial returned by `wlr_session_lock_surface_v1_configure`. */ uint32_t configure_serial; /** Listener for the `destroy` signal of `wlr_session_lock_surface_v1`. */ struct wl_listener destroy_listener; /** Listener for `commit` signal of `wlr_session_lock_surface_v1::surface`. */ struct wl_listener surface_commit_listener; }; static void _wlmaker_lock_mgr_handle_new_lock( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_lock_mgr_handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static wlmaker_lock_t *_wlmaker_lock_create( struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_seat *wlr_seat_ptr, wlmtk_root_t *root_ptr, _wlmaker_lock_surface_configure_t injected_surface_configure, _wlmaker_lock_send_locked_t injected_send_locked); static void _wlmaker_lock_destroy(wlmaker_lock_t *lock_ptr); static wlmtk_element_t *_wlmaker_lock_element(wlmaker_lock_t *lock_ptr); static void _wlmaker_lock_handle_new_surface( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_lock_handle_unlock( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_lock_handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void *_wlmaker_lock_output_create( struct wlr_output *wlr_output_ptr, void *ud_ptr); static void _wlmaker_lock_output_update( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr); static void _wlmaker_lock_output_destroy( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr); static bool _wlmaker_lock_output_create_surface( wlmaker_lock_output_t *lock_output_ptr, struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr, wlmaker_lock_t *lock_ptr); static void _wlmaker_lock_output_destroy_surface( wlmaker_lock_output_t *lock_output_ptr); static void _wlmaker_lock_output_handle_surface_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_lock_output_handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr); static bool _wlmaker_lock_output_surface_is_committed( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_lock_mgr_t *wlmaker_lock_mgr_create( wlmaker_server_t *server_ptr) { wlmaker_lock_mgr_t *lock_mgr_ptr = logged_calloc( 1, sizeof(wlmaker_lock_mgr_t)); if (NULL == lock_mgr_ptr) return NULL; lock_mgr_ptr->server_ptr = server_ptr; lock_mgr_ptr->wlr_session_lock_manager_v1_ptr = wlr_session_lock_manager_v1_create(server_ptr->wl_display_ptr); if (NULL == lock_mgr_ptr->wlr_session_lock_manager_v1_ptr) { bs_log(BS_ERROR, "Failed wlr_session_lock_manager_v1_create(%p)", server_ptr->wl_display_ptr); wlmaker_lock_mgr_destroy(lock_mgr_ptr); return NULL; } wlmtk_util_connect_listener_signal( &lock_mgr_ptr->wlr_session_lock_manager_v1_ptr->events.new_lock, &lock_mgr_ptr->new_lock_listener, _wlmaker_lock_mgr_handle_new_lock); wlmtk_util_connect_listener_signal( &lock_mgr_ptr->wlr_session_lock_manager_v1_ptr->events.destroy, &lock_mgr_ptr->destroy_listener, _wlmaker_lock_mgr_handle_destroy); return lock_mgr_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_lock_mgr_destroy(wlmaker_lock_mgr_t *lock_mgr_ptr) { wlmtk_util_disconnect_listener(&lock_mgr_ptr->destroy_listener); wlmtk_util_disconnect_listener(&lock_mgr_ptr->new_lock_listener); // Note: No destroy method for wlr_session_lock_manager_v1_ptr. free(lock_mgr_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Handler for the `new_lock` signal of `wlr_session_lock_manager_v1`: creates * the corresponding lock. * * @param listener_ptr * @param data_ptr */ void _wlmaker_lock_mgr_handle_new_lock( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_lock_mgr_t *lock_mgr_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_lock_mgr_t, new_lock_listener); struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr = data_ptr; wlmaker_lock_t *lock_ptr = _wlmaker_lock_create( wlr_session_lock_v1_ptr, lock_mgr_ptr->server_ptr->wlr_output_layout_ptr, lock_mgr_ptr->server_ptr->wlr_seat_ptr, lock_mgr_ptr->server_ptr->root_ptr, wlr_session_lock_surface_v1_configure, wlr_session_lock_v1_send_locked); if (NULL == lock_ptr) { wl_resource_post_error( wlr_session_lock_v1_ptr->resource, WL_DISPLAY_ERROR_NO_MEMORY, "Failed wlmt_lock_create(%p, %p)", wlr_session_lock_v1_ptr, lock_mgr_ptr->server_ptr->root_ptr); bs_log(BS_WARNING, "Failed wlmt_lock_create(%p, %p)", wlr_session_lock_v1_ptr, lock_mgr_ptr->server_ptr->root_ptr); return; } bs_log(BS_INFO, "Lock manager %p: New lock %p", lock_mgr_ptr, lock_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal of `wlr_session_lock_manager_v1`: Cleans * up associated resources. * * @param listener_ptr * @param data_ptr */ void _wlmaker_lock_mgr_handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_lock_mgr_t *lock_mgr_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_lock_mgr_t, destroy_listener); wlmaker_lock_mgr_destroy(lock_mgr_ptr); } /* == lock methods ========================================================= */ /* ------------------------------------------------------------------------- */ /** * Creates a session lock handle. * * @param wlr_session_lock_v1_ptr * @param wlr_output_layout_ptr * @param wlr_seat_ptr * @param root_ptr * @param injected_surface_configure * @param injected_send_locked * * @return The lock handle or NULL on error. */ wlmaker_lock_t *_wlmaker_lock_create( struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_seat *wlr_seat_ptr, wlmtk_root_t *root_ptr, _wlmaker_lock_surface_configure_t injected_surface_configure, _wlmaker_lock_send_locked_t injected_send_locked) { wlmaker_lock_t *lock_ptr = logged_calloc(1, sizeof(wlmaker_lock_t)); if (NULL == lock_ptr) return NULL; lock_ptr->wlr_session_lock_v1_ptr = wlr_session_lock_v1_ptr; lock_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr; lock_ptr->wlr_seat_ptr = wlr_seat_ptr; lock_ptr->root_ptr = root_ptr; lock_ptr->injected_surface_configure = injected_surface_configure; lock_ptr->injected_send_locked = injected_send_locked; lock_ptr->output_tracker_ptr = wlmtk_output_tracker_create( wlr_output_layout_ptr, lock_ptr, _wlmaker_lock_output_create, _wlmaker_lock_output_update, _wlmaker_lock_output_destroy); if (!wlmtk_container_init(&lock_ptr->container)) { _wlmaker_lock_destroy(lock_ptr); return NULL; } wlmtk_util_connect_listener_signal( &lock_ptr->wlr_session_lock_v1_ptr->events.new_surface, &lock_ptr->new_surface_listener, _wlmaker_lock_handle_new_surface); wlmtk_util_connect_listener_signal( &lock_ptr->wlr_session_lock_v1_ptr->events.unlock, &lock_ptr->unlock_listener, _wlmaker_lock_handle_unlock); wlmtk_util_connect_listener_signal( &lock_ptr->wlr_session_lock_v1_ptr->events.destroy, &lock_ptr->destroy_listener, _wlmaker_lock_handle_destroy); return lock_ptr; } /* ------------------------------------------------------------------------- */ /** * Destroys the session lock handle. * * @param lock_ptr */ void _wlmaker_lock_destroy(wlmaker_lock_t *lock_ptr) { if (NULL != lock_ptr->output_tracker_ptr) { wlmtk_output_tracker_destroy(lock_ptr->output_tracker_ptr); lock_ptr->output_tracker_ptr = NULL; } wlmtk_util_disconnect_listener(&lock_ptr->destroy_listener); wlmtk_util_disconnect_listener(&lock_ptr->unlock_listener); wlmtk_util_disconnect_listener(&lock_ptr->new_surface_listener); wlmtk_root_lock_unreference(lock_ptr->root_ptr, _wlmaker_lock_element(lock_ptr)); wlmtk_container_fini(&lock_ptr->container); free(lock_ptr); } /* ------------------------------------------------------------------------- */ /** * @returns Pointer to @ref wlmtk_element_t of @ref wlmaker_lock_t::container. * */ wlmtk_element_t *_wlmaker_lock_element(wlmaker_lock_t *lock_ptr) { return &lock_ptr->container.super_element; } /* ------------------------------------------------------------------------- */ /** Locks the session, if all output surfaces are ready & not locked yet. */ void _wlmaker_lock_if_ready(wlmaker_lock_t *lock_ptr) { if (bs_dllist_empty(&lock_ptr->outputs)) return; if (!bs_dllist_all(&lock_ptr->outputs, _wlmaker_lock_output_surface_is_committed, NULL)) return; if (wlmtk_root_locked(lock_ptr->root_ptr)) return; if (!wlmtk_root_lock(lock_ptr->root_ptr, _wlmaker_lock_element(lock_ptr))) { if (NULL != lock_ptr->wlr_session_lock_v1_ptr->resource) { wl_resource_post_error( lock_ptr->wlr_session_lock_v1_ptr->resource, WL_DISPLAY_ERROR_INVALID_METHOD, "Failed wlmtk_root_lock(%p, %p): Already locked?", lock_ptr->root_ptr, _wlmaker_lock_element(lock_ptr)); } return; } wlmtk_element_set_visible(&lock_ptr->container.super_element, true); // Grant keyboard focus to the first-found surface that's committed. bs_dllist_node_t *dlnode_ptr = bs_dllist_find( &lock_ptr->outputs, _wlmaker_lock_output_surface_is_committed, NULL); if (NULL == dlnode_ptr) return; wlmaker_lock_output_t *lock_output_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmaker_lock_output_t, dlnode); wlmtk_surface_set_activated(lock_output_ptr->wlmtk_surface_ptr, true); // Root is locked. Send confirmation to the client. lock_ptr->injected_send_locked(lock_ptr->wlr_session_lock_v1_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `new_surface` signal of `wlr_session_lock_v1`: Creates the * associated surface and enables it on the screenlock container. * * @param listener_ptr * @param data_ptr */ void _wlmaker_lock_handle_new_surface( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_lock_t *lock_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_lock_t, new_surface_listener); struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr = data_ptr; // Guard clause: We expect the output to be set. if (NULL == wlr_session_lock_surface_v1_ptr->output) { bs_log(BS_ERROR, "Session lock surface %p does not have an output!", wlr_session_lock_surface_v1_ptr); if (NULL != wlr_session_lock_surface_v1_ptr->resource) { wl_resource_post_error( wlr_session_lock_surface_v1_ptr->resource, WL_DISPLAY_ERROR_INVALID_METHOD, "Session lock surface does not have an output!"); } return; } // Additionally, we expect the output to be part of the output layout. wlmaker_lock_output_t *lock_output_ptr = wlmtk_output_tracker_get_output( lock_ptr->output_tracker_ptr, wlr_session_lock_surface_v1_ptr->output); if (NULL == lock_output_ptr) { bs_log(BS_ERROR, "Session lock surface %p refers to invalid output %p", wlr_session_lock_surface_v1_ptr, wlr_session_lock_surface_v1_ptr->output); if (NULL != wlr_session_lock_surface_v1_ptr->resource) { wl_resource_post_error( wlr_session_lock_surface_v1_ptr->resource, WL_DISPLAY_ERROR_INVALID_METHOD, "Session lock surface refers to invalid output!"); } return; } if (!_wlmaker_lock_output_create_surface( lock_output_ptr, wlr_session_lock_surface_v1_ptr, lock_ptr)) { wl_resource_post_error( wlr_session_lock_surface_v1_ptr->resource, WL_DISPLAY_ERROR_NO_MEMORY, "Failed _wlmaker_lock_output_create_surface(%p, %p, %p)", lock_output_ptr, wlr_session_lock_surface_v1_ptr->surface, lock_ptr); return; } bs_log(BS_INFO, "Lock %p, output %p: New lock surface %p", lock_ptr, lock_output_ptr, wlr_session_lock_surface_v1_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `unlock` signal of `wlr_session_lock_v1`: Marks the session * as unlocked. * * @param listener_ptr * @param data_ptr */ void _wlmaker_lock_handle_unlock( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { __UNUSED__ wlmaker_lock_t *lock_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_lock_t, unlock_listener); wlmtk_element_set_visible(&lock_ptr->container.super_element, false); wlmtk_root_unlock(lock_ptr->root_ptr, _wlmaker_lock_element(lock_ptr)); } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal of `wlr_session_lock_v1`: Destroy the lock. * * @param listener_ptr * @param data_ptr */ void _wlmaker_lock_handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_lock_t *lock_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_lock_t, destroy_listener); _wlmaker_lock_destroy(lock_ptr); } /* == Lock output methods ================================================== */ /* ------------------------------------------------------------------------- */ /** Ctor for the lock output. */ void *_wlmaker_lock_output_create( __UNUSED__ struct wlr_output *wlr_output_ptr, void *ud_ptr) { wlmaker_lock_t *lock_ptr = ud_ptr; wlmaker_lock_output_t *lock_output_ptr = logged_calloc( 1, sizeof(wlmaker_lock_output_t)); if (NULL == lock_output_ptr) return NULL; lock_output_ptr->lock_ptr = lock_ptr; bs_dllist_push_back(&lock_ptr->outputs, &lock_output_ptr->dlnode); return lock_output_ptr; } /* ------------------------------------------------------------------------- */ /** Layout update: Dimensions of the surface might have changed. Update. */ void _wlmaker_lock_output_update( __UNUSED__ struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr) { wlmaker_lock_t *lock_ptr = ud_ptr; wlmaker_lock_output_t *lock_output_ptr = output_ptr; // The output dimensions may have changed. Send a configure(). lock_ptr->injected_surface_configure( lock_output_ptr->wlr_session_lock_surface_v1_ptr, lock_output_ptr->wlr_session_lock_surface_v1_ptr->output->width, lock_output_ptr->wlr_session_lock_surface_v1_ptr->output->height); struct wlr_box box; wlr_output_layout_get_box( lock_ptr->wlr_output_layout_ptr, lock_output_ptr->wlr_session_lock_surface_v1_ptr->output, &box); wlmtk_element_set_position( wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr), box.x, box.y); } /* ------------------------------------------------------------------------- */ /** Dtor for the lock output. */ void _wlmaker_lock_output_destroy( __UNUSED__ struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr) { wlmaker_lock_t *lock_ptr = ud_ptr; wlmaker_lock_output_t *lock_output_ptr = output_ptr; _wlmaker_lock_output_destroy_surface(lock_output_ptr); bs_dllist_remove( &lock_output_ptr->lock_ptr->outputs, &lock_output_ptr->dlnode); free(lock_output_ptr); // Activating the first-found surface ensures there's still one that // is activated. bs_dllist_node_t *dlnode_ptr = bs_dllist_find( &lock_ptr->outputs, _wlmaker_lock_output_surface_is_committed, NULL); if (NULL == dlnode_ptr) return; lock_output_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmaker_lock_output_t, dlnode); wlmtk_surface_set_activated(lock_output_ptr->wlmtk_surface_ptr, true); } /* ------------------------------------------------------------------------- */ /** * Creates a lock surface. * * @param lock_output_ptr * @param wlr_session_lock_surface_v1_ptr * @param lock_ptr * * @return The lock surface or NULL on error. */ bool _wlmaker_lock_output_create_surface( wlmaker_lock_output_t *lock_output_ptr, struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr, wlmaker_lock_t *lock_ptr) { if (NULL != lock_output_ptr->wlr_session_lock_surface_v1_ptr) { bs_log(BS_ERROR, "Lock %p, output %p already has surface %p (vs %p)", lock_ptr, lock_output_ptr, lock_output_ptr->wlr_session_lock_surface_v1_ptr, wlr_session_lock_surface_v1_ptr); return false; } lock_output_ptr->wlr_session_lock_surface_v1_ptr = wlr_session_lock_surface_v1_ptr; lock_output_ptr->wlmtk_surface_ptr = wlmtk_surface_create( wlr_session_lock_surface_v1_ptr->surface, lock_ptr->wlr_seat_ptr); if (NULL == lock_output_ptr->wlmtk_surface_ptr) { bs_log(BS_ERROR, "Failed wlmtk_surface_create(%p)", wlr_session_lock_surface_v1_ptr->surface); _wlmaker_lock_output_destroy_surface(lock_output_ptr); return false; } wlmtk_util_connect_listener_signal( &wlr_session_lock_surface_v1_ptr->events.destroy, &lock_output_ptr->destroy_listener, _wlmaker_lock_output_handle_surface_destroy); wlmtk_util_connect_listener_signal( &wlr_session_lock_surface_v1_ptr->surface->events.commit, &lock_output_ptr->surface_commit_listener, _wlmaker_lock_output_handle_surface_commit); // We need computed & scaled output resolution for setting the lock // surface's dimensions. int w, h; wlr_output_effective_resolution( wlr_session_lock_surface_v1_ptr->output, &w, &h); lock_output_ptr->configure_serial = lock_ptr->injected_surface_configure( wlr_session_lock_surface_v1_ptr, w, h); struct wlr_box box; wlr_output_layout_get_box( lock_ptr->wlr_output_layout_ptr, wlr_session_lock_surface_v1_ptr->output, &box); wlmtk_element_set_position( wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr), box.x, box.y); wlmtk_container_add_element( &lock_ptr->container, wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr)); wlmtk_element_set_visible( wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr), true); return true; } /* ------------------------------------------------------------------------- */ /** * Destroys the lock surface. * * @param lock_output_ptr */ void _wlmaker_lock_output_destroy_surface( wlmaker_lock_output_t *lock_output_ptr) { bs_log(BS_INFO, "Lock %p, output %p: Destroying lock surface %p", lock_output_ptr->lock_ptr, lock_output_ptr, lock_output_ptr->wlr_session_lock_surface_v1_ptr); if (NULL != lock_output_ptr->wlmtk_surface_ptr) { wlmtk_container_remove_element( &lock_output_ptr->lock_ptr->container, wlmtk_surface_element(lock_output_ptr->wlmtk_surface_ptr)); wlmtk_util_disconnect_listener(&lock_output_ptr->surface_commit_listener); wlmtk_util_disconnect_listener(&lock_output_ptr->destroy_listener); wlmtk_surface_destroy(lock_output_ptr->wlmtk_surface_ptr); lock_output_ptr->wlmtk_surface_ptr = NULL; } lock_output_ptr->committed = false; lock_output_ptr->wlr_session_lock_surface_v1_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal of `wlr_session_lock_surface_v1`: Destroy * the surface. * * @param listener_ptr * @param data_ptr */ void _wlmaker_lock_output_handle_surface_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_lock_output_t *lock_output_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_lock_output_t, destroy_listener); _wlmaker_lock_output_destroy_surface(lock_output_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `commit` signal of `wlr_session_lock_surface_v1::surface`. * * Checks whether the serial is at-or-above the 'configure' serial, and * reports the surface and output as locked. Once all surfaces are locked, * a 'send_locked' event will be sent. * * @param listener_ptr * @param data_ptr */ void _wlmaker_lock_output_handle_surface_commit( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_lock_output_t *lock_output_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_lock_output_t, surface_commit_listener); struct wlr_session_lock_surface_v1 *wlr_session_lock_surface_v1_ptr = lock_output_ptr->wlr_session_lock_surface_v1_ptr; // Do not accept locking for commits before the requested configuration. if (wlr_session_lock_surface_v1_ptr->current.configure_serial >= lock_output_ptr->configure_serial) { lock_output_ptr->committed = true; _wlmaker_lock_if_ready(lock_output_ptr->lock_ptr); } } /* ------------------------------------------------------------------------- */ /** Iterator for @ref wlmaker_lock_t::outputs. Is the output committed? */ bool _wlmaker_lock_output_surface_is_committed( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { wlmaker_lock_output_t *lock_output_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmaker_lock_output_t, dlnode); return (NULL != lock_output_ptr->wlr_session_lock_surface_v1_ptr && lock_output_ptr->committed); } /* == Unit tests =========================================================== */ static void test_lock_unlock(bs_test_t *test_ptr); static void test_lock_crash(bs_test_t *test_ptr); static void test_lock_multi_output(bs_test_t *test_ptr); static uint32_t _mock_wlr_session_lock_surface_v1_configure( struct wlr_session_lock_surface_v1 *lock_surface, uint32_t width, uint32_t height); static void _mock_wlr_session_lock_v1_send_locked( struct wlr_session_lock_v1 *lock); static void _init_test_session_lock( struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr); static void _init_test_surface(struct wlr_surface *wlr_surface_ptr); /** Unit test cases. */ const bs_test_case_t wlmaker_lock_mgr_test_cases[] = { { true, "lock_unlock", test_lock_unlock }, { true, "lock_crash", test_lock_crash }, { true, "lock_multi_output", test_lock_multi_output }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_lock_mgr_test_set = BS_TEST_SET( true, "lock_mgr", wlmaker_lock_mgr_test_cases); /** Return value for @ref _mock_wlr_session_lock_surface_v1_configure. */ static uint32_t _mock_configure_serial; /** Arg of last call to @ref _mock_wlr_session_lock_surface_v1_configure. */ static uint32_t _mock_configure_width; /** Arg of last call to @ref _mock_wlr_session_lock_surface_v1_configure. */ static uint32_t _mock_configure_height; /** Arg of last call to @ref _mock_wlr_session_lock_surface_v1_configure. */ static struct wlr_session_lock_surface_v1 *_mock_configure_lock_surface; /** Arg of last call to @ref _mock_wlr_session_lock_v1_send_locked. */ static struct wlr_session_lock_v1 *_mock_send_locked_lock; /** Mock for configure(). */ uint32_t _mock_wlr_session_lock_surface_v1_configure( struct wlr_session_lock_surface_v1 *lock_surface, uint32_t width, uint32_t height) { _mock_configure_lock_surface = lock_surface; _mock_configure_width = width; _mock_configure_height = height; return _mock_configure_serial; } /** Mock for send_locked(). */ void _mock_wlr_session_lock_v1_send_locked( struct wlr_session_lock_v1 *lock) { _mock_send_locked_lock = lock; } /** Initializes the minimum required attributes of the session lock. */ void _init_test_session_lock( struct wlr_session_lock_v1 *wlr_session_lock_v1_ptr) { wl_signal_init(&wlr_session_lock_v1_ptr->events.new_surface); wl_signal_init(&wlr_session_lock_v1_ptr->events.unlock); wl_signal_init(&wlr_session_lock_v1_ptr->events.destroy); } /** Initializes the minimum required attributes of the wlr_surface. */ void _init_test_surface(struct wlr_surface *wlr_surface_ptr) { wl_list_init(&wlr_surface_ptr->current.subsurfaces_below); wl_list_init(&wlr_surface_ptr->current.subsurfaces_above); wl_signal_init(&wlr_surface_ptr->events.commit); wl_signal_init(&wlr_surface_ptr->events.destroy); wl_signal_init(&wlr_surface_ptr->events.map); wl_signal_init(&wlr_surface_ptr->events.unmap); } /* ------------------------------------------------------------------------- */ /** Tests locking & unlocking, proper sequence, single output. */ void test_lock_unlock(bs_test_t *test_ptr) { wlmaker_server_t server = { .wl_display_ptr = wl_display_create() }; BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr); server.wlr_output_layout_ptr = wlr_output_layout_create( server.wl_display_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &output); server.root_ptr = wlmtk_root_create(NULL, server.wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr); struct wlmtk_tile_style tile_style = {}; wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create( server.wlr_output_layout_ptr, "name", &tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr); wlmtk_root_add_workspace(server.root_ptr, workspace_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr)); struct wlr_session_lock_v1 wlr_session_lock_v1 = {}; _init_test_session_lock(&wlr_session_lock_v1); wlmaker_lock_t *lock_ptr = _wlmaker_lock_create( &wlr_session_lock_v1, server.wlr_output_layout_ptr, NULL, server.root_ptr, _mock_wlr_session_lock_surface_v1_configure, _mock_wlr_session_lock_v1_send_locked); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, lock_ptr); struct wlr_surface wlr_surface = {}; _init_test_surface(&wlr_surface); struct wlr_session_lock_surface_v1 lock_surface = { .surface = &wlr_surface, .output = &output, }; wl_signal_init(&lock_surface.events.destroy); // A new surface request will be greeted by a configure() event. _mock_configure_serial = 42; _mock_send_locked_lock = NULL; wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface); BS_TEST_VERIFY_EQ(test_ptr, &lock_surface, _mock_configure_lock_surface); BS_TEST_VERIFY_EQ(test_ptr, 1024, _mock_configure_width); BS_TEST_VERIFY_EQ(test_ptr, 768, _mock_configure_height); // A commit, but with too-low serial. Will be ignored. lock_surface.current.configure_serial = 41; wl_signal_emit(&wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, _mock_send_locked_lock); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr)); // Another commit, with matching serial. Will mark as locked. wlr_surface.current.width = 1024; wlr_surface.current.height = 768; lock_surface.current.configure_serial = 42; wl_signal_emit(&wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, &wlr_session_lock_v1, _mock_send_locked_lock); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr)); // Client unlocks. wl_signal_emit(&wlr_session_lock_v1.events.unlock, NULL); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr)); _wlmaker_lock_destroy(lock_ptr); wlmtk_root_remove_workspace(server.root_ptr, workspace_ptr); wlmtk_workspace_destroy(workspace_ptr); wlmtk_root_destroy(server.root_ptr); wl_display_destroy(server.wl_display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests locking, and then the session lock going away: Must remain locked. */ void test_lock_crash(bs_test_t *test_ptr) { wlmaker_server_t server = { .wl_display_ptr = wl_display_create() }; BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr); server.wlr_output_layout_ptr = wlr_output_layout_create( server.wl_display_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &output); server.root_ptr = wlmtk_root_create(NULL, server.wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr); struct wlmtk_tile_style tile_style = {}; wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create( server.wlr_output_layout_ptr, "name", &tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr); wlmtk_root_add_workspace(server.root_ptr, workspace_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr)); struct wlr_session_lock_v1 wlr_session_lock_v1 = {}; _init_test_session_lock(&wlr_session_lock_v1); wlmaker_lock_t *lock_ptr = _wlmaker_lock_create( &wlr_session_lock_v1, server.wlr_output_layout_ptr, NULL, server.root_ptr, _mock_wlr_session_lock_surface_v1_configure, _mock_wlr_session_lock_v1_send_locked); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, lock_ptr); struct wlr_surface wlr_surface = {}; _init_test_surface(&wlr_surface); struct wlr_session_lock_surface_v1 lock_surface = { .surface = &wlr_surface, .output = &output, }; wl_signal_init(&lock_surface.events.destroy); // A new surface request will be greeted by a configure() event. _mock_configure_serial = 42; _mock_send_locked_lock = NULL; wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface); BS_TEST_VERIFY_EQ(test_ptr, &lock_surface, _mock_configure_lock_surface); BS_TEST_VERIFY_EQ(test_ptr, 1024, _mock_configure_width); BS_TEST_VERIFY_EQ(test_ptr, 768, _mock_configure_height); // Commit with matching serial. Will mark as locked. wlr_surface.current.width = 1024; wlr_surface.current.height = 768; lock_surface.current.configure_serial = 42; wl_signal_emit(&wlr_surface.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, &wlr_session_lock_v1, _mock_send_locked_lock); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr)); // No unlock. If the session lock is destroyed without: Lock remains. _wlmaker_lock_destroy(lock_ptr); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr)); wlmtk_root_remove_workspace(server.root_ptr, workspace_ptr); wlmtk_workspace_destroy(workspace_ptr); wlmtk_root_destroy(server.root_ptr); wl_display_destroy(server.wl_display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests locking with multiple outputs. Lock only when all outputs covered. */ void test_lock_multi_output(bs_test_t *test_ptr) { wlmaker_server_t server = { .wl_display_ptr = wl_display_create() }; BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr); server.wlr_output_layout_ptr = wlr_output_layout_create( server.wl_display_ptr); struct wlr_output o1 = { .width = 1024, .height = 768, .scale = 1 }; struct wlr_output o2 = { .width = 1024, .height = 768, .scale = 1 }; struct wlr_output o3 = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&o1); wlmtk_test_wlr_output_init(&o2); wlmtk_test_wlr_output_init(&o3); wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &o1); // But not: o2. wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &o3); server.root_ptr = wlmtk_root_create(NULL, server.wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr); struct wlmtk_tile_style tile_style = {}; wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create( server.wlr_output_layout_ptr, "name", &tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr); wlmtk_root_add_workspace(server.root_ptr, workspace_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr)); struct wlr_session_lock_v1 wlr_session_lock_v1 = {}; _init_test_session_lock(&wlr_session_lock_v1); wlmaker_lock_t *lock_ptr = _wlmaker_lock_create( &wlr_session_lock_v1, server.wlr_output_layout_ptr, NULL, server.root_ptr, _mock_wlr_session_lock_surface_v1_configure, _mock_wlr_session_lock_v1_send_locked); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, lock_ptr); struct wlr_surface wlr_surface1 = {}; _init_test_surface(&wlr_surface1); struct wlr_session_lock_surface_v1 lock_surface1 = { .surface = &wlr_surface1, .output = &o1, }; wl_signal_init(&lock_surface1.events.destroy); struct wlr_surface wlr_surface2 = {}; _init_test_surface(&wlr_surface2); struct wlr_session_lock_surface_v1 lock_surface2 = { .surface = &wlr_surface2, .output = &o2, }; wl_signal_init(&lock_surface2.events.destroy); struct wlr_surface wlr_surface3 = {}; _init_test_surface(&wlr_surface3); struct wlr_session_lock_surface_v1 lock_surface3 = { .surface = &wlr_surface3, .output = &o3, }; wl_signal_init(&lock_surface3.events.destroy); // Surface 1. Create, configure, commit. No lock yet. _mock_configure_serial = 42; _mock_send_locked_lock = NULL; wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface1); BS_TEST_VERIFY_EQ(test_ptr, &lock_surface1, _mock_configure_lock_surface); BS_TEST_VERIFY_EQ(test_ptr, 1024, _mock_configure_width); BS_TEST_VERIFY_EQ(test_ptr, 768, _mock_configure_height); wlr_surface1.current.width = 1024; wlr_surface1.current.height = 768; lock_surface1.current.configure_serial = 42; wl_signal_emit(&wlr_surface1.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, _mock_send_locked_lock); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr)); wlmtk_surface_t *surface_ptr = wlr_surface1.data; int x, y; wlmtk_element_get_position(wlmtk_surface_element(surface_ptr), &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 0, x); BS_TEST_VERIFY_EQ(test_ptr, 0, y); // Surface 2. Create, configure, commit. Non-layout output -> ignored. _mock_configure_serial = 42; _mock_send_locked_lock = NULL; wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface2); // no 'configure'. wlr_surface2.current.width = 1024; wlr_surface2.current.height = 768; lock_surface2.current.configure_serial = 42; wl_signal_emit(&wlr_surface2.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, _mock_send_locked_lock); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr)); wl_signal_emit(&lock_surface2.events.destroy, NULL); // Surface 3. _mock_configure_serial = 42; _mock_send_locked_lock = NULL; wl_signal_emit(&wlr_session_lock_v1.events.new_surface, &lock_surface3); BS_TEST_VERIFY_EQ(test_ptr, &lock_surface3, _mock_configure_lock_surface); BS_TEST_VERIFY_EQ(test_ptr, 1024, _mock_configure_width); BS_TEST_VERIFY_EQ(test_ptr, 768, _mock_configure_height); wlr_surface3.current.width = 1024; wlr_surface3.current.height = 768; lock_surface3.current.configure_serial = 42; wl_signal_emit(&wlr_surface3.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, &wlr_session_lock_v1, _mock_send_locked_lock); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr)); surface_ptr = wlr_surface3.data; wlmtk_element_get_position(wlmtk_surface_element(surface_ptr), &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 1024, x); BS_TEST_VERIFY_EQ(test_ptr, 0, y); // o3 changes size & position. Test configure(). Remains locked. o3.width = 1920; o3.height = 1080; wlr_output_layout_add(server.wlr_output_layout_ptr, &o3, 1200, 200); _mock_configure_serial = 43; _mock_configure_lock_surface = NULL; wl_signal_emit(&server.wlr_output_layout_ptr->events.change, server.wlr_output_layout_ptr); // Note: Issues two configure() events, the second one is for o3. BS_TEST_VERIFY_EQ(test_ptr, &lock_surface3, _mock_configure_lock_surface); BS_TEST_VERIFY_EQ(test_ptr, 1920, _mock_configure_width); BS_TEST_VERIFY_EQ(test_ptr, 1080, _mock_configure_height); wlr_surface3.current.width = 1024; wlr_surface3.current.height = 768; lock_surface3.current.configure_serial = 42; _mock_send_locked_lock = NULL; wl_signal_emit(&wlr_surface3.events.commit, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, _mock_send_locked_lock); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_root_locked(server.root_ptr)); surface_ptr = wlr_surface1.data; wlmtk_element_get_position(wlmtk_surface_element(surface_ptr), &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 3120, x); BS_TEST_VERIFY_EQ(test_ptr, 200, y); surface_ptr = wlr_surface3.data; wlmtk_element_get_position(wlmtk_surface_element(surface_ptr), &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 1200, x); BS_TEST_VERIFY_EQ(test_ptr, 200, y); // Confirm: The earliest added surface is active. surface_ptr = wlr_surface1.data; BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, surface_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_surface_is_activated(surface_ptr)); wlr_output_layout_remove(server.wlr_output_layout_ptr, &o1); _mock_configure_serial = 44; _mock_configure_lock_surface = NULL; wl_signal_emit(&server.wlr_output_layout_ptr->events.change, server.wlr_output_layout_ptr); // Now want surface1 active. surface_ptr = wlr_surface3.data; BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, surface_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_surface_is_activated(surface_ptr)); // Unlock correctly. wl_signal_emit(&wlr_session_lock_v1.events.unlock, NULL); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_workspace_enabled(workspace_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_root_locked(server.root_ptr)); _wlmaker_lock_destroy(lock_ptr); wlmtk_root_remove_workspace(server.root_ptr, workspace_ptr); wlmtk_workspace_destroy(workspace_ptr); wlmtk_root_destroy(server.root_ptr); wl_display_destroy(server.wl_display_ptr); } /* == End of lock_mgr.c ==================================================== */ wlmaker-0.8.1/src/root_menu.h0000644000175100017510000000414415222270710015540 0ustar runnerrunner/* ========================================================================= */ /** * @file root_menu.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_ROOT_MENU_H__ #define __WLMAKER_ROOT_MENU_H__ #include #include "toolkit/toolkit.h" /** Forward declaration: State of root menu. */ typedef struct _wlmaker_root_menu_t wlmaker_root_menu_t; #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a root menu. * * @param server_ptr * @param arg_root_menu_file_ptr * @param menu_style_ref_ptr * @param window_style_ref_ptr * * @return Handle of the root menu, or NULL on error. */ wlmaker_root_menu_t *wlmaker_root_menu_create( wlmaker_server_t *server_ptr, const char *arg_root_menu_file_ptr, wlmtk_window_style_ref_t *window_style_ref_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr); /** * Destroys the root menu. * * @param root_menu_ptr */ void wlmaker_root_menu_destroy(wlmaker_root_menu_t *root_menu_ptr); /** @return the window holding the root menu. */ wlmtk_window_t *wlmaker_root_menu_window(wlmaker_root_menu_t *root_menu_ptr); /** @return Pointer to @ref wlmtk_menu_t of the root menu. */ wlmtk_menu_t *wlmaker_root_menu_menu(wlmaker_root_menu_t *root_menu_ptr); /** Unit test set. */ extern const bs_test_set_t wlmaker_root_menu_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __ROOT_MENU_H__ */ /* == End of root_menu.h =================================================== */ wlmaker-0.8.1/src/background.c0000644000175100017510000002243315222270710015644 0ustar runnerrunner/* ========================================================================= */ /** * @file background.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "background.h" #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE struct wlr_output; /* == Declarations ========================================================= */ /** Background state. */ struct wlmaker_background { /** List node, linked via @ref wlmaker_server_t::backgrounds. */ bs_dllist_node_t dlnode; /** Links to layer. */ wlmtk_layer_t *layer_ptr; /** color of the background. */ uint32_t color; /** Tracks the outputs available. */ wlmtk_output_tracker_t *output_tracker_ptr; }; /** Background panel: The workspace's backgrund for the output. */ typedef struct { /** A layer background for one output is a panel. */ wlmtk_panel_t super_panel; /** Initial implementation: The background is a uni-color rectangle. */ wlmtk_rectangle_t *rectangle_ptr; } wlmaker_background_panel_t; static uint32_t _wlmaker_background_panel_request_size( wlmtk_panel_t *panel_ptr, int width, int height); static void *_wlmaker_background_panel_create( struct wlr_output *wlr_output_ptr, void *ud_ptr); static void _wlmaker_background_panel_destroy( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr); static void _wlmaker_background_panel_element_destroy( wlmtk_element_t *element_ptr); static void _wlmaker_backgrund_panel_set_color( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr, void *arg_ptr); /* == Data ================================================================= */ /** The background panels' virtual method table. */ static const wlmtk_panel_vmt_t _wlmaker_background_panel_vmt = { .request_size = _wlmaker_background_panel_request_size }; /** The panel's superclass element virtual method table. */ static const wlmtk_element_vmt_t _wlmaker_background_panel_element_vmt = { .destroy = _wlmaker_background_panel_element_destroy }; /** Panel's position: Anchored to all 4 edges, and auto-sized. */ static const wlmtk_panel_positioning_t _wlmaker_background_panel_position = { .desired_width = 0, .desired_height = 0, .anchor = WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bs_dllist_node_t *wlmaker_background_create( wlmtk_workspace_t *workspace_ptr, struct wlr_output_layout *wlr_output_layout_ptr, uint32_t color) { struct wlmaker_background *bg_ptr = logged_calloc(1, sizeof(*bg_ptr)); if (NULL == bg_ptr) return NULL; bg_ptr->layer_ptr = wlmtk_workspace_get_layer( workspace_ptr, WLMTK_WORKSPACE_LAYER_BACKGROUND), bg_ptr->color = color; bg_ptr->output_tracker_ptr = wlmtk_output_tracker_create( wlr_output_layout_ptr, bg_ptr, _wlmaker_background_panel_create, NULL, _wlmaker_background_panel_destroy); return &bg_ptr->dlnode; } /* ------------------------------------------------------------------------- */ void wlmaker_background_dlnode_destroy( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { struct wlmaker_background *bg_ptr = BS_CONTAINER_OF( dlnode_ptr, struct wlmaker_background, dlnode); if (NULL != bg_ptr->output_tracker_ptr) { wlmtk_output_tracker_destroy(bg_ptr->output_tracker_ptr); bg_ptr->output_tracker_ptr = NULL; } free(bg_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_background_dlnode_set_color( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { struct wlmaker_background *bg_ptr = BS_CONTAINER_OF( dlnode_ptr, struct wlmaker_background, dlnode); wlmtk_output_tracker_for_each( bg_ptr->output_tracker_ptr, _wlmaker_backgrund_panel_set_color, ud_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_panel_vmt_t::request_size. Updates the panel size. */ uint32_t _wlmaker_background_panel_request_size( wlmtk_panel_t *panel_ptr, int width, int height) { wlmaker_background_panel_t *background_ptr = BS_CONTAINER_OF( panel_ptr, wlmaker_background_panel_t, super_panel); wlmtk_rectangle_set_size(background_ptr->rectangle_ptr, width, height); wlmtk_panel_commit( &background_ptr->super_panel, 0, &_wlmaker_background_panel_position); return 0; } /* ------------------------------------------------------------------------- */ /** Ctor. */ void *_wlmaker_background_panel_create( struct wlr_output *wlr_output_ptr, void *ud_ptr) { struct wlmaker_background *background_ptr = ud_ptr; wlmaker_background_panel_t *background_panel_ptr = logged_calloc( 1, sizeof(wlmaker_background_panel_t)); if (NULL == background_panel_ptr) return NULL; if (!wlmtk_panel_init(&background_panel_ptr->super_panel, &_wlmaker_background_panel_position)) { _wlmaker_background_panel_destroy( NULL, NULL, background_panel_ptr); return NULL; } wlmtk_panel_extend(&background_panel_ptr->super_panel, &_wlmaker_background_panel_vmt); wlmtk_element_extend( wlmtk_panel_element(&background_panel_ptr->super_panel), &_wlmaker_background_panel_element_vmt); background_panel_ptr->rectangle_ptr = wlmtk_rectangle_create( 0, 0, background_ptr->color); if (NULL == background_panel_ptr->rectangle_ptr) { _wlmaker_background_panel_destroy( NULL, NULL, background_panel_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_rectangle_element(background_panel_ptr->rectangle_ptr), true); wlmtk_container_add_element( &background_panel_ptr->super_panel.super_container, wlmtk_rectangle_element(background_panel_ptr->rectangle_ptr)); wlmtk_element_set_visible( wlmtk_panel_element(&background_panel_ptr->super_panel), true); wlmtk_layer_add_panel( background_ptr->layer_ptr, &background_panel_ptr->super_panel, wlr_output_ptr); return background_panel_ptr; } /* ------------------------------------------------------------------------- */ /** Dtor. */ void _wlmaker_background_panel_destroy( __UNUSED__ struct wlr_output *wlr_output_ptr, __UNUSED__ void *ud_ptr, void *output_ptr) { wlmaker_background_panel_t *background_panel_ptr = output_ptr; _wlmaker_background_panel_element_destroy( wlmtk_panel_element(&background_panel_ptr->super_panel)); free(background_panel_ptr); } /* ------------------------------------------------------------------------- */ /** Dtor for the panel's element. Leaves the output handle intact. */ void _wlmaker_background_panel_element_destroy(wlmtk_element_t *element_ptr) { wlmaker_background_panel_t *background_panel_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_background_panel_t, super_panel.super_container.super_element); if (NULL != wlmtk_panel_get_layer( &background_panel_ptr->super_panel)) { wlmtk_layer_remove_panel( wlmtk_panel_get_layer(&background_panel_ptr->super_panel), &background_panel_ptr->super_panel); } if (NULL != background_panel_ptr->rectangle_ptr) { wlmtk_container_remove_element( &background_panel_ptr->super_panel.super_container, wlmtk_rectangle_element(background_panel_ptr->rectangle_ptr)); wlmtk_rectangle_destroy(background_panel_ptr->rectangle_ptr); background_panel_ptr->rectangle_ptr = NULL; } wlmtk_panel_fini(&background_panel_ptr->super_panel); } /* ------------------------------------------------------------------------- */ /** Sets the background color for the panel on this output. */ void _wlmaker_backgrund_panel_set_color( __UNUSED__ struct wlr_output *wlr_output_ptr, __UNUSED__ void *ud_ptr, void *output_ptr, void *arg_ptr) { wlmaker_background_panel_t *background_panel_ptr = output_ptr; uint32_t *color_ptr = arg_ptr; if (NULL != background_panel_ptr->rectangle_ptr) { wlmtk_rectangle_set_color( background_panel_ptr->rectangle_ptr, *color_ptr); } } /* == End of background.c ================================================== */ wlmaker-0.8.1/src/tl_menu.h0000644000175100017510000000313115222270710015167 0ustar runnerrunner/* ========================================================================= */ /** * @file tl_menu.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_TL_MENU_H__ #define __WLMAKER_TL_MENU_H__ #include "toolkit/toolkit.h" #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: State of a toplevel's menu. */ typedef struct _wlmaker_tl_menu_t wlmaker_tl_menu_t; /** * Creates a (window) menu for a toplevel (window). * * @param window_ptr * @param server_ptr * * @return pointer to the toplevel's menu state or NULL on error. */ wlmaker_tl_menu_t *wlmaker_tl_menu_create( wlmtk_window_t *window_ptr, wlmaker_server_t *server_ptr); /** * Destroys the toplevel's menu. * * @param tl_menu_ptr */ void wlmaker_tl_menu_destroy(wlmaker_tl_menu_t *tl_menu_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __TL_MENU_H__ */ /* == End of tl_menu.h ===================================================== */ wlmaker-0.8.1/src/xwl.c0000644000175100017510000003023715222270710014340 0ustar runnerrunner/* ========================================================================= */ /** * @file xwl.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @internal * The current XWayland implementation is not very cleanly designed and should * be considered *experimental*. * TODO(kaeser@gubbe.ch): Re-design, once object model is updated. * * Known issues: * * * Scene graph API nodes for toplevel windows are created early. This leads * to issues with ownership (cleanup?), stacking order, and when properties * (position) are set. It'd be better to only create them when mapping a * window (and destroying when unmapping). * * * Windows with parents are created as plain surfaces and don't clearly show * their stacking order. Decorations may not get applied in all cases. * * * Stacking order is not tackled, eg. popups may appear below. Reproduce: * Open `emacs`, click a menu, and hover over a menu item for the tooltip to * appear. When moving across menus, the tooltip sometimes appears below the * menu window. * * * Popups or dialogs may not be activated or focussed correctly. Reproduce: * Open `emacs`, open the `File` menu, and `Visit New File...`. The dialogue * does not accept mouse events. Moving the dialogue window moves the entire * emacs window. * * * `modal` windows are not identified and treated as such. * * * Positioning of windows: Applications such as `gimp` are setting the main * window's position based on the earlier application's status. We currently * don't translate this to the toplevel window's position, but apply it to * the surface within the tree => leading to a title bar that's oddly offset. * Reproduce: Open a gimp menu, and view the tooltip being off. * * * The window types are not well understood. Eg. `gimp` menu tooltips are * created as windows without parent. We can identify them as TOOLTIP windows * that won't have a border; but we don't have a well-understood set of * properties for the window types. */ #include "xwl.h" #if defined(WLMAKER_HAVE_XWAYLAND) #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #endif // defined(WLMAKER_HAVE_XWAYLAND) #include #include #if defined(WLMAKER_HAVE_XWAYLAND) #include #include #include #include #include "backend/backend.h" #include "server.h" #include "toolkit/toolkit.h" #include "xwl_surface.h" #include "x11_cursor.xpm" #endif // defined(WLMAKER_HAVE_XWAYLAND) /* == Declarations ========================================================= */ /** XWayland interface state. */ struct _wlmaker_xwl_t { /** Back-link to server. */ wlmaker_server_t *server_ptr; #if defined(WLMAKER_HAVE_XWAYLAND) /** XWayland server and XWM. */ struct wlr_xwayland *wlr_xwayland_ptr; /** Listener for the `ready` signal raised by `wlr_xwayland`. */ struct wl_listener ready_listener; /** Listener for the `new_surface` signal raised by `wlr_xwayland`. */ struct wl_listener new_surface_listener; /** XCB atoms we consider relevant. */ xcb_atom_t xcb_atoms[XWL_MAX_ATOM_ID]; #endif // defined(WLMAKER_HAVE_XWAYLAND) }; #if defined(WLMAKER_HAVE_XWAYLAND) static void handle_ready( struct wl_listener *listener_ptr, void *data_ptr); static void handle_new_surface( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Lookup map for some of XCB atom identifiers. */ static const char *xwl_atom_name_map[XWL_MAX_ATOM_ID] = { [NET_WM_WINDOW_TYPE_NORMAL] = "_NET_WM_WINDOW_TYPE_NORMAL", [NET_WM_WINDOW_TYPE_DIALOG] = "_NET_WM_WINDOW_TYPE_DIALOG", [NET_WM_WINDOW_TYPE_UTILITY] = "_NET_WM_WINDOW_TYPE_UTILITY", [NET_WM_WINDOW_TYPE_TOOLBAR] = "_NET_WM_WINDOW_TYPE_TOOLBAR", [NET_WM_WINDOW_TYPE_SPLASH] = "_NET_WM_WINDOW_TYPE_SPLASH", [NET_WM_WINDOW_TYPE_MENU] = "_NET_WM_WINDOW_TYPE_MENU", [NET_WM_WINDOW_TYPE_DROPDOWN_MENU] = "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", [NET_WM_WINDOW_TYPE_POPUP_MENU] = "_NET_WM_WINDOW_TYPE_POPUP_MENU", [NET_WM_WINDOW_TYPE_TOOLTIP] = "_NET_WM_WINDOW_TYPE_TOOLTIP", [NET_WM_WINDOW_TYPE_NOTIFICATION] = "_NET_WM_WINDOW_TYPE_NOTIFICATION", }; /* == Exported methods ===================================================== */ #endif // defined(WLMAKER_HAVE_XWAYLAND) /* ------------------------------------------------------------------------- */ wlmaker_xwl_t *wlmaker_xwl_create(wlmaker_server_t *server_ptr) { wlmaker_xwl_t *xwl_ptr = logged_calloc(1, sizeof(wlmaker_xwl_t)); if (NULL == xwl_ptr) return NULL; xwl_ptr->server_ptr = server_ptr; #if defined(WLMAKER_HAVE_XWAYLAND) xwl_ptr->wlr_xwayland_ptr = wlr_xwayland_create( server_ptr->wl_display_ptr, wlmbe_backend_compositor(server_ptr->backend_ptr), false); if (NULL == xwl_ptr->wlr_xwayland_ptr) { bs_log(BS_ERROR, "Failed wlr_xwayland_create(%p, %p, false).", server_ptr->wl_display_ptr, wlmbe_backend_compositor(server_ptr->backend_ptr)); wlmaker_xwl_destroy(xwl_ptr); return NULL; } wlmtk_util_connect_listener_signal( &xwl_ptr->wlr_xwayland_ptr->events.ready, &xwl_ptr->ready_listener, handle_ready); wlmtk_util_connect_listener_signal( &xwl_ptr->wlr_xwayland_ptr->events.new_surface, &xwl_ptr->new_surface_listener, handle_new_surface); // TODO(kaeser@gubbe.ch): That's a bit ugly. We should only do a setenv // as we create & fork the subprocesses. Needs infrastructure, though. setenv("DISPLAY", xwl_ptr->wlr_xwayland_ptr->display_name, true); #endif // defined(WLMAKER_HAVE_XWAYLAND) return xwl_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_xwl_destroy(wlmaker_xwl_t *xwl_ptr) { #if defined(WLMAKER_HAVE_XWAYLAND) wlmtk_util_disconnect_listener(&xwl_ptr->ready_listener); wlmtk_util_disconnect_listener(&xwl_ptr->new_surface_listener); if (NULL != xwl_ptr->wlr_xwayland_ptr) { wlr_xwayland_destroy(xwl_ptr->wlr_xwayland_ptr); xwl_ptr->wlr_xwayland_ptr = NULL; } #endif // defined(WLMAKER_HAVE_XWAYLAND) free(xwl_ptr); } #if defined(WLMAKER_HAVE_XWAYLAND) /* ------------------------------------------------------------------------- */ /** * Returns whether the XWayland surface has any of the window types. * * @param xwl_ptr * @param wlr_xwayland_surface_ptr * @param atom_identifiers NULL-terminated set of window type we're looking * for. * * @return Whether `atom_identifiers` is in any of the window types. */ bool xwl_is_window_type( wlmaker_xwl_t *xwl_ptr, struct wlr_xwayland_surface *wlr_xwayland_surface_ptr, const xwl_atom_identifier_t *atom_identifiers) { for (; *atom_identifiers < XWL_MAX_ATOM_ID; ++atom_identifiers) { for (size_t i = 0; i < wlr_xwayland_surface_ptr->window_type_len; ++i) { if (wlr_xwayland_surface_ptr->window_type[i] == xwl_ptr->xcb_atoms[*atom_identifiers]) { return true; } } } return false; } /* ------------------------------------------------------------------------- */ const char *xwl_atom_name( wlmaker_xwl_t *xwl_ptr, xcb_atom_t atom) { for (size_t atom_idx = 0; atom_idx < XWL_MAX_ATOM_ID; ++atom_idx) { if (xwl_ptr->xcb_atoms[atom_idx] == atom) { return xwl_atom_name_map[atom_idx]; } } return NULL; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Event handler for the `ready` signal raised by `wlr_xwayland`. * * @param listener_ptr * @param data_ptr */ void handle_ready(struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_t *xwl_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_t, ready_listener); xcb_connection_t *xcb_connection_ptr = xcb_connect( xwl_ptr->wlr_xwayland_ptr->display_name, NULL); int error = xcb_connection_has_error(xcb_connection_ptr); if (0 != error) { bs_log(BS_ERROR, "Failed xcb_connect(%s, NULL): %d", xwl_ptr->wlr_xwayland_ptr->display_name, error); return; } xcb_intern_atom_cookie_t atom_cookies[XWL_MAX_ATOM_ID]; for (size_t i = 0; i < XWL_MAX_ATOM_ID; ++i) { const char *name_ptr = xwl_atom_name_map[i]; atom_cookies[i] = xcb_intern_atom( xcb_connection_ptr, 0, strlen(name_ptr), name_ptr); } for (size_t i = 0; i < XWL_MAX_ATOM_ID; ++i) { xcb_generic_error_t *error_ptr = NULL; xcb_intern_atom_reply_t *atom_reply_ptr = xcb_intern_atom_reply( xcb_connection_ptr, atom_cookies[i], &error_ptr); if (NULL != atom_reply_ptr) { if (NULL == error_ptr) { xwl_ptr->xcb_atoms[i] = atom_reply_ptr->atom; bs_log(BS_DEBUG, "XCB lookup on %s: atom %s = 0x%"PRIx32, xwl_ptr->wlr_xwayland_ptr->display_name, xwl_atom_name_map[i], atom_reply_ptr->atom); } free(atom_reply_ptr); } if (NULL != error_ptr) { bs_log(BS_ERROR, "Failed xcb_intern_atom_reply(%p, %s, %p): %d", xcb_connection_ptr, xwl_atom_name_map[i], &error_ptr, error_ptr->error_code); free(error_ptr); break; } } xcb_disconnect(xcb_connection_ptr); // Sets the default cursor to use for XWayland surfaces, unless overrideen. #if WLR_VERSION_NUM >= (20 << 8) bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_xpm_create_from_data(x11_cursor_xpm); if (NULL == gfxbuf_ptr) return; struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( gfxbuf_ptr->width, gfxbuf_ptr->height); if (NULL != wlr_buffer_ptr) { bs_gfxbuf_copy(bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), gfxbuf_ptr); wlr_xwayland_set_cursor( xwl_ptr->wlr_xwayland_ptr, wlr_buffer_ptr, 0, 0); wlr_buffer_drop(wlr_buffer_ptr); } bs_gfxbuf_destroy(gfxbuf_ptr); #else bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_xpm_create_from_data(x11_cursor_xpm); if (NULL != gfxbuf_ptr) { wlr_xwayland_set_cursor( xwl_ptr->wlr_xwayland_ptr, (uint8_t*)gfxbuf_ptr->data_ptr, gfxbuf_ptr->pixels_per_line * sizeof(uint32_t), gfxbuf_ptr->width, gfxbuf_ptr->height, 0, 0); bs_gfxbuf_destroy(gfxbuf_ptr); } #endif } /* ------------------------------------------------------------------------- */ /** * Event handler for the `new_surface` signal raised by `wlr_xwayland`. * * @param listener_ptr * @param data_ptr */ void handle_new_surface(struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xwl_t *xwl_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_t, new_surface_listener); struct wlr_xwayland_surface *wlr_xwayland_surface_ptr = data_ptr; wlmaker_xwl_surface_t *xwl_surface_ptr = wlmaker_xwl_surface_create( wlr_xwayland_surface_ptr, xwl_ptr, xwl_ptr->server_ptr); if (NULL == xwl_surface_ptr) { bs_log(BS_ERROR, "Failed wlmaker_xwl_surface_create(%p)", wlr_xwayland_surface_ptr); } } #endif // defined(WLMAKER_HAVE_XWAYLAND) /* == End of xwl.c ========================================================= */ wlmaker-0.8.1/src/clip.c0000644000175100017510000011006415222270710014452 0ustar runnerrunner/* ========================================================================= */ /** * @file clip.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "clip.h" #include #include #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #undef WLR_USE_UNSTABLE #include "backend/backend.h" #include "backend/output_config.h" #include "files.h" #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** Clip handle. */ struct _wlmaker_clip_t { /** The clip happens to be derived from a tile. */ wlmtk_tile_t super_tile; /** Original virtual method table fo the superclass' element. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Backlink to the server. */ wlmaker_server_t *server_ptr; /** The toolkit dock, holding the clip tile. */ wlmtk_dock_t *wlmtk_dock_ptr; /** The tile's texture buffer without any buttons pressed */ struct wlr_buffer *tile_buffer_ptr; /** The tile's texture buffer with the 'Next' buttons pressed. */ struct wlr_buffer *next_pressed_tile_buffer_ptr; /** The tile's texture buffer with the 'Previous' buttons pressed. */ struct wlr_buffer *prev_pressed_tile_buffer_ptr; /** Overlay buffer element: Contains the workspace's title and number. */ wlmtk_buffer_t overlay_buffer; /** Path to the image file. */ char *image_path_ptr; /** Clip image. */ wlmtk_image_t *image_ptr; /** Description of the desired output, if any. */ wlmbe_output_description_t output_description; /** Whether the pointer is currently inside the 'prev' button. */ bool pointer_inside_prev_button; /** Whether the pointer is currently inside the 'next' button. */ bool pointer_inside_next_button; /** Whether the 'prev' button had been pressed. */ bool prev_button_pressed; /** Whether the 'next' button had been pressed. */ bool next_button_pressed; /** Listener for @ref wlmtk_root_events_t::workspace_changed. */ struct wl_listener workspace_changed_listener; /** Listener for wlr_output_layout::events.change. */ struct wl_listener output_layout_change_listener; /** Listener for @ref wlmtk_element_events_t::pointer_motion. */ struct wl_listener pointer_motion_listener; /** Listener for @ref wlmtk_element_events_t::pointer_leave. */ struct wl_listener pointer_leave_listener; /** Listener for @ref wlmaker_server_t::theme_changed_event. */ struct wl_listener theme_changed_listener; /** The clip's style. */ wlmaker_config_clip_style_t style; }; static bool _wlmaker_clip_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); static bool _wlmaker_clip_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static bool _wlmaker_clip_update_tiles( wlmaker_clip_t *clip_ptr, const struct wlmtk_tile_style *tile_style_ptr); static void _wlmaker_clip_apply_button_state(wlmaker_clip_t *clip_ptr); static bool _wlmaker_clip_update_overlay( wlmaker_clip_t *clip_ptr, const struct wlmtk_tile_style *tile_style_ptr, const wlmaker_config_clip_style_t *clip_style_ptr); static bool _wlmaker_clip_update_image( wlmaker_clip_t *clip_ptr, const struct wlmtk_tile_style *tile_style_ptr); static struct wlr_buffer *_wlmaker_clip_create_tile( const struct wlmtk_tile_style *tile_style_ptr, bool prev_pressed, bool next_pressed); static void _wlmaker_clip_handle_workspace_changed( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_clip_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_clip_handle_pointer_motion( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_clip_handle_pointer_leave( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_clip_handle_theme_changed( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** The clip's extension to @ref wlmtk_element_t virtual method table. */ static const wlmtk_element_vmt_t _wlmaker_clip_element_vmt = { .pointer_axis = _wlmaker_clip_pointer_axis, .pointer_button = _wlmaker_clip_pointer_button, }; /** TODO: Replace this. */ typedef struct { /** Positioning data. */ wlmtk_dock_positioning_t positioning; } parse_args; /** Enum descriptor for `enum wlr_edges`. */ static const bspl_enum_desc_t _wlmaker_clip_edges[] = { BSPL_ENUM("TOP", WLR_EDGE_TOP), BSPL_ENUM("BOTTOM", WLR_EDGE_BOTTOM), BSPL_ENUM("LEFT", WLR_EDGE_LEFT), BSPL_ENUM("RIGHT", WLR_EDGE_RIGHT), BSPL_ENUM_SENTINEL(), }; /** Descriptor for the clip's plist. */ const bspl_desc_t _wlmaker_clip_desc[] = { BSPL_DESC_ENUM("Edge", true, parse_args, positioning.edge, positioning.edge, WLR_EDGE_NONE, _wlmaker_clip_edges), BSPL_DESC_ENUM("Anchor", true, parse_args, positioning.anchor, positioning.anchor, WLR_EDGE_NONE, _wlmaker_clip_edges), BSPL_DESC_SENTINEL(), }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_clip_t *wlmaker_clip_create( wlmaker_server_t *server_ptr, bspl_dict_t *state_dict_ptr, const wlmaker_config_style_t *style_ptr) { wlmaker_clip_t *clip_ptr = logged_calloc(1, sizeof(wlmaker_clip_t)); if (NULL == clip_ptr) return NULL; clip_ptr->server_ptr = server_ptr; clip_ptr->style = style_ptr->clip; parse_args args = {}; bspl_dict_t *dict_ptr = bspl_dict_get_dict(state_dict_ptr, "Clip"); if (NULL == dict_ptr) { bs_log(BS_ERROR, "No 'Clip' dict found in state."); wlmaker_clip_destroy(clip_ptr); return NULL; } bspl_decode_dict(dict_ptr, _wlmaker_clip_desc, &args); bspl_dict_t *output_dict_ptr = bspl_dict_get_dict(dict_ptr, "Output"); if (NULL != output_dict_ptr) { if (!wlmbe_output_description_init_from_plist( &clip_ptr->output_description, output_dict_ptr)) { wlmaker_clip_destroy(clip_ptr); return NULL; } } clip_ptr->wlmtk_dock_ptr = wlmtk_dock_create( &args.positioning, &style_ptr->dock); wlmtk_element_set_visible( wlmtk_dock_element(clip_ptr->wlmtk_dock_ptr), true); if (!wlmtk_tile_init( &clip_ptr->super_tile, &style_ptr->tile)) { wlmaker_clip_destroy(clip_ptr); return NULL; } clip_ptr->orig_super_element_vmt = wlmtk_element_extend( wlmtk_tile_element(&clip_ptr->super_tile), &_wlmaker_clip_element_vmt); wlmtk_util_connect_listener_signal( &wlmtk_tile_element(&clip_ptr->super_tile)->events.pointer_motion, &clip_ptr->pointer_motion_listener, _wlmaker_clip_handle_pointer_motion); wlmtk_util_connect_listener_signal( &wlmtk_tile_element(&clip_ptr->super_tile)->events.pointer_leave, &clip_ptr->pointer_leave_listener, _wlmaker_clip_handle_pointer_leave); if (!_wlmaker_clip_update_tiles(clip_ptr, &style_ptr->tile)) { wlmaker_clip_destroy(clip_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_tile_element(&clip_ptr->super_tile), true); wlmtk_tile_set_background_buffer( &clip_ptr->super_tile, clip_ptr->tile_buffer_ptr); wlmtk_dock_add_tile(clip_ptr->wlmtk_dock_ptr, &clip_ptr->super_tile); if (!wlmtk_buffer_init(&clip_ptr->overlay_buffer)) { wlmaker_clip_destroy(clip_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_buffer_element(&clip_ptr->overlay_buffer), true); struct wlr_output *wlr_output_ptr = wlmbe_output_description_first_fnmatch( &clip_ptr->output_description, server_ptr->wlr_output_layout_ptr); if (NULL == wlr_output_ptr) { wlr_output_ptr = wlmbe_primary_output( server_ptr->wlr_output_layout_ptr); } wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(server_ptr->root_ptr); wlmtk_layer_t *layer_ptr = wlmtk_workspace_get_layer( workspace_ptr, WLMTK_WORKSPACE_LAYER_TOP); if (!wlmtk_layer_add_panel( layer_ptr, wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr), wlr_output_ptr)) { wlmaker_clip_destroy(clip_ptr); return NULL; } // Resolves to a full path, and verifies the icon file exists. clip_ptr->image_path_ptr = wlmaker_files_xdg_data_find( server_ptr->files_ptr, "icons/clip-56x56.png", S_IFREG); if (NULL == clip_ptr->image_path_ptr) { bs_log( BS_WARNING, "Failed to locate ${XDG_DATA_DIRS}/wlmaker/icons/clip-56x56.png"); #ifdef WLMAKER_SOURCE_DIR clip_ptr->image_path_ptr = logged_strdup( WLMAKER_SOURCE_DIR "/share/wlmaker/icons/clip-56x56.png"); #endif } if (NULL == clip_ptr->image_path_ptr) { wlmaker_clip_destroy(clip_ptr); return NULL; } if (!_wlmaker_clip_update_image(clip_ptr, &style_ptr->tile)) { wlmaker_clip_destroy(clip_ptr); return NULL; } if (!_wlmaker_clip_update_overlay( clip_ptr, &clip_ptr->super_tile.style, &clip_ptr->style)) { wlmaker_clip_destroy(clip_ptr); return NULL; }; wlmtk_util_connect_listener_signal( &wlmtk_root_events(server_ptr->root_ptr)->workspace_changed, &clip_ptr->workspace_changed_listener, _wlmaker_clip_handle_workspace_changed); wlmtk_util_connect_listener_signal( &server_ptr->theme_changed_event, &clip_ptr->theme_changed_listener, _wlmaker_clip_handle_theme_changed); // TODO(kaeser@gubbe.ch): This is a very hacky way of updating the output // before the layer's handler removes all associated panels. Should be // a native method of wlmtk_dock_t or wlmtk_panel_t. clip_ptr->output_layout_change_listener.notify = _wlmaker_clip_handle_output_layout_change; wl_list_insert( server_ptr->wlr_output_layout_ptr->events.change.listener_list.next, &clip_ptr->output_layout_change_listener.link); server_ptr->clip_dock_ptr = clip_ptr->wlmtk_dock_ptr; bs_log(BS_INFO, "Created clip %p", clip_ptr); return clip_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_clip_destroy(wlmaker_clip_t *clip_ptr) { if (NULL != clip_ptr->server_ptr) { clip_ptr->server_ptr->clip_dock_ptr = NULL; } wlmtk_util_disconnect_listener(&clip_ptr->output_layout_change_listener); wlmtk_util_disconnect_listener(&clip_ptr->workspace_changed_listener); wlmtk_util_disconnect_listener(&clip_ptr->theme_changed_listener); if (wlmtk_tile_element(&clip_ptr->super_tile)->parent_container_ptr) { wlmtk_tile_set_content(&clip_ptr->super_tile, NULL); wlmtk_tile_set_overlay(&clip_ptr->super_tile, NULL); wlmtk_dock_remove_tile( clip_ptr->wlmtk_dock_ptr, &clip_ptr->super_tile); } wlmtk_util_disconnect_listener(&clip_ptr->pointer_leave_listener); wlmtk_util_disconnect_listener(&clip_ptr->pointer_motion_listener); wlmtk_tile_fini(&clip_ptr->super_tile); wlmtk_buffer_fini(&clip_ptr->overlay_buffer); if (NULL != clip_ptr->image_ptr) { wlmtk_image_destroy(clip_ptr->image_ptr); clip_ptr->image_ptr = NULL; } if (NULL != clip_ptr->image_path_ptr) { free(clip_ptr->image_path_ptr); clip_ptr->image_path_ptr = NULL; } if (NULL != clip_ptr->wlmtk_dock_ptr) { if (NULL != wlmtk_panel_get_layer( wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr))) { wlmtk_layer_remove_panel( wlmtk_panel_get_layer(wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr)), wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr)); } wlmtk_dock_destroy(clip_ptr->wlmtk_dock_ptr); clip_ptr->wlmtk_dock_ptr = NULL; } if (NULL != clip_ptr->tile_buffer_ptr) { wlr_buffer_drop(clip_ptr->tile_buffer_ptr); clip_ptr->tile_buffer_ptr = NULL; } if (NULL != clip_ptr->prev_pressed_tile_buffer_ptr) { wlr_buffer_drop(clip_ptr->prev_pressed_tile_buffer_ptr); clip_ptr->prev_pressed_tile_buffer_ptr = NULL; } if (NULL != clip_ptr->next_pressed_tile_buffer_ptr) { wlr_buffer_drop(clip_ptr->next_pressed_tile_buffer_ptr); clip_ptr->next_pressed_tile_buffer_ptr = NULL; } wlmbe_output_description_fini(&clip_ptr->output_description); free(clip_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::pointer_axis. * * Moves to the next or previous workspace, depending on the axis (scroll- * wheel) direction. * * @param element_ptr * @param wlr_pointer_axis_event_ptr * * @return true */ bool _wlmaker_clip_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_clip_t, super_tile.super_container.super_element); if (0 > wlr_pointer_axis_event_ptr->delta) { // Scroll wheel "up" -> next. wlmtk_root_switch_to_next_workspace(clip_ptr->server_ptr->root_ptr); } else if (0 < wlr_pointer_axis_event_ptr->delta) { // Scroll wheel "down" -> next. wlmtk_root_switch_to_previous_workspace(clip_ptr->server_ptr->root_ptr); } return true; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::pointer_button. * * Checks if the button press is on either 'next' or 'prev' button area, * updates visualization if pressed, and switches workspace if needed. * * @param element_ptr * @param button_event_ptr * * @return true. */ bool _wlmaker_clip_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_clip_t, super_tile.super_container.super_element); if (BTN_LEFT != button_event_ptr->button) return true; switch (button_event_ptr->type) { case WLMTK_BUTTON_DOWN: // Pointer button tressed. Translate to button press if in area. if (clip_ptr->pointer_inside_next_button || clip_ptr->pointer_inside_next_button) { clip_ptr->next_button_pressed = true; clip_ptr->prev_button_pressed = false; } else if (clip_ptr->pointer_inside_prev_button || clip_ptr->pointer_inside_prev_button) { clip_ptr->next_button_pressed = false; clip_ptr->prev_button_pressed = true; } break; case WLMTK_BUTTON_UP: // Button is released (closed the click). If we're within the area of // the pressed button: Trigger the action. if ((clip_ptr->pointer_inside_next_button || clip_ptr->pointer_inside_next_button) && clip_ptr->next_button_pressed) { clip_ptr->next_button_pressed = false; wlmtk_root_switch_to_next_workspace( clip_ptr->server_ptr->root_ptr); } else if ((clip_ptr->pointer_inside_prev_button || clip_ptr->pointer_inside_prev_button) && clip_ptr->prev_button_pressed) { clip_ptr->prev_button_pressed = false; wlmtk_root_switch_to_previous_workspace( clip_ptr->server_ptr->root_ptr); } break; case WLMTK_BUTTON_CLICK: default: break; } _wlmaker_clip_apply_button_state(clip_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Updates the button textures, based on current state what's pressed. */ static void _wlmaker_clip_apply_button_state(wlmaker_clip_t *clip_ptr) { struct wlr_buffer *wlr_buffer_ptr = clip_ptr->tile_buffer_ptr; if ((clip_ptr->pointer_inside_next_button || clip_ptr->pointer_inside_next_button)&& clip_ptr->next_button_pressed) { wlr_buffer_ptr = clip_ptr->next_pressed_tile_buffer_ptr; } else if ((clip_ptr->pointer_inside_prev_button || clip_ptr->pointer_inside_prev_button) && clip_ptr->prev_button_pressed) { wlr_buffer_ptr = clip_ptr->prev_pressed_tile_buffer_ptr; } wlmtk_tile_set_background_buffer(&clip_ptr->super_tile, wlr_buffer_ptr); } /* ------------------------------------------------------------------------- */ /** Updates the overlay buffer's content with workspace name and index. */ bool _wlmaker_clip_update_overlay( wlmaker_clip_t *clip_ptr, const struct wlmtk_tile_style *tile_style_ptr, const wlmaker_config_clip_style_t *clip_style_ptr) { struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( tile_style_ptr->size, tile_style_ptr->size); if (NULL == wlr_buffer_ptr) return false; int index = 0; const char *name_ptr = NULL; wlmtk_workspace_get_details( wlmtk_root_get_current_workspace(clip_ptr->server_ptr->root_ptr), &name_ptr, &index); cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); return false; } cairo_select_font_face( cairo_ptr, clip_style_ptr->font.face, CAIRO_FONT_SLANT_NORMAL, wlmtk_style_font_weight_cairo_from_wlmtk(clip_style_ptr->font.weight)); cairo_set_font_size(cairo_ptr, clip_style_ptr->font.size); cairo_set_source_argb8888(cairo_ptr, clip_style_ptr->text_color); cairo_move_to( cairo_ptr, clip_style_ptr->font.size * 4 / 12, clip_style_ptr->font.size * 2 / 12 + clip_style_ptr->font.size); cairo_show_text(cairo_ptr, name_ptr); cairo_move_to( cairo_ptr, tile_style_ptr->size - clip_style_ptr->font.size * 14 / 12, tile_style_ptr->size - clip_style_ptr->font.size * 8 / 12); char buf[10]; snprintf(buf, sizeof(buf), "%d", index); cairo_show_text(cairo_ptr, buf); cairo_destroy(cairo_ptr); wlmtk_buffer_set(&clip_ptr->overlay_buffer, wlr_buffer_ptr); wlr_buffer_drop(wlr_buffer_ptr); wlmtk_tile_set_overlay( &clip_ptr->super_tile, wlmtk_buffer_element(&clip_ptr->overlay_buffer)); return true; } /* ------------------------------------------------------------------------- */ /** Updates (reloads) the content image. */ bool _wlmaker_clip_update_image( wlmaker_clip_t *clip_ptr, const struct wlmtk_tile_style *tile_style_ptr) { wlmtk_image_t *i = wlmtk_image_create_scaled( clip_ptr->image_path_ptr, tile_style_ptr->content_size, tile_style_ptr->content_size); if (NULL == i) return false; wlmtk_element_set_visible(wlmtk_image_element(i), true); wlmtk_tile_set_content(&clip_ptr->super_tile, wlmtk_image_element(i)); if (NULL != clip_ptr->image_ptr) wlmtk_image_destroy(clip_ptr->image_ptr); clip_ptr->image_ptr = i; return true; } /* ------------------------------------------------------------------------- */ /** * Creates (or updates) the tile buffers for the button states. * * @param clip_ptr * @param tile_style_ptr * * @return true on success */ bool _wlmaker_clip_update_tiles( wlmaker_clip_t *clip_ptr, const struct wlmtk_tile_style *tile_style_ptr) { struct wlr_buffer *tile, *tile_prev, *tile_next; tile = _wlmaker_clip_create_tile(tile_style_ptr, false, false); tile_prev = _wlmaker_clip_create_tile(tile_style_ptr, true, false); tile_next = _wlmaker_clip_create_tile(tile_style_ptr, false, true); if (NULL == tile || NULL == tile_prev || NULL == tile_next) { if (tile) wlr_buffer_drop(tile); if (tile_prev) wlr_buffer_drop(tile_prev); if (tile_next) wlr_buffer_drop(tile_next); return false; } if (NULL != clip_ptr->tile_buffer_ptr) { wlr_buffer_drop(clip_ptr->tile_buffer_ptr); } if (NULL != clip_ptr->prev_pressed_tile_buffer_ptr) { wlr_buffer_drop(clip_ptr->prev_pressed_tile_buffer_ptr); } if (NULL != clip_ptr->next_pressed_tile_buffer_ptr) { wlr_buffer_drop(clip_ptr->next_pressed_tile_buffer_ptr); } clip_ptr->tile_buffer_ptr = tile; clip_ptr->prev_pressed_tile_buffer_ptr = tile_prev; clip_ptr->next_pressed_tile_buffer_ptr = tile_next; _wlmaker_clip_apply_button_state(clip_ptr); return true; } /* ------------------------------------------------------------------------- */ /** * Creates a wlr_buffer with texture suitable to show the 'next' and 'prev' * buttons in each raised or pressed state. * * @param tile_style_ptr * @param prev_pressed * @param next_pressed * * @return A wlr buffer. */ struct wlr_buffer *_wlmaker_clip_create_tile( const struct wlmtk_tile_style *tile_style_ptr, bool prev_pressed, bool next_pressed) { struct wlr_buffer* wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( tile_style_ptr->size, tile_style_ptr->size); if (NULL == wlr_buffer_ptr) return NULL; double tsize = tile_style_ptr->size; double bsize = 22.0 / 64.0 * tile_style_ptr->size; double margin = tile_style_ptr->bezel_width; cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); return NULL; } wlmaker_primitives_cairo_fill(cairo_ptr, &tile_style_ptr->fill); // Northern + Western sides. Drawn clock-wise. wlmaker_primitives_set_bezel_color(cairo_ptr, true); cairo_move_to(cairo_ptr, 0, 0); cairo_line_to(cairo_ptr, tsize - bsize, 0); cairo_line_to(cairo_ptr, tsize - bsize, margin); cairo_line_to(cairo_ptr, margin, margin); cairo_line_to(cairo_ptr, margin, tsize - bsize); cairo_line_to(cairo_ptr, 0, tsize - bsize); cairo_line_to(cairo_ptr, 0, 0); cairo_fill(cairo_ptr); // Southern + Eastern sides. Also drawn Also clock-wise. wlmaker_primitives_set_bezel_color(cairo_ptr, false); cairo_move_to(cairo_ptr, tsize, tsize); cairo_line_to(cairo_ptr, bsize, tsize); cairo_line_to(cairo_ptr, bsize, tsize - margin); cairo_line_to(cairo_ptr, tsize - margin, tsize - margin); cairo_line_to(cairo_ptr, tsize - margin, bsize); cairo_line_to(cairo_ptr, tsize, bsize); cairo_line_to(cairo_ptr, tsize, tsize); cairo_fill(cairo_ptr); // Diagonal at the north-eastern corner. Drawn clockwise. wlmaker_primitives_set_bezel_color(cairo_ptr, true); cairo_move_to(cairo_ptr, tsize - bsize, 0); cairo_line_to(cairo_ptr, tsize, bsize); cairo_line_to(cairo_ptr, tsize - margin, bsize); cairo_line_to(cairo_ptr, tsize - bsize, margin); cairo_line_to(cairo_ptr, tsize - bsize, 0); cairo_fill(cairo_ptr); // Diagonal at south-western corner. Drawn clockwise. wlmaker_primitives_set_bezel_color(cairo_ptr, false); cairo_move_to(cairo_ptr, 0, tsize - bsize); cairo_line_to(cairo_ptr, margin, tsize - bsize); cairo_line_to(cairo_ptr, bsize, tsize - margin); cairo_line_to(cairo_ptr, bsize, tsize); cairo_line_to(cairo_ptr, 0, tsize - bsize); cairo_fill(cairo_ptr); // The "Next" button, north-eastern corner. // Northern edge, illuminated when raised wlmaker_primitives_set_bezel_color(cairo_ptr, !next_pressed); cairo_move_to(cairo_ptr, tsize - bsize, 0); cairo_line_to(cairo_ptr, tsize, 0); cairo_line_to(cairo_ptr, tsize - margin, margin); cairo_line_to(cairo_ptr, tsize - bsize + 2 * margin, margin); cairo_line_to(cairo_ptr, tsize - bsize , 0); cairo_fill(cairo_ptr); // Eastern edge, illuminated when pressed wlmaker_primitives_set_bezel_color(cairo_ptr, next_pressed); cairo_move_to(cairo_ptr, tsize, 0); cairo_line_to(cairo_ptr, tsize, bsize); cairo_line_to(cairo_ptr, tsize - margin, bsize - 2 * margin); cairo_line_to(cairo_ptr, tsize - margin, margin); cairo_line_to(cairo_ptr, tsize, 0); cairo_fill(cairo_ptr); // Diagonal, illuminated when pressed. wlmaker_primitives_set_bezel_color(cairo_ptr, next_pressed); cairo_move_to(cairo_ptr, tsize - bsize, 0); cairo_line_to(cairo_ptr, tsize - bsize + 2 * margin, margin); cairo_line_to(cairo_ptr, tsize - margin, bsize - 2 *margin); cairo_line_to(cairo_ptr, tsize, bsize); cairo_line_to(cairo_ptr, tsize - bsize, 0); cairo_fill(cairo_ptr); // The black triangle. Use relative sizes. double tpad = bsize * 5.0 / 22.0; double trsize = bsize * 7.0 / 22.0; double tmargin = bsize * 1.0 / 22.0; cairo_set_source_rgba(cairo_ptr, 0, 0, 0, 1.0); cairo_move_to(cairo_ptr, tsize - tpad, tpad); cairo_line_to(cairo_ptr, tsize - tpad, trsize + tpad); cairo_line_to(cairo_ptr, tsize - tpad - trsize, tpad); cairo_line_to(cairo_ptr, tsize - tpad, tpad); cairo_fill(cairo_ptr); // Northern edge of triangle, not illuminated. wlmaker_primitives_set_bezel_color(cairo_ptr, false); cairo_move_to(cairo_ptr, tsize - tpad, tpad); cairo_line_to(cairo_ptr, tsize - tpad - trsize, tpad); cairo_line_to(cairo_ptr, tsize - tpad - trsize - tmargin, tpad - tmargin); cairo_line_to(cairo_ptr, tsize - tpad + tmargin, tpad - tmargin); cairo_line_to(cairo_ptr, tsize - tpad, tpad); cairo_fill(cairo_ptr); // Eastern side of triangle, illuminated. wlmaker_primitives_set_bezel_color(cairo_ptr, true); cairo_move_to(cairo_ptr, tsize - tpad, tpad); cairo_line_to(cairo_ptr, tsize - tpad + tmargin, tpad - tmargin); cairo_line_to(cairo_ptr, tsize - tpad + tmargin, tpad + trsize + tmargin); cairo_line_to(cairo_ptr, tsize - tpad, tpad + trsize); cairo_line_to(cairo_ptr, tsize - tpad, tpad); cairo_fill(cairo_ptr); // The "Prev" button, south-western corner. // Southern edge, illuminated when pressed. wlmaker_primitives_set_bezel_color(cairo_ptr, prev_pressed); cairo_move_to(cairo_ptr, 0, tsize); cairo_line_to(cairo_ptr, margin, tsize - margin); cairo_line_to(cairo_ptr, bsize - 2 * margin, tsize - margin); cairo_line_to(cairo_ptr, bsize, tsize); cairo_line_to(cairo_ptr, 0, tsize); cairo_fill(cairo_ptr); // Western edge, illuminated when raised. wlmaker_primitives_set_bezel_color(cairo_ptr, !prev_pressed); cairo_move_to(cairo_ptr, 0, tsize); cairo_line_to(cairo_ptr, 0, tsize - bsize + 0); cairo_line_to(cairo_ptr, margin, tsize - bsize + 2 * margin); cairo_line_to(cairo_ptr, margin, tsize - margin); cairo_line_to(cairo_ptr, 0, tsize); cairo_fill(cairo_ptr); // Diagonal, illuminated when raised. wlmaker_primitives_set_bezel_color(cairo_ptr, !prev_pressed); cairo_move_to(cairo_ptr, 0, tsize - bsize + 0); cairo_line_to(cairo_ptr, bsize, tsize); cairo_line_to(cairo_ptr, bsize - 2 * margin, tsize - margin); cairo_line_to(cairo_ptr, margin, tsize - bsize + 2 * margin); cairo_line_to(cairo_ptr, 0, tsize - bsize + 0); cairo_fill(cairo_ptr); // The black triangle. Use relative sizes. cairo_set_source_rgba(cairo_ptr, 0, 0, 0, 1.0); cairo_move_to(cairo_ptr, tpad, tsize - tpad); cairo_line_to(cairo_ptr, tpad, tsize - trsize - tpad); cairo_line_to(cairo_ptr, tpad + trsize, tsize - tpad); cairo_line_to(cairo_ptr, tpad, tsize - tpad); cairo_fill(cairo_ptr); // Southern edge of triangle, illuminated. wlmaker_primitives_set_bezel_color(cairo_ptr, true); cairo_move_to(cairo_ptr, tpad, tsize - tpad); cairo_line_to(cairo_ptr, tpad + trsize, tsize - tpad); cairo_line_to(cairo_ptr, tpad + trsize + tmargin, tsize - tpad + tmargin); cairo_line_to(cairo_ptr, tpad - tmargin, tsize - tpad + tmargin); cairo_line_to(cairo_ptr, tpad, tsize - tpad); cairo_fill(cairo_ptr); // Eastern side of triangle, not illuminated. wlmaker_primitives_set_bezel_color(cairo_ptr, false); cairo_move_to(cairo_ptr, tpad, tsize - tpad); cairo_line_to(cairo_ptr, tpad - tmargin, tsize - tpad + tmargin); cairo_line_to(cairo_ptr, tpad - tmargin, tsize - tpad - trsize - tmargin); cairo_line_to(cairo_ptr, tpad, tsize - tpad - trsize); cairo_line_to(cairo_ptr, tpad, tsize - tpad); cairo_fill(cairo_ptr); cairo_destroy(cairo_ptr); return wlr_buffer_ptr; } /* -------------------------------------------------------------------------- */ /** * Handler for the `workspace_changed` signal of `wlmaker_server_t`. * * Will redraw the clip contents with the current workspace, and re-map the * clip to the new workspace. * * @param listener_ptr * @param data_ptr Points to the new `wlmtk_workspace_t`. */ void _wlmaker_clip_handle_workspace_changed( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_clip_t, workspace_changed_listener); wlmtk_panel_t *panel_ptr = wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr); wlmtk_layer_t *current_layer_ptr = wlmtk_panel_get_layer(panel_ptr); wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(clip_ptr->server_ptr->root_ptr); wlmtk_layer_t *new_layer_ptr = wlmtk_workspace_get_layer( workspace_ptr, WLMTK_WORKSPACE_LAYER_TOP); if (current_layer_ptr == new_layer_ptr) return; if (NULL != current_layer_ptr) { wlmtk_layer_remove_panel(current_layer_ptr, panel_ptr); } BS_ASSERT(wlmtk_layer_add_panel( new_layer_ptr, panel_ptr, wlmbe_primary_output( clip_ptr->server_ptr->wlr_output_layout_ptr))); _wlmaker_clip_update_overlay( clip_ptr, &clip_ptr->super_tile.style, &clip_ptr->style); } /* ------------------------------------------------------------------------- */ /** Handles when output layout changes; Re-computes the output to attach. */ void _wlmaker_clip_handle_output_layout_change( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_clip_t, output_layout_change_listener); struct wlr_output *wlr_output_ptr = wlmbe_output_description_first_fnmatch( &clip_ptr->output_description, clip_ptr->server_ptr->wlr_output_layout_ptr); if (NULL == wlr_output_ptr) { wlr_output_ptr = wlmbe_primary_output( clip_ptr->server_ptr->wlr_output_layout_ptr); } wlmtk_layer_t *layer_ptr = wlmtk_panel_get_layer( wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr)); wlmtk_layer_remove_panel(layer_ptr, wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr)); if (NULL != wlr_output_ptr) { BS_ASSERT(wlmtk_layer_add_panel( layer_ptr, wlmtk_dock_panel(clip_ptr->wlmtk_dock_ptr), wlr_output_ptr)); } } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_element_events_t::pointer_leave. Resets buttons. */ void _wlmaker_clip_handle_pointer_leave( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_clip_t, pointer_leave_listener); clip_ptr->pointer_inside_prev_button = false; clip_ptr->pointer_inside_next_button = false; _wlmaker_clip_apply_button_state(clip_ptr); } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_element_events_t::pointer_motion. */ void _wlmaker_clip_handle_pointer_motion( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_clip_t, pointer_motion_listener); wlmtk_pointer_motion_event_t *motion_event_ptr = data_ptr; clip_ptr->pointer_inside_prev_button = false; clip_ptr->pointer_inside_next_button = false; double tile_size = clip_ptr->super_tile.style.size; double button_size = (22.0 / 64.0) * tile_size; if (motion_event_ptr->x >= tile_size - button_size && motion_event_ptr->x < tile_size && motion_event_ptr->y >= 0 && motion_event_ptr->y < button_size) { // Next button. clip_ptr->pointer_inside_next_button = true; } else if (motion_event_ptr->x >= 0 && motion_event_ptr->x < button_size && motion_event_ptr->y >= tile_size - button_size && motion_event_ptr->y < tile_size) { // Prev button. clip_ptr->pointer_inside_prev_button = true; } _wlmaker_clip_apply_button_state(clip_ptr); } /* ------------------------------------------------------------------------- */ /** Event listener: The theme changed. Applies the new style to the clip. */ void _wlmaker_clip_handle_theme_changed( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_clip_t *clip_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_clip_t, theme_changed_listener); wlmaker_config_style_t *style_ptr = data_ptr; if (!_wlmaker_clip_update_tiles(clip_ptr, &style_ptr->tile)) return; if (!_wlmaker_clip_update_overlay( clip_ptr, &style_ptr->tile, &style_ptr->clip)) return; if (!_wlmaker_clip_update_image(clip_ptr, &style_ptr->tile)) return; clip_ptr->style = style_ptr->clip; wlmtk_dock_set_style( clip_ptr->wlmtk_dock_ptr, &style_ptr->dock, &style_ptr->tile); // Need to apply the button state (again) after @ref wlmtk_dock_set_style, // since the latter will apply the generic tile backend. Which the tile // overwrites. _wlmaker_clip_apply_button_state(clip_ptr); } /* == Unit tests =========================================================== */ static void test_draw_tile(bs_test_t *test_ptr); /** Test cases. */ static const bs_test_case_t wlmaker_clip_test_cases[] = { { true, "draw_tile", test_draw_tile }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_clip_test_set = BS_TEST_SET( true, "clip", wlmaker_clip_test_cases); /* ------------------------------------------------------------------------- */ /** Tests that the clip tile is drawn correctly. */ void test_draw_tile(bs_test_t *test_ptr) { static const struct wlmtk_tile_style style = { .fill = { .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .dgradient = { .from = 0xffa6a6b6, .to = 0xff515561 } } }, .bezel_width = 2, .size = 64 }; struct wlr_buffer* wlr_buffer_ptr; bs_gfxbuf_t *gfxbuf_ptr; wlr_buffer_ptr = _wlmaker_clip_create_tile(&style, false, false); BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlr_buffer_ptr); gfxbuf_ptr = bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "clip_raised.png"); wlr_buffer_drop(wlr_buffer_ptr); wlr_buffer_ptr = _wlmaker_clip_create_tile(&style, true, true); BS_TEST_VERIFY_NEQ(test_ptr, NULL, wlr_buffer_ptr); gfxbuf_ptr = bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "clip_pressed.png"); wlr_buffer_drop(wlr_buffer_ptr); } /* == End of clip.c ======================================================== */ wlmaker-0.8.1/src/backend/0000755000175100017510000000000015222270710014744 5ustar runnerrunnerwlmaker-0.8.1/src/backend/output_manager.c0000644000175100017510000004744615222270710020161 0ustar runnerrunner/* ========================================================================= */ /** * @file output_manager.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "output_manager.h" #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "output.h" struct wl_list; /* == Declarations ========================================================= */ /** Implementation of the wlr output manager. */ struct _wlmbe_output_manager_t { /** Points to wlroots `struct wlr_output_manager_v1`. */ struct wlr_output_manager_v1 *wlr_output_manager_v1_ptr; /** Listener for wlr_output_manager_v1::events::destroy. */ struct wl_listener wlr_om_destroy_listener; /** Listener for wlr_output_manager_v1::events::apply. */ struct wl_listener apply_listener; /** Listener for wlr_output_manager_v1::events::test. */ struct wl_listener test_listener; /** Points to wlroots 'struct wlr_xdg_output_manager_v1`. */ struct wlr_xdg_output_manager_v1 *wlr_xdg_output_manager_v1_ptr; /** Listener for wlr_xdg_output_manager_v1::events::destroy. */ struct wl_listener xdg_om_destroy_listener; /** Listener for wlr_output_layout::events::destroy. */ struct wl_listener output_layout_destroy_listener; /** Listener for wlr_output_layout::events::change. */ struct wl_listener output_layout_change_listener; // Below: Not owned by @ref wlmbe_output_manager_t. /** The scene. */ struct wlr_scene *wlr_scene_ptr; /** Points to struct wlr_output_layout. */ struct wlr_output_layout *wlr_output_layout_ptr; /** Points to struct wlr_backend. */ struct wlr_backend *wlr_backend_ptr; }; /** Argument to @ref _wlmaker_output_manager_config_head_apply. */ typedef struct { /** Points to struct wlr_output_layout. */ struct wlr_output_layout *wlr_output_layout_ptr; /** Whether to test only, or to apply "really". */ bool really; } _wlmaker_output_manager_config_head_apply_arg_t; static bool _wlmbe_output_manager_update_output_configuration( struct wl_list *link_ptr, void *ud_ptr); static bool _wlmaker_output_manager_config_head_scale( struct wl_list *link_ptr, void *ud_ptr); static bool _wlmaker_output_manager_config_head_apply( struct wl_list *link_ptr, void *ud_ptr); static bool _wlmbe_output_manager_apply( wlmbe_output_manager_t *output_manager_ptr, struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr, bool really); static void _wlmbe_output_manager_handle_wlr_om_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmbe_output_manager_handle_apply( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmbe_output_manager_handle_test( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmbe_output_manager_handle_xdg_om_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmbe_output_manager_handle_output_layout_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmbe_output_manager_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmbe_output_manager_t *wlmbe_output_manager_create( struct wl_display *wl_display_ptr, struct wlr_scene *wlr_scene_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_backend *wlr_backend_ptr) { wlmbe_output_manager_t *output_manager_ptr = logged_calloc( 1, sizeof(wlmbe_output_manager_t)); if (NULL == output_manager_ptr) return NULL; output_manager_ptr->wlr_backend_ptr = wlr_backend_ptr; output_manager_ptr->wlr_scene_ptr = wlr_scene_ptr; output_manager_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr; output_manager_ptr->wlr_output_manager_v1_ptr = wlr_output_manager_v1_create(wl_display_ptr); if (NULL == output_manager_ptr->wlr_output_manager_v1_ptr) { wlmbe_output_manager_destroy(output_manager_ptr); return NULL; } wlmtk_util_connect_listener_signal( &output_manager_ptr->wlr_output_manager_v1_ptr->events.destroy, &output_manager_ptr->wlr_om_destroy_listener, _wlmbe_output_manager_handle_wlr_om_destroy); wlmtk_util_connect_listener_signal( &output_manager_ptr->wlr_output_manager_v1_ptr->events.apply, &output_manager_ptr->apply_listener, _wlmbe_output_manager_handle_apply); wlmtk_util_connect_listener_signal( &output_manager_ptr->wlr_output_manager_v1_ptr->events.test, &output_manager_ptr->test_listener, _wlmbe_output_manager_handle_test); wlmtk_util_connect_listener_signal( &wlr_output_layout_ptr->events.destroy, &output_manager_ptr->output_layout_destroy_listener, _wlmbe_output_manager_handle_output_layout_destroy); wlmtk_util_connect_listener_signal( &wlr_output_layout_ptr->events.change, &output_manager_ptr->output_layout_change_listener, _wlmbe_output_manager_handle_output_layout_change); output_manager_ptr->wlr_xdg_output_manager_v1_ptr = wlr_xdg_output_manager_v1_create( wl_display_ptr, wlr_output_layout_ptr); if (NULL == output_manager_ptr->wlr_xdg_output_manager_v1_ptr) { wlmbe_output_manager_destroy(output_manager_ptr); return NULL; } wlmtk_util_connect_listener_signal( &output_manager_ptr->wlr_xdg_output_manager_v1_ptr->events.destroy, &output_manager_ptr->xdg_om_destroy_listener, _wlmbe_output_manager_handle_xdg_om_destroy); // Initializes the output manager from current output layout. _wlmbe_output_manager_handle_output_layout_change( &output_manager_ptr->output_layout_change_listener, wlr_output_layout_ptr); return output_manager_ptr; } /* ------------------------------------------------------------------------- */ void wlmbe_output_manager_destroy( wlmbe_output_manager_t *output_manager_ptr) { _wlmbe_output_manager_handle_output_layout_destroy( &output_manager_ptr->output_layout_destroy_listener, NULL); _wlmbe_output_manager_handle_wlr_om_destroy( &output_manager_ptr->wlr_om_destroy_listener, NULL); _wlmbe_output_manager_handle_xdg_om_destroy( &output_manager_ptr->xdg_om_destroy_listener, NULL); free(output_manager_ptr); } /* ------------------------------------------------------------------------- */ void wlmbe_output_manager_scale( wlmbe_output_manager_t *output_manager_ptr, double scale) { struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr = wlr_output_configuration_v1_create(); if (NULL == wlr_output_configuration_v1_ptr) { bs_log(BS_ERROR, "Failed wlr_output_configuration_v1_create()"); return; } // First: retrieve each head's status, then scale them. Then, apply. if (wlmtk_util_wl_list_for_each( &output_manager_ptr->wlr_output_layout_ptr->outputs, _wlmbe_output_manager_update_output_configuration, wlr_output_configuration_v1_ptr)) { wlmtk_util_wl_list_for_each( &wlr_output_configuration_v1_ptr->heads, _wlmaker_output_manager_config_head_scale, &scale); _wlmbe_output_manager_apply( output_manager_ptr, wlr_output_configuration_v1_ptr, true); } wlr_output_configuration_v1_destroy(wlr_output_configuration_v1_ptr); // Not to forget: Propagate to potential layout manager clients. _wlmbe_output_manager_handle_output_layout_change( &output_manager_ptr->output_layout_change_listener, NULL); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Iterator for the `struct wlr_output_layout_output` referenced from * `struct wlr_output_layout::outputs`. * * Adds the configuration head for the given output to the provided output * configuration. * * @param link_ptr struct wlr_output_layout_output::link. * @param ud_ptr The output configuration, a pointer to * `struct wlr_output_configuration_v1`. * * @return true on success. */ bool _wlmbe_output_manager_update_output_configuration( struct wl_list *link_ptr, void *ud_ptr) { struct wlr_output_layout_output *wlr_output_layout_output_ptr = BS_CONTAINER_OF(link_ptr, struct wlr_output_layout_output, link); struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr = ud_ptr; struct wlr_output_configuration_head_v1 *head_v1_ptr = wlr_output_configuration_head_v1_create( wlr_output_configuration_v1_ptr, wlr_output_layout_output_ptr->output); if (NULL == head_v1_ptr) { bs_log(BS_ERROR, "Failed wlr_output_configuration_head_v1_create(%p, %p)", wlr_output_configuration_v1_ptr, wlr_output_layout_output_ptr->output); return false; } struct wlr_box box; wlr_output_layout_get_box( wlr_output_layout_output_ptr->layout, wlr_output_layout_output_ptr->output, &box); head_v1_ptr->state.x = box.x; head_v1_ptr->state.y = box.y; return true; } /* ------------------------------------------------------------------------- */ /** * Applies the scale factor at `ud_ptr` to the head. * * Callback for @ref wlmtk_util_wl_list_for_each. * * @param link_ptr * @param ud_ptr * * @return true. */ bool _wlmaker_output_manager_config_head_scale( struct wl_list *link_ptr, void *ud_ptr) { struct wlr_output_configuration_head_v1 *head_v1_ptr = BS_CONTAINER_OF( link_ptr, struct wlr_output_configuration_head_v1, link); head_v1_ptr->state.scale = BS_MAX(1.0, head_v1_ptr->state.scale * *(double*)ud_ptr); return true; } /* ------------------------------------------------------------------------- */ /** * Applies the heads's output configuration. * * Callback for @ref wlmtk_util_wl_list_for_each. * * @param link_ptr * @param ud_ptr * * @return true if the tests & apply methods succeeded. */ static bool _wlmaker_output_manager_config_head_apply( struct wl_list *link_ptr, void *ud_ptr) { struct wlr_output_configuration_head_v1 *head_v1_ptr = BS_CONTAINER_OF( link_ptr, struct wlr_output_configuration_head_v1, link); struct wlr_output_state state = {}; _wlmaker_output_manager_config_head_apply_arg_t *arg_ptr = ud_ptr; // Convenience pointers. Guard against accidental misses. struct wlr_output *wlr_output_ptr = head_v1_ptr->state.output; if (NULL == wlr_output_ptr) { bs_log(BS_ERROR, "Unexpected NULL output in head %p", head_v1_ptr); return false; } wlr_output_head_v1_state_apply(&head_v1_ptr->state, &state); state.scale = BS_MAX(1.0, state.scale); if (!wlr_output_test_state(wlr_output_ptr, &state)) return false; if (!arg_ptr->really) return true; if (!wlr_output_commit_state(wlr_output_ptr, &state)) return false; int x = head_v1_ptr->state.x, y = head_v1_ptr->state.y; struct wlr_output_layout *wlr_output_layout_ptr = arg_ptr->wlr_output_layout_ptr; if (head_v1_ptr->state.enabled && !wlr_output_layout_add(wlr_output_layout_ptr, wlr_output_ptr, x, y)) { bs_log(BS_ERROR, "Failed wlr_output_layout_add(%p, %p, %d, %d)", wlr_output_layout_ptr, wlr_output_ptr, x, y); return false; } else if (!head_v1_ptr->state.enabled) { wlr_output_layout_remove(wlr_output_layout_ptr, wlr_output_ptr); } bool has_position = false; struct wlr_output_layout_output *wlr_output_layout_output_ptr = wlr_output_layout_get( arg_ptr->wlr_output_layout_ptr, wlr_output_ptr); if (NULL != wlr_output_layout_output_ptr) { has_position = !wlr_output_layout_output_ptr->auto_configured; } wlmbe_output_update_attributes(wlr_output_ptr->data, x, y, has_position); bs_log(BS_INFO, "Applied: Output <%s> %s to %dx%d@%.2f x%.2f position (%d,%d) %s", wlmbe_output_description(wlr_output_ptr->data), wlr_output_ptr->enabled ? "enabled" : "disabled", wlr_output_ptr->width, wlr_output_ptr->height, 1e-3 * wlr_output_ptr->refresh, wlr_output_ptr->scale, x, y, has_position ? "explicit" : "auto"); return true; } /* ------------------------------------------------------------------------- */ /** * Tests and applies an output configuration. * * @param output_manager_ptr * @param wlr_output_configuration_v1_ptr * @param really Whether to not just test, but also apply it. * * @return true on success. */ bool _wlmbe_output_manager_apply( wlmbe_output_manager_t *output_manager_ptr, struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr, bool really) { _wlmaker_output_manager_config_head_apply_arg_t arg = { .wlr_output_layout_ptr = output_manager_ptr->wlr_output_layout_ptr, .really = really }; if (!wlmtk_util_wl_list_for_each( &wlr_output_configuration_v1_ptr->heads, _wlmaker_output_manager_config_head_apply, &arg)) { return false; } size_t states_len; struct wlr_backend_output_state *wlr_backend_output_state_ptr = wlr_output_configuration_v1_build_state( wlr_output_configuration_v1_ptr, &states_len); if (NULL == wlr_backend_output_state_ptr) { bs_log(BS_ERROR, "Failed wlr_output_configuration_v1_build_state(%p, %p)", wlr_output_configuration_v1_ptr, &states_len); return false; } bool rv = wlr_backend_test( output_manager_ptr->wlr_backend_ptr, wlr_backend_output_state_ptr, states_len); if (rv && really) { rv = wlr_backend_commit( output_manager_ptr->wlr_backend_ptr, wlr_backend_output_state_ptr, states_len); } free(wlr_backend_output_state_ptr); return rv; } /* ------------------------------------------------------------------------- */ /** Handler for wlr_output_manager_v1::events.destroy. Detaches. */ void _wlmbe_output_manager_handle_wlr_om_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmbe_output_manager_t *output_manager_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_manager_t, wlr_om_destroy_listener); if (NULL == output_manager_ptr->wlr_output_manager_v1_ptr) return; wlmtk_util_disconnect_listener( &output_manager_ptr->test_listener); wlmtk_util_disconnect_listener( &output_manager_ptr->apply_listener); wlmtk_util_disconnect_listener( &output_manager_ptr->wlr_om_destroy_listener); output_manager_ptr->wlr_output_manager_v1_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Handler for wlr_output_manager_v1::events.apply. Cleans up. */ void _wlmbe_output_manager_handle_apply( struct wl_listener *listener_ptr, void *data_ptr) { wlmbe_output_manager_t *om_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_manager_t, apply_listener); struct wlr_output_configuration_v1 *wlr_output_config_ptr = data_ptr; if (_wlmbe_output_manager_apply(om_ptr, wlr_output_config_ptr, true)) { wlr_output_configuration_v1_send_succeeded(wlr_output_config_ptr); } else { wlr_output_configuration_v1_send_failed(wlr_output_config_ptr); } } /* ------------------------------------------------------------------------- */ /** Handler for wlr_output_manager_v1::events.test. */ void _wlmbe_output_manager_handle_test( struct wl_listener *listener_ptr, void *data_ptr) { wlmbe_output_manager_t *om_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_manager_t, test_listener); struct wlr_output_configuration_v1 *wlr_output_config_ptr = data_ptr; if (_wlmbe_output_manager_apply(om_ptr, wlr_output_config_ptr, false)) { wlr_output_configuration_v1_send_succeeded(wlr_output_config_ptr); } else { wlr_output_configuration_v1_send_failed(wlr_output_config_ptr); } } /* ------------------------------------------------------------------------- */ /** Handler for wlr_xdg_output_manager_v1::events.destroy. Detaches. */ void _wlmbe_output_manager_handle_xdg_om_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmbe_output_manager_t *output_manager_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_manager_t, xdg_om_destroy_listener); if (NULL == output_manager_ptr->wlr_xdg_output_manager_v1_ptr) return; wlmtk_util_disconnect_listener( &output_manager_ptr->xdg_om_destroy_listener); output_manager_ptr->wlr_xdg_output_manager_v1_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Handles dtor for @ref wlmbe_output_manager_t::wlr_output_layout_ptr. */ void _wlmbe_output_manager_handle_output_layout_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmbe_output_manager_t *output_manager_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_manager_t, output_layout_destroy_listener); if (NULL == output_manager_ptr->wlr_output_layout_ptr) return; wlmtk_util_disconnect_listener( &output_manager_ptr->output_layout_change_listener); wlmtk_util_disconnect_listener( &output_manager_ptr->output_layout_destroy_listener); output_manager_ptr->wlr_output_layout_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Handles layout change events. */ void _wlmbe_output_manager_handle_output_layout_change( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmbe_output_manager_t *output_manager_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_manager_t, output_layout_change_listener); // Guard clause. if (NULL == output_manager_ptr->wlr_output_manager_v1_ptr) return; struct wlr_output_configuration_v1 *wlr_output_configuration_v1_ptr = wlr_output_configuration_v1_create(); if (NULL == wlr_output_configuration_v1_ptr) { bs_log(BS_ERROR, "Failed wlr_output_configuration_v1_create()."); return; } if (wlmtk_util_wl_list_for_each( &output_manager_ptr->wlr_output_layout_ptr->outputs, _wlmbe_output_manager_update_output_configuration, wlr_output_configuration_v1_ptr)) { wlr_output_manager_v1_set_configuration( output_manager_ptr->wlr_output_manager_v1_ptr, wlr_output_configuration_v1_ptr); return; } wlr_output_configuration_v1_destroy(wlr_output_configuration_v1_ptr); } /* == End of output_manager.c ============================================== */ wlmaker-0.8.1/src/backend/backend.c0000644000175100017510000006722415222270710016512 0ustar runnerrunner/* ========================================================================= */ /** * @file backend.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "backend.h" #include #include #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "output.h" #include "output_config.h" #include "output_manager.h" /* == Declarations ========================================================= */ /** State of the server's backend. */ struct _wlmbe_backend_t { /** wlroots backend. */ struct wlr_backend *wlr_backend_ptr; /** wlroots session. Populated from wlr_backend_autocreate(). */ struct wlr_session *wlr_session_ptr; /** wlroots renderer. */ struct wlr_renderer *wlr_renderer_ptr; /** The allocator. */ struct wlr_allocator *wlr_allocator_ptr; /** The scene output layout. */ struct wlr_scene_output_layout *wlr_scene_output_layout_ptr; /** The compositor is necessary for clients to allocate surfaces. */ struct wlr_compositor *wlr_compositor_ptr; /** * The subcompositor allows to assign the role of subsurfaces to * surfaces. */ struct wlr_subcompositor *wlr_subcompositor_ptr; /** The screencopy manager. */ struct wlr_screencopy_manager_v1 *wlr_screencopy_manager_v1_ptr; /** The output manager(s). */ wlmbe_output_manager_t *output_manager_ptr; /** Listener for wlr_backend::events::new_input. */ struct wl_listener new_output_listener; /** Desired output width, for windowed mode. 0 for no preference. */ uint32_t width; /** Desired output height, for windowed mode. 0 for no preference. */ uint32_t height; /** * A list of @ref wlmbe_output_config_t items, configured through * wlmaker's configuration file. * * Discovered outputs are attempted to matched through * @ref wlmbe_output_config_fnmatches for configured attributes. */ bs_dllist_t output_configs; /** * Another list of @ref wlmbe_output_config_t items. This is * initialized from wlmaker's state file upon startup, ie. in * @ref wlmbe_backend_create. * * If a discovered output equals to one of the nodes here, it's attributes * will be taken from here. Otherwise, a new entry is created in this list. * The intent is to memorize state of connected configs, so that * re-connected outputs are using the same attributes they left with. * * These configurations can be saved by calling * @ref wlmbe_backend_save_ephemeral_output_configs, otherwise any updates * will be lost on restart. */ bs_dllist_t ephemeral_output_configs; /** List of outputs. Connects @ref wlmbe_output_t::dlnode. */ bs_dllist_t outputs; // Elements below not owned by @ref wlmbe_backend_t. /** Back-link to the wlroots scene. */ struct wlr_scene *wlr_scene_ptr; /** Points to struct wlr_output_layout. */ struct wlr_output_layout *wlr_output_layout_ptr; /** Name of the file for storing the output's state. */ char *state_fname_ptr; }; static void _wlmbe_backend_handle_new_output( struct wl_listener *listener_ptr, void *data_ptr); static bool _wlmbe_backend_decode_item( bspl_object_t *obj_ptr, size_t i, void *dest_ptr); static bspl_object_t *_wlmbe_backend_encode_all( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); static void _wlmbe_backend_decode_fini(void *dest_ptr); static void _wlmbe_backend_config_dlnode_destroy( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /* == Data ================================================================= */ /** Descriptor for the output configuration. */ static const bspl_desc_t _wlmbe_output_configs_desc[] = { BSPL_DESC_ARRAY("Outputs", true, wlmbe_backend_t, output_configs, output_configs, _wlmbe_backend_decode_item, _wlmbe_backend_encode_all, NULL, // init. _wlmbe_backend_decode_fini), BSPL_DESC_SENTINEL(), }; /** Descriptor for ephemeral output onfigurations, stored as plist. */ static const bspl_desc_t _wlmbe_outputs_state_desc[] = { BSPL_DESC_ARRAY("Outputs", true, wlmbe_backend_t, ephemeral_output_configs, ephemeral_output_configs, _wlmbe_backend_decode_item, _wlmbe_backend_encode_all, NULL, _wlmbe_backend_decode_fini), BSPL_DESC_SENTINEL(), }; /** Magnification factor: 3rd square root of 2.0. ~10, doubles exactly. */ static const double _wlmbke_backend_magnification = 1.0905077326652577; /** The window title to set, when on a Wayland backend. */ static const char *_wlmbe_wl_title = "wlmaker"; /** The application ID to set, when on a Wayland backend. */ static const char *_wlmbe_wl_app_id = "wlmaker"; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmbe_backend_t *wlmbe_backend_create( struct wl_display *wl_display_ptr, struct wlr_scene *wlr_scene_ptr, struct wlr_output_layout *wlr_output_layout_ptr, int width, int height, bspl_dict_t *config_dict_ptr, const char *state_fname_ptr) { wlmbe_backend_t *backend_ptr = logged_calloc(1, sizeof(wlmbe_backend_t)); if (NULL == backend_ptr) return NULL; backend_ptr->wlr_scene_ptr = wlr_scene_ptr; backend_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr; backend_ptr->width = width; backend_ptr->height = height; if (!bspl_decode_dict( config_dict_ptr, _wlmbe_output_configs_desc, backend_ptr)) { wlmbe_backend_destroy(backend_ptr); return NULL; } // Loads state (if available). backend_ptr->state_fname_ptr = logged_strdup(state_fname_ptr); if (NULL == backend_ptr->state_fname_ptr) { wlmbe_backend_destroy(backend_ptr); return NULL; } if (bs_file_realpath_is(backend_ptr->state_fname_ptr, S_IFREG)) { bspl_object_t *o = bspl_create_object_from_plist_file( backend_ptr->state_fname_ptr); if (NULL == o) { wlmbe_backend_destroy(backend_ptr); return NULL; } bool rv = bspl_decode_dict( bspl_dict_from_object(o), _wlmbe_outputs_state_desc, backend_ptr); bspl_object_unref(o); if (!rv) { wlmbe_backend_destroy(backend_ptr); return NULL; } bs_log(BS_INFO, "Loaded output state from \"%s\"", state_fname_ptr); } // Auto-create the wlroots backend. Can be X11 or direct. backend_ptr->wlr_backend_ptr = wlr_backend_autocreate( wl_display_get_event_loop(wl_display_ptr), &backend_ptr->wlr_session_ptr); if (NULL == backend_ptr->wlr_backend_ptr) { bs_log(BS_ERROR, "Failed wlr_backend_autocreate()"); wlmbe_backend_destroy(backend_ptr); return NULL; } // Auto-create a renderer. Can be specified using WLR_RENDERER env var. backend_ptr->wlr_renderer_ptr = wlr_renderer_autocreate( backend_ptr->wlr_backend_ptr); if (NULL == backend_ptr->wlr_renderer_ptr) { bs_log(BS_ERROR, "Failed wlr_renderer_autocreate()"); wlmbe_backend_destroy(backend_ptr); return NULL; } if (!wlr_renderer_init_wl_display( backend_ptr->wlr_renderer_ptr, wl_display_ptr)) { bs_log(BS_ERROR, "Failed wlr_render_init_wl_display()"); wlmbe_backend_destroy(backend_ptr); return NULL; } backend_ptr->wlr_allocator_ptr = wlr_allocator_autocreate( backend_ptr->wlr_backend_ptr, backend_ptr->wlr_renderer_ptr); if (NULL == backend_ptr->wlr_allocator_ptr) { bs_log(BS_ERROR, "Failed wlr_allocator_autocreate()"); wlmbe_backend_destroy(backend_ptr); return NULL; } backend_ptr->wlr_scene_output_layout_ptr = wlr_scene_attach_output_layout( wlr_scene_ptr, backend_ptr->wlr_output_layout_ptr); if (NULL == backend_ptr->wlr_scene_output_layout_ptr) { bs_log(BS_ERROR, "Failed wlr_scene_attach_output_layout()"); wlmbe_backend_destroy(backend_ptr); return NULL; } backend_ptr->wlr_compositor_ptr = wlr_compositor_create( wl_display_ptr, 5, backend_ptr->wlr_renderer_ptr); if (NULL == backend_ptr->wlr_compositor_ptr) { bs_log(BS_ERROR, "Failed wlr_compositor_create()"); wlmbe_backend_destroy(backend_ptr); return NULL; } backend_ptr->wlr_subcompositor_ptr = wlr_subcompositor_create( wl_display_ptr); if (NULL == backend_ptr->wlr_subcompositor_ptr) { bs_log(BS_ERROR, "Failed wlr_subcompositor_create()"); wlmbe_backend_destroy(backend_ptr); return NULL; } backend_ptr->wlr_screencopy_manager_v1_ptr = wlr_screencopy_manager_v1_create(wl_display_ptr); if (NULL == backend_ptr->wlr_screencopy_manager_v1_ptr) { bs_log(BS_ERROR, "Failed wlr_screencopy_manager_v1_create()"); wlmbe_backend_destroy(backend_ptr); return NULL; } backend_ptr->output_manager_ptr = wlmbe_output_manager_create( wl_display_ptr, wlr_scene_ptr, wlr_output_layout_ptr, backend_ptr->wlr_backend_ptr); if (NULL == backend_ptr->output_manager_ptr) { wlmbe_backend_destroy(backend_ptr); return NULL; } wlmtk_util_connect_listener_signal( &backend_ptr->wlr_backend_ptr->events.new_output, &backend_ptr->new_output_listener, _wlmbe_backend_handle_new_output); return backend_ptr; } /* ------------------------------------------------------------------------- */ void wlmbe_backend_destroy(wlmbe_backend_t *backend_ptr) { wlmtk_util_disconnect_listener(&backend_ptr->new_output_listener); if (NULL != backend_ptr->output_manager_ptr) { wlmbe_output_manager_destroy(backend_ptr->output_manager_ptr); backend_ptr->output_manager_ptr = NULL; } if (NULL != backend_ptr->wlr_renderer_ptr) { wlr_renderer_destroy(backend_ptr->wlr_renderer_ptr); backend_ptr->wlr_renderer_ptr = NULL; } if (NULL != backend_ptr->wlr_allocator_ptr) { wlr_allocator_destroy(backend_ptr->wlr_allocator_ptr); backend_ptr->wlr_allocator_ptr = NULL; } bspl_decoded_destroy(_wlmbe_outputs_state_desc, backend_ptr); bspl_decoded_destroy(_wlmbe_output_configs_desc, backend_ptr); // @ref wlmbe_backend_t::wlr_backend_ptr is destroyed from wl_display. if (NULL != backend_ptr->state_fname_ptr) { free(backend_ptr->state_fname_ptr); backend_ptr->state_fname_ptr = NULL; } free(backend_ptr); } /* ------------------------------------------------------------------------- */ void wlmbe_backend_switch_to_vt(wlmbe_backend_t *backend_ptr, unsigned vt_num) { // Guard clause: @ref wlmbe_backend_t::session_ptr will be populated only // if wlroots created a session, eg. when running from the terminal. if (NULL == backend_ptr->wlr_session_ptr) { bs_log(BS_DEBUG, "wlmbe_backend_switch_to_vt: No session, ignored."); return; } if (!wlr_session_change_vt(backend_ptr->wlr_session_ptr, vt_num)) { bs_log(BS_WARNING, "Failed wlr_session_change_vt(, %u)", vt_num); } } /* ------------------------------------------------------------------------- */ struct wlr_backend *wlmbe_backend_wlr(wlmbe_backend_t *backend_ptr) { return backend_ptr->wlr_backend_ptr; } /* ------------------------------------------------------------------------- */ struct wlr_compositor *wlmbe_backend_compositor(wlmbe_backend_t *backend_ptr) { return backend_ptr->wlr_compositor_ptr; } /* ------------------------------------------------------------------------- */ struct wlr_output *wlmbe_primary_output( struct wlr_output_layout *wlr_output_layout_ptr) { if (0 >= wl_list_length(&wlr_output_layout_ptr->outputs)) return NULL; struct wlr_output_layout_output* wolo = BS_CONTAINER_OF( wlr_output_layout_ptr->outputs.next, struct wlr_output_layout_output, link); return wolo->output; } /* ------------------------------------------------------------------------- */ size_t wlmbe_num_outputs(struct wlr_output_layout *wlr_output_layout_ptr) { return wl_list_length(&wlr_output_layout_ptr->outputs); } /* ------------------------------------------------------------------------- */ void wlmbe_backend_magnify(wlmbe_backend_t *backend_ptr) { wlmbe_output_manager_scale( backend_ptr->output_manager_ptr, _wlmbke_backend_magnification); } /* ------------------------------------------------------------------------- */ void wlmbe_backend_reduce(wlmbe_backend_t *backend_ptr) { wlmbe_output_manager_scale( backend_ptr->output_manager_ptr, 1.0 / _wlmbke_backend_magnification); } /* ------------------------------------------------------------------------- */ bool wlmbe_backend_save_ephemeral_output_configs(wlmbe_backend_t *backend_ptr) { bspl_dict_t *dict_ptr = bspl_encode_dict( _wlmbe_outputs_state_desc, backend_ptr); if (NULL != dict_ptr) { bs_dynbuf_t dynbuf = {}; if (bs_dynbuf_init(&dynbuf, getpagesize(), SIZE_MAX)) { if (bspl_object_write( bspl_object_from_dict(dict_ptr), &dynbuf) && bs_dynbuf_write_file( &dynbuf, backend_ptr->state_fname_ptr, S_IRUSR | S_IWUSR)) { return true; } bs_dynbuf_fini(&dynbuf); } bspl_dict_unref(dict_ptr); } return false; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Adds the output to the backend. * * @param backend_ptr * @param output_ptr * * @return true on success. */ bool _wlmbe_backend_add_output( wlmbe_backend_t *backend_ptr, wlmbe_output_t *output_ptr) { const wlmbe_output_config_attributes_t *attr_ptr = wlmbe_output_attributes(output_ptr); BS_ASSERT(NULL != attr_ptr); struct wlr_output *wlrop = wlmbe_wlr_output_from_output(output_ptr); struct wlr_output_layout_output *wlr_output_layout_output_ptr = NULL; if (attr_ptr->has_position) { wlr_output_layout_output_ptr = wlr_output_layout_add( backend_ptr->wlr_output_layout_ptr, wlrop, attr_ptr->position.x, attr_ptr->position.y); } else { wlr_output_layout_output_ptr = wlr_output_layout_add_auto( backend_ptr->wlr_output_layout_ptr, wlrop); } if (NULL == wlr_output_layout_output_ptr) { bs_log(BS_WARNING, "Failed wlr_output_layout_add(%p, %p, %d, %d) for \"%s\"", backend_ptr->wlr_output_layout_ptr, wlrop, attr_ptr->position.x, attr_ptr->position.y, wlrop->name); return false; } struct wlr_scene_output *wlr_scene_output_ptr = wlr_scene_output_create( backend_ptr->wlr_scene_ptr, wlrop); wlr_scene_output_layout_add_output( backend_ptr->wlr_scene_output_layout_ptr, wlr_output_layout_output_ptr, wlr_scene_output_ptr); bs_dllist_push_back( &backend_ptr->outputs, wlmbe_dlnode_from_output(output_ptr)); bs_log(BS_INFO, "Created: Output <%s> %s to %dx%d@%.2f x%.2f position (%d,%d) %s", wlmbe_output_description(output_ptr), wlrop->enabled ? "enabled" : "disabled", wlrop->width, wlrop->height, 1e-3 * wlrop->refresh, wlrop->scale, wlr_output_layout_output_ptr->x, wlr_output_layout_output_ptr->y, attr_ptr->has_position ? "explicit" : "auto"); return true; } /* ------------------------------------------------------------------------- */ /** Handles new output events: Creates @ref wlmbe_output_t and adds them. */ void _wlmbe_backend_handle_new_output( struct wl_listener *listener_ptr, void *data_ptr) { wlmbe_backend_t *backend_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_backend_t, new_output_listener); struct wlr_output *wlr_output_ptr = data_ptr; // New output: If it's using a Wayland backend, set title & App ID. if (wlr_output_is_wl(wlr_output_ptr)) { wlr_wl_output_set_app_id(wlr_output_ptr, _wlmbe_wl_app_id); wlr_wl_output_set_title(wlr_output_ptr, _wlmbe_wl_title); } // See if there is an exact match among the ephemeral output configs. If // yes, pick that configuration. Otherwise, create a new one. wlmbe_output_config_t *config_ptr = wlmbe_output_config_from_dlnode( bs_dllist_find( &backend_ptr->ephemeral_output_configs, wlmbe_output_config_equals, wlr_output_ptr)); if (NULL != config_ptr && !wlmbe_output_config_attributes(config_ptr)->enabled) { // Explicitly configured to be disabled. Skip that output. wlr_output_destroy(wlr_output_ptr); return; } if (NULL == config_ptr) { config_ptr = wlmbe_output_config_create_from_wlr(wlr_output_ptr); bs_dllist_push_front( &backend_ptr->ephemeral_output_configs, wlmbe_dlnode_from_output_config(config_ptr)); // See if we have a corresponding entry among configured outputs. If // yes, apply the attributes to our new config. wlmbe_output_config_t *outputs_config_ptr = wlmbe_output_config_from_dlnode( bs_dllist_find( &backend_ptr->output_configs, wlmbe_output_config_fnmatches, wlr_output_ptr)); if (NULL != outputs_config_ptr) { wlmbe_output_config_apply_attributes( config_ptr, wlmbe_output_config_attributes(outputs_config_ptr)); } } wlmbe_output_t *output_ptr = wlmbe_output_create( wlr_output_ptr, backend_ptr->wlr_allocator_ptr, backend_ptr->wlr_renderer_ptr, backend_ptr->wlr_scene_ptr, config_ptr, backend_ptr->width, backend_ptr->height); if (NULL != output_ptr) { if (_wlmbe_backend_add_output(backend_ptr, output_ptr)) return; wlmbe_output_destroy(output_ptr); } wlr_output_destroy(wlr_output_ptr); } /* ------------------------------------------------------------------------- */ /** Decodes an item of `Outputs`. */ bool _wlmbe_backend_decode_item( bspl_object_t *obj_ptr, size_t i, void *dest_ptr) { bs_dllist_t *dllist_ptr = dest_ptr; bspl_dict_t *dict_ptr = bspl_dict_from_object(obj_ptr); if (NULL == dict_ptr) { bs_log(BS_WARNING, "Element %zu is not a dict", i); return false; } wlmbe_output_config_t *config_ptr = wlmbe_output_config_create_from_plist(dict_ptr); if (NULL != config_ptr) { bs_dllist_push_back( dllist_ptr, wlmbe_dlnode_from_output_config(config_ptr)); return true; } return false; } /* ------------------------------------------------------------------------- */ /** Encodes the dllist item and stores it into the array at `ud_ptr`. */ bool _wlmbe_backend_encode_item( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { bspl_array_t *array_ptr = ud_ptr; wlmbe_output_config_t *c = wlmbe_output_config_from_dlnode(dlnode_ptr); bspl_dict_t *dict_ptr = wlmbe_output_config_create_into_plist(c); if (NULL == dict_ptr) return false; bool rv = bspl_array_push_back(array_ptr, bspl_object_from_dict(dict_ptr)); bspl_dict_unref(dict_ptr); return rv; } /* ------------------------------------------------------------------------- */ /** Encodes all array (items) into a plist array. */ bspl_object_t *_wlmbe_backend_encode_all( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { const bs_dllist_t *output_configs_ptr = value_ptr; bspl_array_t *array_ptr = bspl_array_create(); if (NULL != array_ptr && bs_dllist_all( output_configs_ptr, _wlmbe_backend_encode_item, array_ptr)) { return bspl_object_from_array(array_ptr); } bspl_array_unref(array_ptr); return NULL; } /* ------------------------------------------------------------------------- */ /** Frees all resources of the @ref wlmbe_output_config_t in the list. */ void _wlmbe_backend_decode_fini(void *dest_ptr) { bs_dllist_t *dllist_ptr = dest_ptr; bs_dllist_for_each(dllist_ptr, _wlmbe_backend_config_dlnode_destroy, NULL); } /* ------------------------------------------------------------------------- */ /** * Iterator callback: Destroys a @ref wlmbe_output_config_t. * * @param dlnode_ptr To @ref wlmbe_output_config_t::dlnode, and * identifies the config node to destroy. * @param ud_ptr unused. */ void _wlmbe_backend_config_dlnode_destroy( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { wlmbe_output_config_destroy(wlmbe_output_config_from_dlnode(dlnode_ptr)); } /* == Unit tests =========================================================== */ static void _wlmbe_backend_test_find(bs_test_t *test_ptr); static void _wlmbe_backend_test_encode_output_configs(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmbe_backend_test_cases[] = { { 1, "find", _wlmbe_backend_test_find }, { 1, "encode_output_configs", _wlmbe_backend_test_encode_output_configs }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmbe_backend_test_set = BS_TEST_SET( true, "backend", _wlmbe_backend_test_cases); /* ------------------------------------------------------------------------- */ /** Tests that output configurations are found as desired. */ void _wlmbe_backend_test_find(bs_test_t *test_ptr) { wlmbe_backend_t be = {}; bspl_dict_t *config_dict_ptr = bspl_dict_from_object( bspl_create_object_from_plist_string( "{Outputs = (" "{Transformation=Normal; Scale=1.0; Name=\"DP-*\"}," ")}")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, config_dict_ptr); BS_TEST_VERIFY_TRUE_OR_RETURN( test_ptr, bspl_decode_dict(config_dict_ptr, _wlmbe_output_configs_desc, &be)); bspl_dict_t *state_dict_ptr = bspl_dict_from_object( bspl_create_object_from_plist_string( "{Outputs = (" "{Transformation=Normal; Scale=2.0; Name=\"DP-0\"}," "{Transformation=Flip; Scale=3.0; Name=\"DP-1\"}," ")}")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, state_dict_ptr); BS_TEST_VERIFY_TRUE_OR_RETURN( test_ptr, bspl_decode_dict(state_dict_ptr, _wlmbe_outputs_state_desc, &be)); BS_TEST_VERIFY_EQ( test_ptr, 2, bs_dllist_size(&be.ephemeral_output_configs)); wlmbe_output_config_t *o; struct wlr_output wlr_output = {}; // These are found in the configured state. wlr_output.name = "DP-0"; o = wlmbe_output_config_from_dlnode( bs_dllist_find( &be.ephemeral_output_configs, wlmbe_output_config_equals, &wlr_output)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_EQ(test_ptr, 2.0, wlmbe_output_config_attributes(o)->scale); wlr_output.name = "DP-1"; o = wlmbe_output_config_from_dlnode( bs_dllist_find( &be.ephemeral_output_configs, wlmbe_output_config_equals, &wlr_output)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_EQ(test_ptr, 3.0, wlmbe_output_config_attributes(o)->scale); // Only has a fit through config match. Will add a state entry. wlr_output.name = "DP-2"; o = wlmbe_output_config_from_dlnode( bs_dllist_find( &be.output_configs, wlmbe_output_config_fnmatches, &wlr_output)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_EQ(test_ptr, 1.0, wlmbe_output_config_attributes(o)->scale); bspl_decoded_destroy(_wlmbe_outputs_state_desc, &be); bspl_decoded_destroy(_wlmbe_output_configs_desc, &be); bspl_dict_unref(state_dict_ptr); bspl_dict_unref(config_dict_ptr); } /* ------------------------------------------------------------------------- */ /** Tests encoding of the ephemeral output configs */ void _wlmbe_backend_test_encode_output_configs(bs_test_t *test_ptr) { wlmbe_backend_t backend = {}; struct wlr_output w = { .name = "Name0", .width = 1024, .height = 768, .scale = 2.0, .refresh = 60000, }; wlmbe_output_config_t *c1 = wlmbe_output_config_create_from_wlr(&w); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, c1); bs_dllist_push_back( &backend.ephemeral_output_configs, wlmbe_dlnode_from_output_config(c1)); w = (struct wlr_output){ .enabled = true, .name = "Name1", .width = 640, .height = 480, .scale = 1.0, .refresh = 80000 }; wlmbe_output_config_t *c2 = wlmbe_output_config_create_from_wlr(&w); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, c2); bs_dllist_push_back( &backend.ephemeral_output_configs, wlmbe_dlnode_from_output_config(c2)); bspl_dict_t *dict_ptr = bspl_encode_dict( _wlmbe_outputs_state_desc, &backend); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dict_ptr); bs_dynbuf_t dynbuf = {}; BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_init(&dynbuf, 1024, SIZE_MAX)); BS_TEST_VERIFY_TRUE( test_ptr, bspl_object_write(bspl_object_from_dict(dict_ptr), &dynbuf)); static const char *expected = "{\n" " Outputs = (\n" " {\n" " Enabled = False;\n" " Mode = \"1024x768@60.0\";\n" " Name = Name0;\n" " Scale = \"2.000000e+00\";\n" " Transformation = Normal;\n" " },\n" " {\n" " Enabled = True;\n" " Mode = \"640x480@80.0\";\n" " Name = Name1;\n" " Scale = \"1.000000e+00\";\n" " Transformation = Normal;\n" " }\n" " );\n" "}\n"; BS_TEST_VERIFY_MEMEQ(test_ptr, expected, dynbuf.data_ptr, dynbuf.length); bs_dynbuf_fini(&dynbuf); bspl_dict_unref(dict_ptr); wlmbe_output_config_destroy(c2); wlmbe_output_config_destroy(c1); } /* == End of backend.c ===================================================== */ wlmaker-0.8.1/src/backend/output.c0000644000175100017510000002730715222270710016461 0ustar runnerrunner/* ========================================================================= */ /** * @file output.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "output.h" #include #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #undef WLR_USE_UNSTABLE /* == Declarations ========================================================= */ /** Handle for a compositor output device. */ struct _wlmbe_output_t { /** List node for insertion in @ref wlmbe_backend_t::outputs. */ bs_dllist_node_t dlnode; /** Listener for `destroy` signals raised by `wlr_output`. */ struct wl_listener output_destroy_listener; /** Listener for `frame` signals raised by `wlr_output`. */ struct wl_listener output_frame_listener; /** Listener for `request_state` signals raised by `wlr_output`. */ struct wl_listener output_request_state_listener; /** Descriptive name, showing manufacturer, model and serial. */ char *description_ptr; // Below: Not owned by @ref wlmbe_output_t. /** Refers to the compositor output region, from wlroots. */ struct wlr_output *wlr_output_ptr; /** Refers to the scene graph used. */ struct wlr_scene *wlr_scene_ptr; /** This output's configuration. */ wlmbe_output_config_t *output_config_ptr; }; static void _wlmbe_output_handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmbe_output_handle_frame( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmbe_output_handle_request_state( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmbe_output_t *wlmbe_output_create( struct wlr_output *wlr_output_ptr, struct wlr_allocator *wlr_allocator_ptr, struct wlr_renderer *wlr_renderer_ptr, struct wlr_scene *wlr_scene_ptr, wlmbe_output_config_t *config_ptr, int width, int height) { wlmbe_output_t *output_ptr = logged_calloc(1, sizeof(wlmbe_output_t)); if (NULL == output_ptr) return NULL; output_ptr->wlr_output_ptr = wlr_output_ptr; output_ptr->wlr_scene_ptr = wlr_scene_ptr; output_ptr->output_config_ptr = config_ptr; output_ptr->wlr_output_ptr->data = output_ptr; output_ptr->description_ptr = bs_strdupf( "\"%s\": Manufacturer: \"%s\", Model \"%s\", Serial \"%s\"", wlr_output_ptr->name, wlr_output_ptr->make ? wlr_output_ptr->make : "Unknown", wlr_output_ptr->model ? wlr_output_ptr->model : "Unknown", wlr_output_ptr->serial ? wlr_output_ptr->serial : "Unknown"); if (NULL == output_ptr->description_ptr) { wlmbe_output_destroy(output_ptr); return NULL; } wlmtk_util_connect_listener_signal( &output_ptr->wlr_output_ptr->events.destroy, &output_ptr->output_destroy_listener, _wlmbe_output_handle_destroy); wlmtk_util_connect_listener_signal( &output_ptr->wlr_output_ptr->events.frame, &output_ptr->output_frame_listener, _wlmbe_output_handle_frame); wlmtk_util_connect_listener_signal( &output_ptr->wlr_output_ptr->events.request_state, &output_ptr->output_request_state_listener, _wlmbe_output_handle_request_state); // From tinwywl: Configures the output created by the backend to use our // allocator and our renderer. Must be done once, before commiting the // output. if (!wlr_output_init_render( output_ptr->wlr_output_ptr, wlr_allocator_ptr, wlr_renderer_ptr)) { bs_log(BS_ERROR, "Failed wlr_output_init_renderer() on %s", output_ptr->wlr_output_ptr->name); wlmbe_output_destroy(output_ptr); return NULL; } const wlmbe_output_config_attributes_t *attr_ptr = wlmbe_output_config_attributes(output_ptr->output_config_ptr); struct wlr_output_state state; wlr_output_state_init(&state); wlr_output_state_set_enabled(&state, attr_ptr->enabled); wlr_output_state_set_scale(&state, BS_MAX(1.0, attr_ptr->scale)); // Issue #97: Found that X11 and transformations do not translate // cursor coordinates well. Force it to 'Normal'. enum wl_output_transform transformation = attr_ptr->transformation; if (wlr_output_is_x11(wlr_output_ptr) && transformation != WL_OUTPUT_TRANSFORM_NORMAL) { bs_log(BS_WARNING, "X11 backend: Transformation changed to 'Normal'."); transformation = WL_OUTPUT_TRANSFORM_NORMAL; } wlr_output_state_set_transform(&state, transformation); // Set modes for backends that have them. if (attr_ptr->has_mode) { wlr_output_state_set_custom_mode( &state, attr_ptr->mode.width, attr_ptr->mode.height, attr_ptr->mode.refresh); } else { if (!wl_list_empty(&output_ptr->wlr_output_ptr->modes)) { struct wlr_output_mode *mode_ptr = wlr_output_preferred_mode( output_ptr->wlr_output_ptr); bs_log(BS_INFO, "Setting mode %dx%d @ %.2fHz", mode_ptr->width, mode_ptr->height, 1e-3 * mode_ptr->refresh); wlr_output_state_set_mode(&state, mode_ptr); } else { bs_log(BS_INFO, "No modes available on %s", output_ptr->wlr_output_ptr->name); } } if ((wlr_output_is_x11(wlr_output_ptr) || wlr_output_is_wl(wlr_output_ptr)) && 0 < width && 0 < height) { bs_log(BS_INFO, "Overriding output dimensions to %"PRIu32"x%"PRIu32, width, height); wlr_output_state_set_custom_mode( &state, width, height, 0); } if (!wlr_output_test_state(output_ptr->wlr_output_ptr, &state)) { bs_log(BS_ERROR, "Failed wlr_output_test_state() on %s", output_ptr->wlr_output_ptr->name); wlmbe_output_destroy(output_ptr); wlr_output_state_finish(&state); return NULL; } // Enable the output and commit. bool rv = wlr_output_commit_state(output_ptr->wlr_output_ptr, &state); wlr_output_state_finish(&state); if (!rv) { bs_log(BS_ERROR, "Failed wlr_output_commit_state() on %s", output_ptr->wlr_output_ptr->name); wlmbe_output_destroy(output_ptr); return NULL; } return output_ptr; } /* ------------------------------------------------------------------------- */ void wlmbe_output_destroy(wlmbe_output_t *output_ptr) { if (NULL != output_ptr->wlr_output_ptr) { bs_log(BS_INFO, "Destroy output %s", output_ptr->wlr_output_ptr->name); } _wlmbe_output_handle_destroy(&output_ptr->output_destroy_listener, NULL); if (NULL != output_ptr->description_ptr) { free(output_ptr->description_ptr); output_ptr->description_ptr = NULL; } free(output_ptr); } /* ------------------------------------------------------------------------- */ const char *wlmbe_output_description(wlmbe_output_t *output_ptr) { return output_ptr->description_ptr; } /* ------------------------------------------------------------------------- */ struct wlr_output *wlmbe_wlr_output_from_output(wlmbe_output_t *output_ptr) { return output_ptr->wlr_output_ptr; } /* ------------------------------------------------------------------------- */ const wlmbe_output_config_attributes_t *wlmbe_output_attributes( wlmbe_output_t *output_ptr) { return wlmbe_output_config_attributes(output_ptr->output_config_ptr); } /* ------------------------------------------------------------------------- */ void wlmbe_output_update_attributes( wlmbe_output_t *output_ptr, int x, int y, bool has_position) { wlmbe_output_config_update_attributes( output_ptr->output_config_ptr, output_ptr->wlr_output_ptr, x, y, has_position); } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *wlmbe_dlnode_from_output(wlmbe_output_t *output_ptr) { return &output_ptr->dlnode; } /* ------------------------------------------------------------------------- */ wlmbe_output_t *wlmbe_output_from_dlnode(bs_dllist_node_t *dlnode_ptr) { if (NULL == dlnode_ptr) return NULL; return BS_CONTAINER_OF(dlnode_ptr, wlmbe_output_t, dlnode); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Event handler for the `destroy` signal raised by `wlr_output`. * * @param listener_ptr * @param data_ptr */ void _wlmbe_output_handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmbe_output_t *output_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_t, output_destroy_listener); wlmtk_util_disconnect_listener(&output_ptr->output_request_state_listener); wlmtk_util_disconnect_listener(&output_ptr->output_frame_listener); wlmtk_util_disconnect_listener(&output_ptr->output_destroy_listener); output_ptr->wlr_output_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** * Event handler for the `frame` signal raised by `wlr_output`. * * @param listener_ptr * @param data_ptr */ void _wlmbe_output_handle_frame( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmbe_output_t *output_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_t, output_frame_listener); struct wlr_scene_output *wlr_scene_output_ptr = wlr_scene_get_scene_output( output_ptr->wlr_scene_ptr, output_ptr->wlr_output_ptr); wlr_scene_output_commit(wlr_scene_output_ptr, NULL); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(wlr_scene_output_ptr, &now); } /* ------------------------------------------------------------------------- */ /** * Event handler for the `request_state` signal raised by `wlr_output`. * * @param listener_ptr * @param data_ptr */ void _wlmbe_output_handle_request_state( struct wl_listener *listener_ptr, void *data_ptr) { wlmbe_output_t *output_ptr = BS_CONTAINER_OF( listener_ptr, wlmbe_output_t, output_request_state_listener); const struct wlr_output_event_request_state *event_ptr = data_ptr; if (wlr_output_commit_state(output_ptr->wlr_output_ptr, event_ptr->state)) { wlmbe_output_update_attributes(output_ptr, 0, 0, false); } else { bs_log(BS_WARNING, "Failed wlr_output_commit_state('%s', %p)", output_ptr->wlr_output_ptr->name, event_ptr->state); } } /* == End of output.c ====================================================== */ wlmaker-0.8.1/src/backend/CMakeLists.txt0000644000175100017510000000310115222270710017477 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) set(public_header_files backend.h output.h output_config.h output_manager.h) add_library(backend STATIC backend.c output.c output_config.c output_manager.c) target_include_directories( backend PUBLIC "${PROJECT_SOURCE_DIR}/include" PRIVATE "${WAYLAND_SERVER_INCLUDE_DIRS}" "${WLROOTS_INCLUDE_DIRS}" "${PROJECT_SOURCE_DIR}/include" ) target_include_directories( backend PRIVATE "${PROJECT_SOURCE_DIR}/include/backend" ) set_target_properties( backend PROPERTIES VERSION 1.0 PUBLIC_HEADER "${public_header_files}" ) target_compile_options( backend PRIVATE "${WAYLAND_SERVER_CFLAGS}" "${WAYLAND_SERVER_CFLAGS_OTHER}" ) target_link_libraries( backend PUBLIC libbase libbase_plist PkgConfig::WAYLAND_SERVER PRIVATE toolkit PkgConfig::WLROOTS) if(iwyu_path_and_options) set_target_properties( backend PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8.1/src/backend/output_config.c0000644000175100017510000006756115222270710020014 0ustar runnerrunner/* ========================================================================= */ /** * @file output_config.c * Copyright (c) 2025 by Philipp Kaeser */ #include "output_config.h" #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** Output configuration. */ struct _wlmbe_output_config_t { /** * List node, element of @ref wlmbe_backend_t::output_configs, or * @ref wlmbe_backend_t::ephemeral_output_configs. */ bs_dllist_node_t dlnode; /** The output's description. */ wlmbe_output_description_t description; /** The attributes. */ wlmbe_output_config_attributes_t attributes; }; static bool _wlmbe_output_position_decode( bspl_object_t *object_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); static bspl_object_t *_wlmbe_output_position_encode( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); static bool _wlmbe_output_position_decode_init(void *dest_ptr); static bool _wlmbe_output_mode_decode( bspl_object_t *object_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); static bspl_object_t *_wlmbe_output_mode_encode( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); static bool _wlmbe_output_mode_decode_init(void *dest_ptr); /* == Data ================================================================= */ /** Descriptor for output transformations. */ /** [OutputTransformation] */ static const bspl_enum_desc_t _wlmbe_output_transformation_desc[] = { BSPL_ENUM("Normal", WL_OUTPUT_TRANSFORM_NORMAL), BSPL_ENUM("Rotate90", WL_OUTPUT_TRANSFORM_90), BSPL_ENUM("Rotate180", WL_OUTPUT_TRANSFORM_180), BSPL_ENUM("Rotate270", WL_OUTPUT_TRANSFORM_270), BSPL_ENUM("Flip", WL_OUTPUT_TRANSFORM_FLIPPED), BSPL_ENUM("FlipAndRotate90", WL_OUTPUT_TRANSFORM_FLIPPED_90), BSPL_ENUM("FlipAndRotate180", WL_OUTPUT_TRANSFORM_FLIPPED_180), BSPL_ENUM("FlipAndRotate270", WL_OUTPUT_TRANSFORM_FLIPPED_270), BSPL_ENUM_SENTINEL(), }; /** [OutputTransformation] */ /** Plist descriptor for @ref wlmbe_output_description_t. */ static const bspl_desc_t _wlmbe_output_description_desc[] = { BSPL_DESC_STRING( "Name", false, wlmbe_output_description_t, name_ptr, has_name, "*"), BSPL_DESC_STRING( "Manufacturer", false, wlmbe_output_description_t, manufacturer_ptr, has_manufacturer, ""), BSPL_DESC_STRING( "Model", false, wlmbe_output_description_t, model_ptr, has_model, ""), BSPL_DESC_STRING( "Serial", false, wlmbe_output_description_t, serial_ptr, has_serial, ""), BSPL_DESC_SENTINEL() }; /** Descriptor for the output configuration. */ static const bspl_desc_t _wlmbe_output_config_desc[] = { BSPL_DESC_ENUM( "Transformation", true, wlmbe_output_config_t, attributes.transformation, attributes.transformation, WL_OUTPUT_TRANSFORM_NORMAL, _wlmbe_output_transformation_desc), BSPL_DESC_DOUBLE( "Scale", true, wlmbe_output_config_t, attributes.scale, attributes.scale, 1.0), BSPL_DESC_BOOL( "Enabled", false, wlmbe_output_config_t, attributes.enabled, attributes.enabled, true), BSPL_DESC_CUSTOM( "Position", false, wlmbe_output_config_t, attributes.position, attributes.has_position, _wlmbe_output_position_decode, _wlmbe_output_position_encode, _wlmbe_output_position_decode_init, NULL), BSPL_DESC_CUSTOM( "Mode", false, wlmbe_output_config_t, attributes.mode, attributes.has_mode, _wlmbe_output_mode_decode, _wlmbe_output_mode_encode, _wlmbe_output_mode_decode_init, NULL), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmbe_output_config_t *wlmbe_output_config_from_dlnode( bs_dllist_node_t *dlnode_ptr) { if (NULL == dlnode_ptr) return NULL; return BS_CONTAINER_OF(dlnode_ptr, wlmbe_output_config_t, dlnode); } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *wlmbe_dlnode_from_output_config( wlmbe_output_config_t *config_ptr) { return &config_ptr->dlnode; } /* ------------------------------------------------------------------------- */ const wlmbe_output_config_attributes_t *wlmbe_output_config_attributes( wlmbe_output_config_t *config_ptr) { return &config_ptr->attributes; } /* ------------------------------------------------------------------------- */ void wlmbe_output_config_update_attributes( wlmbe_output_config_t *config_ptr, struct wlr_output *wlr_output_ptr, int x, int y, bool has_position) { config_ptr->attributes.transformation = wlr_output_ptr->transform; config_ptr->attributes.scale = BS_MAX(1.0, wlr_output_ptr->scale); config_ptr->attributes.enabled = wlr_output_ptr->enabled; if (has_position) { config_ptr->attributes.position.x = x; config_ptr->attributes.position.y = y; config_ptr->attributes.has_position = has_position; } config_ptr->attributes.mode.width = wlr_output_ptr->width; config_ptr->attributes.mode.height = wlr_output_ptr->height; config_ptr->attributes.mode.refresh = wlr_output_ptr->refresh; config_ptr->attributes.has_mode = true; } /* ------------------------------------------------------------------------- */ void wlmbe_output_config_apply_attributes( wlmbe_output_config_t *config_ptr, const wlmbe_output_config_attributes_t *attributes_ptr) { config_ptr->attributes.transformation = attributes_ptr->transformation; config_ptr->attributes.scale = BS_MAX(1.0, attributes_ptr->scale); config_ptr->attributes.enabled = attributes_ptr->enabled; if (attributes_ptr->has_position) { config_ptr->attributes.position = attributes_ptr->position; config_ptr->attributes.has_position = true; } if (attributes_ptr->has_mode) { config_ptr->attributes.mode = attributes_ptr->mode; config_ptr->attributes.has_mode = true; } } /* ------------------------------------------------------------------------- */ wlmbe_output_config_t *wlmbe_output_config_create_from_wlr( struct wlr_output *wlr_output_ptr) { BS_ASSERT(NULL != wlr_output_ptr); BS_ASSERT(NULL != wlr_output_ptr->name); wlmbe_output_config_t *config_ptr = logged_calloc( 1, sizeof(wlmbe_output_config_t)); if (NULL == config_ptr) return NULL; wlmbe_output_description_t *desc_ptr = &config_ptr->description; struct strings { char **d; const char *s; } s[4] = { { .d = &desc_ptr->name_ptr, .s = wlr_output_ptr->name }, { .d = &desc_ptr->manufacturer_ptr, .s = wlr_output_ptr->make }, { .d = &desc_ptr->model_ptr, .s = wlr_output_ptr->model }, { .d = &desc_ptr->serial_ptr, .s = wlr_output_ptr->serial }, }; for (size_t i = 0; i < sizeof(s) / sizeof(struct strings); ++i) { if (NULL == s[i].s) continue; *s[i].d = logged_strdup(s[i].s); if (NULL == *s[i].d) { wlmbe_output_config_destroy(config_ptr); return NULL; } } desc_ptr->has_name = NULL != desc_ptr->name_ptr; desc_ptr->has_manufacturer = NULL != desc_ptr->manufacturer_ptr; desc_ptr->has_model = NULL != desc_ptr->model_ptr; desc_ptr->has_serial = NULL != desc_ptr->serial_ptr; config_ptr->attributes.transformation = wlr_output_ptr->transform; config_ptr->attributes.scale = wlr_output_ptr->scale; config_ptr->attributes.enabled = wlr_output_ptr->enabled; config_ptr->attributes.position.x = 0; config_ptr->attributes.position.y = 0; config_ptr->attributes.has_position = false; config_ptr->attributes.mode.width = wlr_output_ptr->width; config_ptr->attributes.mode.height = wlr_output_ptr->height; config_ptr->attributes.mode.refresh = wlr_output_ptr->refresh; config_ptr->attributes.has_mode = true; return config_ptr; } /* ------------------------------------------------------------------------- */ wlmbe_output_config_t *wlmbe_output_config_create_from_plist( bspl_dict_t *dict_ptr) { wlmbe_output_config_t *config_ptr = logged_calloc( 1, sizeof(wlmbe_output_config_t)); if (NULL != config_ptr) { if (wlmbe_output_description_init_from_plist( &config_ptr->description, dict_ptr) && bspl_decode_dict( dict_ptr, _wlmbe_output_config_desc, config_ptr)) { return config_ptr; } free(config_ptr); } return NULL; } /* ------------------------------------------------------------------------- */ bspl_dict_t *wlmbe_output_config_create_into_plist( const wlmbe_output_config_t *config_ptr) { bspl_dict_t *dict_ptr = bspl_encode_dict( _wlmbe_output_config_desc, config_ptr); if (NULL != dict_ptr) { if (bspl_encode_into_dict( _wlmbe_output_description_desc, &config_ptr->description, dict_ptr)) { return dict_ptr; } bspl_dict_unref(dict_ptr); } return NULL; } /* ------------------------------------------------------------------------- */ void wlmbe_output_config_destroy(wlmbe_output_config_t *config_ptr) { wlmbe_output_description_fini(&config_ptr->description); free(config_ptr); } /* ------------------------------------------------------------------------- */ bool wlmbe_output_config_equals( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmbe_output_config_t *config_ptr = wlmbe_output_config_from_dlnode(dlnode_ptr); struct wlr_output *wlr_output_ptr = ud_ptr; return wlmbe_output_description_equals( &config_ptr->description, wlr_output_ptr); } /* ------------------------------------------------------------------------- */ bool wlmbe_output_config_fnmatches( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmbe_output_config_t *config_ptr = wlmbe_output_config_from_dlnode(dlnode_ptr); struct wlr_output *wlr_output_ptr = ud_ptr; return wlmbe_output_description_fnmatches( &config_ptr->description, wlr_output_ptr); } /* ------------------------------------------------------------------------- */ bool wlmbe_output_description_init_from_plist( wlmbe_output_description_t *desc_ptr, bspl_dict_t *dict_ptr) { return bspl_decode_dict( dict_ptr, _wlmbe_output_description_desc, desc_ptr); } /* ------------------------------------------------------------------------- */ void wlmbe_output_description_fini(wlmbe_output_description_t *desc_ptr) { if (NULL != desc_ptr->serial_ptr) { free(desc_ptr->serial_ptr); desc_ptr->serial_ptr = NULL; } if (NULL != desc_ptr->model_ptr) { free(desc_ptr->model_ptr); desc_ptr->model_ptr = NULL; } if (NULL != desc_ptr->manufacturer_ptr) { free(desc_ptr->manufacturer_ptr); desc_ptr->manufacturer_ptr = NULL; } if (NULL != desc_ptr->name_ptr) { free(desc_ptr->name_ptr); desc_ptr->name_ptr = NULL; } } /* ------------------------------------------------------------------------- */ bool wlmbe_output_description_equals( wlmbe_output_description_t *desc_ptr, struct wlr_output *wlr_output_ptr) { // Maps the presence indicator and fields to match. struct fields { bool present; char *cfg_val; char *o_val; } f[4] = { { .present = desc_ptr->has_name, .cfg_val = desc_ptr->name_ptr, .o_val = wlr_output_ptr->name, }, { .present = desc_ptr->has_manufacturer, .cfg_val = desc_ptr->manufacturer_ptr, .o_val = wlr_output_ptr->make, }, { .present = desc_ptr->has_model, .cfg_val = desc_ptr->model_ptr, .o_val = wlr_output_ptr->model, }, { .present = desc_ptr->has_serial, .cfg_val = desc_ptr->serial_ptr, .o_val = wlr_output_ptr->serial, }, }; // Presence of the field must match in both config and output. bool any_field_equals = false; for (size_t i = 0; i < sizeof(f) / sizeof(struct fields); ++i) { if (f[i].present != (NULL != f[i].o_val)) return false; if (!f[i].present) continue; if (0 != strcmp(f[i].cfg_val, f[i].o_val)) return false; any_field_equals = true; } return any_field_equals; } /* ------------------------------------------------------------------------- */ bool wlmbe_output_description_fnmatches( wlmbe_output_description_t *desc_ptr, struct wlr_output *wlr_output_ptr) { // Maps the presence indicator and fields to match. struct fields { bool present; char *cfg_val; char *o_val; } f[4] = { { .present = desc_ptr->has_name, .cfg_val = desc_ptr->name_ptr, .o_val = wlr_output_ptr->name, }, { .present = desc_ptr->has_manufacturer, .cfg_val = desc_ptr->manufacturer_ptr, .o_val = wlr_output_ptr->make, }, { .present = desc_ptr->has_model, .cfg_val = desc_ptr->model_ptr, .o_val = wlr_output_ptr->model, }, { .present = desc_ptr->has_serial, .cfg_val = desc_ptr->serial_ptr, .o_val = wlr_output_ptr->serial, }, }; for (size_t i = 0; i < sizeof(f) / sizeof(struct fields); ++i) { if (!f[i].present) continue; if (NULL == f[i].cfg_val || NULL == f[i].o_val) return false; if (0 != fnmatch(f[i].cfg_val, f[i].o_val, 0)) return false; } return true; } /* ------------------------------------------------------------------------- */ struct wlr_output *wlmbe_output_description_first_fnmatch( wlmbe_output_description_t *desc_ptr, struct wlr_output_layout *wlr_output_layout_ptr) { struct wl_list *link_ptr; for (link_ptr = wlr_output_layout_ptr->outputs.next; link_ptr != &wlr_output_layout_ptr->outputs; link_ptr = link_ptr->next) { struct wlr_output_layout_output *wlr_output_layout_output_ptr = BS_CONTAINER_OF(link_ptr, struct wlr_output_layout_output, link); if (wlmbe_output_description_fnmatches( desc_ptr, wlr_output_layout_output_ptr->output)) { return wlr_output_layout_output_ptr->output; } } return NULL; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Decodes a plist "x,y" string into @ref wlmbe_output_config_position_t. */ bool _wlmbe_output_position_decode( bspl_object_t *object_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value(bspl_string_from_object(object_ptr)); if (NULL == s) return false; // Extracts the first arg into buf. Large enough for a INT32_MIN. char buf[12]; size_t i = 0; for (; s[i] != '\0' && s[i] != ',' && i < sizeof(buf) - 1; ++i) { buf[i] = s[i]; } buf[i] = '\0'; if (s[i] == ',') ++i; int64_t x, y; if (!bs_strconvert_int64(buf, &x, 10) || !bs_strconvert_int64(s + i, &y, 10)) { bs_log(BS_WARNING, "Failed to decode position \"%s\"", s); return false; } if (x < INT32_MIN || x > INT32_MAX || y < INT32_MIN || y > INT32_MAX) { bs_log(BS_WARNING, "Position out of range for \"%s\"", s); return false; } wlmbe_output_config_position_t *pos_ptr = value_ptr; *pos_ptr = (wlmbe_output_config_position_t){ .x = x, .y = y }; return true; } /* ------------------------------------------------------------------------- */ /** Encodes the output position as "x,y" into a plist string object. */ bspl_object_t *_wlmbe_output_position_encode( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { const wlmbe_output_config_position_t *pos_ptr = value_ptr; char buf[24]; // enough for "-2147483648,-2147483648". int rv = snprintf(buf, sizeof(buf), "%d,%d", pos_ptr->x, pos_ptr->y); if (rv < 0 || (size_t)rv >= sizeof(buf)) return NULL; return bspl_object_from_string(bspl_string_create(buf)); } /* ------------------------------------------------------------------------- */ /** Initializes @ref wlmbe_output_config_position_t at `dest_ptr`. */ bool _wlmbe_output_position_decode_init(void *dest_ptr) { wlmbe_output_config_position_t *pos_ptr = dest_ptr; *pos_ptr = (wlmbe_output_config_position_t){}; return true; } /* ------------------------------------------------------------------------- */ /** Decodes a plist "WxH@R" string into @ref wlmbe_output_config_mode_t. */ bool _wlmbe_output_mode_decode( bspl_object_t *object_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value(bspl_string_from_object(object_ptr)); const char *full_s = s; if (NULL == s) return false; // Extracts the first arg into buf. Large enough for a INT32_MIN. char width[12], height[12]; size_t i = 0; for (i = 0; s[i] != '\0' && s[i] != 'x' && i < sizeof(width) - 1; ++i) { width[i] = s[i]; } width[i] = '\0'; s += i; if (*s == 'x') ++s; for (i = 0; s[i] != '\0' && s[i] != '@' && i < sizeof(height) - 1; ++i) { height[i] = s[i]; } height[i] = '\0'; if (s[i] == '@') { s = s + i + 1; } else if (s[i] == '\0') { s = "0"; } else { bs_log(BS_WARNING, "Failed to decode mode \"%s\"", full_s); return false; } int64_t w, h; double r; if (!bs_strconvert_int64(width, &w, 10) || !bs_strconvert_int64(height, &h, 10) || !bs_strconvert_double(s, &r)) { bs_log(BS_WARNING, "Failed to decode mode \"%s\"", full_s); return false; } if (w < INT32_MIN || w > INT32_MAX || h < INT32_MIN || h > INT32_MAX || r < 0 || r > INT32_MAX / 1000) { bs_log(BS_WARNING, "Mode values out of range for \"%s\"", full_s); return false; } wlmbe_output_config_mode_t *mode_ptr = value_ptr; *mode_ptr = (wlmbe_output_config_mode_t){ .width = w, .height = h, .refresh = r * 1000 // Hz -> mHz. }; return true; } /* ------------------------------------------------------------------------- */ /** Decodes the mode as "WxH@R" into a plist string. */ bspl_object_t *_wlmbe_output_mode_encode( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { const wlmbe_output_config_mode_t *mode_ptr = value_ptr; char buf[37]; // Enough for INT32_MIN in all fields. int rv = -1; if (0 != mode_ptr->refresh) { rv = snprintf(buf, sizeof(buf), "%dx%d@%d.%d", mode_ptr->width, mode_ptr->height, mode_ptr->refresh / 1000, abs(mode_ptr->refresh % 1000)); } else { rv = snprintf(buf, sizeof(buf), "%dx%d", mode_ptr->width, mode_ptr->height); } if (0 > rv || (size_t)rv >= sizeof(buf)) return NULL; return bspl_object_from_string(bspl_string_create(buf)); } /* ------------------------------------------------------------------------- */ /** Initializes @ref wlmbe_output_config_mode_t at `dest_ptr`. */ bool _wlmbe_output_mode_decode_init(void *dest_ptr) { wlmbe_output_config_mode_t *mode_ptr = dest_ptr; *mode_ptr = (wlmbe_output_config_mode_t){}; return true; } /* == Unit tests =========================================================== */ static void _wlmbe_output_test_config_parse(bs_test_t *test_ptr); static void _wlmbe_output_test_decode_position(bs_test_t *test_ptr); static void _wlmbe_output_test_encode_position(bs_test_t *test_ptr); static void _wlmbe_output_test_decode_mode(bs_test_t *test_ptr); static void _wlmbe_output_test_encode_mode(bs_test_t *test_ptr); static void _wlmbe_output_test_first_fnmatch(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmbe_output_config_test_cases[] = { { 1, "config_parse", _wlmbe_output_test_config_parse }, { 1, "decode_position", _wlmbe_output_test_decode_position }, { 1, "encode_position", _wlmbe_output_test_encode_position }, { 1, "decode_mode", _wlmbe_output_test_decode_mode }, { 1, "encode_mode", _wlmbe_output_test_encode_mode }, { 1, "first_fnmatch", _wlmbe_output_test_first_fnmatch }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmbe_output_config_test_set = BS_TEST_SET( true, "output_config", _wlmbe_output_config_test_cases); /* ------------------------------------------------------------------------- */ /** Verifies parsing. */ void _wlmbe_output_test_config_parse(bs_test_t *test_ptr) { bspl_dict_t *dict_ptr = bspl_dict_from_object( bspl_create_object_from_plist_string( "{Transformation=Flip;Scale=1;Name=X11}")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dict_ptr); wlmbe_output_config_t *c = wlmbe_output_config_create_from_plist(dict_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, c); BS_TEST_VERIFY_STREQ(test_ptr, "X11", c->description.name_ptr); BS_TEST_VERIFY_EQ( test_ptr, WL_OUTPUT_TRANSFORM_FLIPPED, c->attributes.transformation); BS_TEST_VERIFY_EQ(test_ptr, 1.0, c->attributes.scale); wlmbe_output_config_destroy(c); bspl_dict_unref(dict_ptr); } /* ------------------------------------------------------------------------- */ /** Tests decoding of a position field. */ void _wlmbe_output_test_decode_position(bs_test_t *test_ptr) { wlmbe_output_config_position_t p; _wlmbe_output_position_decode_init(&p); BS_TEST_VERIFY_EQ(test_ptr, 0, p.x); BS_TEST_VERIFY_EQ(test_ptr, 0, p.y); bspl_object_t *o = bspl_object_from_string(bspl_string_create("1,2")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_TRUE(test_ptr, _wlmbe_output_position_decode(o, NULL, &p)); BS_TEST_VERIFY_EQ(test_ptr, 1, p.x); BS_TEST_VERIFY_EQ(test_ptr, 2, p.y); bspl_object_unref(o); o = bspl_object_from_string(bspl_string_create("2147483647,-2147483648")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_TRUE(test_ptr, _wlmbe_output_position_decode(o, NULL, &p)); BS_TEST_VERIFY_EQ(test_ptr, INT32_MAX, p.x); BS_TEST_VERIFY_EQ(test_ptr, INT32_MIN, p.y); bspl_object_unref(o); o = bspl_object_from_string(bspl_string_create("2147483648,-2147483649")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_FALSE(test_ptr, _wlmbe_output_position_decode(o, NULL, &p)); bspl_object_unref(o); } /* ------------------------------------------------------------------------- */ /** Tests encoding of the position field. */ void _wlmbe_output_test_encode_position(bs_test_t *test_ptr) { bspl_string_t *s; wlmbe_output_config_position_t p = { .x = INT32_MIN, .y = INT32_MIN }; s = bspl_string_from_object(_wlmbe_output_position_encode(NULL, &p)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, s); BS_TEST_VERIFY_STREQ( test_ptr, "-2147483648,-2147483648", bspl_string_value(s)); bspl_string_unref(s); p = (wlmbe_output_config_position_t ){ .x = INT32_MAX, .y = INT32_MAX }; s = bspl_string_from_object(_wlmbe_output_position_encode(NULL, &p)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, s); BS_TEST_VERIFY_STREQ( test_ptr, "2147483647,2147483647", bspl_string_value(s)); bspl_string_unref(s); } /* ------------------------------------------------------------------------- */ /** Tests decoding of a position field. */ void _wlmbe_output_test_decode_mode(bs_test_t *test_ptr) { wlmbe_output_config_mode_t m; _wlmbe_output_mode_decode_init(&m); BS_TEST_VERIFY_EQ(test_ptr, 0, m.width); BS_TEST_VERIFY_EQ(test_ptr, 0, m.height); BS_TEST_VERIFY_EQ(test_ptr, 0, m.refresh); bspl_object_t *o = bspl_object_from_string(bspl_string_create("1x2@3.4")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_TRUE(test_ptr, _wlmbe_output_mode_decode(o, NULL, &m)); BS_TEST_VERIFY_EQ(test_ptr, 1, m.width); BS_TEST_VERIFY_EQ(test_ptr, 2, m.height); BS_TEST_VERIFY_EQ(test_ptr, 3400, m.refresh); bspl_object_unref(o); o = bspl_object_from_string( bspl_string_create("2147483647x-2147483648@2147483")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_TRUE(test_ptr, _wlmbe_output_mode_decode(o, NULL, &m)); BS_TEST_VERIFY_EQ(test_ptr, INT32_MAX, m.width); BS_TEST_VERIFY_EQ(test_ptr, INT32_MIN, m.height); BS_TEST_VERIFY_EQ(test_ptr, 2147483000, m.refresh); bspl_object_unref(o); o = bspl_object_from_string( bspl_string_create("2147483648x-2147483649@2147483648")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_FALSE(test_ptr, _wlmbe_output_mode_decode(o, NULL, &m)); bspl_object_unref(o); o = bspl_object_from_string(bspl_string_create("3x4")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_TRUE(test_ptr, _wlmbe_output_mode_decode(o, NULL, &m)); BS_TEST_VERIFY_EQ(test_ptr, 3, m.width); BS_TEST_VERIFY_EQ(test_ptr, 4, m.height); BS_TEST_VERIFY_EQ(test_ptr, 0, m.refresh); bspl_object_unref(o); } /* ------------------------------------------------------------------------- */ /** Tests encoding the mode field. */ void _wlmbe_output_test_encode_mode(bs_test_t *test_ptr) { bspl_string_t *s; wlmbe_output_config_mode_t m = { .width = INT32_MIN, .height = INT32_MIN, .refresh = INT32_MIN }; s = bspl_string_from_object(_wlmbe_output_mode_encode(NULL, &m)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, s); BS_TEST_VERIFY_STREQ( test_ptr, "-2147483648x-2147483648@-2147483.648", bspl_string_value(s)); bspl_string_unref(s); m = (wlmbe_output_config_mode_t){.width = INT32_MAX, .height = INT32_MAX}; s = bspl_string_from_object(_wlmbe_output_mode_encode(NULL, &m)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, s); BS_TEST_VERIFY_STREQ( test_ptr, "2147483647x2147483647", bspl_string_value(s)); bspl_string_unref(s); } /* ------------------------------------------------------------------------- */ /** Tests @ref wlmbe_output_description_first_fnmatch. */ void _wlmbe_output_test_first_fnmatch(bs_test_t *test_ptr) { struct wl_display *display = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display); struct wlr_output_layout *wol_ptr = wlr_output_layout_create(display); struct wlr_output o1 = { .name = "Name1" }; wlmtk_test_wlr_output_init(&o1); wlr_output_layout_add_auto(wol_ptr, &o1); struct wlr_output o2 = { .name = "Other1" }; wlmtk_test_wlr_output_init(&o2); wlr_output_layout_add_auto(wol_ptr, &o2); struct wlr_output o3 = { .name = "Other2" }; wlmtk_test_wlr_output_init(&o3); wlr_output_layout_add_auto(wol_ptr, &o3); wlmbe_output_description_t d = {}; BS_TEST_VERIFY_EQ( test_ptr, &o1, wlmbe_output_description_first_fnmatch(&d, wol_ptr)); d = (wlmbe_output_description_t){ .name_ptr = "Oth*", .has_name = true }; BS_TEST_VERIFY_EQ( test_ptr, &o2, wlmbe_output_description_first_fnmatch(&d, wol_ptr)); d.name_ptr = "Name1"; BS_TEST_VERIFY_EQ( test_ptr, &o1, wlmbe_output_description_first_fnmatch(&d, wol_ptr)); d.name_ptr = "*2"; BS_TEST_VERIFY_EQ( test_ptr, &o3, wlmbe_output_description_first_fnmatch(&d, wol_ptr)); wl_display_destroy(display); } /* == End of output_config.c =============================================== */ wlmaker-0.8.1/src/xwl_surface.c0000644000175100017510000010531415222270710016047 0ustar runnerrunner/* ========================================================================= */ /** * @file xwl_surface.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #if defined(WLMAKER_HAVE_XWAYLAND) #include "xwl_surface.h" #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #undef WLR_USE_UNSTABLE #include "input/cursor.h" #include "input/manager.h" #include "server.h" #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** State of the XWayland window surface. */ struct _wlmaker_xwl_surface_t { /** Holds @ref wlmaker_xwl_surface_t::surface_ptr and child surfaces. */ wlmtk_base_t base; /** Corresponding wlroots XWayland surface. */ struct wlr_xwayland_surface *wlr_xwayland_surface_ptr; /** Back-link to server. */ wlmaker_server_t *server_ptr; /** Back-link to the XWayland server. */ wlmaker_xwl_t *xwl_ptr; /** Listener for the `destroy` signal of `wlr_xwayland_surface`. */ struct wl_listener destroy_listener; /** Listener for `request_configure` signal of `wlr_xwayland_surface`. */ struct wl_listener request_configure_listener; /** Listener for the `associate` signal of `wlr_xwayland_surface`. */ struct wl_listener associate_listener; /** Listener for the `dissociate` signal of `wlr_xwayland_surface`. */ struct wl_listener dissociate_listener; /** Listener for the `set_title` signal of `wlr_xwayland_surface`. */ struct wl_listener set_title_listener; /** Listener for the `set_parent` signal of `wlr_xwayland_surface`. */ struct wl_listener set_parent_listener; /** Listener for the `set_decorations` signal of `wlr_xwayland_surface`. */ struct wl_listener set_decorations_listener; /** Listener for the `set_geometry` signal of `wlr_xwayland_surface`. */ struct wl_listener set_geometry_listener; /** Listener for the `map` signal of `wlr_xwayland_surface`. */ struct wl_listener surface_map_listener; /** Listener for the `unmap` signal of `wlr_xwayland_surface`. */ struct wl_listener surface_unmap_listener; /** Listener for the `commit` signal. */ struct wl_listener surface_commit_listener; /** Listener for @ref wlmtk_window_events_t::request_close. */ struct wl_listener window_request_close_listener; /** Listener for @ref wlmtk_window_events_t::set_activated. */ struct wl_listener window_set_activated_listener; /** Listener for @ref wlmtk_window_events_t::request_size. */ struct wl_listener window_request_size_listener; /** Listener for @ref wlmtk_window_events_t::request_fullscreen. */ struct wl_listener window_request_fullscreen_listener; /** Listener for @ref wlmtk_window_events_t::request_maximized. */ struct wl_listener window_request_maximized_listener; /** The toolkit surface. Only available once 'associated'. */ wlmtk_surface_t *surface_ptr; /** The toolkit window, in case the surface does not have a parent. */ wlmtk_window_t *window_ptr; /** Or, the parent surface. In that case, window_ptr is NULL. */ wlmaker_xwl_surface_t *parent_surface_ptr; /** The XWL surface's title. May be set before window is created. */ char *title_ptr; }; static void _xwl_surface_handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_request_configure( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xwl_surface_base_element_destroy( wlmtk_element_t *element_ptr); static void _xwl_surface_handle_associate( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_dissociate( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_set_title( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_set_parent( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_set_decorations( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_set_geometry( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_surface_map( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_surface_unmap( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xwl_surface_handle_window_request_close( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xwl_surface_handle_window_set_activated( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xwl_surface_handle_window_request_size( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xwl_surface_handle_window_request_fullscreen( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xwl_surface_handle_window_request_maximized( struct wl_listener *listener_ptr, void *data_ptr); static void _xwl_surface_apply_decorations( wlmaker_xwl_surface_t *xwl_surface_ptr); static void _xwl_surface_adjust_absolute_pos( wlmaker_xwl_surface_t *surface_ptr, int *x_ptr, int *y_ptr); /* == Data ================================================================= */ /** Virtual method table of the base's @ref wlmtk_element_t. */ static const wlmtk_element_vmt_t _wlmaker_xwl_surface_base_element_vmt = { .destroy = _wlmaker_xwl_surface_base_element_destroy }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_xwl_surface_t *wlmaker_xwl_surface_create( struct wlr_xwayland_surface *wlr_xwayland_surface_ptr, wlmaker_xwl_t *xwl_ptr, wlmaker_server_t *server_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = logged_calloc( 1, sizeof(wlmaker_xwl_surface_t)); if (NULL == xwl_surface_ptr) return NULL; xwl_surface_ptr->wlr_xwayland_surface_ptr = wlr_xwayland_surface_ptr; wlr_xwayland_surface_ptr->data = xwl_surface_ptr; xwl_surface_ptr->xwl_ptr = xwl_ptr; xwl_surface_ptr->server_ptr = server_ptr; if (!wlmtk_base_init(&xwl_surface_ptr->base, NULL)) { wlmaker_xwl_surface_destroy(xwl_surface_ptr); return NULL; } wlmtk_element_extend( wlmtk_base_element(&xwl_surface_ptr->base), &_wlmaker_xwl_surface_base_element_vmt); wlmtk_util_connect_listener_signal( &wlr_xwayland_surface_ptr->events.destroy, &xwl_surface_ptr->destroy_listener, _xwl_surface_handle_destroy); wlmtk_util_connect_listener_signal( &wlr_xwayland_surface_ptr->events.request_configure, &xwl_surface_ptr->request_configure_listener, _xwl_surface_handle_request_configure); wlmtk_util_connect_listener_signal( &wlr_xwayland_surface_ptr->events.associate, &xwl_surface_ptr->associate_listener, _xwl_surface_handle_associate); wlmtk_util_connect_listener_signal( &wlr_xwayland_surface_ptr->events.dissociate, &xwl_surface_ptr->dissociate_listener, _xwl_surface_handle_dissociate); wlmtk_util_connect_listener_signal( &wlr_xwayland_surface_ptr->events.set_title, &xwl_surface_ptr->set_title_listener, _xwl_surface_handle_set_title); wlmtk_util_connect_listener_signal( &wlr_xwayland_surface_ptr->events.set_parent, &xwl_surface_ptr->set_parent_listener, _xwl_surface_handle_set_parent); wlmtk_util_connect_listener_signal( &wlr_xwayland_surface_ptr->events.set_decorations, &xwl_surface_ptr->set_decorations_listener, _xwl_surface_handle_set_decorations); wlmtk_util_connect_listener_signal( &wlr_xwayland_surface_ptr->events.set_geometry, &xwl_surface_ptr->set_geometry_listener, _xwl_surface_handle_set_geometry); bs_log(BS_INFO, "Created XWL surface %p for wlr_xwayland_surface %p", xwl_surface_ptr, wlr_xwayland_surface_ptr); return xwl_surface_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_xwl_surface_destroy(wlmaker_xwl_surface_t *xwl_surface_ptr) { bs_log(BS_INFO, "Destroy XWL surface %p", xwl_surface_ptr); _xwl_surface_handle_dissociate(&xwl_surface_ptr->dissociate_listener, 0); wlmtk_util_disconnect_listener(&xwl_surface_ptr->set_geometry_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->set_decorations_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->set_parent_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->set_title_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->dissociate_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->associate_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->request_configure_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->destroy_listener); if (NULL != xwl_surface_ptr->title_ptr) { free(xwl_surface_ptr->title_ptr); xwl_surface_ptr->title_ptr = NULL; } wlmtk_base_fini(&xwl_surface_ptr->base); xwl_surface_ptr->surface_ptr = NULL; free(xwl_surface_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` event of `struct wlr_xwayland_surface`. * * @param listener_ptr * @param data_ptr */ void _xwl_surface_handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, destroy_listener); wlmaker_xwl_surface_destroy(xwl_surface_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `request_configure` event of `struct wlr_xwayland_surface`. * * @param listener_ptr * @param data_ptr */ void _xwl_surface_handle_request_configure( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, request_configure_listener); struct wlr_xwayland_surface_configure_event *cfg_event_ptr = data_ptr; bs_log(BS_INFO, "Request configure for %p: " "%"PRId16",%"PRId16" size %"PRIu16" x %"PRIu16" mask 0x%"PRIx16, xwl_surface_ptr, cfg_event_ptr->x, cfg_event_ptr->y, cfg_event_ptr->width, cfg_event_ptr->height, cfg_event_ptr->mask); // FIXME: // -> if we have surface: check what that means, with respect to // the surface::commit handler. // It appears this needs to be ACKed with a surface_configure. wlr_xwayland_surface_configure( xwl_surface_ptr->wlr_xwayland_surface_ptr, cfg_event_ptr->x, cfg_event_ptr->y, cfg_event_ptr->width, cfg_event_ptr->height); } /* ------------------------------------------------------------------------- */ /** * Dtor for @ref wlmaker_xwl_surface_t::base. * * This may be called when the parent window is destroyed, but the child(ren) * have not yet. It implies that this window already had been removed from the * parent surface's base. To prevent double cleanup, we can set * @ref wlmaker_xwl_surface_t::parent_surface_ptr to NULL. * * @param element_ptr */ void _wlmaker_xwl_surface_base_element_destroy( wlmtk_element_t *element_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_xwl_surface_t, base.super_container.super_element); xwl_surface_ptr->parent_surface_ptr = NULL; wlmaker_xwl_surface_destroy(xwl_surface_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `associate` event of `struct wlr_xwayland_surface`. * * The `associate` event is triggered once an X11 window becomes associated * with the surface. Understanding this is a moment the surface can be mapped. * * @param listener_ptr * @param data_ptr */ void _xwl_surface_handle_associate( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, associate_listener); wlmaker_xwl_surface_t *parent_xwl_surface_ptr = NULL; if (NULL != xwl_surface_ptr->wlr_xwayland_surface_ptr->parent) { parent_xwl_surface_ptr = xwl_surface_ptr->wlr_xwayland_surface_ptr->parent->data; } for (size_t i = 0; i < xwl_surface_ptr->wlr_xwayland_surface_ptr->window_type_len; ++i) { const char *name_ptr = xwl_atom_name( xwl_surface_ptr->xwl_ptr, xwl_surface_ptr->wlr_xwayland_surface_ptr->window_type[i]); if (NULL != name_ptr) { bs_log(BS_INFO, " XWL surface %p has window type %s", xwl_surface_ptr, name_ptr); } } BS_ASSERT(NULL == xwl_surface_ptr->surface_ptr); xwl_surface_ptr->surface_ptr = wlmtk_surface_create( xwl_surface_ptr->wlr_xwayland_surface_ptr->surface, xwl_surface_ptr->server_ptr->wlr_seat_ptr); if (NULL == xwl_surface_ptr->surface_ptr) { // TODO(kaeser@gubbe.ch): Relay error to client, instead of crash. bs_log(BS_FATAL, "Failed wlmtk_surface_create."); return; } wlmtk_surface_connect_map_listener_signal( xwl_surface_ptr->surface_ptr, &xwl_surface_ptr->surface_map_listener, _xwl_surface_handle_surface_map); wlmtk_surface_connect_unmap_listener_signal( xwl_surface_ptr->surface_ptr, &xwl_surface_ptr->surface_unmap_listener, _xwl_surface_handle_surface_unmap); wlmtk_util_connect_listener_signal( &xwl_surface_ptr->wlr_xwayland_surface_ptr->surface->events.commit, &xwl_surface_ptr->surface_commit_listener, _xwl_surface_handle_surface_commit); _xwl_surface_handle_surface_commit( &xwl_surface_ptr->surface_commit_listener, NULL); wlmtk_base_set_content_element( &xwl_surface_ptr->base, wlmtk_surface_element(xwl_surface_ptr->surface_ptr)); // Currently we treat parent-less windows AND modal windows as toplevel. // Modal windows should actually be child wlmtk_window_t, but that isn't // supported yet. if (NULL == xwl_surface_ptr->wlr_xwayland_surface_ptr->parent || xwl_surface_ptr->wlr_xwayland_surface_ptr->modal) { BS_ASSERT(NULL == xwl_surface_ptr->window_ptr); xwl_surface_ptr->window_ptr = wlmtk_window_create( wlmtk_base_element(&xwl_surface_ptr->base), wlmtk_window_style_to_ref( xwl_surface_ptr->server_ptr->style_ptr->window_style_ptr), wlmtk_menu_style_to_ref( xwl_surface_ptr->server_ptr->style_ptr->menu_style_ptr)); if (NULL == xwl_surface_ptr->window_ptr) { // TODO(kaeser@gubbe.ch): Relay error to client, instead of crash. bs_log(BS_FATAL, "Failed wlmtk_window_create."); return; } _xwl_surface_apply_decorations(xwl_surface_ptr); wlmtk_window_set_options( xwl_surface_ptr->window_ptr, wlmim_cursor_options(xwl_surface_ptr->server_ptr->input_manager_ptr )->move_window_modifier); wlmtk_window_set_properties( xwl_surface_ptr->window_ptr, WLMTK_WINDOW_PROPERTY_RESIZABLE | WLMTK_WINDOW_PROPERTY_ICONIFIABLE | WLMTK_WINDOW_PROPERTY_CLOSABLE); wlmtk_util_client_t client = { .pid = xwl_surface_ptr->wlr_xwayland_surface_ptr->pid }; wlmtk_window_set_client(xwl_surface_ptr->window_ptr, &client); wlmtk_window_set_title(xwl_surface_ptr->window_ptr, xwl_surface_ptr->title_ptr); wlmtk_util_connect_listener_signal( &wlmtk_window_events(xwl_surface_ptr->window_ptr)->request_close, &xwl_surface_ptr->window_request_close_listener, _wlmaker_xwl_surface_handle_window_request_close); wlmtk_util_connect_listener_signal( &wlmtk_window_events(xwl_surface_ptr->window_ptr)->set_activated, &xwl_surface_ptr->window_set_activated_listener, _wlmaker_xwl_surface_handle_window_set_activated); wlmtk_util_connect_listener_signal( &wlmtk_window_events(xwl_surface_ptr->window_ptr)->request_size, &xwl_surface_ptr->window_request_size_listener, _wlmaker_xwl_surface_handle_window_request_size); wlmtk_util_connect_listener_signal( &wlmtk_window_events(xwl_surface_ptr->window_ptr)->request_fullscreen, &xwl_surface_ptr->window_request_fullscreen_listener, _wlmaker_xwl_surface_handle_window_request_fullscreen); wlmtk_util_connect_listener_signal( &wlmtk_window_events(xwl_surface_ptr->window_ptr)->request_maximized, &xwl_surface_ptr->window_request_maximized_listener, _wlmaker_xwl_surface_handle_window_request_maximized); wl_signal_emit( &xwl_surface_ptr->server_ptr->window_created_event, xwl_surface_ptr->window_ptr); } bs_log(BS_INFO, "Associated XWL surface %p with wlr_surface %p, parent %p at %d, %d", xwl_surface_ptr, xwl_surface_ptr->wlr_xwayland_surface_ptr->surface, parent_xwl_surface_ptr, xwl_surface_ptr->wlr_xwayland_surface_ptr->x, xwl_surface_ptr->wlr_xwayland_surface_ptr->y); } /* ------------------------------------------------------------------------- */ /** * Handler for the `dissociate` event of `struct wlr_xwayland_surface`. * * @param listener_ptr * @param data_ptr */ void _xwl_surface_handle_dissociate( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, dissociate_listener); bs_log(BS_INFO, "Dissociate XWL surface %p from wlr_surface %p", xwl_surface_ptr, xwl_surface_ptr->wlr_xwayland_surface_ptr->surface); if (NULL != xwl_surface_ptr->window_ptr) { wlmtk_util_disconnect_listener( &xwl_surface_ptr->window_request_close_listener); wlmtk_util_disconnect_listener( &xwl_surface_ptr->window_set_activated_listener); wlmtk_util_disconnect_listener( &xwl_surface_ptr->window_request_size_listener); wlmtk_util_disconnect_listener( &xwl_surface_ptr->window_request_fullscreen_listener); wlmtk_util_disconnect_listener( &xwl_surface_ptr->window_request_maximized_listener); wl_signal_emit( &xwl_surface_ptr->server_ptr->window_destroyed_event, xwl_surface_ptr->window_ptr); wlmtk_window_destroy(xwl_surface_ptr->window_ptr); xwl_surface_ptr->window_ptr = NULL; } if (NULL != xwl_surface_ptr->parent_surface_ptr) { wlmtk_base_pop_element( &xwl_surface_ptr->parent_surface_ptr->base, wlmtk_base_element(&xwl_surface_ptr->base)); xwl_surface_ptr->parent_surface_ptr = NULL; } wlmtk_util_disconnect_listener(&xwl_surface_ptr->surface_commit_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->surface_map_listener); wlmtk_util_disconnect_listener(&xwl_surface_ptr->surface_unmap_listener); wlmtk_base_set_content_element(&xwl_surface_ptr->base, NULL); xwl_surface_ptr->surface_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** * Handler for the `set_title` event of `struct wlr_xwayland_surface`. * * @param listener_ptr * @param data_ptr */ void _xwl_surface_handle_set_title( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xs_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, set_title_listener); if (NULL != xs_ptr->title_ptr) { free(xs_ptr->title_ptr); xs_ptr->title_ptr = NULL; } if (NULL != xs_ptr->wlr_xwayland_surface_ptr->title) { xs_ptr->title_ptr = logged_strdup(xs_ptr->wlr_xwayland_surface_ptr->title); if (NULL == xs_ptr->title_ptr) return; } if (NULL != xs_ptr->window_ptr) { wlmtk_window_set_title(xs_ptr->window_ptr, xs_ptr->title_ptr); } } /* ------------------------------------------------------------------------- */ /** * Handler for the `set_parent` event of `struct wlr_xwayland_surface`. * * @param listener_ptr * @param data_ptr */ void _xwl_surface_handle_set_parent( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, set_parent_listener); wlmaker_xwl_surface_t *parent_xwl_surface_ptr = NULL; if (NULL != xwl_surface_ptr->wlr_xwayland_surface_ptr->parent) { parent_xwl_surface_ptr = xwl_surface_ptr->wlr_xwayland_surface_ptr->parent->data; } if (xwl_surface_ptr->parent_surface_ptr == parent_xwl_surface_ptr) return; if (NULL != xwl_surface_ptr->parent_surface_ptr) { wlmtk_base_pop_element( &xwl_surface_ptr->parent_surface_ptr->base, wlmtk_base_element(&xwl_surface_ptr->base)); xwl_surface_ptr->parent_surface_ptr = NULL; } if (NULL == parent_xwl_surface_ptr) return; // TODO(kaeser@gubbe.ch): We're currently treating modal windows as // toplevel windows. They're not popups, for sure. To support this, // we'll need wlmtk_window_t to support child wlmtk_window_t. if (xwl_surface_ptr->wlr_xwayland_surface_ptr->modal) return; wlmtk_base_push_element( &parent_xwl_surface_ptr->base, wlmtk_base_element(&xwl_surface_ptr->base)); xwl_surface_ptr->parent_surface_ptr = parent_xwl_surface_ptr; } /* ------------------------------------------------------------------------- */ /** * Handler for the `set_decorations` event of `struct wlr_xwayland_surface`. * * Applies server-side decoration, if the X11 window is supposed to have * decorations. * * @param listener_ptr * @param data_ptr */ void _xwl_surface_handle_set_decorations( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, set_decorations_listener); _xwl_surface_apply_decorations(xwl_surface_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `set_geometry` event of `struct wlr_xwayland_surface`. * * Called from wlroots/xwayland/xwm.c, whenever the geometry (position or * dimensions) of the window (precisely: the xwayland_surface) changes. * * @param listener_ptr * @param data_ptr */ void _xwl_surface_handle_set_geometry( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, set_geometry_listener); // For XWayland, the surface's position is given relative to the "root" // of the specified surface. For @ref wlmtk_element_t, the position is // just relative to the parent @ref wlmtk_container_t. So we need to // subtract each parent surface's position. int x = xwl_surface_ptr->wlr_xwayland_surface_ptr->x; int y = xwl_surface_ptr->wlr_xwayland_surface_ptr->y; _xwl_surface_adjust_absolute_pos(xwl_surface_ptr, &x, &y); wlmtk_element_set_position( wlmtk_base_element(&xwl_surface_ptr->base), x, y); } /* ------------------------------------------------------------------------- */ /** Handles when the surface is mapped: Map it to the workspace. */ void _xwl_surface_handle_surface_map( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, surface_map_listener); if (NULL == xwl_surface_ptr->window_ptr) return; wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace( xwl_surface_ptr->server_ptr->root_ptr); wlmtk_workspace_map_window(workspace_ptr, xwl_surface_ptr->window_ptr); } /* ------------------------------------------------------------------------- */ /** Unmaps the window */ void _xwl_surface_handle_surface_unmap( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, surface_unmap_listener); if (NULL == xwl_surface_ptr->window_ptr) return; wlmtk_workspace_unmap_window( wlmtk_window_get_workspace(xwl_surface_ptr->window_ptr), xwl_surface_ptr->window_ptr); } /* ------------------------------------------------------------------------- */ /** Surface committed. Update size. */ void _xwl_surface_handle_surface_commit( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, surface_commit_listener); if (NULL == xwl_surface_ptr->window_ptr) return; wlmtk_window_commit_size( xwl_surface_ptr->window_ptr, xwl_surface_ptr->wlr_xwayland_surface_ptr->surface->current.width, xwl_surface_ptr->wlr_xwayland_surface_ptr->surface->current.height); } /* ------------------------------------------------------------------------- */ /** Close button got clicked, forward to the XWL surface. */ void _wlmaker_xwl_surface_handle_window_request_close( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, window_request_close_listener); wlr_xwayland_surface_close(xwl_surface_ptr->wlr_xwayland_surface_ptr); } /* ------------------------------------------------------------------------- */ /** Surface became activated. Do that. */ void _wlmaker_xwl_surface_handle_window_set_activated( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, window_set_activated_listener); wlr_xwayland_surface_activate( xwl_surface_ptr->wlr_xwayland_surface_ptr, wlmtk_window_is_activated(xwl_surface_ptr->window_ptr)); wlmtk_surface_set_activated( xwl_surface_ptr->surface_ptr, wlmtk_window_is_activated(xwl_surface_ptr->window_ptr)); } /* ------------------------------------------------------------------------- */ /** A new size was requested. Forward to the XWL surface. */ void _wlmaker_xwl_surface_handle_window_request_size( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, window_request_size_listener); const struct wlr_box *box_ptr = data_ptr; wlr_xwayland_surface_configure( xwl_surface_ptr->wlr_xwayland_surface_ptr, 0, 0, box_ptr->width, box_ptr->height); } /* ------------------------------------------------------------------------- */ /** The window is requested to go fullscreen. Forward and commit that. */ void _wlmaker_xwl_surface_handle_window_request_fullscreen( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, window_request_fullscreen_listener); bool *fullscreen_ptr = data_ptr; wlr_xwayland_surface_set_fullscreen( xwl_surface_ptr->wlr_xwayland_surface_ptr, *fullscreen_ptr); wlmtk_window_commit_fullscreen( xwl_surface_ptr->window_ptr, *fullscreen_ptr); // TODO(kaeser@gubbe.ch): In windowed mode, there appears something off // with XWL drawing fullscreen surfaces. See to report to wlroots. } /* ------------------------------------------------------------------------- */ /** The window is requested to go maximized. Forward and commit that. */ void _wlmaker_xwl_surface_handle_window_request_maximized( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xwl_surface_t *xwl_surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xwl_surface_t, window_request_maximized_listener); bool *maximized_ptr = data_ptr; #if WLR_VERSION_NUM >= (19 << 8) // wlroots-0.19 has a madimized_horz, maximized_vert argument. wlr_xwayland_surface_set_maximized( xwl_surface_ptr->wlr_xwayland_surface_ptr, *maximized_ptr, *maximized_ptr); #else wlr_xwayland_surface_set_maximized( xwl_surface_ptr->wlr_xwayland_surface_ptr, *maximized_ptr); #endif wlmtk_window_commit_maximized( xwl_surface_ptr->window_ptr, *maximized_ptr); } /* ------------------------------------------------------------------------- */ /** * Sets whether this window should be server-side-decorated. * * @param xwl_surface_ptr */ void _xwl_surface_apply_decorations(wlmaker_xwl_surface_t *xwl_surface_ptr) { static const xwl_atom_identifier_t borderless_window_types[] = { NET_WM_WINDOW_TYPE_TOOLTIP, XWL_MAX_ATOM_ID}; if (NULL == xwl_surface_ptr->window_ptr) return; // TODO(kaeser@gubbe.ch): Adapt whether NO_BORDER or NO_TITLE was set. if (xwl_surface_ptr->wlr_xwayland_surface_ptr->decorations == WLR_XWAYLAND_SURFACE_DECORATIONS_ALL && !xwl_is_window_type( xwl_surface_ptr->xwl_ptr, xwl_surface_ptr->wlr_xwayland_surface_ptr, borderless_window_types)) { wlmtk_window_set_server_side_decorated( xwl_surface_ptr->window_ptr, true); } else { wlmtk_window_set_server_side_decorated( xwl_surface_ptr->window_ptr, false); } } /* ------------------------------------------------------------------------- */ /** * Adjusts the absolute position by subtracting each parent's position. * * @param surface_ptr * @param x_ptr * @param y_ptr */ void _xwl_surface_adjust_absolute_pos( wlmaker_xwl_surface_t *surface_ptr, int *x_ptr, int *y_ptr) { if (NULL == surface_ptr || NULL == surface_ptr->parent_surface_ptr) return; wlmtk_element_t *element_ptr = wlmtk_base_element(&surface_ptr->base); *x_ptr = *x_ptr - element_ptr->x; *y_ptr = *y_ptr - element_ptr->y; _xwl_surface_adjust_absolute_pos( surface_ptr->parent_surface_ptr, x_ptr, y_ptr); } /* == Unit Tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); static void test_nested(bs_test_t *test_ptr); static void fake_init_wlr_xwayland_surface( struct wlr_xwayland_surface* wlr_xwayland_surface_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_xwl_surface_test_cases[] = { { true, "create_destroy", test_create_destroy }, { true, "nested", test_nested }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_xwl_surface_test_set = BS_TEST_SET( true, "xwl_surface", wlmaker_xwl_surface_test_cases); /* ------------------------------------------------------------------------- */ /** Tests setup and teardown. */ void test_create_destroy(bs_test_t *test_ptr) { wlmaker_server_t server = {}; struct wlr_xwayland_surface wlr_xwayland_surface; fake_init_wlr_xwayland_surface(&wlr_xwayland_surface); wlmaker_xwl_surface_t *xwl_surface_ptr = wlmaker_xwl_surface_create( &wlr_xwayland_surface, NULL, &server); BS_TEST_VERIFY_NEQ(test_ptr, NULL, xwl_surface_ptr); wlmaker_xwl_surface_destroy(xwl_surface_ptr); } /* ------------------------------------------------------------------------- */ /** Tests nesting of XWayland surfaces, ie. parenting. */ void test_nested(bs_test_t *test_ptr) { wlmaker_server_t server = {}; // FIXME: Make this test something. struct wlr_xwayland_surface surface0; fake_init_wlr_xwayland_surface(&surface0); wlmaker_xwl_surface_t *surface0_ptr = wlmaker_xwl_surface_create( &surface0, NULL, &server); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, surface0_ptr); struct wlr_xwayland_surface surface1; fake_init_wlr_xwayland_surface(&surface1); wlmaker_xwl_surface_t *surface1_ptr = wlmaker_xwl_surface_create( &surface1, NULL, &server); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, surface1_ptr); struct wlr_xwayland_surface surface2; fake_init_wlr_xwayland_surface(&surface2); wlmaker_xwl_surface_t *surface2_ptr = wlmaker_xwl_surface_create( &surface2, NULL, &server); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, surface2_ptr); surface2.parent = &surface1; wl_signal_emit_mutable(&surface2.events.set_parent, NULL); surface2.x = 120; surface2.y = 12; wl_signal_emit_mutable(&surface2.events.set_geometry, NULL); wlmaker_xwl_surface_destroy(surface2_ptr); wlmaker_xwl_surface_destroy(surface1_ptr); wlmaker_xwl_surface_destroy(surface0_ptr); } /* ------------------------------------------------------------------------- */ /** Fake-initializes the `wlr_xwayland_surface_ptr`. */ void fake_init_wlr_xwayland_surface( struct wlr_xwayland_surface* wlr_xwayland_surface_ptr) { *wlr_xwayland_surface_ptr = (struct wlr_xwayland_surface){}; wl_signal_init(&wlr_xwayland_surface_ptr->events.destroy); wl_signal_init(&wlr_xwayland_surface_ptr->events.request_configure); wl_signal_init(&wlr_xwayland_surface_ptr->events.associate); wl_signal_init(&wlr_xwayland_surface_ptr->events.dissociate); wl_signal_init(&wlr_xwayland_surface_ptr->events.set_title); wl_signal_init(&wlr_xwayland_surface_ptr->events.set_parent); wl_signal_init(&wlr_xwayland_surface_ptr->events.set_decorations); wl_signal_init(&wlr_xwayland_surface_ptr->events.set_geometry); } #endif // defined(WLMAKER_HAVE_XWAYLAND) /* == End of xwl_surface.c ================================================= */ wlmaker-0.8.1/src/layer_panel.c0000644000175100017510000005061515222270710016023 0ustar runnerrunner/* ========================================================================= */ /** * @file layer_panel.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "layer_panel.h" #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #undef WLR_USE_UNSTABLE #include "toolkit/toolkit.h" #include "wlr-layer-shell-unstable-v1-protocol.h" #include "xdg_popup.h" #include "server.h" /* == Declarations ========================================================= */ /** State of a layer panel. */ struct _wlmaker_layer_panel_t { /** We're deriving this from a @ref wlmtk_panel_t as superclass. */ wlmtk_panel_t super_panel; /** Links to the wlroots layer surface for this panel. */ struct wlr_layer_surface_v1 *wlr_layer_surface_v1_ptr; /** Back-link to @ref wlmaker_server_t. */ wlmaker_server_t *server_ptr; /** The wrapped surface, will be the principal element of the panel. */ wlmtk_surface_t *wlmtk_surface_ptr; /** Listener for the `map` signal raised by `wlmtk_surface_t`. */ struct wl_listener surface_map_listener; /** Listener for the `unmap` signal raised by `wlmtk_surface_t`. */ struct wl_listener surface_unmap_listener; /** Listener for the `commit` signal raised by `wlr_surface`. */ struct wl_listener surface_commit_listener; /** Listener for the `destroy` signal raised by `wlr_layer_surface_v1`. */ struct wl_listener destroy_listener; /** Listener for `new_popup` signal raised by `wlr_layer_surface_v1`. */ struct wl_listener new_popup_listener; }; wlmaker_layer_panel_t *_wlmaker_layer_panel_create_from_surface( struct wlr_layer_surface_v1 *wlr_layer_surface_v1_ptr, wlmaker_server_t *server_ptr, wlmtk_surface_t *wlmtk_surface_ptr); static void _wlmaker_layer_panel_destroy( wlmaker_layer_panel_t *layer_panel_ptr); static void _wlmaker_layer_panel_element_destroy( wlmtk_element_t *element_ptr); static bool _wlmaker_layer_panel_apply_keyboard( wlmaker_layer_panel_t *layer_panel_ptr, enum zwlr_layer_surface_v1_keyboard_interactivity interactivity, enum zwlr_layer_shell_v1_layer zwlr_layer); static bool _wlmaker_layer_panel_apply_layer( wlmaker_layer_panel_t *layer_panel_ptr, enum zwlr_layer_shell_v1_layer zwlr_layer); static uint32_t _wlmaker_layer_panel_request_size( wlmtk_panel_t *panel_ptr, int width, int height); static void _wlmaker_layer_panel_handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_layer_panel_handle_surface_map( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_layer_panel_handle_surface_unmap( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_layer_panel_handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_layer_panel_handle_new_popup( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Virtual method table for the layer panel. */ static const wlmtk_panel_vmt_t _wlmaker_layer_panel_vmt = { .request_size = _wlmaker_layer_panel_request_size, }; /** Virtual method table for the layer panel's superclass element. */ static const wlmtk_element_vmt_t _wlmaker_layer_panel_element_vmt = { .destroy = _wlmaker_layer_panel_element_destroy, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_layer_panel_t *wlmaker_layer_panel_create( struct wlr_layer_surface_v1 *wlr_layer_surface_v1_ptr, wlmaker_server_t *server_ptr) { wlmtk_surface_t *surface_ptr = wlmtk_surface_create( wlr_layer_surface_v1_ptr->surface, server_ptr->wlr_seat_ptr); if (NULL == surface_ptr) return NULL; wlmaker_layer_panel_t *panel_ptr = _wlmaker_layer_panel_create_from_surface( wlr_layer_surface_v1_ptr, server_ptr, surface_ptr); if (NULL == panel_ptr) { wlmtk_surface_destroy(surface_ptr); return NULL; } return panel_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Constructor for the layer panel, with already-created surface. * * @param wlr_layer_surface_v1_ptr * @param server_ptr * @param wlmtk_surface_ptr * * @return The handler for the layer surface or NULL on error. */ wlmaker_layer_panel_t *_wlmaker_layer_panel_create_from_surface( struct wlr_layer_surface_v1 *wlr_layer_surface_v1_ptr, wlmaker_server_t *server_ptr, wlmtk_surface_t *wlmtk_surface_ptr) { wlmaker_layer_panel_t *layer_panel_ptr = logged_calloc( 1, sizeof(wlmaker_layer_panel_t)); if (NULL == layer_panel_ptr) return NULL; layer_panel_ptr->wlr_layer_surface_v1_ptr = wlr_layer_surface_v1_ptr; layer_panel_ptr->server_ptr = server_ptr; layer_panel_ptr->wlmtk_surface_ptr = wlmtk_surface_ptr; wlmtk_panel_positioning_t pos = {}; if (!wlmtk_panel_init(&layer_panel_ptr->super_panel, &pos)) { _wlmaker_layer_panel_destroy(layer_panel_ptr); return NULL; } wlmtk_panel_extend( &layer_panel_ptr->super_panel, &_wlmaker_layer_panel_vmt); wlmtk_element_extend( wlmtk_panel_element(&layer_panel_ptr->super_panel), &_wlmaker_layer_panel_element_vmt); wlmtk_container_add_element_atop( &layer_panel_ptr->super_panel.super_container, NULL, wlmtk_surface_element(layer_panel_ptr->wlmtk_surface_ptr)); wlmtk_element_set_visible( wlmtk_surface_element(layer_panel_ptr->wlmtk_surface_ptr), true); wlmtk_surface_connect_map_listener_signal( layer_panel_ptr->wlmtk_surface_ptr, &layer_panel_ptr->surface_map_listener, _wlmaker_layer_panel_handle_surface_map); wlmtk_surface_connect_unmap_listener_signal( layer_panel_ptr->wlmtk_surface_ptr, &layer_panel_ptr->surface_unmap_listener, _wlmaker_layer_panel_handle_surface_unmap); wlmtk_util_connect_listener_signal( &wlr_layer_surface_v1_ptr->surface->events.commit, &layer_panel_ptr->surface_commit_listener, _wlmaker_layer_panel_handle_surface_commit); wlmtk_util_connect_listener_signal( &wlr_layer_surface_v1_ptr->events.destroy, &layer_panel_ptr->destroy_listener, _wlmaker_layer_panel_handle_destroy); wlmtk_util_connect_listener_signal( &wlr_layer_surface_v1_ptr->events.new_popup, &layer_panel_ptr->new_popup_listener, _wlmaker_layer_panel_handle_new_popup); bs_log(BS_INFO, "Created layer panel %p with wlmtk surface %p", layer_panel_ptr, layer_panel_ptr->wlmtk_surface_ptr); return layer_panel_ptr; } /* ------------------------------------------------------------------------- */ /** * Destroys the layer panel and frees up all associated resources. * * @param layer_panel_ptr */ void _wlmaker_layer_panel_destroy(wlmaker_layer_panel_t *layer_panel_ptr) { bs_log(BS_INFO, "Destroying layer panel %p with wlmtk surface %p", layer_panel_ptr, layer_panel_ptr->wlmtk_surface_ptr); wlmtk_layer_t *layer_ptr = wlmtk_panel_get_layer( &layer_panel_ptr->super_panel); if (NULL != layer_ptr) { wlmtk_layer_remove_panel(layer_ptr, &layer_panel_ptr->super_panel); } wlmtk_util_disconnect_listener(&layer_panel_ptr->new_popup_listener); wlmtk_util_disconnect_listener(&layer_panel_ptr->destroy_listener); wlmtk_util_disconnect_listener(&layer_panel_ptr->surface_commit_listener); wlmtk_util_disconnect_listener(&layer_panel_ptr->surface_unmap_listener); wlmtk_util_disconnect_listener(&layer_panel_ptr->surface_map_listener); if (NULL != layer_panel_ptr->wlmtk_surface_ptr) { wlmtk_container_remove_element( &layer_panel_ptr->super_panel.super_container, wlmtk_surface_element(layer_panel_ptr->wlmtk_surface_ptr)); wlmtk_surface_destroy(layer_panel_ptr->wlmtk_surface_ptr); layer_panel_ptr->wlmtk_surface_ptr = NULL; } wlmtk_panel_fini(&layer_panel_ptr->super_panel); if (layer_panel_ptr->wlr_layer_surface_v1_ptr) { wlr_layer_surface_v1_destroy(layer_panel_ptr->wlr_layer_surface_v1_ptr); layer_panel_ptr->wlr_layer_surface_v1_ptr = NULL; } free(layer_panel_ptr); } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::destroy, forwards to * @ref _wlmaker_layer_panel_destroy. * * @param element_ptr */ void _wlmaker_layer_panel_element_destroy( wlmtk_element_t *element_ptr) { wlmaker_layer_panel_t *layer_panel_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_layer_panel_t, super_panel.super_container.super_element); _wlmaker_layer_panel_destroy(layer_panel_ptr); } /* ------------------------------------------------------------------------- */ /** @return Layer number, translated from protocol value. -1 on error. */ wlmtk_workspace_layer_t _wlmaker_layer_from_zwlr_layer( enum zwlr_layer_shell_v1_layer zwlr_layer) { wlmtk_workspace_layer_t layer; switch (zwlr_layer) { case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: layer = WLMTK_WORKSPACE_LAYER_BACKGROUND; break; case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: layer = WLMTK_WORKSPACE_LAYER_BOTTOM; break; case ZWLR_LAYER_SHELL_V1_LAYER_TOP: layer = WLMTK_WORKSPACE_LAYER_TOP; break; case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: layer = WLMTK_WORKSPACE_LAYER_OVERLAY; break; default: layer = -1; break; } return layer; } /* ------------------------------------------------------------------------- */ /** * Applies the requested keyboard setting. * * Supports 'NONE' and 'EXCLUSIVE' interactivity, but the latter only on * top and overlay layers. * * TODO(kaeser@gubbe.ch): Implement full support, once layer elements have a * means to organically obtain and release keyboard focus (eg. through pointer * button clicks). * * @param layer_panel_ptr * @param interactivity * @param zwlr_layer * * @return true on success. */ bool _wlmaker_layer_panel_apply_keyboard( wlmaker_layer_panel_t *layer_panel_ptr, enum zwlr_layer_surface_v1_keyboard_interactivity interactivity, enum zwlr_layer_shell_v1_layer zwlr_layer) { switch (interactivity) { case ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE: break; case ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE: if (ZWLR_LAYER_SHELL_V1_LAYER_TOP != zwlr_layer && ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY != zwlr_layer) { wl_resource_post_error( layer_panel_ptr->wlr_layer_surface_v1_ptr->resource, WL_DISPLAY_ERROR_IMPLEMENTATION, "Exclusive interactivity unsupported on layer %d", zwlr_layer); return false; } wlmtk_surface_set_activated(layer_panel_ptr->wlmtk_surface_ptr, true); break; case ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND: default: wl_resource_post_error( layer_panel_ptr->wlr_layer_surface_v1_ptr->resource, WL_DISPLAY_ERROR_IMPLEMENTATION, "Unsupported setting for keyboard interactivity: %d", interactivity); return false; } return true; } /* ------------------------------------------------------------------------- */ /** Updates the layer this panel is part of. Posts an error if invalid. */ bool _wlmaker_layer_panel_apply_layer( wlmaker_layer_panel_t *layer_panel_ptr, enum zwlr_layer_shell_v1_layer zwlr_layer) { wlmtk_workspace_layer_t layer; switch (zwlr_layer) { case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND: layer = WLMTK_WORKSPACE_LAYER_BACKGROUND; break; case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM: layer = WLMTK_WORKSPACE_LAYER_BOTTOM; break; case ZWLR_LAYER_SHELL_V1_LAYER_TOP: layer = WLMTK_WORKSPACE_LAYER_TOP; break; case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY: layer = WLMTK_WORKSPACE_LAYER_OVERLAY; break; default: wl_resource_post_error( layer_panel_ptr->wlr_layer_surface_v1_ptr->resource, ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER, "Invalid value for for zwlr_layer value: %d", layer_panel_ptr->wlr_layer_surface_v1_ptr->pending.layer); return false; } wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(layer_panel_ptr->server_ptr->root_ptr); wlmtk_layer_t *layer_ptr = wlmtk_workspace_get_layer(workspace_ptr, layer); wlmtk_layer_t *current_layer_ptr = wlmtk_panel_get_layer( &layer_panel_ptr->super_panel); if (layer_ptr == current_layer_ptr) return true; if (NULL != current_layer_ptr) { wlmtk_layer_remove_panel( current_layer_ptr, &layer_panel_ptr->super_panel); } if (NULL != layer_ptr) { wlmtk_layer_add_panel( layer_ptr, &layer_panel_ptr->super_panel, layer_panel_ptr->wlr_layer_surface_v1_ptr->output); } return true; } /* ------------------------------------------------------------------------- */ /** Implements wlmtk_panel_vmt_t::request_size. */ uint32_t _wlmaker_layer_panel_request_size( wlmtk_panel_t *panel_ptr, int width, int height) { wlmaker_layer_panel_t *layer_panel_ptr = BS_CONTAINER_OF( panel_ptr, wlmaker_layer_panel_t, super_panel); return wlr_layer_surface_v1_configure( layer_panel_ptr->wlr_layer_surface_v1_ptr, width, height); } /* ------------------------------------------------------------------------- */ /** * Handler for the `commit` signal of `wlr_surface`. * * Updates positioning and layer of the panel, as required. * * @param listener_ptr * @param data_ptr */ void _wlmaker_layer_panel_handle_surface_commit( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_layer_panel_t *layer_panel_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_layer_panel_t, surface_commit_listener); struct wlr_layer_surface_v1_state *state_ptr = &layer_panel_ptr->wlr_layer_surface_v1_ptr->pending; wlmtk_panel_positioning_t pos = { .anchor = state_ptr->anchor, .desired_width = state_ptr->desired_width, .desired_height = state_ptr->desired_height, .margin_left = state_ptr->margin.left, .margin_top = state_ptr->margin.top, .margin_right = state_ptr->margin.right, .margin_bottom = state_ptr->margin.bottom, .exclusive_zone = state_ptr->exclusive_zone }; // Sanity check position and anchor values. if ((0 == pos.desired_width && 0 == (pos.anchor & (WLR_EDGE_LEFT | WLR_EDGE_RIGHT))) || (0 == pos.desired_height && 0 == (pos.anchor & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)))) { wl_resource_post_error( layer_panel_ptr->wlr_layer_surface_v1_ptr->resource, ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE, "Invalid size %d x %d for anchor 0x%"PRIx32, pos.desired_width, pos.desired_height, pos.anchor); } wlmtk_panel_commit( &layer_panel_ptr->super_panel, state_ptr->configure_serial, &pos); // Updates keyboard and layer values. Ignore failures here. _wlmaker_layer_panel_apply_layer( layer_panel_ptr, state_ptr->layer); _wlmaker_layer_panel_apply_keyboard( layer_panel_ptr, state_ptr->keyboard_interactive, state_ptr->layer); } /* ------------------------------------------------------------------------- */ /** * Handler for the `map` signal of `wlmtk_surface_t`: Maps the panel to layer. * * @param listener_ptr * @param data_ptr */ void _wlmaker_layer_panel_handle_surface_map( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_layer_panel_t *layer_panel_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_layer_panel_t, surface_map_listener); wlmtk_element_set_visible( wlmtk_panel_element(&layer_panel_ptr->super_panel), true); } /* ------------------------------------------------------------------------- */ /** * Handler for the `unmap` signal of `wlmtk_surface_t`: Unmaps the panel. * * @param listener_ptr * @param data_ptr */ void _wlmaker_layer_panel_handle_surface_unmap( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_layer_panel_t *layer_panel_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_layer_panel_t, surface_unmap_listener); wlmtk_element_set_visible( wlmtk_panel_element(&layer_panel_ptr->super_panel), false); } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal of the `wlr_layer_surface_v1`: Destroys * the panel. * * @param listener_ptr * @param data_ptr */ void _wlmaker_layer_panel_handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_layer_panel_t *layer_panel_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_layer_panel_t, destroy_listener); layer_panel_ptr->wlr_layer_surface_v1_ptr = NULL; _wlmaker_layer_panel_destroy(layer_panel_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `new_popup` signal of the `wlr_layer_surface_v1`: Creates * a new popup for this panel. * * @param listener_ptr * @param data_ptr Points to the new `struct wlr_xdg_popup`. */ void _wlmaker_layer_panel_handle_new_popup( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_layer_panel_t *layer_panel_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_layer_panel_t, new_popup_listener); struct wlr_xdg_popup *wlr_xdg_popup_ptr = data_ptr; wlmaker_xdg_popup_t *popup_ptr = wlmaker_xdg_popup_create( wlr_xdg_popup_ptr, layer_panel_ptr->server_ptr->wlr_seat_ptr); if (NULL == popup_ptr) { wl_resource_post_error( wlr_xdg_popup_ptr->resource, WL_DISPLAY_ERROR_NO_MEMORY, "Failed wlmtk_xdg_popup2_create."); return; } wlmtk_container_add_element( &layer_panel_ptr->super_panel.popup_container, wlmaker_xdg_popup_element(popup_ptr)); } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_layer_panel_test_cases[] = { { false, "create_destroy", test_create_destroy }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_layer_panel_test_set = BS_TEST_SET( true, "layer_panel", wlmaker_layer_panel_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises creation and teardown. */ void test_create_destroy(bs_test_t *test_ptr) { struct wlr_layer_surface_v1 wlr_layer_surface_v1 = {}; wlmaker_server_t server = {}; wlmtk_surface_t *surface_ptr = wlmtk_surface_create(NULL, NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, surface_ptr); wl_signal_init(&wlr_layer_surface_v1.events.destroy); wl_signal_init(&wlr_layer_surface_v1.events.new_popup); wlmaker_layer_panel_t *layer_panel_ptr = _wlmaker_layer_panel_create_from_surface( &wlr_layer_surface_v1, &server, surface_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, layer_panel_ptr); wl_signal_emit(&wlr_layer_surface_v1.events.destroy, NULL); } /* == End of layer_panel.c ================================================== */ wlmaker-0.8.1/src/config.h0000644000175100017510000001314415222270710014776 0ustar runnerrunner/* ========================================================================= */ /** * @file config.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the iLicense. */ #ifndef __CONFIG_H__ #define __CONFIG_H__ #include #include #include #include #include #include "files.h" #include "input/cursor.h" #include "task_list.h" // IWYU pragma: keep #include "toolkit/toolkit.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Preference for decoration. */ typedef enum { /** Mode NONE will be set to CLIENT; but other modes left unchanged. */ WLMAKER_CONFIG_DECORATION_SUGGEST_CLIENT, /** Mode NONE will be set to SERVER; but other modes left unchanged. */ WLMAKER_CONFIG_DECORATION_SUGGEST_SERVER, /** Will set all windows to CLIENT, no matter what they requested. */ WLMAKER_CONFIG_DECORATION_ENFORCE_CLIENT, /** Will set all windows to SERVER, no matter what they requested. */ WLMAKER_CONFIG_DECORATION_ENFORCE_SERVER } wlmaker_config_decoration_t; /** Style of the clip's configurables. */ typedef struct { /** Font to use. */ wlmtk_style_font_t font; /** Text color for tasks listed. */ uint32_t text_color; } wlmaker_config_clip_style_t; /** Style information. */ typedef struct { /** Background color, unless overriden in "Workspace" state. */ uint32_t background_color; /** The tile. */ struct wlmtk_tile_style tile; /** Dock optics: Margin. */ struct wlmtk_dock_style dock; /** Window style. */ struct wlmtk_window_style *window_style_ptr; /** Presence flag for window style. */ bool has_window_style; /** Menu style. */ struct wlmtk_menu_style *menu_style_ptr; /** Presence flag for menu style. */ bool has_menu_style; /** Clip style. */ wlmaker_config_clip_style_t clip; /** Task list style. */ struct wlmaker_task_list_style task_list; /** Cursor style. */ struct wlmim_cursor_style cursor; } wlmaker_config_style_t; /** * Loads a plist object from the given config file or data. * * Useful to load configuration files from the provided name in `fname_ptr` or * an in-memory buffer, as a compiled-in fallback option. * * @param files_ptr * @param name_ptr Name to use when logging about the plist. * @param arg_fname_ptr Explicit filename to use for loading the file, * eg. from the commandline. Or NULL. * @param xdg_config_fname_ptr File name relative to XDG config home. See * @ref wlmaker_files_xdg_config_find. * @param default_data_ptr Points to in-memory plist data, or NULL. Will be * used if fname_ptr was NULL. * @param default_data_size The size of the in-memory plist data. * * @returns a bspl_object_t on success, or NULL if none of the options had * data, or if there was a file or parsing error. */ bspl_object_t *wlmaker_config_object_load( wlmaker_files_t *files_ptr, const char *name_ptr, const char *arg_fname_ptr, const char *xdg_config_fname_ptr, const uint8_t *default_data_ptr, size_t default_data_size); /** * Loads the configuration for wlmaker. * * @param files_ptr * @param fname_ptr Optional: Name of the file to load it from. May * be NULL. * * @return A dict object, or NULL on error. Errors will already be logged. * The caller must free the associated resources by calling * bspl_object_unref(). */ bspl_dict_t *wlmaker_config_load( wlmaker_files_t *files_ptr, const char *fname_ptr); /** * Loads the state for wlmaker. * * Behaviour is similar to @ref wlmaker_config_load. * * @param files_ptr * @param fname_ptr * * @return A dict object or NULL on error. */ bspl_dict_t *wlmaker_state_load( wlmaker_files_t *files_ptr, const char *fname_ptr); /** * Loads the theme file for wlmaker, stores into `style_ptr`. * * @param files_ptr * @param fname_ptr File to load from. If NULL, will load the defualt * style. Otherwise will try to locate the file * relative to XDG Data directories. * @param style_ptr Points to the style. If loading and decoding * succeeds, resources at `style_ptr` will be freed. * `style_ptr` is untouched on failure. * * @return true on success. */ bool wlmaker_theme_load( wlmaker_files_t *files_ptr, const char *fname_ptr, wlmaker_config_style_t *style_ptr); /** Releases resources associated with the style. */ void wlmaker_config_style_release(wlmaker_config_style_t *style_ptr); extern const bspl_desc_t wlmaker_config_style_desc[]; /** Unit test set. */ extern const bs_test_set_t wlmaker_config_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __CONFIG_H__ */ /* == End of config.h ====================================================== */ wlmaker-0.8.1/src/x11_cursor.xpm0000644000175100017510000000343115222270710016112 0ustar runnerrunner/* XPM */ static char * x11_cursor_xpm[] = { "20 20 85 1", " c None", ". c #000000", "+ c #FFFFFF", "@ c #A2A2A2", "# c #DDDDDD", "$ c #969696", "% c #2C2C2C", "& c #E1E1E1", "* c #8E8E8E", "= c #0E0E0E", "- c #343434", "; c #E3E3E3", "> c #8C8C8C", ", c #090909", "' c #151515", ") c #414141", "! c #E8E8E8", "~ c #7F7F7F", "{ c #030303", "] c #101010", "^ c #1C1C1C", "/ c #4A4A4A", "( c #EAEAEA", "_ c #7D7D7D", ": c #0B0B0B", "< c #171717", "[ c #242424", "} c #565656", "| c #ECECEC", "1 c #727272", "2 c #050505", "3 c #121212", "4 c #1E1E1E", "5 c #2B2B2B", "6 c #626262", "7 c #EEEEEE", "8 c #666666", "9 c #0C0C0C", "0 c #191919", "a c #262626", "b c #323232", "c c #6B6B6B", "d c #646464", "e c #070707", "f c #141414", "g c #202020", "h c #2D2D2D", "i c #393939", "j c #777777", "k c #585858", "l c #020202", "m c #1B1B1B", "n c #272727", "o c #404040", "p c #7C7C7C", "q c #535353", "r c #DEDEDE", "s c #EFEFEF", "t c #4E4E4E", "u c #1F1F1F", "v c #6A6A6A", "w c #C2C2C2", "x c #222222", "y c #E0E0E0", "z c #434343", "A c #B1B1B1", "B c #828282", "C c #878787", "D c #B7B7B7", "E c #E5E5E5", "F c #767676", "G c #1A1A1A", "H c #FCFCFC", "I c #737373", "J c #BCBCBC", "K c #808080", "L c #B4B4B4", "M c #757575", "N c #818181", "O c #4F4F4F", "P c #EBEBEB", "Q c #BEBEBE", "R c #444444", "S c #131313", "T c #BDBDBD", "T ", "+@ ", "+#$ ", "+%&* ", "+=-;> ", "+,')!~ ", "+{]^/(_ ", "+.:<[}|1 ", "+.2345678 ", "+..90abc7d ", "+..efghij7k ", "+..l=mn-op7q ", "+...,0r++++st ", "+..uv]jw ", "+.xy7m4!z ", "+a;~AB3CD ", "+EF )|Gn7b ", "HI JKfLM ", "N OPQ!n ", " RkS "}; wlmaker-0.8.1/src/toolkit/0000755000175100017510000000000015222270710015042 5ustar runnerrunnerwlmaker-0.8.1/src/toolkit/toolkit.md0000644000175100017510000004431615222270710017061 0ustar runnerrunner # Toolkit {#toolkit_page} ## Compositor Elements ### Class Hierarchy * Where do we use composition, vs. inheritance? ```plantuml class Element { int x, y struct wlr_scene_node *node_ptr Container *parent_container_ptr bool init(handlers) bool init_attached(handlers, struct wlr_scene_tree *) void fini() -void set_parent_container(Container*) -void attach_to_scene_graph() void set_visible(bool) bool is_visible() void set_position(int, int) void get_position(int*, int*) void get_size(int*, int*) {abstract}#void destroy() {abstract}#struct wlr_scene_node *create_scene_node(parent_node*) #void pointer_motion(double, double) #void pointer_button(wlmtk_button_event_t) #void pointer_leave() } note right of Element::"set_parent_container(Container*)" Will invoke set_parent_container. end note note right of Element::"attach_to_scene_graph()" Will create or reparent this element's node to the parent's scene tree, or detach and destroy this element's node, if the parent does not have a scene tree. end note class Container { Element super_element Element elements[] bool init(handlers) void fini() add_element(Element*) remove_element(Element*) -struct wlr_scene_tree *wlr_scene_tree() {abstract}#void destroy() #void configure() } Element <|-- Container note right of Element::"add_element(Element*)" Both add_element() and remove_element() will call Element::set_parent_container() and thus alignt he element's scene node with the container's tree. end note class Workspace { Container super_container Layer layers[] Container *create() void destroy() map_window(Window*) unmap_window(Window*) activate_window(Window*) begin_window_move(Window*) map_panel(Panel *, layer) unmap_panel(Panel *) } Container *-- Workspace class Box { Container super_container bool init(handlers, wlmtk_box_orientation_t) } Container <|-- Box abstract class Surface { Element super_element request_size() get_size() } abstract class Content { Container super_container Surface surface Surface popups[] init(surface) fini() request_size() get_size() request_close() set_activated() } class Toplevel { Content super_content -- because: implement request_close, set_activated, ... -- but: Window ? } class Popup { Surface super_surface } abstract class Content { Element super_element init(handlers) fini() struct wlr_scene_node *create_scene_node() Element *element() -set_window(Window*) {abstract}#void get_size(int *, int *) {abstract}#void set_size(int, int) {abstract}#void set_activated(bool) {abstract}#void set_maximized(bool) {abstract}#void set_fullscreen(bool) } Element <|-- Content note right of Content Interface for Window contents. A surface (or... buffer? ...). Ultimately wraps a node, thus may be an element. end note class Layer { Container super_container add_panel() remove_panel() } Container <|-- Layer class Panel { Element super_element {abstract}configure() #set_layer(Layer *) } Element <|-- Panel class XdgToplevelSurface { } Content <|-- XdgToplevelSurface class Buffer { Element parent init(handlers, struct wlr_buffer *) set(struct wlr_buffer *) } Element <|-- Buffer class Button { Buffer super_buffer init(handlers, texture_up, texture_down, texture_blurred) update(texture_up, texture_down, texture_blurred) } Buffer <|-- Button class Window { Box super_box Content *content TitleBar *title_bar Window *create(Content*) destroy() Element *element() set_activated(bool) set_server_side_decorated(bool) get_size(int *, int *) set_size(int, int) } Box *-- Window class TitleBar { Box super_box } Box *-- TitleBar class TitleBarButton { Button super_button init(handlers, overlay_texture, texture, posx, posy) redraw(texture, posx, posy) get_width(int *) set_width(int) } class Menu { Box super_box } Box *-- Menu class MenuItem { } Buffer <|-- MenuItem class Cursor { Cursor *create() void destroy() attach_input_device(struct wlr_input_device*) set_image(const char * } class LayerShell { } Panel *-- LayerShell ``` ### Thoughts on windows, popups and menus ```plantuml object Window Window <-- Window object Pane Pane <-- Window object Popup Popup <-- Pane ``` * Pane * A Menu is an implementation of `Pane` * An XDG or XWL toplevel window is an implementation of `Pane` (FIXME: Really? Or should that just be the surface?) * The pane has attributes (can resize, can close, ...) (really? Menus can't) * Window * may have another Window as child. * may have multiple popups. * The window menu is also a popup. Is it a special popup? * it may have decorations * has attributes such as "can resize, can close", defining decoration. * Methods are... * commit_size * commit_maximized * commit_fullscreen * shade * ... * Contains "content". That content may be ... * resizable, ie. "request_size" (?) (virtual?) * request_maximize (virtual?) * request_fullscreen * request_close * set_activated (or is the "resize" something of the parent, that the window does through "commit"?) (but why not the content element directly? none of these applies to "Pane") * Windows have a "client" (or may be NULL?) * FIXME: Are toplevel windows (parent: workspace) different from child windows (parent: window?) * difference: workspace map_window() only works for toplevels. (but, "map" for child window still makes it "visible", ie. map for parent?) * also, map_window() makes it **VISIBLE** on the workspace. Would we want it to be added before? (meh? no use-case?) * Popup * Holds a surface or a menu (element) * May hold further popup(s) as child * => there is common functionality of Window and Popup: contain popups. * Menu * is the content of a popup or a window. * Popup: is child to a window or to another popup. -> it's body element can be a surface, a menu (others?) a "body element" may want to add a popup to the "parent" -> has a parent. Type of parent? a "pane" ? a "base" ? -> a popup *may* have partial decoration * Menu: Can be the body of a window (root menu) or a popup (window menu). Is a submenu a popup to the menu or to the parent of the menu? (pro "to the menu": interactions are simpler (?) pro "to the parent": consistency <-- that's what it should be) * Menu on right-click mode: - Will remain open until click is outside the menu - item remains highlighted *if pointer is on* OR *corresponding popup is open* * there is common functionality between popup and window. eg. both can have further popups. Both have a child element. Have a common class from which both derive? or is "window" an extension to "popup" ? Should "pane" be a pure virtual, or an instantiable class? * an XDG popup: composed of a pane that wraps the surface, with the XDG sugar. * an XWL popup: composed of a pane that wraps the surface, with XWL sugar. * a menu popup: composed of a pane that wraps the menu box. => surface_pane and menu_pane? (no functional difference?) (this is overlapping with current 'content'; but popup holds only 'element'. but, root menu should be a window Window: * request_fullscreen -> For surfaces, send async to clients. Needs specific implementation. -> For menu: ignore. Should be flagged as "not supported" * request_maximized -> For surfaces, send async to clients. Needs specific implementation. -> For menu: ignore. Should be flagged as "not supported" * request_size -> async call to content element to adjust size. For surfaces, will async send this to clients. (XWL, XDG) => needs specific implementation. -> For menu: ignore. Should be flagged as "not supported" * request_close: request the content to close; will close the window. Needs specific implementation. -> for surfaces: relay back to the client. -> For menu: close the menu. * set_activated: activates (kb focus) -> for surfaces: relay back to the client. -> for menu: update representation (local call). Popup * request_size: should be supported * request_close: request just that window or popup to disappear (?) * set_activated: activates (kb focus) #### Menu * pane: hold multiple popups, hold a child element. * window: holds a "pane", decorations, link to workspace, ... * popup: a pane, link to parent pane. a window menu: when desiring to create a child menu * the menu is an element to a pane. it looks up that pane. * creates a new popup (pane?) and adds it to the parent pane. a root menu: when desiring to create a child menu.. * the menu is an element to a pane. it looks up that pane. * creates a new popup (pane?) and adds it to the parent pane. the popup menu is closed * it is closed when a click goes outside the popup menu (and outside any of it's sub menus. But, these are part of the pane?) * it should know what parent item it was created for. signal there. => The menu *is* an implementation of the pane. It can extend it's handlers. it is a pane that (internally) holds a box, which holds menu items. => the menu can close once the pointer is no longer in any child element, of both pane and popup (panes). #### A window or popup surface * a pane that holds a surface as element. ### Pending work * Separate the "map" method into "attach_to_node" and "set_visible". Elements should be marked as visible even if their parent is not "mapped" yet; thus leading to lazy instantiation of the node, once their parent gets "mapped" (ie. attached to the scene graph). ### User Journeys #### Creating a new XDG toplevel * xdg_toplevel... => on handle_new_surface * XdgToplevelSurface::create(wlr surface) * listeners for map, unmap, destroy => so yes, what will this do when mapped? * Window::create(surface) * registers the window for workspace * creates the container, with parent of window element * if decoration: * will setup listeners for the various events, ... * request maximize * request move * request show window menu * set title * ... set title handler: * window::set_title request maximize handler: * window::request_maximize * window::set_maximized * internally: get view from workspace, ... set_size * callback to surface (if set): set_maximized upon surface::map * workspace::add_window(window) (unsure: do we need this?) => should set "container" of window parent... element to workspace::container (ie. set_parent(...); and add "element" to "container") * workspace::map_window(window) => this should add window to the set of workspace::mapped_windows => window element->container -> map_element(element) (expects the container to be mapped) => will call map(node?) on window element - is implemented in Container: - create a scene tree (from parents node) oc reparent (from parent) - calls map for every item in container upon surface::unmap * workspace::unmap_window => window element->container -> unmap_element(element) => will call unmap() on window element => destroy the node * workspace::remove_window(window) (do we need this?) There is a click ("pointer button event") -> goes to workspace. * use node lookup -> should give data -> element * element::click(...) * Button::click gets called. Has a "button_from_element" & goes from there. Button is pressed => pass down to pointer-focussed element. Would eg. show the "pressed" state of a button, but not activate. button_down Button is released => pass down to pointer-focussed element. (actually: pass down to the element where the button-press was passed to) Would eg. acivate the button, and restore the state of a pressed button. button_up click Button remains pressed and pointer moves. Means: We might be dragging something around. Start a "drag" => pass down a "drag" event to pointer focussed element. Keep track of drag start, and pass on relative drag motion down to element. Keeps passing drag elements to same element until drag ends. Would keep the element pointer focussed (?) A 'button' would ignore drags. drag_begin, drag_end, drag_motion ? A 'titlebar' would use this to begin a move, and update position. A 'iconified' would use this to de-couple from eg. dock drags have a pointer button associated (left, middle, right), and a relative position since. They also have the starting position, relative to the element. button_down [lingering time, some light move] drag_begin drag_motion drag_motion button_up drag_end Button is pressed again, without much move since last press. Means: We have a double-click. Pass down a double-click to the pointer-focussed element. button_down button_up double_click ## Dock and Clip class elements ```plantuml class DockOptions { int anchor int orientation int layer } class Dock { Panel super_panel init(DockOptions options) Tile entries[]; add_entry() remove_entry() } class Tile { Element set_size(int size) } class Launcher { const char *icon_file_path; const char *commandline; } Tile <|-- Launcher class Icon {} Tile <|-- Icon class IconSurface {} Tile <|-- IconSurface class Clip {} Dock <|-- Clip class IconArea {} Dock <|-- IconArea ``` ### Description * A `Dock` is the base class for the Dock, Clip or icon area. It has an anchor to either a corner or an edge of the screen, and an orientation (vertical or horizontal). On screen edges, the orientation must be parallel to the edge's orientation. * A `Tile` is the parent for what's shown in the dock, clip or the icon area. An entry is quadratic, and the size is given by the dock. The size may change during execution. A Tile will accept and may pass on pointer events. * A `Launcher` is an implementation of a Tile. It shows an image (the application icon), and will spawn a subprocess to execute the configured commandline when invoked. A launcher is invoked by a click (TODO: doubleclick?). It shows status of the spawned subprocesses ("running", "starting", "error"). If the application is running, it may show the application's icon (provided by the running application via a Wayland protocol), instead of the pre-configured one. The Launcher is not part of wlmtk, but of wlmaker implementation. ### Thoughts * A running application in WLMaker may (1) have an icon, and (2) be in miniaturized (*iconified*) state. An "application" in this context refers to ... a wayland client? an XDG toplevel? Any toplevel (eg. X11 toplevel)? For UI interaction, a "toplevel" seems a good answer, as it's the *toplevel* that can be *iconified*. That would apply to both X11 and XDG shell toplevels. Implication: WLMaker needs to keep track of "applications". If there is an icon, it should be shown on the launcher that spawned the "application". (showing the icon of the most recently launched one). Otherwise, it should be displayed in the icon area. An "application" that is *iconified* will be shown in the icon area. This is irrespective of whether there is already an icon shown for that "application". ### Input grab * See: https://wayland-book.com/seat.html * See: https://wayland.app/protocols/xdg-shell#xdg_popup:request:grab So, when a XDG popup requests a grab: From that moment on, the coresponding wlr_surface (and the related client) should keep receiving events. But not others. Once the grab is broken, the popup is supposed to be dismissed. So far, we been thinking of passing events from root element along the containers. On a grab, each container would lock the 'grabbing' element. (and inform the grab-holder when another element claims the grab; so would need a cancel_gab method). When the menu requests grab: we also want all pointer and input events going there. When the grab is broken => menu is to close. container_grab(c, element) -> setup grab for element -> will call to parent container as container_grab(parent_c, c.super_element) element_grab_cancel(element) -> cancel a held grab (this is FYI) void *wlmtk_container_pointer_grab(wlmtk_container_t *, wlmtk_element_t *); void wlmtk_container_pointer_grab_release(wlmtk_container_t *, wlmtk_element_t *); void wlmtk_element_pointer_grab_cancel(wlmtk_element_t *element_ptr); For Keyboard: * we have that mechanism partly with container::keyboard_focus_element_ptr * we have keyboard routing through "set_keyboard_focus_element (through wlmtk_surface_t in wlmtk_surface_:set_activated) For Pointer or Touch: * Not done (yet). => HOWEVER: This will route *only* to the surface holding the grab. (this would prevent cursor updates? That's actually how X11 chrome popups/menus are working currently) so... that's probably good/desired. ### Pointer events element: motion(_new) calls virtual method: offer_move(). - if true: update focus (enter/leave), request focus from parent if needed. (can this be denied? YES, in that case, we return false) request_move() // offer_move // offer_motion: Evaluate whether to accept the move (are the coordinates within the element's active region?). Return true if the move is accepted. -> leaf (buffer, rectangle, surface): offer_move return true if within -> container.offer_move() * for each element: element.motion() * return true if yes (this *HAS* to reach a leaf container: offer the request to any sub-elemnts. The first one that accepts the move will request focus (bubbling upwards). container: request_pointer_focus(container, element) -> checks that element is in container, etc -> calls the parent, if available. if false => return false -> if there's a pointer focus element: "blur" it -> set pointer focus element to element. (should triger element::enter. how?) wlmaker-0.8.1/src/toolkit/menu_item.c0000644000175100017510000013157715222270710017206 0ustar runnerrunner/* ========================================================================= */ /** * @file menu_item.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "menu_item.h" #include #include #include #include #include #include #include "base.h" #include "buffer.h" #include "gfxbuf.h" // IWYU pragma: keep #include "input.h" #include "primitives.h" #include "util.h" /* == Declarations ========================================================= */ /** State of a menu item. */ struct _wlmtk_menu_item_t { /** A menu item is a buffer. */ wlmtk_buffer_t super_buffer; /** The superclass' @ref wlmtk_element_t virtual method table. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Event listeners. @see wlmtk_menu_item_events. */ wlmtk_menu_item_events_t events; /** Link to the menu the item belongs to. Can be NULL. */ wlmtk_menu_t *menu_ptr; /** A submenu for this item. Can be NULL. */ wlmtk_menu_t *submenu_ptr; /** Listens to @ref wlmtk_menu_events_t::open_changed. */ struct wl_listener submenu_open_changed_listener; /** Listens to when we obtain pointer focus. */ struct wl_listener pointer_enter_listener; /** Listens to when we lose pointer focus. */ struct wl_listener pointer_leave_listener; /** Listens to when we get pointer motion. To re-gain mouse mode. */ struct wl_listener pointer_motion_listener; /** List node, within @ref wlmtk_menu_t::items. */ bs_dllist_node_t dlnode; /** Text to be shown for the menu item. */ char *text_ptr; /** Mode of the menu (and the item). */ enum wlmtk_menu_mode mode; /** Texture buffer holding the item in enabled state. */ struct wlr_buffer *enabled_wlr_buffer_ptr; /** Texture buffer holding the item in highlighted state. */ struct wlr_buffer *highlighted_wlr_buffer_ptr; /** Texture buffer holding the item in disabled state. */ struct wlr_buffer *disabled_wlr_buffer_ptr; /** Whether the item is enabled. */ bool enabled; /** State of the menu item. */ wlmtk_menu_item_state_t state; /** Reference to the menu's style. */ wlmtk_menu_style_ref_t *style_ref_ptr; /** Style of the menu item. */ const struct wlmtk_menu_style *style_ptr; }; static bool _wlmtk_menu_item_redraw( wlmtk_menu_item_t *menu_item_ptr, const struct wlmtk_menu_item_style *style_ptr); static void _wlmtk_menu_item_set_state( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_item_state_t state); static void _wlmtk_menu_item_draw_state(wlmtk_menu_item_t *menu_item_ptr); static struct wlr_buffer *_wlmtk_menu_item_create_buffer( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_item_state_t state, const struct wlmtk_menu_item_style *style_ptr); static void _wlmtk_menu_item_draw_submenu_hint( cairo_t *cairo_ptr, const struct wlmtk_menu_item_style *style_ptr, double x, double y); static bool _wlmtk_menu_item_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static void _wlmtk_menu_item_element_destroy( wlmtk_element_t *element_ptr); static void _wlmtk_menu_item_handle_pointer_enter( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_menu_item_handle_pointer_leave( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_menu_item_handle_pointer_motion( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_menu_item_handle_open_changed( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Virtual method table for the menu item's super class: Element. */ static const wlmtk_element_vmt_t _wlmtk_menu_item_element_vmt = { .pointer_button = _wlmtk_menu_item_element_pointer_button, .destroy = _wlmtk_menu_item_element_destroy, }; const bspl_desc_t wlmtk_menu_item_style_desc[] = { BSPL_DESC_CUSTOM( "Fill", true, struct wlmtk_menu_item_style, fill, fill, wlmtk_style_decode_fill, NULL, NULL, NULL), BSPL_DESC_CUSTOM( "HighlightedFill", true, struct wlmtk_menu_item_style, highlighted_fill, highlighted_fill, wlmtk_style_decode_fill, NULL, NULL, NULL), BSPL_DESC_DICT( "Font", true, struct wlmtk_menu_item_style, font, font, wlmtk_style_font_desc), BSPL_DESC_ARGB32( "EnabledTextColor", true, struct wlmtk_menu_item_style, enabled_text_color, enabled_text_color, 0), BSPL_DESC_ARGB32( "HighlightedTextColor", true, struct wlmtk_menu_item_style, highlighted_text_color, highlighted_text_color, 0), BSPL_DESC_ARGB32( "DisabledTextColor", true, struct wlmtk_menu_item_style, disabled_text_color, disabled_text_color, 0), BSPL_DESC_UINT64( "Height", true, struct wlmtk_menu_item_style, height, height, 20), BSPL_DESC_UINT64( "BezelWidth", true, struct wlmtk_menu_item_style, bezel_width, bezel_width, 1), BSPL_DESC_UINT64( "Width", true, struct wlmtk_menu_item_style, width, width, 80), BSPL_DESC_SENTINEL() }; /** Style definition used for unit tests. */ static const struct wlmtk_menu_style _test_style = { .item = { .fill = { .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .dgradient = { .from = 0xff102040, .to = 0xff4080ff }} }, .highlighted_fill = { .type = WLMTK_STYLE_COLOR_SOLID, .param = { .solid = { .color = 0xffc0d0e0 } } }, .font = { .face = "Helvetica", .size = 14 }, .height = 24, .bezel_width = 1, .width = 200, .enabled_text_color = 0xfff0f060, .highlighted_text_color = 0xff204080, .disabled_text_color = 0xff807060, } }; /* == Exported methods ===================================================== */ /* -------------------------------------------------------------------------*/ wlmtk_menu_item_t *wlmtk_menu_item_create( wlmtk_menu_style_ref_t *style_ref_ptr) { wlmtk_menu_item_t *menu_item_ptr = logged_calloc( 1, sizeof(wlmtk_menu_item_t)); if (NULL == menu_item_ptr) return NULL; wl_signal_init(&menu_item_ptr->events.triggered); wl_signal_init(&menu_item_ptr->events.destroy); menu_item_ptr->style_ref_ptr = style_ref_ptr; menu_item_ptr->style_ptr = wlmtk_menu_style_ref_retain(style_ref_ptr); if (!wlmtk_buffer_init(&menu_item_ptr->super_buffer)) { wlmtk_menu_item_destroy(menu_item_ptr); return NULL; } menu_item_ptr->orig_super_element_vmt = wlmtk_element_extend( &menu_item_ptr->super_buffer.super_element, &_wlmtk_menu_item_element_vmt); wlmtk_util_connect_listener_signal( &menu_item_ptr->super_buffer.super_element.events.pointer_enter, &menu_item_ptr->pointer_enter_listener, _wlmtk_menu_item_handle_pointer_enter); wlmtk_util_connect_listener_signal( &menu_item_ptr->super_buffer.super_element.events.pointer_leave, &menu_item_ptr->pointer_leave_listener, _wlmtk_menu_item_handle_pointer_leave); wlmtk_util_connect_listener_signal( &menu_item_ptr->super_buffer.super_element.events.pointer_motion, &menu_item_ptr->pointer_motion_listener, _wlmtk_menu_item_handle_pointer_motion); // TODO(kaeser@gubbe.ch): Should not be required! menu_item_ptr->enabled = true; _wlmtk_menu_item_set_state(menu_item_ptr, WLMTK_MENU_ITEM_ENABLED); _wlmtk_menu_item_redraw(menu_item_ptr, &menu_item_ptr->style_ptr->item); wlmtk_element_set_visible(wlmtk_menu_item_element(menu_item_ptr), true); return menu_item_ptr; } /* -------------------------------------------------------------------------*/ void wlmtk_menu_item_destroy(wlmtk_menu_item_t *menu_item_ptr) { wl_signal_emit(&menu_item_ptr->events.destroy, NULL); wlmtk_util_disconnect_listener(&menu_item_ptr->pointer_motion_listener); wlmtk_util_disconnect_listener(&menu_item_ptr->pointer_leave_listener); wlmtk_util_disconnect_listener(&menu_item_ptr->pointer_enter_listener); if (NULL != menu_item_ptr->submenu_ptr) { wlmtk_util_disconnect_listener( &menu_item_ptr->submenu_open_changed_listener); if (NULL != menu_item_ptr->menu_ptr) { wlmtk_base_push_element( wlmtk_menu_base(menu_item_ptr->menu_ptr), wlmtk_menu_element(menu_item_ptr->submenu_ptr)); } wlmtk_menu_destroy(menu_item_ptr->submenu_ptr); menu_item_ptr->submenu_ptr = NULL; } if (NULL != menu_item_ptr->text_ptr) { free(menu_item_ptr->text_ptr); menu_item_ptr->text_ptr = NULL; } wlr_buffer_drop_nullify(&menu_item_ptr->enabled_wlr_buffer_ptr); wlr_buffer_drop_nullify(&menu_item_ptr->highlighted_wlr_buffer_ptr); wlr_buffer_drop_nullify(&menu_item_ptr->disabled_wlr_buffer_ptr); wlmtk_buffer_fini(&menu_item_ptr->super_buffer); if (NULL != menu_item_ptr->style_ref_ptr) { wlmtk_menu_style_ref_release(menu_item_ptr->style_ref_ptr); menu_item_ptr->style_ref_ptr = NULL; } free(menu_item_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_menu_item_events_t *wlmtk_menu_item_events( wlmtk_menu_item_t *menu_item_ptr) { return &menu_item_ptr->events; } /* ------------------------------------------------------------------------- */ bool wlmtk_menu_item_set_style( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_style_ref_t *style_ref_ptr) { wlmtk_menu_style_ref_t *old_ref_ptr = menu_item_ptr->style_ref_ptr; menu_item_ptr->style_ref_ptr = style_ref_ptr; menu_item_ptr->style_ptr = wlmtk_menu_style_ref_retain(style_ref_ptr); bool rv = true; rv &= _wlmtk_menu_item_redraw( menu_item_ptr, &menu_item_ptr->style_ptr->item); if (NULL != menu_item_ptr->submenu_ptr) { rv &= wlmtk_menu_set_style( menu_item_ptr->submenu_ptr, menu_item_ptr->style_ref_ptr); } wlmtk_menu_style_ref_release(old_ref_ptr); wlmtk_element_invalidate_parent_layout( wlmtk_menu_item_element(menu_item_ptr)); return rv; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_item_set_parent_menu( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_t *menu_ptr) { if (menu_item_ptr->menu_ptr == menu_ptr) return; if (NULL != menu_item_ptr->menu_ptr && NULL != menu_item_ptr->submenu_ptr) { wlmtk_base_pop_element( wlmtk_menu_base(menu_item_ptr->menu_ptr), wlmtk_menu_element(menu_item_ptr->submenu_ptr)); } menu_item_ptr->menu_ptr = menu_ptr; if (NULL != menu_item_ptr->menu_ptr && NULL != menu_item_ptr->submenu_ptr) { wlmtk_base_push_element( wlmtk_menu_base(menu_item_ptr->menu_ptr), wlmtk_menu_element(menu_item_ptr->submenu_ptr)); } } /* ------------------------------------------------------------------------- */ void wlmtk_menu_item_set_submenu( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_t *submenu_ptr) { if (menu_item_ptr->submenu_ptr == submenu_ptr) return; if (NULL != menu_item_ptr->submenu_ptr) { wlmtk_util_disconnect_listener( &menu_item_ptr->submenu_open_changed_listener); if (NULL != menu_item_ptr->menu_ptr) { wlmtk_base_pop_element( wlmtk_menu_base(menu_item_ptr->menu_ptr), wlmtk_menu_element(menu_item_ptr->submenu_ptr)); } wlmtk_menu_set_parent_item(submenu_ptr, NULL); } menu_item_ptr->submenu_ptr = submenu_ptr; if (NULL != menu_item_ptr->submenu_ptr) { wlmtk_util_connect_listener_signal( &wlmtk_menu_events(menu_item_ptr->submenu_ptr)->open_changed, &menu_item_ptr->submenu_open_changed_listener, _wlmtk_menu_item_handle_open_changed); if (NULL != menu_item_ptr->menu_ptr) { wlmtk_base_push_element( wlmtk_menu_base(menu_item_ptr->menu_ptr), wlmtk_menu_element(menu_item_ptr->submenu_ptr)); } wlmtk_menu_set_mode(menu_item_ptr->submenu_ptr, menu_item_ptr->mode); wlmtk_menu_set_parent_item(submenu_ptr, menu_item_ptr); } _wlmtk_menu_item_redraw(menu_item_ptr, &menu_item_ptr->style_ptr->item); } /* ------------------------------------------------------------------------- */ wlmtk_menu_t *wlmtk_menu_item_get_submenu( wlmtk_menu_item_t *menu_item_ptr) { return menu_item_ptr->submenu_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_item_set_mode( wlmtk_menu_item_t *menu_item_ptr, enum wlmtk_menu_mode mode) { menu_item_ptr->mode = mode; if (NULL != menu_item_ptr->submenu_ptr) { wlmtk_menu_set_mode(menu_item_ptr->submenu_ptr, menu_item_ptr->mode); } } /* ------------------------------------------------------------------------- */ enum wlmtk_menu_mode wlmtk_menu_item_get_mode( wlmtk_menu_item_t *menu_item_ptr) { return menu_item_ptr->mode; } /* ------------------------------------------------------------------------- */ wlmtk_menu_item_state_t wlmtk_menu_item_get_state( wlmtk_menu_item_t *menu_item_ptr) { return menu_item_ptr->state; } /* ------------------------------------------------------------------------- */ bool wlmtk_menu_item_set_text( wlmtk_menu_item_t *menu_item_ptr, const char *text_ptr) { char *new_text_ptr = logged_strdup(text_ptr); if (NULL == new_text_ptr) return false; if (NULL != menu_item_ptr->text_ptr) free(menu_item_ptr->text_ptr); menu_item_ptr->text_ptr = new_text_ptr; return _wlmtk_menu_item_redraw( menu_item_ptr, &menu_item_ptr->style_ptr->item); } /* -------------------------------------------------------------------------*/ void wlmtk_menu_item_set_enabled( wlmtk_menu_item_t *menu_item_ptr, bool enabled) { if (menu_item_ptr->enabled == enabled) return; menu_item_ptr->enabled = enabled; if (menu_item_ptr->enabled) { if (menu_item_ptr->menu_ptr && wlmtk_menu_item_element(menu_item_ptr)->pointer_inside) { wlmtk_menu_request_item_highlight( menu_item_ptr->menu_ptr, menu_item_ptr); } else { _wlmtk_menu_item_set_state( menu_item_ptr, WLMTK_MENU_ITEM_ENABLED); } } else { _wlmtk_menu_item_set_state( menu_item_ptr, WLMTK_MENU_ITEM_DISABLED); } } /* -------------------------------------------------------------------------*/ bool wlmtk_menu_item_set_highlighted( wlmtk_menu_item_t *menu_item_ptr, bool highlighted) { if (!menu_item_ptr->enabled) return false; if (highlighted) { _wlmtk_menu_item_set_state(menu_item_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED); } else { _wlmtk_menu_item_set_state(menu_item_ptr, WLMTK_MENU_ITEM_ENABLED); } return true; } /* -------------------------------------------------------------------------*/ void wlmtk_menu_item_trigger(wlmtk_menu_item_t *menu_item_ptr) { switch (menu_item_ptr->state) { case WLMTK_MENU_ITEM_ENABLED: case WLMTK_MENU_ITEM_HIGHLIGHTED: wl_signal_emit(&menu_item_ptr->events.triggered, NULL); break; case WLMTK_MENU_ITEM_DISABLED: default: bs_log(BS_WARNING, "Ignoring trigger for item %p (\"%s\"), state %d", menu_item_ptr, menu_item_ptr->text_ptr ? menu_item_ptr->text_ptr : "", menu_item_ptr->state); } } /* -------------------------------------------------------------------------*/ bs_dllist_node_t *wlmtk_dlnode_from_menu_item( wlmtk_menu_item_t *menu_item_ptr) { // Guard clause. if (NULL == menu_item_ptr) return NULL; return &menu_item_ptr->dlnode; } /* -------------------------------------------------------------------------*/ wlmtk_menu_item_t *wlmtk_menu_item_from_dlnode(bs_dllist_node_t *dlnode_ptr) { // Guard clause. if (NULL == dlnode_ptr) return NULL; return BS_CONTAINER_OF(dlnode_ptr, wlmtk_menu_item_t, dlnode); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_menu_item_element(wlmtk_menu_item_t *menu_item_ptr) { return wlmtk_buffer_element(&menu_item_ptr->super_buffer); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Redraws the buffers for the menu item. Also updates the buffer state. */ bool _wlmtk_menu_item_redraw( wlmtk_menu_item_t *menu_item_ptr, const struct wlmtk_menu_item_style *style_ptr) { struct wlr_buffer *e, *h, *d; e = _wlmtk_menu_item_create_buffer( menu_item_ptr, WLMTK_MENU_ITEM_ENABLED, style_ptr); h = _wlmtk_menu_item_create_buffer( menu_item_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, style_ptr); d = _wlmtk_menu_item_create_buffer( menu_item_ptr, WLMTK_MENU_ITEM_DISABLED, style_ptr); if (NULL == e || NULL == d || NULL == h) { wlr_buffer_drop_nullify(&e); wlr_buffer_drop_nullify(&h); wlr_buffer_drop_nullify(&d); return false; } wlr_buffer_drop_nullify(&menu_item_ptr->enabled_wlr_buffer_ptr); menu_item_ptr->enabled_wlr_buffer_ptr = e; wlr_buffer_drop_nullify(&menu_item_ptr->highlighted_wlr_buffer_ptr); menu_item_ptr->highlighted_wlr_buffer_ptr = h; wlr_buffer_drop_nullify(&menu_item_ptr->disabled_wlr_buffer_ptr); menu_item_ptr->disabled_wlr_buffer_ptr = d; _wlmtk_menu_item_draw_state(menu_item_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Sets menu state: Sets the parent buffer's content accordingly. */ void _wlmtk_menu_item_set_state( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_item_state_t state) { if (menu_item_ptr->state == state) return; menu_item_ptr->state = state; _wlmtk_menu_item_draw_state(menu_item_ptr); if (NULL != menu_item_ptr->submenu_ptr) { wlmtk_element_t *e = wlmtk_menu_element(menu_item_ptr->submenu_ptr); int x, y, t, r; wlmtk_element_t *ie_ptr = wlmtk_menu_item_element(menu_item_ptr); wlmtk_element_get_position(ie_ptr, &x, &y); wlmtk_element_get_dimensions(ie_ptr, NULL, &t, &r, NULL); x += r; y += t; wlmtk_element_set_position(e, x, y); wlmtk_menu_set_open( menu_item_ptr->submenu_ptr, menu_item_ptr->state == WLMTK_MENU_ITEM_HIGHLIGHTED); } } /* ------------------------------------------------------------------------- */ /** Applies the state: Sets the parent buffer's content accordingly. */ void _wlmtk_menu_item_draw_state(wlmtk_menu_item_t *menu_item_ptr) { switch (menu_item_ptr->state) { case WLMTK_MENU_ITEM_ENABLED: wlmtk_buffer_set(&menu_item_ptr->super_buffer, menu_item_ptr->enabled_wlr_buffer_ptr); break; case WLMTK_MENU_ITEM_HIGHLIGHTED: wlmtk_buffer_set(&menu_item_ptr->super_buffer, menu_item_ptr->highlighted_wlr_buffer_ptr); break; case WLMTK_MENU_ITEM_DISABLED: wlmtk_buffer_set(&menu_item_ptr->super_buffer, menu_item_ptr->disabled_wlr_buffer_ptr); break; default: bs_log(BS_FATAL, "Unhandled state %d", menu_item_ptr->state); } } /* ------------------------------------------------------------------------- */ /** * Creates a wlr_buffer with the menu item drawn for the given state. * * @param menu_item_ptr * @param state * @param style_ptr * * @return A wlr_buffer, or NULL on error. */ struct wlr_buffer *_wlmtk_menu_item_create_buffer( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_item_state_t state, const struct wlmtk_menu_item_style *style_ptr) { struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( style_ptr->width, style_ptr->height); if (NULL == wlr_buffer_ptr) { bs_log(BS_ERROR, "Failed bs_gfxbuf_create_wlr_buffer(%"PRIu64",, %"PRIu64")", style_ptr->width, style_ptr->height); return NULL; } cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { bs_log(BS_ERROR, "Failed cairo_create_from_wlr_buffer(%p)", wlr_buffer_ptr); wlr_buffer_drop(wlr_buffer_ptr); return NULL; } const char *text_ptr = ""; if (NULL != menu_item_ptr->text_ptr) text_ptr = menu_item_ptr->text_ptr; const wlmtk_style_fill_t *fill_ptr = &style_ptr->fill; uint32_t color = style_ptr->enabled_text_color; if (WLMTK_MENU_ITEM_HIGHLIGHTED == state) { fill_ptr = &style_ptr->highlighted_fill; color = style_ptr->highlighted_text_color; } else if (WLMTK_MENU_ITEM_DISABLED == state) { color = style_ptr->disabled_text_color; } wlmaker_primitives_cairo_fill(cairo_ptr, fill_ptr); wlmaker_primitives_draw_bezel( cairo_ptr, style_ptr->bezel_width, true); if (NULL != menu_item_ptr->submenu_ptr) { _wlmtk_menu_item_draw_submenu_hint( cairo_ptr, style_ptr, style_ptr->width - style_ptr->height * 0.6, style_ptr->height * 0.3); } wlmaker_primitives_draw_text( cairo_ptr, 6, 2 + style_ptr->font.size, &style_ptr->font, color, text_ptr); cairo_destroy(cairo_ptr); return wlr_buffer_ptr; } /* ------------------------------------------------------------------------- */ /** * Draws the hint for submenu (triangle) into the cairo at (x, y). * * @param cairo_ptr * @param style_ptr * @param x * @param y */ void _wlmtk_menu_item_draw_submenu_hint( cairo_t *cairo_ptr, const struct wlmtk_menu_item_style *style_ptr, double x, double y) { double h = style_ptr->height; cairo_save(cairo_ptr); cairo_set_line_cap(cairo_ptr, CAIRO_LINE_CAP_BUTT); cairo_set_line_width(cairo_ptr, style_ptr->bezel_width); cairo_move_to(cairo_ptr, x, y); cairo_set_source_rgba(cairo_ptr, 0.0, 0.0, 0.0, 0.4); cairo_line_to(cairo_ptr, x, h * 0.7); cairo_stroke(cairo_ptr); cairo_set_source_rgba(cairo_ptr, 1.0, 1.0, 1.0, 0.8); cairo_move_to(cairo_ptr, x, h * 0.7); cairo_line_to(cairo_ptr, x + h * 0.4, h * 0.5); cairo_stroke(cairo_ptr); cairo_set_source_rgba(cairo_ptr, 0.5, 0.5, 0.5, 0.7); cairo_move_to(cairo_ptr, x + h * 0.4, h * 0.5); cairo_line_to(cairo_ptr, x, y); cairo_stroke(cairo_ptr); cairo_restore(cairo_ptr); } /* ------------------------------------------------------------------------- */ /** Checks if the button event is a click, and calls the handler. */ bool _wlmtk_menu_item_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_menu_item_t *menu_item_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_menu_item_t, super_buffer.super_element); // Normal mode and a left click in the area when enabled: Trigger it! if (WLMTK_MENU_MODE_NORMAL == menu_item_ptr->mode && BTN_LEFT == button_event_ptr->button && WLMTK_BUTTON_CLICK == button_event_ptr->type && WLMTK_MENU_ITEM_DISABLED != menu_item_ptr->state && wlmtk_menu_item_element(menu_item_ptr)->pointer_inside) { wlmtk_menu_item_trigger(menu_item_ptr); return true; } // Right-click mode & releasing the right button when highlighted: Trigger! if (WLMTK_MENU_MODE_RIGHTCLICK == menu_item_ptr->mode && BTN_RIGHT == button_event_ptr->button && WLMTK_BUTTON_UP == button_event_ptr->type && WLMTK_MENU_ITEM_DISABLED != menu_item_ptr->state && wlmtk_menu_item_element(menu_item_ptr)->pointer_inside) { wlmtk_menu_item_trigger(menu_item_ptr); return true; } // Note: All left button events are accepted. return true; } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_element_vmt_t::destroy. Dtor for the menu item. */ void _wlmtk_menu_item_element_destroy( wlmtk_element_t *element_ptr) { wlmtk_menu_item_t *menu_item_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_menu_item_t, super_buffer.super_element); wlmtk_menu_item_destroy(menu_item_ptr); } /* ------------------------------------------------------------------------- */ /** Handles when the pointer enters the element: Highlights, if enabled. */ void _wlmtk_menu_item_handle_pointer_enter( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_menu_item_t *menu_item_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_menu_item_t, pointer_enter_listener); if (menu_item_ptr->enabled && NULL != menu_item_ptr->menu_ptr) { wlmtk_menu_request_item_highlight( menu_item_ptr->menu_ptr, menu_item_ptr); } } /* ------------------------------------------------------------------------- */ /** * Handles when the pointer leaves the element: Ends highlight, in case there * is no submenu currently visible. * * @param listener_ptr * @param data_ptr */ void _wlmtk_menu_item_handle_pointer_leave( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_menu_item_t *menu_item_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_menu_item_t, pointer_leave_listener); if (menu_item_ptr->enabled && WLMTK_MENU_ITEM_HIGHLIGHTED == menu_item_ptr->state && NULL != menu_item_ptr->menu_ptr && (NULL == menu_item_ptr->submenu_ptr || !wlmtk_menu_is_open(menu_item_ptr->submenu_ptr))) { wlmtk_menu_request_item_highlight(menu_item_ptr->menu_ptr, NULL); } } /* ------------------------------------------------------------------------- */ /** * Handles when there is pointer motion within the element. (Re)gain highlight. * * @param listener_ptr * @param data_ptr */ void _wlmtk_menu_item_handle_pointer_motion( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_menu_item_t *menu_item_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_menu_item_t, pointer_motion_listener); if (menu_item_ptr->enabled && NULL != menu_item_ptr->menu_ptr) { wlmtk_menu_request_item_highlight( menu_item_ptr->menu_ptr, menu_item_ptr); } } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_menu_events_t::open_changed. Updates item highlight. */ void _wlmtk_menu_item_handle_open_changed( struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_menu_item_t *menu_item_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_menu_item_t, submenu_open_changed_listener); wlmtk_menu_t *submenu_ptr = data_ptr; BS_ASSERT(submenu_ptr == menu_item_ptr->submenu_ptr); if (wlmtk_menu_is_open(submenu_ptr) && NULL != menu_item_ptr->menu_ptr) { wlmtk_menu_request_item_highlight( menu_item_ptr->menu_ptr, menu_item_ptr); } } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); static void test_buffers(bs_test_t *test_ptr); static void test_pointer(bs_test_t *test_ptr); static void test_triggered(bs_test_t *test_ptr); static void test_right_click(bs_test_t *test_ptr); static void test_submenu_highlight(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_menu_item_test_cases[] = { { 1, "create_destroy", test_create_destroy }, // TODO(kaeser@gubbe.ch): Re-enable, once figuring out why these fail on // Trixie when running as a github action. { 0, "buffers", test_buffers }, { 1, "pointer", test_pointer }, { 1, "triggered", test_triggered }, { 1, "right_click", test_right_click }, { 1, "submenu_highlight", test_submenu_highlight }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_menu_item_test_set = BS_TEST_SET( true, "menu_item", _wlmtk_menu_item_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises setup and teardown and a few accessors. */ void test_create_destroy(bs_test_t *test_ptr) { struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _test_style; wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_create( wlmtk_menu_style_to_ref(s)); BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, item_ptr); bs_dllist_node_t *dlnode_ptr = wlmtk_dlnode_from_menu_item(item_ptr); BS_TEST_VERIFY_EQ(test_ptr, dlnode_ptr, &item_ptr->dlnode); BS_TEST_VERIFY_EQ( test_ptr, item_ptr, wlmtk_menu_item_from_dlnode(dlnode_ptr)); BS_TEST_VERIFY_EQ( test_ptr, &item_ptr->super_buffer.super_element, wlmtk_menu_item_element(item_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_menu_item_set_text(item_ptr, "Text")); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_menu_item_from_dlnode(NULL)); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_dlnode_from_menu_item(NULL)); wlmtk_menu_item_destroy(item_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* ------------------------------------------------------------------------- */ /** Exercises drawing. */ void test_buffers(bs_test_t *test_ptr) { struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _test_style; s->item.width = 80; wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_create( wlmtk_menu_style_to_ref(s)); BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, item_ptr); wlmtk_menu_item_set_text(item_ptr, "Menu item"); bs_gfxbuf_t *g; g = bs_gfxbuf_from_wlr_buffer(item_ptr->enabled_wlr_buffer_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, g, "toolkit/menu_item_enabled.png"); g = bs_gfxbuf_from_wlr_buffer(item_ptr->highlighted_wlr_buffer_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, g, "toolkit/menu_item_highlighted.png"); g = bs_gfxbuf_from_wlr_buffer(item_ptr->disabled_wlr_buffer_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, g, "toolkit/menu_item_disabled.png"); wlmtk_menu_t *submenu_ptr = wlmtk_menu_create( wlmtk_menu_style_to_ref(s)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, submenu_ptr); wlmtk_menu_item_set_submenu(item_ptr, submenu_ptr); g = bs_gfxbuf_from_wlr_buffer(item_ptr->enabled_wlr_buffer_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, g, "toolkit/menu_item_submenu_enabled.png"); g = bs_gfxbuf_from_wlr_buffer(item_ptr->highlighted_wlr_buffer_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, g, "toolkit/menu_item_submenu_highlighted.png"); g = bs_gfxbuf_from_wlr_buffer(item_ptr->disabled_wlr_buffer_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, g, "toolkit/menu_item_submenu_disabled.png"); wlmtk_menu_item_destroy(item_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* ------------------------------------------------------------------------- */ /** Tests pointer entering & leaving. */ void test_pointer(bs_test_t *test_ptr) { struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _test_style; s->item.width = 80; wlmtk_menu_style_ref_t *sr = wlmtk_menu_style_to_ref(s); wlmtk_menu_t *menu_ptr = wlmtk_menu_create(sr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr); wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_create(sr); BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, item_ptr); wlmtk_menu_add_item(menu_ptr, item_ptr); BS_TEST_VERIFY_EQ(test_ptr, menu_ptr, item_ptr->menu_ptr); wlmtk_element_t *e = wlmtk_menu_item_element(item_ptr); wlmtk_button_event_t lbtn_ev = { .button = BTN_LEFT, .type = WLMTK_BUTTON_CLICK }; wlmtk_menu_item_set_text(item_ptr, "Menu item"); // Initial state: enabled. BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(item_ptr)); BS_TEST_VERIFY_EQ( test_ptr, item_ptr->super_buffer.wlr_buffer_ptr, item_ptr->enabled_wlr_buffer_ptr); // Click event. Not passed, since not highlighted. BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &lbtn_ev)); // Disable it, verify texture and state. wlmtk_menu_item_set_enabled(item_ptr, false); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_DISABLED, wlmtk_menu_item_get_state(item_ptr)); BS_TEST_VERIFY_EQ( test_ptr, item_ptr->super_buffer.wlr_buffer_ptr, item_ptr->disabled_wlr_buffer_ptr); // Pointer enters the item, but remains disabled. wlmtk_pointer_motion_event_t mev = { .x = 20, .y = 10 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_DISABLED, wlmtk_menu_item_get_state(item_ptr)); BS_TEST_VERIFY_EQ( test_ptr, item_ptr->super_buffer.wlr_buffer_ptr, item_ptr->disabled_wlr_buffer_ptr); // When enabled, will be highlighted since pointer is inside. wlmtk_menu_item_set_enabled(item_ptr, true); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(item_ptr)); BS_TEST_VERIFY_EQ( test_ptr, item_ptr->super_buffer.wlr_buffer_ptr, item_ptr->highlighted_wlr_buffer_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &lbtn_ev)); // Pointer moves outside: disabled. mev = (wlmtk_pointer_motion_event_t){ .x = 90, .y = 10 }; BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(item_ptr)); BS_TEST_VERIFY_EQ( test_ptr, item_ptr->super_buffer.wlr_buffer_ptr, item_ptr->enabled_wlr_buffer_ptr); wlmtk_menu_remove_item(menu_ptr, item_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, item_ptr->menu_ptr); wlmtk_menu_item_destroy(item_ptr); wlmtk_menu_destroy(menu_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* ------------------------------------------------------------------------- */ /** Verifies desired clicks are passed to the handler. */ void test_triggered(bs_test_t *test_ptr) { struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _test_style; s->item.width = 80; wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_create( wlmtk_menu_style_to_ref(s)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item_ptr); wlmtk_util_test_listener_t tl; wlmtk_util_connect_test_listener( &wlmtk_menu_item_events(item_ptr)->triggered, &tl); wlmtk_menu_item_set_text(item_ptr, "Menu item"); wlmtk_element_t *e = wlmtk_menu_item_element(item_ptr); wlmtk_button_event_t b = { .button = BTN_LEFT, .type = WLMTK_BUTTON_CLICK }; // Pointer enters to highlight, the click triggers the handler. wlmtk_pointer_motion_event_t mev = { .x = 20, .y = 10 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 1, tl.calls); wlmtk_util_clear_test_listener(&tl); // Pointer enters outside, click does not trigger. mev = (wlmtk_pointer_motion_event_t){ .x = 90, .y = 10 }; BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 0, tl.calls); // Pointer enters again. Element disabled, will not trigger. mev = (wlmtk_pointer_motion_event_t){ .x = 20, .y = 10 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); wlmtk_menu_item_set_enabled(item_ptr, false); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 0, tl.calls); // Element enabled, triggers. wlmtk_menu_item_set_enabled(item_ptr, true); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 1, tl.calls); wlmtk_util_clear_test_listener(&tl); // Right button: No trigger, but button claimed. b.button = BTN_RIGHT; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 0, tl.calls); // Left button, but not a CLICK event: No trigger. b.button = BTN_LEFT; b.type = WLMTK_BUTTON_DOWN; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 0, tl.calls); // Left button, a double-click event: No trigger. b.button = BTN_LEFT; b.type = WLMTK_BUTTON_DOUBLE_CLICK; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 0, tl.calls); wlmtk_util_disconnect_test_listener(&tl); wlmtk_menu_item_destroy(item_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* ------------------------------------------------------------------------- */ /** Tests button events in right-click mode. */ void test_right_click(bs_test_t *test_ptr) { struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _test_style; s->item.width = 80; wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_create( wlmtk_menu_style_to_ref(s)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item_ptr); wlmtk_util_test_listener_t tl; wlmtk_util_connect_test_listener( &wlmtk_menu_item_events(item_ptr)->triggered, &tl); wlmtk_menu_item_set_text(item_ptr, "Menu item"); wlmtk_element_t *e = wlmtk_menu_item_element(item_ptr); wlmtk_button_event_t b = { .button = BTN_LEFT, .type = WLMTK_BUTTON_CLICK }; wlmtk_button_event_t bup = { .button = BTN_RIGHT, .type = WLMTK_BUTTON_UP }; BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_NORMAL, wlmtk_menu_item_get_mode(item_ptr)); // Pointer enters to highlight, click triggers. wlmtk_pointer_motion_event_t mev = { .x = 20, .y = 10 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 1, tl.calls); wlmtk_util_clear_test_listener(&tl); // Pointer remains inside, button-up does not trigger.. BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &bup)); BS_TEST_VERIFY_EQ(test_ptr, 0, tl.calls); // Switch mode to right-click. wlmtk_menu_item_set_mode(item_ptr, WLMTK_MENU_MODE_RIGHTCLICK); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_RIGHTCLICK, wlmtk_menu_item_get_mode(item_ptr)); // Pointer remains inside, click is ignored. mev = (wlmtk_pointer_motion_event_t){ .x = 20, .y = 10 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 0, tl.calls); // Pointer remains inside, button-up triggers. BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &bup)); BS_TEST_VERIFY_EQ(test_ptr, 1, tl.calls); wlmtk_util_clear_test_listener(&tl); // Pointer leaves, button-up does not trigger. mev = (wlmtk_pointer_motion_event_t){ .x = 90, .y = 10 }; BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(e, &b)); BS_TEST_VERIFY_EQ(test_ptr, 0, tl.calls); wlmtk_util_disconnect_test_listener(&tl); wlmtk_menu_item_destroy(item_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* ------------------------------------------------------------------------- */ /** * Tests the interaction of submenu and menu item highlighting. * * We want the following: * - when the item with submenu highlights, the submenu opens. * - move the cursor onto submenu, check it highlights. * - if highlighting is turned off, the item closes the submenu. * * @param test_ptr **/ void test_submenu_highlight(bs_test_t *test_ptr) { struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _test_style; wlmtk_menu_style_ref_t *sr = wlmtk_menu_style_to_ref(s); wlmtk_menu_t *menu_ptr = wlmtk_menu_create(sr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr); wlmtk_menu_set_mode(menu_ptr, WLMTK_MENU_MODE_RIGHTCLICK); wlmtk_element_t *me = wlmtk_menu_element(menu_ptr); wlmtk_element_set_visible(me, true); wlmtk_menu_item_t *i1 = wlmtk_menu_item_create(sr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i1); wlmtk_menu_add_item(menu_ptr, i1); wlmtk_menu_item_t *i2 = wlmtk_menu_item_create(sr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i2); wlmtk_menu_add_item(menu_ptr, i2); wlmtk_menu_t *submenu_ptr = wlmtk_menu_create(sr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, submenu_ptr); wlmtk_menu_item_t *s1 = wlmtk_menu_item_create(sr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, s1); wlmtk_menu_add_item(submenu_ptr, s1); wlmtk_element_layout(wlmtk_menu_element(menu_ptr)); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_menu_get_parent_item(submenu_ptr)); wlmtk_menu_item_set_submenu(i2, submenu_ptr); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_RIGHTCLICK, wlmtk_menu_get_mode(submenu_ptr)); BS_TEST_VERIFY_EQ(test_ptr, i2, wlmtk_menu_get_parent_item(submenu_ptr)); // Begin: Move pointer so that i1 is highlighted. wlmtk_pointer_motion_event_t mev = { .x = 9, .y = 12 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &mev)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i1)); BS_TEST_VERIFY_NEQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i2)); // Then: move pointer into i2. Must highlight and open submenu. mev = (wlmtk_pointer_motion_event_t){ .x = 9, .y = 36 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &mev)); BS_TEST_VERIFY_NEQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i1)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i2)); // Then: Move pointer into i2 and submenu. Must highlight the submenu item. mev = (wlmtk_pointer_motion_event_t){ .x = 209, .y = 36 }; BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i2)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &mev)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(s1)); // Then: Move pointer a bit, within i1. Highlight that, close submenu. mev = (wlmtk_pointer_motion_event_t){ .x = 9, .y = 13 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &mev)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_menu_is_open(submenu_ptr)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i1)); BS_TEST_VERIFY_NEQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(s1)); // Then: Move pointer into i2. Must highlight and (re)open submenu. mev = (wlmtk_pointer_motion_event_t){ .x = 9, .y = 36 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &mev)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_menu_is_open(submenu_ptr)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i2)); BS_TEST_VERIFY_NEQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(s1)); // Then: Move pointer into submenu again. Release button. Must trigger. mev = (wlmtk_pointer_motion_event_t){ .x = 209, .y = 36 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &mev)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(s1)); // Release right button. wlmtk_util_test_listener_t tl; wlmtk_util_connect_test_listener( &wlmtk_menu_item_events(s1)->triggered, &tl); wlmtk_button_event_t bup = { .button = BTN_RIGHT, .type = WLMTK_BUTTON_UP }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_button(me, &bup)); BS_TEST_VERIFY_EQ(test_ptr, 1, tl.calls); // Deliberately: Do not detach submenu, must be cleaned up. wlmtk_util_disconnect_test_listener(&tl); wlmtk_menu_destroy(menu_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* == End of menu_item.c =================================================== */ wlmaker-0.8.1/src/toolkit/element.c0000644000175100017510000011117315222270710016643 0ustar runnerrunner/* ========================================================================= */ /** * @file element.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "container.h" #include "element.h" #include "input.h" #include "util.h" #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include #include /* == Declarations ========================================================= */ static bool _wlmtk_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static bool _wlmtk_element_pointer_axis( __UNUSED__ wlmtk_element_t *element_ptr, __UNUSED__ struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); static void _wlmtk_element_keyboard_blur(wlmtk_element_t *element_ptr); static bool _wlmtk_element_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr); static bool _wlmtk_element_keyboard_sym( wlmtk_element_t *element_ptr, xkb_keysym_t keysym, enum xkb_key_direction direction, uint32_t modifiers); static void _wlmtk_element_layout(wlmtk_element_t *element_ptr); static void handle_wlr_scene_node_destroy( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Default virtual method table. Initializes the non-abstract methods. */ static const wlmtk_element_vmt_t element_vmt = { .pointer_button = _wlmtk_element_pointer_button, .pointer_axis = _wlmtk_element_pointer_axis, .keyboard_blur = _wlmtk_element_keyboard_blur, .keyboard_event = _wlmtk_element_keyboard_event, .keyboard_sym = _wlmtk_element_keyboard_sym, .layout = _wlmtk_element_layout, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_element_init(wlmtk_element_t *element_ptr) { BS_ASSERT(NULL != element_ptr); *element_ptr = (wlmtk_element_t){ .vmt = element_vmt }; wl_signal_init(&element_ptr->events.pointer_enter); wl_signal_init(&element_ptr->events.pointer_leave); wl_signal_init(&element_ptr->events.pointer_motion); element_ptr->last_pointer_motion_event = (wlmtk_pointer_motion_event_t){ .x = NAN, .y = NAN, .time_msec = 0 }; return true; } /* ------------------------------------------------------------------------- */ wlmtk_element_vmt_t wlmtk_element_extend( wlmtk_element_t *element_ptr, const wlmtk_element_vmt_t *element_vmt_ptr) { wlmtk_element_vmt_t orig_vmt = element_ptr->vmt; // Only overwrite provided methods. if (NULL != element_vmt_ptr->destroy) { element_ptr->vmt.destroy = element_vmt_ptr->destroy; } if (NULL != element_vmt_ptr->create_scene_node) { element_ptr->vmt.create_scene_node = element_vmt_ptr->create_scene_node; } if (NULL != element_vmt_ptr->get_dimensions) { element_ptr->vmt.get_dimensions = element_vmt_ptr->get_dimensions; } if (NULL != element_vmt_ptr->pointer_accepts_motion) { element_ptr->vmt.pointer_accepts_motion = element_vmt_ptr->pointer_accepts_motion; } if (NULL != element_vmt_ptr->pointer_button) { element_ptr->vmt.pointer_button = element_vmt_ptr->pointer_button; } if (NULL != element_vmt_ptr->pointer_axis) { element_ptr->vmt.pointer_axis = element_vmt_ptr->pointer_axis; } if (NULL != element_vmt_ptr->pointer_grab_cancel) { element_ptr->vmt.pointer_grab_cancel = element_vmt_ptr->pointer_grab_cancel; } if (NULL != element_vmt_ptr->keyboard_blur) { element_ptr->vmt.keyboard_blur = element_vmt_ptr->keyboard_blur; } if (NULL != element_vmt_ptr->keyboard_event) { element_ptr->vmt.keyboard_event = element_vmt_ptr->keyboard_event; } if (NULL != element_vmt_ptr->keyboard_sym) { element_ptr->vmt.keyboard_sym = element_vmt_ptr->keyboard_sym; } if (NULL != element_vmt_ptr->layout) { element_ptr->vmt.layout = element_vmt_ptr->layout; } return orig_vmt; } /* ------------------------------------------------------------------------- */ void wlmtk_element_fini( wlmtk_element_t *element_ptr) { // Verify we're no longer part of the scene graph, nor part of a container. BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); BS_ASSERT(NULL == element_ptr->parent_container_ptr); *element_ptr = (wlmtk_element_t){}; } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *wlmtk_dlnode_from_element( wlmtk_element_t *element_ptr) { return &element_ptr->dlnode; } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_element_from_dlnode( bs_dllist_node_t *dlnode_ptr) { return BS_CONTAINER_OF(dlnode_ptr, wlmtk_element_t, dlnode); } /* ------------------------------------------------------------------------- */ void wlmtk_element_set_parent_container( wlmtk_element_t *element_ptr, wlmtk_container_t *parent_container_ptr) { if (element_ptr->parent_container_ptr == parent_container_ptr) return; element_ptr->parent_container_ptr = parent_container_ptr; wlmtk_element_attach_to_scene_graph(element_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_element_attach_to_scene_graph( wlmtk_element_t *element_ptr) { struct wlr_scene_tree *parent_wlr_scene_tree_ptr = NULL; if (NULL != element_ptr->parent_container_ptr) { parent_wlr_scene_tree_ptr = wlmtk_container_wlr_scene_tree( element_ptr->parent_container_ptr); } if (NULL == parent_wlr_scene_tree_ptr) { if (NULL != element_ptr->wlr_scene_node_ptr) { wlmtk_util_disconnect_listener(&element_ptr->wlr_scene_node_destroy_listener); wlr_scene_node_destroy(element_ptr->wlr_scene_node_ptr); element_ptr->wlr_scene_node_ptr = NULL; } return; } if (NULL == element_ptr->wlr_scene_node_ptr) { element_ptr->wlr_scene_node_ptr = element_ptr->vmt.create_scene_node( element_ptr, parent_wlr_scene_tree_ptr); wlmtk_util_connect_listener_signal( &element_ptr->wlr_scene_node_ptr->events.destroy, &element_ptr->wlr_scene_node_destroy_listener, handle_wlr_scene_node_destroy); wlr_scene_node_set_enabled(element_ptr->wlr_scene_node_ptr, element_ptr->visible); wlr_scene_node_set_position(element_ptr->wlr_scene_node_ptr, element_ptr->x, element_ptr->y); return; } BS_ASSERT(NULL != element_ptr->wlr_scene_node_ptr); if (element_ptr->wlr_scene_node_ptr->parent == parent_wlr_scene_tree_ptr) { // Parent does not change, nothing to do. return; } wlr_scene_node_reparent(element_ptr->wlr_scene_node_ptr, parent_wlr_scene_tree_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_element_set_visible(wlmtk_element_t *element_ptr, bool visible) { // Nothing to do? if (element_ptr->visible == visible) return; element_ptr->visible = visible; if (NULL != element_ptr->wlr_scene_node_ptr) { wlr_scene_node_set_enabled(element_ptr->wlr_scene_node_ptr, visible); } wlmtk_element_invalidate_parent_layout(element_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_element_invalidate_parent_layout(wlmtk_element_t *element_ptr) { if (NULL != element_ptr->parent_container_ptr) { wlmtk_container_invalidate_layout(element_ptr->parent_container_ptr); } } /* ------------------------------------------------------------------------- */ void wlmtk_element_get_position( wlmtk_element_t *element_ptr, int *x_ptr, int *y_ptr) { // The node may have been moved without us noticing... update it. if (NULL != element_ptr->wlr_scene_node_ptr) { element_ptr->x = element_ptr->wlr_scene_node_ptr->x; element_ptr->y = element_ptr->wlr_scene_node_ptr->y; } if (NULL != x_ptr) *x_ptr = element_ptr->x; if (NULL != y_ptr) *y_ptr = element_ptr->y; } /* ------------------------------------------------------------------------- */ void wlmtk_element_set_position( wlmtk_element_t *element_ptr, int x, int y) { if (NULL != element_ptr->wlr_scene_node_ptr) { wlr_scene_node_set_position(element_ptr->wlr_scene_node_ptr, x, y); } // Optimization clause: Can leave here, if coordinates didn't change. if (element_ptr->x == x && element_ptr->y == y) return; element_ptr->x = x; element_ptr->y = y; wlmtk_element_invalidate_parent_layout(element_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_element_pointer_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr) { element_ptr->last_pointer_motion_event = *motion_event_ptr; if (NULL == element_ptr->vmt.pointer_accepts_motion) return false; if (!element_ptr->visible) return false; bool inside = BS_ASSERT_NOTNULL(element_ptr->vmt.pointer_accepts_motion)( element_ptr, motion_event_ptr); if (inside) { if (wlmtk_element_pointer_focus(element_ptr, motion_event_ptr)) { element_ptr->last_pointer_motion_event = *motion_event_ptr; wl_signal_emit(&element_ptr->events.pointer_motion, motion_event_ptr); return true; } } if (element_ptr->pointer_inside) { wlmtk_element_pointer_blur(element_ptr); } return false; } /* ------------------------------------------------------------------------- */ bool wlmtk_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { return element_ptr->vmt.pointer_button(element_ptr, button_event_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { return element_ptr->vmt.pointer_axis( element_ptr, wlr_pointer_axis_event_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_element_pointer_focus( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr) { if (element_ptr->pointer_inside) return true; if (NULL != element_ptr->parent_container_ptr && !wlmtk_container_request_pointer_focus( element_ptr->parent_container_ptr, element_ptr, motion_event_ptr)) { return false; } element_ptr->last_pointer_motion_event = *motion_event_ptr; element_ptr->pointer_inside = true; wl_signal_emit( &element_ptr->events.pointer_enter, motion_event_ptr->pointer_ptr); return true; } /* ------------------------------------------------------------------------- */ void wlmtk_element_pointer_blur(wlmtk_element_t *element_ptr) { if (!element_ptr->pointer_inside || element_ptr->inhibit_pointer_blur) return; element_ptr->pointer_inside = false; wl_signal_emit(&element_ptr->events.pointer_leave, NULL); if (NULL != element_ptr->parent_container_ptr) { wlmtk_element_pointer_blur( &element_ptr->parent_container_ptr->super_element); } } /* ------------------------------------------------------------------------- */ void wlmtk_element_pointer_grab_cancel( wlmtk_element_t *element_ptr) { if (NULL != element_ptr->vmt.pointer_grab_cancel) { element_ptr->vmt.pointer_grab_cancel(element_ptr); } } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Does nothing, returns false. */ bool _wlmtk_element_pointer_button( __UNUSED__ wlmtk_element_t *element_ptr, __UNUSED__ const wlmtk_button_event_t *button_event_ptr) { return false; } /* ------------------------------------------------------------------------- */ /** Does nothing, returns false. */ bool _wlmtk_element_pointer_axis( __UNUSED__ wlmtk_element_t *element_ptr, __UNUSED__ struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { return false; } /* ------------------------------------------------------------------------- */ /** Handler for losing keyboard focus. Nothing for default impl. */ void _wlmtk_element_keyboard_blur(__UNUSED__ wlmtk_element_t *element_ptr) { // Nothing. } /* ------------------------------------------------------------------------- */ /** Handler for keyboard events. By default: Nothing is handled. */ bool _wlmtk_element_keyboard_event( __UNUSED__ wlmtk_element_t *element_ptr, __UNUSED__ struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr) { return false; } /* ------------------------------------------------------------------------- */ /** Handler for translated keys. By default: Nothing is handled. */ bool _wlmtk_element_keyboard_sym( __UNUSED__ wlmtk_element_t *element_ptr, __UNUSED__ xkb_keysym_t keysym, __UNUSED__ enum xkb_key_direction direction, __UNUSED__ uint32_t modifiers) { return false; } /* ------------------------------------------------------------------------- */ /** Redraws the element contents. By default: Nothing is done. */ void _wlmtk_element_layout(__UNUSED__ wlmtk_element_t *element_ptr) { // Nothing. } /* ------------------------------------------------------------------------- */ /** * Handles the 'destroy' callback of the wlr_scene_node. * * A call here indicates that teardown was not executed properly! * * @param listener_ptr * @param data_ptr */ void handle_wlr_scene_node_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_element_t *element_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_element_t, wlr_scene_node_destroy_listener); bs_log(BS_FATAL, "Unexpected call into node's dtor on %p!", element_ptr); } /* == Fake element, useful for unit tests. ================================= */ static void fake_destroy(wlmtk_element_t *element_ptr); static struct wlr_scene_node *fake_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr); static void fake_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr); static bool fake_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr); static bool fake_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static bool fake_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); static void fake_pointer_grab_cancel( wlmtk_element_t *element_ptr); static void fake_keyboard_blur( wlmtk_element_t *element_ptr); static bool fake_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr); static bool fake_keyboard_sym( wlmtk_element_t *element_ptr, xkb_keysym_t keysym, enum xkb_key_direction direction, uint32_t modifiers); static void fake_layout( wlmtk_element_t *element_ptr); /** Virtual method table for the fake element. */ static const wlmtk_element_vmt_t fake_element_vmt = { .destroy = fake_destroy, .create_scene_node = fake_create_scene_node, .get_dimensions = fake_get_dimensions, .pointer_accepts_motion = fake_pointer_accepts_motion, .pointer_button = fake_pointer_button, .pointer_axis = fake_pointer_axis, .pointer_grab_cancel = fake_pointer_grab_cancel, .keyboard_blur = fake_keyboard_blur, .keyboard_event = fake_keyboard_event, .keyboard_sym = fake_keyboard_sym, .layout = fake_layout, }; /* ------------------------------------------------------------------------- */ wlmtk_fake_element_t *wlmtk_fake_element_create(void) { wlmtk_fake_element_t *fake_element_ptr = logged_calloc( 1, sizeof(wlmtk_fake_element_t)); if (NULL == fake_element_ptr) return NULL; if (!wlmtk_element_init(&fake_element_ptr->element)) { fake_destroy(&fake_element_ptr->element); return NULL; } fake_element_ptr->orig_vmt = wlmtk_element_extend( &fake_element_ptr->element, &fake_element_vmt); return fake_element_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_fake_element_set_dimensions( wlmtk_fake_element_t *fake_element_ptr, int width, int height) { fake_element_ptr->dimensions.width = width; fake_element_ptr->dimensions.height = height; wlmtk_element_invalidate_parent_layout(&fake_element_ptr->element); } /* ------------------------------------------------------------------------- */ void wlmtk_fake_element_grab_keyboard(wlmtk_fake_element_t *fake_element_ptr) { fake_element_ptr->has_keyboard_focus = true; if (NULL != fake_element_ptr->element.parent_container_ptr) { wlmtk_container_set_keyboard_focus_element( fake_element_ptr->element.parent_container_ptr, &fake_element_ptr->element, true); } } /* ------------------------------------------------------------------------- */ /** dtor for the "fake" element used for tests. */ void fake_destroy(wlmtk_element_t *element_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); wlmtk_element_fini(&fake_element_ptr->element); free(fake_element_ptr); } /* ------------------------------------------------------------------------- */ /** A "fake" 'create_scene_node': Creates a non-attached buffer node. */ struct wlr_scene_node *fake_create_scene_node( __UNUSED__ wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr) { struct wlr_scene_buffer *wlr_scene_buffer_ptr = wlr_scene_buffer_create( wlr_scene_tree_ptr, NULL); return &wlr_scene_buffer_ptr->node; } /* ------------------------------------------------------------------------- */ /** A "fake" 'get_dimensions'. */ void fake_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); if (NULL != left_ptr) *left_ptr = fake_element_ptr->dimensions.x; if (NULL != top_ptr) *top_ptr = fake_element_ptr->dimensions.y; if (NULL != right_ptr) *right_ptr = ( fake_element_ptr->dimensions.width + fake_element_ptr->dimensions.x); if (NULL != bottom_ptr) *bottom_ptr = ( fake_element_ptr->dimensions.height + fake_element_ptr->dimensions.y); } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_element_vmt_t::pointer_accepts_motion. */ bool fake_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); fake_element_ptr->pointer_accepts_motion_called = true; return (-1 <= motion_event_ptr->x && motion_event_ptr->x < fake_element_ptr->dimensions.width + 3 && -2 < motion_event_ptr->y && motion_event_ptr->y < fake_element_ptr->dimensions.height + 4); } /* ------------------------------------------------------------------------- */ /** Handles 'button' events for the fake element. */ bool fake_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); fake_element_ptr->pointer_button_called = true; fake_element_ptr->pointer_button_event = *button_event_ptr; return true; } /* ------------------------------------------------------------------------- */ /** Handles 'axis' events for the fake element. */ bool fake_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); fake_element_ptr->pointer_axis_called = true; fake_element_ptr->wlr_pointer_axis_event = *wlr_pointer_axis_event_ptr; return true; } /* ------------------------------------------------------------------------- */ /** Records calls to @ref wlmtk_element_vmt_t::pointer_grab_cancel. */ void fake_pointer_grab_cancel( wlmtk_element_t *element_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); fake_element_ptr->pointer_grab_cancel_called = true; } /* ------------------------------------------------------------------------- */ /** Registers losing keyboard focus. */ void fake_keyboard_blur(wlmtk_element_t *element_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); fake_element_ptr->has_keyboard_focus = false; } /* ------------------------------------------------------------------------- */ /** Handles 'keyboard_event' events for the fake element. */ bool fake_keyboard_event( wlmtk_element_t *element_ptr, __UNUSED__ struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); fake_element_ptr->keyboard_event_called = true; return true; } /* ------------------------------------------------------------------------- */ /** Handles 'keyboard_sym' events for the fake element. */ bool fake_keyboard_sym( wlmtk_element_t *element_ptr, __UNUSED__ xkb_keysym_t keysym, __UNUSED__ enum xkb_key_direction direction, __UNUSED__ uint32_t modifiers) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); fake_element_ptr->keyboard_sym_called = true; return true; } /* ------------------------------------------------------------------------- */ /** Handles 'laoyut' calls for the fake element. */ void fake_layout(wlmtk_element_t *element_ptr) { wlmtk_fake_element_t *fake_element_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_element_t, element); fake_element_ptr->layout_called = true; } /* == Unit tests =========================================================== */ static void test_init_fini(bs_test_t *test_ptr); static void test_set_parent_container(bs_test_t *test_ptr); static void test_set_get_position(bs_test_t *test_ptr); static void test_get_dimensions(bs_test_t *test_ptr); static void test_pointer_motion(bs_test_t *test_ptr); static void test_pointer_button(bs_test_t *test_ptr); static void test_pointer_axis(bs_test_t *test_ptr); static void test_keyboard_focus(bs_test_t *test_ptr); static void test_keyboard_activity(bs_test_t *test_ptr); static void test_layout(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_element_test_cases[] = { { 1, "init_fini", test_init_fini }, { 1, "set_parent_container", test_set_parent_container }, { 1, "set_get_position", test_set_get_position }, { 1, "get_dimensions", test_get_dimensions }, { 1, "pointer_motion", test_pointer_motion }, { 1, "pointer_button", test_pointer_button }, { 1, "pointer_axis", test_pointer_axis }, { 1, "keyboard_focus", test_keyboard_focus }, { 1, "keyboard_activity", test_keyboard_activity }, { 1, "layout", test_layout }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_element_test_set = BS_TEST_SET( true, "element", _wlmtk_element_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises init() and fini() methods, verifies dtor forwarding. */ void test_init_fini(bs_test_t *test_ptr) { wlmtk_element_t element; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_init(&element)); wlmtk_element_extend(&element, &fake_element_vmt); BS_TEST_VERIFY_NEQ(test_ptr, NULL, element.vmt.destroy); wlmtk_element_fini(&element); BS_TEST_VERIFY_EQ(test_ptr, NULL, element.vmt.destroy); } /* ------------------------------------------------------------------------- */ /** Tests set_parent_container, and that scene graph follows. */ void test_set_parent_container(bs_test_t *test_ptr) { wlmtk_element_t element; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_init(&element)); wlmtk_element_extend(&element, &fake_element_vmt); // Setting a parent without a scene graph tree will not set a node. wlmtk_container_t parent_no_tree; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_container_init(&parent_no_tree)); wlmtk_element_set_parent_container(&element, &parent_no_tree); BS_TEST_VERIFY_EQ(test_ptr, NULL, element.wlr_scene_node_ptr); wlmtk_element_set_parent_container(&element, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, element.wlr_scene_node_ptr); wlmtk_container_fini(&parent_no_tree); // Setting a parent with a tree must create & attach the node there. wlmtk_container_t *parent_ptr = wlmtk_container_create_fake_parent(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, parent_ptr); wlmtk_element_set_visible(&element, true); wlmtk_element_set_parent_container(&element, parent_ptr); BS_TEST_VERIFY_EQ( test_ptr, parent_ptr->wlr_scene_tree_ptr, element.wlr_scene_node_ptr->parent); BS_TEST_VERIFY_TRUE(test_ptr, element.wlr_scene_node_ptr->enabled); // Resetting the parent must also re-attach the node. wlmtk_container_t *other_parent_ptr = wlmtk_container_create_fake_parent(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, other_parent_ptr); wlmtk_element_set_parent_container(&element, other_parent_ptr); BS_TEST_VERIFY_EQ( test_ptr, other_parent_ptr->wlr_scene_tree_ptr, element.wlr_scene_node_ptr->parent); BS_TEST_VERIFY_TRUE(test_ptr, element.wlr_scene_node_ptr->enabled); // Clearing the parent most remove the node. wlmtk_element_set_parent_container(&element, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, element.wlr_scene_node_ptr); wlmtk_container_destroy_fake_parent(other_parent_ptr); wlmtk_container_destroy_fake_parent(parent_ptr); wlmtk_element_fini(&element); } /* ------------------------------------------------------------------------- */ /** Tests get_position and set_position, and that scene graph follows. */ void test_set_get_position(bs_test_t *test_ptr) { wlmtk_element_t element; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_init(&element)); wlmtk_element_extend(&element, &fake_element_vmt); // Exercise, must not crash. wlmtk_element_get_position(&element, NULL, NULL); int x, y; wlmtk_element_get_position(&element, &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 0, x); BS_TEST_VERIFY_EQ(test_ptr, 0, y); wlmtk_element_set_position(&element, 10, 20); wlmtk_element_get_position(&element, &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 10, x); BS_TEST_VERIFY_EQ(test_ptr, 20, y); wlmtk_container_t *fake_parent_ptr = wlmtk_container_create_fake_parent(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fake_parent_ptr); wlmtk_element_set_parent_container(&element, fake_parent_ptr); BS_TEST_VERIFY_EQ(test_ptr, 10, element.wlr_scene_node_ptr->x); BS_TEST_VERIFY_EQ(test_ptr, 20, element.wlr_scene_node_ptr->y); wlmtk_element_set_position(&element, 30, 40); BS_TEST_VERIFY_EQ(test_ptr, 30, element.wlr_scene_node_ptr->x); BS_TEST_VERIFY_EQ(test_ptr, 40, element.wlr_scene_node_ptr->y); wlr_scene_node_set_position(element.wlr_scene_node_ptr, 50, 60); wlmtk_element_get_position(&element, &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 50, x); BS_TEST_VERIFY_EQ(test_ptr, 60, y); wlmtk_element_set_parent_container(&element, NULL); wlmtk_element_fini(&element); wlmtk_container_destroy_fake_parent(fake_parent_ptr); } /* ------------------------------------------------------------------------- */ /** Tests get_dimensions. */ void test_get_dimensions(bs_test_t *test_ptr) { wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); fake_element_ptr->dimensions.x = -10; fake_element_ptr->dimensions.y = -20; fake_element_ptr->dimensions.width = 42; fake_element_ptr->dimensions.height = 21; // Must not crash. wlmtk_element_get_dimensions( &fake_element_ptr->element, NULL, NULL, NULL, NULL); int top, left, right, bottom; wlmtk_element_get_dimensions( &fake_element_ptr->element, &top, &left, &right, &bottom); BS_TEST_VERIFY_EQ(test_ptr, -10, top); BS_TEST_VERIFY_EQ(test_ptr, -20, left); BS_TEST_VERIFY_EQ(test_ptr, 32, right); BS_TEST_VERIFY_EQ(test_ptr, 1, bottom); struct wlr_box box = wlmtk_element_get_dimensions_box( &fake_element_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, -10, box.x); BS_TEST_VERIFY_EQ(test_ptr, -20, box.y); BS_TEST_VERIFY_EQ(test_ptr, 42, box.width); BS_TEST_VERIFY_EQ(test_ptr, 21, box.height); wlmtk_element_destroy(&fake_element_ptr->element); } /* ------------------------------------------------------------------------- */ /** Verifies that @ref wlmtk_element_pointer_motion raises signals. */ void test_pointer_motion(bs_test_t *test_ptr) { wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fake_element_ptr); wlmtk_util_test_listener_t enter = {}, leave = {}, motion = {}; wlmtk_util_connect_test_listener( &fake_element_ptr->element.events.pointer_enter, &enter); wlmtk_util_connect_test_listener( &fake_element_ptr->element.events.pointer_leave, &leave); wlmtk_util_connect_test_listener( &fake_element_ptr->element.events.pointer_motion, &motion); BS_TEST_VERIFY_FALSE(test_ptr, fake_element_ptr->element.pointer_inside); // First motion, in the element. But invisible. Must not do anything. wlmtk_pointer_motion_event_t e = { .x = 1.0, .y = 2.0, .time_msec = 3 }; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_motion(&fake_element_ptr->element, &e)); BS_TEST_VERIFY_EQ(test_ptr, 0, enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, motion.calls); // Now make it visible. Must trigger 'enter' and 'motion'. wlmtk_element_set_visible(&fake_element_ptr->element, true); e.pointer_ptr = (wlmtk_pointer_t*)0x1234; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&fake_element_ptr->element, &e)) BS_TEST_VERIFY_EQ(test_ptr, 1, enter.calls); BS_TEST_VERIFY_EQ(test_ptr, e.pointer_ptr, enter.last_data_ptr); BS_TEST_VERIFY_EQ(test_ptr, 0, leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, motion.calls); BS_TEST_VERIFY_EQ(test_ptr, &e, motion.last_data_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ( test_ptr, 1.0, fake_element_ptr->element.last_pointer_motion_event.x); BS_TEST_VERIFY_EQ( test_ptr, 2.0, fake_element_ptr->element.last_pointer_motion_event.y); BS_TEST_VERIFY_EQ( test_ptr, 3, fake_element_ptr->element.last_pointer_motion_event.time_msec); // Another motion, within. Trigger just 'motion'. e = (wlmtk_pointer_motion_event_t){ .x = 1.0, .y = 2.0, .time_msec = 4 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&fake_element_ptr->element, &e)); BS_TEST_VERIFY_EQ(test_ptr, 1, enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, motion.calls); BS_TEST_VERIFY_EQ(test_ptr, &e, motion.last_data_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->element.pointer_inside); // Another motion, outisde. Trigger 'leave'. e = (wlmtk_pointer_motion_event_t){ .x = 10, .y = 20, .time_msec = 5 }; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_motion(&fake_element_ptr->element, &e)); BS_TEST_VERIFY_EQ(test_ptr, 1, enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, leave.calls); BS_TEST_VERIFY_EQ(test_ptr, NULL, leave.last_data_ptr); BS_TEST_VERIFY_EQ(test_ptr, 2, motion.calls); BS_TEST_VERIFY_EQ(test_ptr, &e, motion.last_data_ptr); BS_TEST_VERIFY_FALSE(test_ptr, fake_element_ptr->element.pointer_inside); wlmtk_util_disconnect_test_listener(&motion); wlmtk_util_disconnect_test_listener(&leave); wlmtk_util_disconnect_test_listener(&enter); wlmtk_element_destroy(&fake_element_ptr->element); } /* ------------------------------------------------------------------------- */ /** Exercises "pointer_button" method. */ void test_pointer_button(bs_test_t *test_ptr) { wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fake_element_ptr); wlmtk_button_event_t event = {}; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(&fake_element_ptr->element, &event)); BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->pointer_button_called); wlmtk_element_destroy(&fake_element_ptr->element); } /* ------------------------------------------------------------------------- */ /** Exercises "pointer_axis" method. */ void test_pointer_axis(bs_test_t *test_ptr) { wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fake_element_ptr); struct wlr_pointer_axis_event event = {}; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_axis(&fake_element_ptr->element, &event)); BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->pointer_axis_called); wlmtk_element_destroy(&fake_element_ptr->element); } /* ------------------------------------------------------------------------- */ /** Exercises keyboard grab & blur methods. */ void test_keyboard_focus(bs_test_t *test_ptr) { wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fake_element_ptr); wlmtk_fake_element_grab_keyboard(fake_element_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->has_keyboard_focus); wlmtk_element_keyboard_blur(&fake_element_ptr->element); BS_TEST_VERIFY_FALSE(test_ptr, fake_element_ptr->has_keyboard_focus); wlmtk_element_destroy(&fake_element_ptr->element); } /* ------------------------------------------------------------------------- */ /** Exercises "keyboard_event" and "keyboard_sym" methods. */ void test_keyboard_activity(bs_test_t *test_ptr) { wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fake_element_ptr); struct wlr_keyboard_key_event event = {}; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_event(&fake_element_ptr->element, &event)); BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->keyboard_event_called); BS_TEST_VERIFY_FALSE(test_ptr, fake_element_ptr->keyboard_sym_called); fake_element_ptr->keyboard_event_called = false; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(&fake_element_ptr->element, 0, 0, 0)); BS_TEST_VERIFY_FALSE(test_ptr, fake_element_ptr->keyboard_event_called); BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->keyboard_sym_called); wlmtk_element_destroy(&fake_element_ptr->element); } /* ------------------------------------------------------------------------- */ /** Exercises "layout" methods. */ void test_layout(bs_test_t *test_ptr) { wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fake_element_ptr); wlmtk_element_layout(&fake_element_ptr->element); BS_TEST_VERIFY_TRUE(test_ptr, fake_element_ptr->layout_called); wlmtk_element_destroy(&fake_element_ptr->element); } /* == End of toolkit.c ===================================================== */ wlmaker-0.8.1/src/toolkit/tile.c0000644000175100017510000002521015222270710016143 0ustar runnerrunner/* ========================================================================= */ /** * @file tile.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "tile.h" #include #include #include #include #include #include "gfxbuf.h" // IWYU pragma: keep #include "primitives.h" #include "style.h" /* == Declarations ========================================================= */ static struct wlr_buffer *_wlmtk_tile_create_buffer( const struct wlmtk_tile_style *style_ptr); static void _wlmtk_tile_align_content(wlmtk_tile_t *tile_ptr); /* == Data ================================================================= */ const bspl_desc_t wlmtk_tile_style_desc[] = { BSPL_DESC_UINT64( "Size", true, struct wlmtk_tile_style, size, size, 64), BSPL_DESC_UINT64( "ContentSize", true, struct wlmtk_tile_style, content_size, content_size, 48), BSPL_DESC_UINT64( "BezelWidth", true, struct wlmtk_tile_style, bezel_width, bezel_width, 2), BSPL_DESC_CUSTOM( "Fill", true, struct wlmtk_tile_style, fill, fill, wlmtk_style_decode_fill, NULL, NULL, NULL), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_tile_init( wlmtk_tile_t *tile_ptr, const struct wlmtk_tile_style *style_ptr) { *tile_ptr = (wlmtk_tile_t){ .style = *style_ptr }; if (!wlmtk_container_init(&tile_ptr->super_container)) { wlmtk_tile_fini(tile_ptr); return false; } if (!wlmtk_buffer_init(&tile_ptr->buffer)) { wlmtk_tile_fini(tile_ptr); return false; } wlmtk_element_set_visible(wlmtk_buffer_element(&tile_ptr->buffer), true); wlmtk_container_add_element( &tile_ptr->super_container, wlmtk_buffer_element(&tile_ptr->buffer)); struct wlr_buffer *wlr_buffer_ptr = _wlmtk_tile_create_buffer( &tile_ptr->style); if (NULL == wlr_buffer_ptr) { wlmtk_tile_fini(tile_ptr); return false; } wlmtk_tile_set_background_buffer(tile_ptr, wlr_buffer_ptr); wlr_buffer_drop(wlr_buffer_ptr); return true; } /* ------------------------------------------------------------------------- */ wlmtk_tile_vmt_t wlmtk_tile_extend( wlmtk_tile_t *tile_ptr, const wlmtk_tile_vmt_t *tile_vmt_ptr) { wlmtk_tile_vmt_t orig_vmt = tile_ptr->vmt; if (NULL != tile_vmt_ptr->set_content_size) { tile_ptr->vmt.set_content_size = tile_vmt_ptr->set_content_size; } return orig_vmt; } /* ------------------------------------------------------------------------- */ void wlmtk_tile_fini(wlmtk_tile_t *tile_ptr) { if (NULL != tile_ptr->background_wlr_buffer_ptr) { wlr_buffer_unlock(tile_ptr->background_wlr_buffer_ptr); tile_ptr->background_wlr_buffer_ptr = NULL; } if (wlmtk_buffer_element(&tile_ptr->buffer)->parent_container_ptr) { wlmtk_container_remove_element( &tile_ptr->super_container, wlmtk_buffer_element(&tile_ptr->buffer)); wlmtk_buffer_fini(&tile_ptr->buffer); } wlmtk_container_fini(&tile_ptr->super_container); } /* ------------------------------------------------------------------------- */ bool wlmtk_tile_set_style(wlmtk_tile_t *tile_ptr, const struct wlmtk_tile_style *style_ptr) { // Update buffer. struct wlr_buffer *wlr_buffer_ptr = _wlmtk_tile_create_buffer(style_ptr); if (NULL == wlr_buffer_ptr) return false; tile_ptr->style = *style_ptr; bool rv = wlmtk_tile_set_background_buffer(tile_ptr, wlr_buffer_ptr); wlr_buffer_drop(wlr_buffer_ptr); if (!rv) return false; if (NULL != tile_ptr->vmt.set_content_size) { tile_ptr->vmt.set_content_size(tile_ptr, style_ptr->content_size); } _wlmtk_tile_align_content(tile_ptr); wlmtk_element_invalidate_parent_layout(wlmtk_tile_element(tile_ptr)); return true; } /* ------------------------------------------------------------------------- */ bool wlmtk_tile_set_background_buffer( wlmtk_tile_t *tile_ptr, struct wlr_buffer *wlr_buffer_ptr) { if (tile_ptr->style.size != (uint64_t)wlr_buffer_ptr->width || tile_ptr->style.size != (uint64_t)wlr_buffer_ptr->height) return false; if (NULL != tile_ptr->background_wlr_buffer_ptr) { wlr_buffer_unlock(tile_ptr->background_wlr_buffer_ptr); } tile_ptr->background_wlr_buffer_ptr = wlr_buffer_lock(wlr_buffer_ptr); wlmtk_buffer_set(&tile_ptr->buffer, tile_ptr->background_wlr_buffer_ptr); return true; } /* ------------------------------------------------------------------------- */ void wlmtk_tile_set_content( wlmtk_tile_t *tile_ptr, wlmtk_element_t *element_ptr) { if (element_ptr != tile_ptr->content_element_ptr) { if (NULL != tile_ptr->content_element_ptr) { wlmtk_container_remove_element( &tile_ptr->super_container, tile_ptr->content_element_ptr); tile_ptr->content_element_ptr = NULL; } if (NULL != element_ptr) { wlmtk_container_add_element_atop( &tile_ptr->super_container, wlmtk_buffer_element(&tile_ptr->buffer), element_ptr); tile_ptr->content_element_ptr = element_ptr; } } _wlmtk_tile_align_content(tile_ptr); wlmtk_element_invalidate_parent_layout(wlmtk_tile_element(tile_ptr)); } /* ------------------------------------------------------------------------- */ void wlmtk_tile_set_overlay( wlmtk_tile_t *tile_ptr, wlmtk_element_t *element_ptr) { if (element_ptr == tile_ptr->overlay_element_ptr) return; if (NULL != tile_ptr->overlay_element_ptr) { wlmtk_container_remove_element( &tile_ptr->super_container, tile_ptr->overlay_element_ptr); tile_ptr->overlay_element_ptr = NULL; } if (NULL != element_ptr) { wlmtk_container_add_element(&tile_ptr->super_container, element_ptr); tile_ptr->overlay_element_ptr = element_ptr; struct wlr_box box = wlmtk_element_get_dimensions_box(element_ptr); if ((unsigned)box.width > tile_ptr->style.size || (unsigned)box.height > tile_ptr->style.size) { bs_log(BS_WARNING, "Overlay size %d x %d > tile size %"PRIu64, box.width, box.height, tile_ptr->style.size); } wlmtk_element_set_position( element_ptr, ((int)tile_ptr->style.size - box.width) / 2, ((int)tile_ptr->style.size - box.height) / 2); } } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_tile_element(wlmtk_tile_t *tile_ptr) { return &tile_ptr->super_container.super_element; } /* ------------------------------------------------------------------------- */ wlmtk_tile_t *wlmtk_tile_from_dlnode(bs_dllist_node_t *dlnode_ptr) { return BS_CONTAINER_OF(dlnode_ptr, wlmtk_tile_t, dlnode); } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *wlmtk_dlnode_from_tile(wlmtk_tile_t *tile_ptr) { return &tile_ptr->dlnode; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Crates a wlr_buffer with background, as described in `style_ptr`. */ struct wlr_buffer *_wlmtk_tile_create_buffer( const struct wlmtk_tile_style *style_ptr) { struct wlr_buffer* wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( style_ptr->size, style_ptr->size); if (NULL == wlr_buffer_ptr) return NULL; cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); return NULL; } wlmaker_primitives_cairo_fill(cairo_ptr, &style_ptr->fill); wlmaker_primitives_draw_bezel(cairo_ptr, style_ptr->bezel_width, true); cairo_destroy(cairo_ptr); return wlr_buffer_ptr; } /* ------------------------------------------------------------------------- */ /** (re)centers the content element. */ void _wlmtk_tile_align_content(wlmtk_tile_t *tile_ptr) { wlmtk_element_t *element_ptr = tile_ptr->content_element_ptr; if (NULL == element_ptr) return; struct wlr_box box = wlmtk_element_get_dimensions_box(element_ptr); if ((unsigned)box.width > tile_ptr->style.size || (unsigned)box.height > tile_ptr->style.size) { bs_log(BS_WARNING, "Content size %d x %d > tile size %"PRIu64, box.width, box.height, tile_ptr->style.size); } wlmtk_element_set_position( element_ptr, ((int)tile_ptr->style.size - box.width) / 2, ((int)tile_ptr->style.size - box.height) / 2); } /* == Unit tests =========================================================== */ static void test_init_fini(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_tile_test_cases[] = { { 1, "init_fini", test_init_fini }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_tile_test_set = BS_TEST_SET( true, "tile", _wlmtk_tile_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises setup and teardown. */ static void test_init_fini(bs_test_t *test_ptr) { wlmtk_tile_t tile; struct wlmtk_tile_style style = { .size = 64 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_tile_init(&tile, &style)); BS_TEST_VERIFY_EQ( test_ptr, &tile.super_container.super_element, wlmtk_tile_element(&tile)); // Adds content and verifies it's centered. wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); fe_ptr->dimensions.width = 48; fe_ptr->dimensions.height = 36; wlmtk_tile_set_content(&tile, &fe_ptr->element); int x, y; wlmtk_element_get_position(&fe_ptr->element, &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 8, x); BS_TEST_VERIFY_EQ(test_ptr, 14, y); wlmtk_tile_fini(&tile); } /* == End of tile.c ======================================================== */ wlmaker-0.8.1/src/toolkit/input.c0000644000175100017510000000724615222270710016356 0ustar runnerrunner/* ========================================================================= */ /** * @file input.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "input.h" #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE /* == Declarations ========================================================= */ /** Pointer handle. Wraps to wlr_cursor. */ struct _wlmtk_pointer_t { /** Points to a `wlr_cursor`. */ struct wlr_cursor *wlr_cursor_ptr; /** Points to a `wlr_xcursor_manager`. */ struct wlr_xcursor_manager *wlr_xcursor_manager_ptr; /** Store the current pointer, to reset when we change style. */ wlmtk_pointer_cursor_t cursor; }; /** Lookup table for XCursor names. */ static const char* _wlmtk_pointer_cursor_names[] = { [WLMTK_POINTER_CURSOR_DEFAULT] = "default", [WLMTK_POINTER_CURSOR_RESIZE_S] = "s-resize", [WLMTK_POINTER_CURSOR_RESIZE_SE] = "se-resize", [WLMTK_POINTER_CURSOR_RESIZE_SW] = "sw-resize", [WLMTK_POINTER_CURSOR_MAX] = "default", }; /* == Data ================================================================= */ /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_pointer_t *wlmtk_pointer_create( struct wlr_cursor *wlr_cursor_ptr, struct wlr_xcursor_manager *wlr_xcursor_manager_ptr) { wlmtk_pointer_t *pointer_ptr = logged_calloc(1, sizeof(wlmtk_pointer_t)); if (NULL == pointer_ptr) return NULL; pointer_ptr->cursor = WLMTK_POINTER_CURSOR_DEFAULT; pointer_ptr->wlr_cursor_ptr = wlr_cursor_ptr; pointer_ptr->wlr_xcursor_manager_ptr = wlr_xcursor_manager_ptr; return pointer_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_pointer_destroy(wlmtk_pointer_t *pointer_ptr) { free(pointer_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_pointer_set_cursor( wlmtk_pointer_t *pointer_ptr, wlmtk_pointer_cursor_t cursor) { // TODO(kaeser@gubbe.ch): There should be ASSERT for this to NOT be NULL. if (NULL == pointer_ptr || NULL == pointer_ptr->wlr_cursor_ptr || NULL == pointer_ptr->wlr_xcursor_manager_ptr) return; if (cursor < 0 || cursor >= WLMTK_POINTER_CURSOR_MAX) return; wlr_cursor_set_xcursor( pointer_ptr->wlr_cursor_ptr, pointer_ptr->wlr_xcursor_manager_ptr, _wlmtk_pointer_cursor_names[cursor]); pointer_ptr->cursor = cursor; } /* ------------------------------------------------------------------------- */ void wlmtk_pointer_set_xcursor_manager( wlmtk_pointer_t *pointer_ptr, struct wlr_xcursor_manager *wxm_ptr) { pointer_ptr->wlr_xcursor_manager_ptr = wxm_ptr; wlr_cursor_set_xcursor( pointer_ptr->wlr_cursor_ptr, wxm_ptr, _wlmtk_pointer_cursor_names[pointer_ptr->cursor]); } /* == End of input.c ======================================================= */ wlmaker-0.8.1/src/toolkit/primitives.c0000644000175100017510000004020115222270710017376 0ustar runnerrunner/* ========================================================================= */ /** * @file primitives.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "primitives.h" #include #include /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ void wlmaker_primitives_cairo_fill( cairo_t *cairo_ptr, const wlmtk_style_fill_t *fill_ptr) { cairo_surface_t *surface_ptr = cairo_get_target(cairo_ptr); uint32_t width = cairo_image_surface_get_width(surface_ptr); uint32_t height = cairo_image_surface_get_height(surface_ptr); wlmaker_primitives_cairo_fill_at(cairo_ptr, 0, 0, width, height, fill_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_primitives_cairo_fill_at( cairo_t *cairo_ptr, int x, int y, unsigned width, unsigned height, const wlmtk_style_fill_t *fill_ptr) { cairo_pattern_t *cairo_pattern_ptr; float r, g, b, alpha; switch (fill_ptr->type) { case WLMTK_STYLE_COLOR_SOLID: bs_gfxbuf_argb8888_to_floats( fill_ptr->param.solid.color, &r, &g, &b, &alpha); cairo_pattern_ptr = cairo_pattern_create_rgba(r, g, b, alpha); break; case WLMTK_STYLE_COLOR_HGRADIENT: cairo_pattern_ptr = cairo_pattern_create_linear(0, 0, width, 0); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.hgradient.from, &r, &g, &b, &alpha); cairo_pattern_add_color_stop_rgba( cairo_pattern_ptr, 0, r, g, b, alpha); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.hgradient.to, &r, &g, &b, &alpha); cairo_pattern_add_color_stop_rgba( cairo_pattern_ptr, 1, r, g, b, alpha); break; case WLMTK_STYLE_COLOR_VGRADIENT: cairo_pattern_ptr = cairo_pattern_create_linear(0, 0, 0, height); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.vgradient.from, &r, &g, &b, &alpha); cairo_pattern_add_color_stop_rgba( cairo_pattern_ptr, 0, r, g, b, alpha); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.vgradient.to, &r, &g, &b, &alpha); cairo_pattern_add_color_stop_rgba( cairo_pattern_ptr, 1, r, g, b, alpha); break; case WLMTK_STYLE_COLOR_DGRADIENT: cairo_pattern_ptr = cairo_pattern_create_linear(0, 0, width, height); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.dgradient.from, &r, &g, &b, &alpha); cairo_pattern_add_color_stop_rgba( cairo_pattern_ptr, 0, r, g, b, alpha); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.dgradient.to, &r, &g, &b, &alpha); cairo_pattern_add_color_stop_rgba( cairo_pattern_ptr, 1, r, g, b, alpha); break; case WLMTK_STYLE_COLOR_ADGRADIENT: { // Some geometry needed to compute the destination point for cairo's // interpolation. It is on the line that crosses the bottom-right // corner and lies parallel to the top-right -> bottom-left diaginal; // and on a perpendicular intersection from the top-left corner. double x = 2 * height * height * width / BS_MAX(1.0, width * width + height * height); double y = 2 * height * width * width / BS_MAX(1.0, width * width + height * height); cairo_pattern_ptr = cairo_pattern_create_linear(0, 0, x, y); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.dgradient.from, &r, &g, &b, &alpha); cairo_pattern_add_color_stop_rgba( cairo_pattern_ptr, 0, r, g, b, alpha); bs_gfxbuf_argb8888_to_floats( fill_ptr->param.dgradient.to, &r, &g, &b, &alpha); cairo_pattern_add_color_stop_rgba( cairo_pattern_ptr, 1, r, g, b, alpha); } break; default: bs_log(BS_FATAL, "Unsupported fill_type %d", fill_ptr->type); BS_ABORT(); return; } cairo_save(cairo_ptr); cairo_set_source(cairo_ptr, cairo_pattern_ptr); cairo_pattern_destroy(cairo_pattern_ptr); cairo_rectangle(cairo_ptr, x, y, width, height); cairo_fill(cairo_ptr); cairo_restore(cairo_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_primitives_set_bezel_color( cairo_t *cairo_ptr, bool illuminated) { if (illuminated) { cairo_set_source_rgba(cairo_ptr, 1.0, 1.0, 1.0, 0.6); } else { cairo_set_source_rgba(cairo_ptr, 0.0, 0.0, 0.0, 0.4); } } /* ------------------------------------------------------------------------- */ void wlmaker_primitives_draw_bezel( cairo_t *cairo_ptr, double bezel_width, bool raised) { int width = cairo_image_surface_get_width(cairo_get_target(cairo_ptr)); int height = cairo_image_surface_get_height(cairo_get_target(cairo_ptr)); wlmaker_primitives_draw_bezel_at( cairo_ptr, 0, 0, width, height, bezel_width, raised); } /* ------------------------------------------------------------------------- */ void wlmaker_primitives_draw_bezel_at( cairo_t *cairo_ptr, int x, int y, unsigned width, unsigned height, double bezel_width, bool raised) { cairo_save(cairo_ptr); cairo_set_line_width(cairo_ptr, 0); // Northwestern corner is illuminted when raised. wlmaker_primitives_set_bezel_color(cairo_ptr, raised); cairo_move_to(cairo_ptr, x, y); cairo_line_to(cairo_ptr, x + width, y + 0); cairo_line_to(cairo_ptr, x + width - bezel_width, y + bezel_width); cairo_line_to(cairo_ptr, x + bezel_width, y + bezel_width); cairo_line_to(cairo_ptr, x + bezel_width, y + height - bezel_width); cairo_line_to(cairo_ptr, x + 0, y + height); cairo_line_to(cairo_ptr, x + 0, y + 0); cairo_fill(cairo_ptr); // Southeastern corner is illuminated when sunken. wlmaker_primitives_set_bezel_color(cairo_ptr, !raised); cairo_move_to(cairo_ptr, x + width, y + height); cairo_line_to(cairo_ptr, x + 0, y + height); cairo_line_to(cairo_ptr, x + bezel_width, y + height - bezel_width); cairo_line_to(cairo_ptr, x + width - bezel_width, y + height - bezel_width); cairo_line_to(cairo_ptr, x + width - bezel_width, y + bezel_width); cairo_line_to(cairo_ptr, x + width, y + 0); cairo_line_to(cairo_ptr, x + width, y + height); cairo_fill(cairo_ptr); cairo_restore(cairo_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_primitives_draw_minimize_icon( cairo_t *cairo_ptr, int size, uint32_t color) { double scale = size / 22.0; cairo_save(cairo_ptr); cairo_set_line_width(cairo_ptr, 0); cairo_set_source_argb8888(cairo_ptr, color); cairo_set_fill_rule(cairo_ptr, CAIRO_FILL_RULE_EVEN_ODD); cairo_rectangle(cairo_ptr, 6 * scale, 6 * scale, 10 * scale, 10 * scale); cairo_rectangle(cairo_ptr, (6+ 1) * scale, (6 + 3) * scale, 8 * scale, 6 * scale); cairo_fill(cairo_ptr); cairo_stroke(cairo_ptr); cairo_restore(cairo_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_primitives_draw_close_icon( cairo_t *cairo_ptr, int size, uint32_t color) { double scale = size / 22.0; cairo_save(cairo_ptr); cairo_set_line_width(cairo_ptr, 2.5 * scale); cairo_set_line_cap(cairo_ptr, CAIRO_LINE_CAP_ROUND); cairo_set_source_argb8888(cairo_ptr, color); cairo_move_to(cairo_ptr, 7 * scale, 7 * scale); cairo_line_to(cairo_ptr, (7 + 8) * scale, (7 + 8) * scale); cairo_move_to(cairo_ptr, (7 + 8) * scale, 7 * scale); cairo_line_to(cairo_ptr, 7 * scale, (7 + 8) * scale); cairo_stroke(cairo_ptr); cairo_stroke(cairo_ptr); cairo_restore(cairo_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_primitives_draw_window_title( cairo_t *cairo_ptr, const wlmtk_style_font_t *font_style_ptr, const char *title_ptr, uint32_t color) { wlmaker_primitives_draw_text( cairo_ptr, 6, 2 + font_style_ptr->size, font_style_ptr, color, title_ptr ? title_ptr : "Unnamed"); } /* ------------------------------------------------------------------------- */ void wlmaker_primitives_draw_text( cairo_t *cairo_ptr, int x, int y, const wlmtk_style_font_t *font_style_ptr, uint32_t color, const char *text_ptr) { cairo_save(cairo_ptr); cairo_select_font_face( cairo_ptr, font_style_ptr->face, CAIRO_FONT_SLANT_NORMAL, wlmtk_style_font_weight_cairo_from_wlmtk(font_style_ptr->weight)); cairo_set_font_size(cairo_ptr, font_style_ptr->size); cairo_set_source_argb8888(cairo_ptr, color); cairo_move_to(cairo_ptr, x, y); cairo_show_text(cairo_ptr, text_ptr); cairo_restore(cairo_ptr); } /* == Local (static) methods =============================================== */ static void test_fill(bs_test_t *test_ptr); static void test_close(bs_test_t *test_ptr); static void test_close_large(bs_test_t *test_ptr); static void test_minimize(bs_test_t *test_ptr); static void test_minimize_large(bs_test_t *test_ptr); static void test_text(bs_test_t *test_ptr); static void test_window_title(bs_test_t *test_ptr); /** Unit tests. */ /** Test cases */ static const bs_test_case_t _wlmaker_primitives_test_cases[] = { { 1, "fill", test_fill }, { 1, "close", test_close }, { 1, "close_large", test_close_large }, { 1, "minimize", test_minimize }, { 1, "minimize_large", test_minimize_large }, // TODO(kaeser@gubbe.ch): Re-enable, once figuring out why these fail on // Trixie when running as a github action. { 0, "text", test_text }, { 0, "window_title", test_window_title }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_primitives_test_set = BS_TEST_SET( true, "primitives", _wlmaker_primitives_test_cases); /** Verifies the fill styles */ void test_fill(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(16, 8); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, gfxbuf_ptr); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); // Solid fill. wlmtk_style_fill_t fill_solid = { .type = WLMTK_STYLE_COLOR_SOLID, .param = { .solid = { .color = 0xff4080c0} } }; wlmaker_primitives_cairo_fill(cairo_ptr, &fill_solid); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_fill_solid.png"); // Horizontal gradient fill. wlmtk_style_fill_t fill_hgradient = { .type = WLMTK_STYLE_COLOR_HGRADIENT, .param = { .hgradient = { .from = 0xff102040, .to = 0xff4080ff }} }; wlmaker_primitives_cairo_fill(cairo_ptr, &fill_hgradient); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_fill_hgradient.png"); // Vertical gradient fill. wlmtk_style_fill_t fill_vgradient = { .type = WLMTK_STYLE_COLOR_VGRADIENT, .param = { .vgradient = { .from = 0xff102040, .to = 0xff4080ff }} }; wlmaker_primitives_cairo_fill(cairo_ptr, &fill_vgradient); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_fill_vgradient.png"); // Diagonal fill, cairo style. wlmtk_style_fill_t fill_dgradient = { .type = WLMTK_STYLE_COLOR_DGRADIENT, .param = { .dgradient = { .from = 0xff102040, .to = 0xff4080ff }} }; wlmaker_primitives_cairo_fill(cairo_ptr, &fill_dgradient); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_fill_dgradient.png"); // Diagonal fill, Window Maker styile. wlmtk_style_fill_t fill_adgradient = { .type = WLMTK_STYLE_COLOR_ADGRADIENT, .param = { .dgradient = { .from = 0xff102040, .to = 0xff4080ff }} }; wlmaker_primitives_cairo_fill(cairo_ptr, &fill_adgradient); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_fill_adgradient.png"); cairo_destroy(cairo_ptr); bs_gfxbuf_destroy(gfxbuf_ptr); } /** Verifies the looks of the "close" icon. */ void test_close(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(22, 22); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, cairo_ptr); wlmaker_primitives_draw_close_icon(cairo_ptr, 22, 0xffffffff); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_close_icon.png"); cairo_destroy(cairo_ptr); bs_gfxbuf_destroy(gfxbuf_ptr); } /** Verifies the looks of the "close" icon, with non-default size. */ void test_close_large(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(50, 50); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, cairo_ptr); wlmaker_primitives_draw_close_icon(cairo_ptr, 50, 0xffffffff); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_close_icon_large.png"); cairo_destroy(cairo_ptr); bs_gfxbuf_destroy(gfxbuf_ptr); } /** Verifies the looks of the "minimize" icon. */ void test_minimize(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(22, 22); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, cairo_ptr); wlmaker_primitives_draw_minimize_icon(cairo_ptr, 22, 0xffffffff); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_minimize_icon.png"); cairo_destroy(cairo_ptr); bs_gfxbuf_destroy(gfxbuf_ptr); } /** Verifies the looks of the "minimize" icon, widht non-default size. */ void test_minimize_large(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(50, 50); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, cairo_ptr); wlmaker_primitives_draw_minimize_icon(cairo_ptr, 50, 0xffffffff); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_minimize_icon_large.png"); cairo_destroy(cairo_ptr); bs_gfxbuf_destroy(gfxbuf_ptr); } /** Verifies drawing a text. */ void test_text(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(80, 20); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, gfxbuf_ptr); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, cairo_ptr); static const wlmtk_style_font_t font_style = { .face = "Helvetica", .weight = WLMTK_FONT_WEIGHT_BOLD, .size = 14, }; wlmaker_primitives_draw_text( cairo_ptr, 8, 15, &font_style, 0xffc0d0e0, "Test Text"); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_text.png"); cairo_destroy(cairo_ptr); bs_gfxbuf_destroy(gfxbuf_ptr); } /** Verifies the looks of the window title. */ void test_window_title(bs_test_t *test_ptr) { bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(80, 22); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, cairo_ptr); static const wlmtk_style_font_t font_style = { .face = "Helvetica", .weight = WLMTK_FONT_WEIGHT_BOLD, .size = 15, }; wlmaker_primitives_draw_window_title( cairo_ptr, &font_style, "Title", 0xffffffff); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, gfxbuf_ptr, "toolkit/primitive_window_title.png"); cairo_destroy(cairo_ptr); bs_gfxbuf_destroy(gfxbuf_ptr); } /* == End of primitives.c ================================================== */ wlmaker-0.8.1/src/toolkit/util.c0000644000175100017510000002257515222270710016176 0ustar runnerrunner/* ========================================================================= */ /** * @file util.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "util.h" #include #include #include #include "test.h" // IWYU pragma: keep /* == Declarations ========================================================= */ static void _wlmtk_util_test_listener_handler( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_util_test_wlr_box_listener_handler( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_util_wl_list_for_each( struct wl_list *list_ptr, bool (*func)(struct wl_list *link_ptr, void *ud_ptr), void *ud_ptr) { bool rv = true; if (NULL != list_ptr && NULL != list_ptr->next) { struct wl_list *link_ptr, *next_link_ptr; for (link_ptr = list_ptr->next, next_link_ptr = link_ptr->next; link_ptr != list_ptr; link_ptr = next_link_ptr, next_link_ptr = next_link_ptr->next) { if (!func(link_ptr, ud_ptr)) rv = false; } } return rv; } /* ------------------------------------------------------------------------- */ void wlmtk_util_connect_listener_signal( struct wl_signal *signal_ptr, struct wl_listener *listener_ptr, void (*notifier_func)(struct wl_listener *, void *)) { listener_ptr->notify = notifier_func; wl_signal_add(signal_ptr, listener_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_util_disconnect_listener( struct wl_listener *listener_ptr) { // Guard clause: No disconnect if it hadn't been connected. if (NULL == listener_ptr || NULL == listener_ptr->link.prev) return; wl_list_remove(&listener_ptr->link); } /* ------------------------------------------------------------------------- */ void wlmtk_util_connect_test_listener( struct wl_signal *signal_ptr, wlmtk_util_test_listener_t *test_listener_ptr) { wlmtk_util_connect_listener_signal( signal_ptr, &test_listener_ptr->listener, _wlmtk_util_test_listener_handler); wlmtk_util_clear_test_listener(test_listener_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_util_clear_test_listener( wlmtk_util_test_listener_t *test_listener_ptr) { test_listener_ptr->calls = 0; test_listener_ptr->last_data_ptr = NULL; } /* ------------------------------------------------------------------------- */ void wlmtk_util_disconnect_test_listener( wlmtk_util_test_listener_t *test_listener_ptr) { wlmtk_util_disconnect_listener(&test_listener_ptr->listener); } /* ------------------------------------------------------------------------- */ void wlmtk_util_connect_test_wlr_box_listener( struct wl_signal *signal_ptr, wlmtk_util_test_wlr_box_listener_t *test_wlr_box_listener_ptr) { wlmtk_util_connect_listener_signal( signal_ptr, &test_wlr_box_listener_ptr->listener, _wlmtk_util_test_wlr_box_listener_handler); wlmtk_util_clear_test_wlr_box_listener(test_wlr_box_listener_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_util_clear_test_wlr_box_listener( wlmtk_util_test_wlr_box_listener_t *test_wlr_box_listener_ptr) { test_wlr_box_listener_ptr->calls = 0; test_wlr_box_listener_ptr->box = (struct wlr_box){}; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Handler to record a signal call into the @ref wlmtk_util_test_listener_t. * * @param listener_ptr * @param data_ptr */ void _wlmtk_util_test_listener_handler( struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_util_test_listener_t *test_listener_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_util_test_listener_t, listener); ++test_listener_ptr->calls; test_listener_ptr->last_data_ptr = data_ptr; } /* ------------------------------------------------------------------------- */ /** * Handler to record calls into @ref wlmtk_util_test_wlr_box_listener_t. * * @param listener_ptr * @param data_ptr */ void _wlmtk_util_test_wlr_box_listener_handler( struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_util_test_wlr_box_listener_t *l = BS_CONTAINER_OF( listener_ptr, wlmtk_util_test_wlr_box_listener_t, listener); ++l->calls; l->box = *((struct wlr_box*)data_ptr); } /* == Unit tests =========================================================== */ static void test_wl_list_for_each(bs_test_t *test_ptr); static void test_listener(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_util_test_cases[] = { { 1, "wl_list_for_each", test_wl_list_for_each }, { 1, "listener", test_listener }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_util_test_set = BS_TEST_SET( true, "util", _wlmtk_util_test_cases); /* ------------------------------------------------------------------------- */ /** For tests: a list item. */ typedef struct { struct wl_list link; /*!< list node. */ bool called; /*!< reports calls by @ref _test_wl_list_callback. */ bool rv; /*!< what to return. */ } test_wl_list_it_t; /** Callback for testing @ref wlmtk_util_wl_list_for_each. */ static bool _test_wl_list_callback( struct wl_list *link_ptr, void *ud_ptr) { test_wl_list_it_t *t = BS_CONTAINER_OF(link_ptr, test_wl_list_it_t, link); t->called = true; *((size_t*)ud_ptr) += 1; return t->rv; } /** Tests @ref wlmtk_util_wl_list_for_each. */ static void test_wl_list_for_each(bs_test_t *test_ptr) { test_wl_list_it_t t1 = { .rv = true }, t2 = { .rv = true }; size_t calls = 0; struct wl_list l = {}; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_util_wl_list_for_each(&l, _test_wl_list_callback, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 0, calls); wl_list_init(&l); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_util_wl_list_for_each(&l, _test_wl_list_callback, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 0, calls); wl_list_insert(&l, &t1.link); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_util_wl_list_for_each(&l, _test_wl_list_callback, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 1, calls); calls = 0; BS_TEST_VERIFY_TRUE(test_ptr, t1.called); t1.called = false; wl_list_insert(&l, &t2.link); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_util_wl_list_for_each(&l, _test_wl_list_callback, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 2, calls); calls = 0; BS_TEST_VERIFY_TRUE(test_ptr, t1.called); t1.called = false; BS_TEST_VERIFY_TRUE(test_ptr, t2.called); t2.called = false; t1.rv = false; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_util_wl_list_for_each(&l, _test_wl_list_callback, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 2, calls); calls = 0; t2.rv = false; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_util_wl_list_for_each(&l, _test_wl_list_callback, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 2, calls); } /* ------------------------------------------------------------------------- */ /** A test to verify listener handlers are called in order of subscription. */ static void test_listener(bs_test_t *test_ptr) { struct wl_signal signal; wlmtk_util_test_listener_t l1 = {}, l2 = {}; wl_signal_init(&signal); // First test: disconnect must not crash. wlmtk_util_disconnect_listener(&l1.listener); // Second test: Connect, and verify signal is emitted and handled. wlmtk_util_connect_test_listener(&signal, &l1); BS_TEST_VERIFY_EQ(test_ptr, 0, l1.calls); wl_signal_emit(&signal, test_listener); BS_TEST_VERIFY_EQ(test_ptr, 1, l1.calls); BS_TEST_VERIFY_EQ(test_ptr, test_listener, l1.last_data_ptr); // Third test: One more listener, and verify both handlers aacted. wlmtk_util_connect_test_listener(&signal, &l2); wl_signal_emit(&signal, NULL); BS_TEST_VERIFY_EQ(test_ptr, 2, l1.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, l2.calls); // Cleanup. wlmtk_util_disconnect_test_listener(&l2); wlmtk_util_disconnect_test_listener(&l1); // Exercise the wlr_box listener. wlmtk_util_test_wlr_box_listener_t bl = {}; wlmtk_util_connect_test_wlr_box_listener(&signal, &bl); struct wlr_box box = { .x = 1, .y = 2, .width = 3, .height = 4 }; wl_signal_emit(&signal, &box); BS_TEST_VERIFY_EQ(test_ptr, 1, bl.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 1, 2, 3, 4, bl.box); wlmtk_util_disconnect_listener(&bl.listener); } /* == End of util.c ======================================================== */ wlmaker-0.8.1/src/toolkit/workspace.c0000644000175100017510000024671515222270710017223 0ustar runnerrunner/* ========================================================================= */ /** * @file workspace.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "workspace.h" #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "container.h" #include "fsm.h" #include "input.h" #include "layer.h" #include "test.h" // IWYU pragma: keep #include "tile.h" #include "util.h" /* == Declarations ========================================================= */ /** State of the workspace. */ struct _wlmtk_workspace_t { /** Superclass: Container. */ wlmtk_container_t super_container; /** Original virtual method table. We're overwriting parts. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Link to the @ref wlmtk_root_t this workspace is attached to. */ wlmtk_root_t *root_ptr; /** An element of @ref wlmtk_root_t::workspaces. */ bs_dllist_node_t dlnode; /** Name of the workspace. */ char *name_ptr; /** Index of this workspace. */ int index; /** Current FSM state. */ wlmtk_fsm_t fsm; /** Whether this workspace is enabled, ie. can have activated windows. */ bool enabled; /** Container that holds the windows, ie. the window layer. */ wlmtk_container_t window_container; /** Container that holds the fullscreen elements. Should have only one. */ wlmtk_container_t fullscreen_container; /** List of toplevel windows. Via @ref wlmtk_window_t::dlnode. */ bs_dllist_t windows; /** The activated window. */ wlmtk_window_t *activated_window_ptr; /** The most recent activated window, if none is activated now. */ wlmtk_window_t *formerly_activated_window_ptr; /** The grabbed window. */ wlmtk_window_t *grabbed_window_ptr; /** Motion X */ int motion_x; /** Motion Y */ int motion_y; /** Element's X position when initiating a move or resize. */ int initial_x; /** Element's Y position when initiating a move or resize. */ int initial_y; /** Window's width when initiazing the resize. */ int initial_width; /** Window's height when initiazing the resize. */ int initial_height; /** Edges currently active for resizing: `enum wlr_edges`. */ uint32_t resize_edges; /** Top left X coordinate of workspace. */ int x1; /** Top left Y coordinate of workspace. */ int y1; /** Bottom right X coordinate of workspace. */ int x2; /** Bottom right Y coordinate of workspace. */ int y2; /** Window placement: Counter for stacking windows. OK to overflow. */ uint32_t position_counter; /** Background layer. */ wlmtk_layer_t *background_layer_ptr; /** Bottom layer. */ wlmtk_layer_t *bottom_layer_ptr; /** Top layer. */ wlmtk_layer_t *top_layer_ptr; /** Overlay layer. */ wlmtk_layer_t *overlay_layer_ptr; /** Copy of the tile's style, for dimensions; */ struct wlmtk_tile_style tile_style; /** Listener for wlr_output_layout::events.change. */ struct wl_listener output_layout_change_listener; /** Listener for @ref wlmtk_element_events_t::pointer_leave. */ struct wl_listener element_pointer_leave_listener; /** Listener for @ref wlmtk_element_events_t::pointer_motion. */ struct wl_listener element_pointer_motion_listener; // Elements below not owned by wlmtk_workspace_t. /** Output layout. */ struct wlr_output_layout *wlr_output_layout_ptr; }; static void _wlmtk_workspace_element_destroy(wlmtk_element_t *element_ptr); static void _wlmtk_workspace_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr); static bool _wlmtk_workspace_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static void _wlmtk_workspace_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_workspace_handle_element_pointer_leave( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_workspace_handle_element_pointer_motion( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_window_reposition_window( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static bool _wlmtk_workspace_window_overlaps( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmtk_workspace_place_window( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr); static bool pfsm_move_begin(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); static bool pfsm_move_motion(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); static bool pfsm_resize_begin(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); static bool pfsm_resize_motion(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); static bool pfsm_reset(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); /* == Data ================================================================= */ /** States of the pointer FSM. */ typedef enum { PFSMS_PASSTHROUGH, PFSMS_MOVE, PFSMS_RESIZE } pointer_state_t; /** Events for the pointer FSM. */ typedef enum { PFSME_BEGIN_MOVE, PFSME_BEGIN_RESIZE, PFSME_RELEASED, PFSME_MOTION, PFSME_RESET, } pointer_state_event_t; /** Extensions to the workspace's super element's virtual methods. */ const wlmtk_element_vmt_t workspace_element_vmt = { .destroy = _wlmtk_workspace_element_destroy, .get_dimensions = _wlmtk_workspace_element_get_dimensions, .pointer_button = _wlmtk_workspace_element_pointer_button, }; /** Finite state machine definition for pointer events. */ static const wlmtk_fsm_transition_t pfsm_transitions[] = { { PFSMS_PASSTHROUGH, PFSME_BEGIN_MOVE, PFSMS_MOVE, pfsm_move_begin }, { PFSMS_MOVE, PFSME_MOTION, PFSMS_MOVE, pfsm_move_motion }, { PFSMS_MOVE, PFSME_RELEASED, PFSMS_PASSTHROUGH, pfsm_reset }, { PFSMS_MOVE, PFSME_RESET, PFSMS_PASSTHROUGH, pfsm_reset }, { PFSMS_PASSTHROUGH, PFSME_BEGIN_RESIZE, PFSMS_RESIZE, pfsm_resize_begin }, { PFSMS_RESIZE, PFSME_MOTION, PFSMS_RESIZE, pfsm_resize_motion }, { PFSMS_RESIZE, PFSME_RELEASED, PFSMS_PASSTHROUGH, pfsm_reset }, { PFSMS_RESIZE, PFSME_RESET, PFSMS_PASSTHROUGH, pfsm_reset }, WLMTK_FSM_TRANSITION_SENTINEL, }; /** For window placement: Pixel distance between each stacked position. */ static const int _wlmtk_workspace_placement_step = 24; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_workspace_t *wlmtk_workspace_create( struct wlr_output_layout *wlr_output_layout_ptr, const char *name_ptr, const struct wlmtk_tile_style *tile_style_ptr) { wlmtk_workspace_t *workspace_ptr = logged_calloc(1, sizeof(wlmtk_workspace_t)); if (NULL == workspace_ptr) return NULL; workspace_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr; workspace_ptr->tile_style = *tile_style_ptr; workspace_ptr->name_ptr = logged_strdup(name_ptr); if (NULL == workspace_ptr->name_ptr) { wlmtk_workspace_destroy(workspace_ptr); return NULL; } if (!wlmtk_container_init(&workspace_ptr->super_container)) { wlmtk_workspace_destroy(workspace_ptr); return NULL; } workspace_ptr->orig_super_element_vmt = wlmtk_element_extend( &workspace_ptr->super_container.super_element, &workspace_element_vmt); wlmtk_util_connect_listener_signal( &workspace_ptr->super_container.super_element.events.pointer_leave, &workspace_ptr->element_pointer_leave_listener, _wlmtk_workspace_handle_element_pointer_leave); wlmtk_util_connect_listener_signal( &workspace_ptr->super_container.super_element.events.pointer_motion, &workspace_ptr->element_pointer_motion_listener, _wlmtk_workspace_handle_element_pointer_motion); if (!wlmtk_container_init(&workspace_ptr->window_container)) { wlmtk_workspace_destroy(workspace_ptr); return NULL; } wlmtk_element_set_visible( &workspace_ptr->window_container.super_element, true); wlmtk_container_add_element( &workspace_ptr->super_container, &workspace_ptr->window_container.super_element); if (!wlmtk_container_init(&workspace_ptr->fullscreen_container)) { wlmtk_workspace_destroy(workspace_ptr); return NULL; } wlmtk_element_set_visible( &workspace_ptr->fullscreen_container.super_element, true); wlmtk_container_add_element( &workspace_ptr->super_container, &workspace_ptr->fullscreen_container.super_element); workspace_ptr->background_layer_ptr = wlmtk_layer_create( wlr_output_layout_ptr); if (NULL == workspace_ptr->background_layer_ptr) { wlmtk_workspace_destroy(workspace_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_layer_element(workspace_ptr->background_layer_ptr), true); wlmtk_container_add_element_atop( &workspace_ptr->super_container, NULL, wlmtk_layer_element(workspace_ptr->background_layer_ptr)); wlmtk_layer_set_workspace( workspace_ptr->background_layer_ptr, workspace_ptr); workspace_ptr->bottom_layer_ptr = wlmtk_layer_create( wlr_output_layout_ptr); if (NULL == workspace_ptr->bottom_layer_ptr) { wlmtk_workspace_destroy(workspace_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_layer_element(workspace_ptr->bottom_layer_ptr), true); wlmtk_container_add_element_atop( &workspace_ptr->super_container, wlmtk_layer_element(workspace_ptr->background_layer_ptr), wlmtk_layer_element(workspace_ptr->bottom_layer_ptr)); wlmtk_layer_set_workspace(workspace_ptr->bottom_layer_ptr, workspace_ptr); workspace_ptr->top_layer_ptr = wlmtk_layer_create( wlr_output_layout_ptr); if (NULL == workspace_ptr->top_layer_ptr) { wlmtk_workspace_destroy(workspace_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_layer_element(workspace_ptr->top_layer_ptr), true); wlmtk_container_add_element_atop( &workspace_ptr->super_container, &workspace_ptr->window_container.super_element, wlmtk_layer_element(workspace_ptr->top_layer_ptr)); wlmtk_layer_set_workspace(workspace_ptr->top_layer_ptr, workspace_ptr); workspace_ptr->overlay_layer_ptr = wlmtk_layer_create( wlr_output_layout_ptr); if (NULL == workspace_ptr->overlay_layer_ptr) { wlmtk_workspace_destroy(workspace_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_layer_element(workspace_ptr->overlay_layer_ptr), true); wlmtk_container_add_element_atop( &workspace_ptr->super_container, wlmtk_layer_element(workspace_ptr->top_layer_ptr), wlmtk_layer_element(workspace_ptr->overlay_layer_ptr)); wlmtk_layer_set_workspace(workspace_ptr->overlay_layer_ptr, workspace_ptr); wlmtk_fsm_init(&workspace_ptr->fsm, pfsm_transitions, PFSMS_PASSTHROUGH); wlmtk_util_connect_listener_signal( &workspace_ptr->wlr_output_layout_ptr->events.change, &workspace_ptr->output_layout_change_listener, _wlmtk_workspace_handle_output_layout_change); _wlmtk_workspace_handle_output_layout_change( &workspace_ptr->output_layout_change_listener, workspace_ptr->wlr_output_layout_ptr); return workspace_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_destroy(wlmtk_workspace_t *workspace_ptr) { wlmtk_util_disconnect_listener( &workspace_ptr->output_layout_change_listener); if (NULL != workspace_ptr->overlay_layer_ptr) { wlmtk_layer_set_workspace(workspace_ptr->overlay_layer_ptr, NULL); wlmtk_container_remove_element( &workspace_ptr->super_container, wlmtk_layer_element(workspace_ptr->overlay_layer_ptr)); wlmtk_layer_destroy(workspace_ptr->overlay_layer_ptr); workspace_ptr->overlay_layer_ptr = NULL; } if (NULL != workspace_ptr->top_layer_ptr) { wlmtk_layer_set_workspace(workspace_ptr->top_layer_ptr, NULL); wlmtk_container_remove_element( &workspace_ptr->super_container, wlmtk_layer_element(workspace_ptr->top_layer_ptr)); wlmtk_layer_destroy(workspace_ptr->top_layer_ptr); workspace_ptr->top_layer_ptr = NULL; } if (NULL != workspace_ptr->bottom_layer_ptr) { wlmtk_layer_set_workspace(workspace_ptr->bottom_layer_ptr, NULL); wlmtk_container_remove_element( &workspace_ptr->super_container, wlmtk_layer_element(workspace_ptr->bottom_layer_ptr)); wlmtk_layer_destroy(workspace_ptr->bottom_layer_ptr); workspace_ptr->bottom_layer_ptr = NULL; } if (NULL != workspace_ptr->background_layer_ptr) { wlmtk_layer_set_workspace(workspace_ptr->background_layer_ptr, NULL); wlmtk_container_remove_element( &workspace_ptr->super_container, wlmtk_layer_element(workspace_ptr->background_layer_ptr)); wlmtk_layer_destroy(workspace_ptr->background_layer_ptr); workspace_ptr->background_layer_ptr = NULL; } if (NULL != workspace_ptr->fullscreen_container.super_element.parent_container_ptr) { wlmtk_container_remove_element( &workspace_ptr->super_container, &workspace_ptr->fullscreen_container.super_element); } wlmtk_util_disconnect_listener( &workspace_ptr->element_pointer_leave_listener); wlmtk_util_disconnect_listener( &workspace_ptr->element_pointer_motion_listener); wlmtk_container_fini(&workspace_ptr->fullscreen_container); if (NULL != workspace_ptr->window_container.super_element.parent_container_ptr) { wlmtk_container_remove_element( &workspace_ptr->super_container, &workspace_ptr->window_container.super_element); } wlmtk_container_fini(&workspace_ptr->window_container); wlmtk_container_fini(&workspace_ptr->super_container); if (NULL != workspace_ptr->name_ptr) { free(workspace_ptr->name_ptr); workspace_ptr->name_ptr = NULL; } free(workspace_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_set_details( wlmtk_workspace_t *workspace_ptr, int index) { workspace_ptr->index = index; } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_get_details( wlmtk_workspace_t *workspace_ptr, const char **name_ptr_ptr, int *index_ptr) { *index_ptr = workspace_ptr->index; *name_ptr_ptr = workspace_ptr->name_ptr; } /* ------------------------------------------------------------------------- */ struct wlr_box wlmtk_workspace_get_maximize_extents( wlmtk_workspace_t *workspace_ptr, struct wlr_output *wlr_output_ptr) { struct wlr_box extents = {}; // No output provided. Pick the primary (first one in layout). if (NULL == wlr_output_ptr) { if (wl_list_empty(&workspace_ptr->wlr_output_layout_ptr->outputs)) { return extents; } struct wlr_output_layout_output *wol_output_ptr = BS_CONTAINER_OF( workspace_ptr->wlr_output_layout_ptr->outputs.next, struct wlr_output_layout_output, link); wlr_output_ptr = wol_output_ptr->output; } wlr_output_layout_get_box( workspace_ptr->wlr_output_layout_ptr, wlr_output_ptr, &extents); if (!wlr_box_empty(&extents)) { // TODO(kaeser@gubbe.ch): Well, actually compute something sensible. extents.width -= workspace_ptr->tile_style.size; extents.height -= workspace_ptr->tile_style.size; } return extents; } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_confine_within( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr) { // Only act if the window belongs to this workspace. if (workspace_ptr != wlmtk_window_get_workspace(window_ptr)) return; struct wlr_box box = { .x = workspace_ptr->x1, .y = workspace_ptr->y1, .width = workspace_ptr->x2 - workspace_ptr->x1, .height = workspace_ptr->y2 - workspace_ptr->y1 }; struct wlr_box elem_box = wlmtk_element_get_dimensions_box( wlmtk_window_element(window_ptr)); int x, y; wlmtk_element_get_position(wlmtk_window_element(window_ptr), &x, &y); int max_x = x - elem_box.x + elem_box.width; if (max_x > box.width) x -= max_x - box.width; int max_y = y - elem_box.y + elem_box.height; if (max_y > box.height) y -= max_y - box.height; if (x < box.x) x = box.x; if (y < box.y) y = box.y; wlmtk_workspace_set_window_position(workspace_ptr, window_ptr, x, y); } /* ------------------------------------------------------------------------- */ struct wlr_output_layout *wlmtk_workspace_get_wlr_output_layout( wlmtk_workspace_t *workspace_ptr) { return workspace_ptr->wlr_output_layout_ptr; } /* ------------------------------------------------------------------------- */ struct wlr_box wlmtk_workspace_get_fullscreen_extents( wlmtk_workspace_t *workspace_ptr, struct wlr_output *wlr_output_ptr) { struct wlr_box extents = {}; // No output provided. Pick the primary (first one in layout). if (NULL == wlr_output_ptr) { if (wl_list_empty(&workspace_ptr->wlr_output_layout_ptr->outputs)) { return extents; } struct wlr_output_layout_output *wol_output_ptr = BS_CONTAINER_OF( workspace_ptr->wlr_output_layout_ptr->outputs.next, struct wlr_output_layout_output, link); wlr_output_ptr = wol_output_ptr->output; } wlr_output_layout_get_box( workspace_ptr->wlr_output_layout_ptr, wlr_output_ptr, &extents); return extents; } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_enable(wlmtk_workspace_t *workspace_ptr, bool enabled) { if (workspace_ptr->enabled == enabled) return; workspace_ptr->enabled = enabled; if (!enabled) { wlmtk_workspace_activate_window(workspace_ptr, NULL); } else { wlmtk_workspace_activate_window( workspace_ptr, workspace_ptr->formerly_activated_window_ptr); } } /* ------------------------------------------------------------------------- */ bool wlmtk_workspace_enabled(wlmtk_workspace_t *workspace_ptr) { return workspace_ptr->enabled; } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_map_window(wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr) { BS_ASSERT(NULL == wlmtk_window_get_workspace(window_ptr)); _wlmtk_workspace_place_window(workspace_ptr, window_ptr); wlmtk_element_set_visible(wlmtk_window_element(window_ptr), true); wlmtk_container_add_element( &workspace_ptr->window_container, wlmtk_window_element(window_ptr)); bs_dllist_push_front(&workspace_ptr->windows, wlmtk_dlnode_from_window(window_ptr)); wlmtk_window_set_workspace(window_ptr, workspace_ptr); wlmtk_workspace_activate_window(workspace_ptr, window_ptr); if (NULL != workspace_ptr->root_ptr) { wl_signal_emit( &wlmtk_root_events(workspace_ptr->root_ptr)->window_mapped, window_ptr); } } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_unmap_window(wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr) { bool need_activation = false; BS_ASSERT(workspace_ptr == wlmtk_window_get_workspace(window_ptr)); if (workspace_ptr->grabbed_window_ptr == window_ptr) { wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_RESET, NULL); BS_ASSERT(NULL == workspace_ptr->grabbed_window_ptr); } if (workspace_ptr->activated_window_ptr == window_ptr) { wlmtk_workspace_activate_window(workspace_ptr, NULL); need_activation = true; } if (workspace_ptr->formerly_activated_window_ptr == window_ptr) { workspace_ptr->formerly_activated_window_ptr = NULL; need_activation = true; } wlmtk_element_set_visible(wlmtk_window_element(window_ptr), false); if (wlmtk_window_is_fullscreen(window_ptr)) { wlmtk_container_remove_element( &workspace_ptr->fullscreen_container, wlmtk_window_element(window_ptr)); } else { wlmtk_container_remove_element( &workspace_ptr->window_container, wlmtk_window_element(window_ptr)); } bs_dllist_remove(&workspace_ptr->windows, wlmtk_dlnode_from_window(window_ptr)); wlmtk_window_set_workspace(window_ptr, NULL); if (NULL != workspace_ptr->root_ptr) { wl_signal_emit( &wlmtk_root_events(workspace_ptr->root_ptr)->window_unmapped, window_ptr); } if (need_activation) { // FIXME: What about raising? bs_dllist_node_t *dlnode_ptr = workspace_ptr->window_container.elements.head_ptr; if (NULL != dlnode_ptr) { wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); wlmtk_window_t *window_ptr = wlmtk_window_from_element(element_ptr); wlmtk_workspace_activate_window(workspace_ptr, window_ptr); } } } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_set_window_position( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr, int x, int y) { if (workspace_ptr != wlmtk_window_get_workspace(window_ptr)) return; wlmtk_element_set_position(wlmtk_window_element(window_ptr), x, y); wlmtk_window_position_changed(window_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_layer_t *wlmtk_workspace_get_layer( wlmtk_workspace_t *workspace_ptr, wlmtk_workspace_layer_t layer) { wlmtk_layer_t *layer_ptr = NULL; switch (layer) { case WLMTK_WORKSPACE_LAYER_BACKGROUND: layer_ptr = workspace_ptr->background_layer_ptr; break; case WLMTK_WORKSPACE_LAYER_BOTTOM: layer_ptr = workspace_ptr->bottom_layer_ptr; break; case WLMTK_WORKSPACE_LAYER_TOP: layer_ptr = workspace_ptr->top_layer_ptr; break; case WLMTK_WORKSPACE_LAYER_OVERLAY: layer_ptr = workspace_ptr->overlay_layer_ptr; break; default: break; } return layer_ptr; } /* ------------------------------------------------------------------------- */ bs_dllist_t *wlmtk_workspace_get_windows_dllist( wlmtk_workspace_t *workspace_ptr) { return &workspace_ptr->windows; } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_window_to_fullscreen( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr, bool fullscreen) { BS_ASSERT(workspace_ptr == wlmtk_window_get_workspace(window_ptr)); BS_ASSERT(fullscreen == wlmtk_window_is_fullscreen(window_ptr)); if (fullscreen) { BS_ASSERT( bs_dllist_contains( &workspace_ptr->window_container.elements, wlmtk_dlnode_from_element(wlmtk_window_element(window_ptr)))); // TODO(kaeser@gubbe.ch): Add a method to just reparent an element. The // current implementation will destroy, then re-create each of the // scene nodes. wlmtk_container_remove_element( &workspace_ptr->window_container, wlmtk_window_element(window_ptr)); wlmtk_container_add_element( &workspace_ptr->fullscreen_container, wlmtk_window_element(window_ptr)); // TODO(kaeser@gubbe.ch): Removing the element appears to not reset // the keyboard focus, hence the unset/set combo. Debug & fix this. wlmtk_workspace_activate_window(workspace_ptr, NULL); wlmtk_workspace_activate_window(workspace_ptr, window_ptr); } else { BS_ASSERT( bs_dllist_contains( &workspace_ptr->fullscreen_container.elements, wlmtk_dlnode_from_element(wlmtk_window_element(window_ptr)))); wlmtk_container_remove_element( &workspace_ptr->fullscreen_container, wlmtk_window_element(window_ptr)); wlmtk_container_add_element( &workspace_ptr->window_container, wlmtk_window_element(window_ptr)); wlmtk_workspace_activate_window(workspace_ptr, NULL); wlmtk_workspace_activate_window(workspace_ptr, window_ptr); // The un-fullscreened window will come on top of the container. Also // reflect that in @ref wlmtk_workspace_t::windows. bs_dllist_remove(&workspace_ptr->windows, wlmtk_dlnode_from_window(window_ptr)); bs_dllist_push_front(&workspace_ptr->windows, wlmtk_dlnode_from_window(window_ptr)); } } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_begin_window_move( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr) { BS_ASSERT(NULL != workspace_ptr); BS_ASSERT(workspace_ptr == wlmtk_window_get_workspace(window_ptr)); wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_BEGIN_MOVE, window_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_begin_window_resize( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr, uint32_t edges) { BS_ASSERT(NULL != workspace_ptr); BS_ASSERT(workspace_ptr == wlmtk_window_get_workspace(window_ptr)); workspace_ptr->resize_edges = edges; wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_BEGIN_RESIZE, window_ptr); } /* ------------------------------------------------------------------------- */ /** Acticates `window_ptr`. Will de-activate an earlier window. */ void wlmtk_workspace_activate_window( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr) { // Nothing to do. if (workspace_ptr->activated_window_ptr == window_ptr) return; if (NULL != workspace_ptr->activated_window_ptr) { wlmtk_window_t *w_ptr = workspace_ptr->activated_window_ptr; workspace_ptr->formerly_activated_window_ptr = workspace_ptr->activated_window_ptr; workspace_ptr->activated_window_ptr = NULL; wlmtk_window_set_activated(w_ptr, false); } if (NULL != window_ptr) { if (workspace_ptr->enabled) { workspace_ptr->activated_window_ptr = window_ptr; wlmtk_window_set_activated(window_ptr, true); } workspace_ptr->formerly_activated_window_ptr = window_ptr; } } /* ------------------------------------------------------------------------- */ wlmtk_window_t *wlmtk_workspace_get_activated_window( wlmtk_workspace_t *workspace_ptr) { return workspace_ptr->activated_window_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_activate_previous_window( wlmtk_workspace_t *workspace_ptr) { bs_dllist_node_t *dlnode_ptr = NULL; if (NULL != workspace_ptr->activated_window_ptr) { dlnode_ptr = wlmtk_dlnode_from_window( workspace_ptr->activated_window_ptr); dlnode_ptr = dlnode_ptr->prev_ptr; } if (NULL == dlnode_ptr) { dlnode_ptr = workspace_ptr->windows.tail_ptr; } if (NULL == dlnode_ptr) return; wlmtk_workspace_activate_window( workspace_ptr, wlmtk_window_from_dlnode(dlnode_ptr)); } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_activate_next_window( wlmtk_workspace_t *workspace_ptr) { bs_dllist_node_t *dlnode_ptr = NULL; if (NULL != workspace_ptr->activated_window_ptr) { dlnode_ptr = wlmtk_dlnode_from_window( workspace_ptr->activated_window_ptr); dlnode_ptr = dlnode_ptr->next_ptr; } if (NULL == dlnode_ptr) { dlnode_ptr = workspace_ptr->windows.head_ptr; } if (NULL == dlnode_ptr) return; wlmtk_workspace_activate_window( workspace_ptr, wlmtk_window_from_dlnode(dlnode_ptr)); } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_raise_window( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr) { BS_ASSERT(workspace_ptr == wlmtk_window_get_workspace(window_ptr)); bs_dllist_remove(&workspace_ptr->windows, wlmtk_dlnode_from_window(window_ptr)); bs_dllist_push_front(&workspace_ptr->windows, wlmtk_dlnode_from_window(window_ptr)); wlmtk_container_raise_element_to_top(&workspace_ptr->window_container, wlmtk_window_element(window_ptr)); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_workspace_element(wlmtk_workspace_t *workspace_ptr) { return &workspace_ptr->super_container.super_element; } /* ------------------------------------------------------------------------- */ wlmtk_root_t *wlmtk_workspace_get_root(wlmtk_workspace_t *workspace_ptr) { return workspace_ptr->root_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_workspace_set_root( wlmtk_workspace_t *workspace_ptr, wlmtk_root_t *root_ptr) { workspace_ptr->root_ptr = root_ptr; } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *wlmtk_dlnode_from_workspace( wlmtk_workspace_t *workspace_ptr) { return &workspace_ptr->dlnode; } /* ------------------------------------------------------------------------- */ wlmtk_workspace_t *wlmtk_workspace_from_dlnode( bs_dllist_node_t *dlnode_ptr) { if (NULL == dlnode_ptr) return NULL; return BS_CONTAINER_OF(dlnode_ptr, wlmtk_workspace_t, dlnode); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Virtual destructor, wraps to our dtor. */ void _wlmtk_workspace_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_workspace_t, super_container.super_element); wlmtk_workspace_destroy(workspace_ptr); } /* ------------------------------------------------------------------------- */ /** Returns the workspace area. */ void _wlmtk_workspace_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_workspace_t, super_container.super_element); if (NULL != left_ptr) *left_ptr = workspace_ptr->x1; if (NULL != top_ptr) *top_ptr = workspace_ptr->y1; if (NULL != right_ptr) *right_ptr = workspace_ptr->x2; if (NULL != bottom_ptr) *bottom_ptr = workspace_ptr->y2; } /* ------------------------------------------------------------------------- */ /** * Extends wlmtk_container_t::pointer_button. * * @param element_ptr * @param button_event_ptr * * @return Whether the button event was consumed. */ bool _wlmtk_workspace_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_workspace_t, super_container.super_element); // TODO(kaeser@gubbe.ch): We should retract as to which event had triggered // the move, and then figure out the exit condition (button up? key? ...) // from there. // See xdg_toplevel::move doc at https://wayland.app/protocols/xdg-shell. if (button_event_ptr->button == BTN_LEFT && button_event_ptr->type == WLMTK_BUTTON_UP) { wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_RELEASED, NULL); } return workspace_ptr->orig_super_element_vmt.pointer_button( element_ptr, button_event_ptr); } /* ------------------------------------------------------------------------- */ /** Handles output changes: Updates own extents, updates layers. */ void _wlmtk_workspace_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_workspace_t, output_layout_change_listener); struct wlr_output_layout *wlr_output_layout_ptr = data_ptr; struct wlr_box extents; wlr_output_layout_get_box(wlr_output_layout_ptr, NULL, &extents); workspace_ptr->x1 = extents.x; workspace_ptr->y1 = extents.y; workspace_ptr->x2 = extents.x + extents.width; workspace_ptr->y2 = extents.y + extents.height; bs_dllist_for_each( &workspace_ptr->windows, _wlmtk_window_reposition_window, workspace_ptr); } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_element_events_t::pointer_leave. Reset state machine. */ void _wlmtk_workspace_handle_element_pointer_leave( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_workspace_t, element_pointer_leave_listener); wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_RESET, NULL); } /* ------------------------------------------------------------------------- */ /** @ref wlmtk_element_events_t::pointer_motion. Feeds into state machine. */ void _wlmtk_workspace_handle_element_pointer_motion( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_workspace_t, element_pointer_motion_listener); wlmtk_fsm_event(&workspace_ptr->fsm, PFSME_MOTION, NULL); } /* ------------------------------------------------------------------------- */ /** Repositions the window. To be called when output layout changes. */ void _wlmtk_window_reposition_window( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_window_t *window_ptr = wlmtk_window_from_dlnode(dlnode_ptr); wlmtk_workspace_t *workspace_ptr = ud_ptr; // No re-positioning if extents are zero. if (workspace_ptr->x1 >= workspace_ptr->x2 || workspace_ptr->y1 >= workspace_ptr->y2) { return; } // Fullscreen window? Re-position it. We commit right away, to re-position // the element. if (wlmtk_window_is_fullscreen(window_ptr)) { wlmtk_window_request_fullscreen(window_ptr, true); struct wlr_box fsbox = wlmtk_workspace_get_fullscreen_extents( workspace_ptr, wlmtk_window_get_wlr_output(window_ptr)); wlmtk_workspace_set_window_position( workspace_ptr, window_ptr, fsbox.x, fsbox.y); return; } // Maximized window? Re-request maximized, will polll the size. if (wlmtk_window_is_maximized(window_ptr)) { wlmtk_window_request_maximized(window_ptr, true); struct wlr_box box = wlmtk_workspace_get_maximize_extents( workspace_ptr, wlmtk_window_get_wlr_output(window_ptr)); wlmtk_workspace_set_window_position( workspace_ptr, window_ptr, box.x, box.y); return; } // Otherwise: See if the window dimensions (still) intersect. If yes: OK. struct wlr_box wbox = wlmtk_window_get_bounding_box(window_ptr); if (wlr_output_layout_intersects( workspace_ptr->wlr_output_layout_ptr, NULL, &wbox)) return; // Otherwise: Re-position. double closest_x, closest_y; wlr_output_layout_closest_point( workspace_ptr->wlr_output_layout_ptr, NULL, // reference. wbox.x + wbox.width / 2.0, wbox.y + wbox.height / 2.0, &closest_x, &closest_y); // May return NULL, but that's handled by wlr_output_layout_get_box(). struct wlr_output *closest_wlr_output_ptr = wlr_output_layout_output_at( workspace_ptr->wlr_output_layout_ptr, closest_x, closest_y); struct wlr_box output_box; wlr_output_layout_get_box( workspace_ptr->wlr_output_layout_ptr, closest_wlr_output_ptr, &output_box); if (wlr_box_empty(&output_box)) { bs_log(BS_WARNING, "No nearby output found to re-position window %p", window_ptr); return; } wlmtk_workspace_set_window_position( workspace_ptr, window_ptr, output_box.x, output_box.y); wlmtk_window_request_size(window_ptr, &wbox); } // wlr_box_contains_box only available from wlroots >= 0.19.0. #if WLR_VERSION_NUM < (19 << 8) /** Whether `smaller` is fully contained in `bigger`. */ static bool wlr_box_contains_box( const struct wlr_box *parent_box_ptr, const struct wlr_box *child_box_ptr) { if (wlr_box_empty(parent_box_ptr) || wlr_box_empty(child_box_ptr)) { return false; } return ( child_box_ptr->x >= parent_box_ptr->x && child_box_ptr->x + child_box_ptr->width <= parent_box_ptr->x + parent_box_ptr->width && child_box_ptr->y >= parent_box_ptr->y && child_box_ptr->y + child_box_ptr->height <= parent_box_ptr->y + parent_box_ptr->height); } #endif // WLR_VERSION_NUM < (19 << 8) /* ------------------------------------------------------------------------- */ /** Iterator for `bs_dllist_any`: Does window at `dlnode_ptr` overlap? */ bool _wlmtk_workspace_window_overlaps( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { struct wlr_box *window_box_ptr = ud_ptr; wlmtk_window_t *iter_window_ptr = wlmtk_window_from_dlnode(dlnode_ptr); struct wlr_box iter_box = wlmtk_window_get_bounding_box(iter_window_ptr); struct wlr_box intersection_box; return wlr_box_intersection(&intersection_box, window_box_ptr, &iter_box); } /* ------------------------------------------------------------------------- */ /** * Window placement: Finds a place for the window. * * Looks for a non-overlapping space at the current position of `window_ptr`, * and then for tiles right or below it. If none is found, build a stacking * order, at @ref _wlmtk_workspace_placement_step pixels apart. * * @param workspace_ptr * @param window_ptr */ void _wlmtk_workspace_place_window(wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr) { wlmtk_workspace_confine_within(workspace_ptr, window_ptr); struct wlr_box wbox = wlmtk_window_get_bounding_box(window_ptr); if (0 >= wbox.width || 0 >= wbox.height) return; struct wlr_box extents = wlmtk_workspace_get_maximize_extents( workspace_ptr, wlmtk_window_get_wlr_output(window_ptr)); int x = wbox.x; for (; wbox.y + 1 < extents.height; wbox.y += wbox.height) { for (wbox.x = x; wbox.x + 1 < extents.width; wbox.x += wbox.width) { if (!wlr_box_contains_box(&extents, &wbox)) continue; if (!bs_dllist_any(&workspace_ptr->windows, _wlmtk_workspace_window_overlaps, &wbox)) { wlmtk_element_set_position( wlmtk_window_element(window_ptr), wbox.x, wbox.y); wlmtk_window_position_changed(window_ptr); return; } } } const unsigned n = _wlmtk_workspace_placement_step; unsigned max_steps = BS_MAX( 1u, BS_MIN(extents.width / n - 2, extents.height / n - 2)); unsigned steps = workspace_ptr->position_counter++ % max_steps + 1; wlmtk_element_set_position( wlmtk_window_element(window_ptr), steps * n, steps * n); } /* ------------------------------------------------------------------------- */ /** Initiates a move. */ bool pfsm_move_begin(wlmtk_fsm_t *fsm_ptr, void *ud_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( fsm_ptr, wlmtk_workspace_t, fsm); workspace_ptr->grabbed_window_ptr = ud_ptr; workspace_ptr->motion_x = workspace_ptr->super_container.super_element.last_pointer_motion_event.x; workspace_ptr->motion_y = workspace_ptr->super_container.super_element.last_pointer_motion_event.y; wlmtk_element_get_position( wlmtk_window_element(workspace_ptr->grabbed_window_ptr), &workspace_ptr->initial_x, &workspace_ptr->initial_y); // TODO(kaeser@gubbe.ch): When in move mode, set (and keep) a corresponding // cursor image. return true; } /* ------------------------------------------------------------------------- */ /** Handles motion during a move. */ bool pfsm_move_motion(wlmtk_fsm_t *fsm_ptr, __UNUSED__ void *ud_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( fsm_ptr, wlmtk_workspace_t, fsm); double rel_x = workspace_ptr->super_container.super_element.last_pointer_motion_event.x - workspace_ptr->motion_x; double rel_y = workspace_ptr->super_container.super_element.last_pointer_motion_event.y - workspace_ptr->motion_y; wlmtk_workspace_set_window_position( workspace_ptr, workspace_ptr->grabbed_window_ptr, workspace_ptr->initial_x + rel_x, workspace_ptr->initial_y + rel_y); return true; } /* ------------------------------------------------------------------------- */ /** Initiates a resize. */ bool pfsm_resize_begin(wlmtk_fsm_t *fsm_ptr, void *ud_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( fsm_ptr, wlmtk_workspace_t, fsm); workspace_ptr->grabbed_window_ptr = ud_ptr; workspace_ptr->motion_x = workspace_ptr->super_container.super_element.last_pointer_motion_event.x; workspace_ptr->motion_y = workspace_ptr->super_container.super_element.last_pointer_motion_event.y; wlmtk_element_get_position( wlmtk_window_element(workspace_ptr->grabbed_window_ptr), &workspace_ptr->initial_x, &workspace_ptr->initial_y); struct wlr_box box = wlmtk_window_get_size( workspace_ptr->grabbed_window_ptr); workspace_ptr->initial_width = box.width; workspace_ptr->initial_height = box.height; wlmtk_window_set_resize_edges( workspace_ptr->grabbed_window_ptr, workspace_ptr->resize_edges); return true; } /* ------------------------------------------------------------------------- */ /** Handles motion during a resize. */ bool pfsm_resize_motion(wlmtk_fsm_t *fsm_ptr, __UNUSED__ void *ud_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( fsm_ptr, wlmtk_workspace_t, fsm); double rel_x = workspace_ptr->super_container.super_element.last_pointer_motion_event.x - workspace_ptr->motion_x; double rel_y = workspace_ptr->super_container.super_element.last_pointer_motion_event.y - workspace_ptr->motion_y; // Update new boundaries by the relative movement. int top = workspace_ptr->initial_y; int bottom = workspace_ptr->initial_y + workspace_ptr->initial_height; if (workspace_ptr->resize_edges & WLR_EDGE_TOP) { top += rel_y; if (top >= bottom) top = bottom - 1; } else if (workspace_ptr->resize_edges & WLR_EDGE_BOTTOM) { bottom += rel_y; if (bottom <= top) bottom = top + 1; } int left = workspace_ptr->initial_x; int right = workspace_ptr->initial_x + workspace_ptr->initial_width; if (workspace_ptr->resize_edges & WLR_EDGE_LEFT) { left += rel_x; if (left >= right) left = right - 1 ; } else if (workspace_ptr->resize_edges & WLR_EDGE_RIGHT) { right += rel_x; if (right <= left) right = left + 1; } struct wlr_box box = { .width = right - left, .height = bottom - top }; wlmtk_window_request_size( workspace_ptr->grabbed_window_ptr, &box); return true; } /* ------------------------------------------------------------------------- */ /** Resets the state machine. */ bool pfsm_reset(wlmtk_fsm_t *fsm_ptr, __UNUSED__ void *ud_ptr) { wlmtk_workspace_t *workspace_ptr = BS_CONTAINER_OF( fsm_ptr, wlmtk_workspace_t, fsm); wlmtk_window_set_resize_edges(workspace_ptr->grabbed_window_ptr, 0); workspace_ptr->grabbed_window_ptr = NULL; return true; } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); static void test_map_unmap(bs_test_t *test_ptr); static void test_move(bs_test_t *test_ptr); static void test_unmap_during_move(bs_test_t *test_ptr); static void test_resize(bs_test_t *test_ptr); static void test_enable(bs_test_t *test_ptr); static void test_activate(bs_test_t *test_ptr); static void test_activate_cycling(bs_test_t *test_ptr); static void test_multi_output_extents(bs_test_t *test_ptr); static void test_multi_output_reposition(bs_test_t *test_ptr); static void test_window_position(bs_test_t *test_ptr); static void *_wlmtk_workspace_test_setup(void); static void _wlmtk_workspace_test_teardown(void *setup_context_ptr); static void _wlmtk_workspace_test_unmap_and_destroy_window( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_workspace_test_cases[] = { { 1, "create_destroy", test_create_destroy }, { 1, "map_unmap", test_map_unmap }, { 1, "move", test_move }, { 1, "unmap_during_move", test_unmap_during_move }, { 1, "resize", test_resize }, { 1, "enable", test_enable } , { 1, "activate", test_activate }, { 1, "activate_cycling", test_activate_cycling }, { 1, "multi_output_extents", test_multi_output_extents }, { 1, "multi_output_reposition", test_multi_output_reposition }, { 1, "window_position", test_window_position }, BS_TEST_CASE_SENTINEL() }; /** Tile style used in tests. */ static const struct wlmtk_tile_style _wlmtk_workspace_test_tile_style = { .size = 64 }; /** Context for workspace tests. */ struct _wlmtk_workspace_test_context { /** Display. */ struct wl_display *wl_display_ptr; /** The output layout. */ struct wlr_output_layout *wlr_output_layout_ptr; /** Workspace for the output layout. */ wlmtk_workspace_t *workspace_ptr; }; const bs_test_set_t wlmtk_workspace_test_set = BS_TEST_SET_CONTEXT( true, "workspace", _wlmtk_workspace_test_cases, _wlmtk_workspace_test_setup, _wlmtk_workspace_test_teardown); /* ------------------------------------------------------------------------- */ /** Context setup for workspace tests. */ void *_wlmtk_workspace_test_setup(void) { struct _wlmtk_workspace_test_context *c_ptr = calloc(1, sizeof(*c_ptr)); if (NULL == c_ptr) return NULL; c_ptr->wl_display_ptr = wl_display_create(); if (NULL == c_ptr->wl_display_ptr) goto error; c_ptr->wlr_output_layout_ptr = wlr_output_layout_create( c_ptr->wl_display_ptr); if (NULL == c_ptr->wlr_output_layout_ptr) goto error; c_ptr->workspace_ptr = wlmtk_workspace_create( c_ptr->wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); if (NULL == c_ptr->workspace_ptr) goto error; return c_ptr; error: _wlmtk_workspace_test_teardown(c_ptr); return NULL; } /* ------------------------------------------------------------------------- */ /** Context teardown for workspace tests. */ void _wlmtk_workspace_test_teardown(void *setup_context_ptr) { struct _wlmtk_workspace_test_context *c_ptr = setup_context_ptr; if (NULL != c_ptr->workspace_ptr) { bs_dllist_for_each( &c_ptr->workspace_ptr->windows, _wlmtk_workspace_test_unmap_and_destroy_window, c_ptr->workspace_ptr); wlmtk_workspace_destroy(c_ptr->workspace_ptr); } if (NULL != c_ptr->wlr_output_layout_ptr) { wlr_output_layout_destroy(c_ptr->wlr_output_layout_ptr); } if (NULL != c_ptr->wl_display_ptr) { wl_display_destroy(c_ptr->wl_display_ptr); } free(c_ptr); } /* ------------------------------------------------------------------------- */ /** Iterator for `bs_dllist_for_each`: Unmaps and destroys any window. */ void _wlmtk_workspace_test_unmap_and_destroy_window( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_window_t *window_ptr = wlmtk_window_from_dlnode(dlnode_ptr); wlmtk_workspace_t *workspace_ptr = ud_ptr; wlmtk_workspace_unmap_window(workspace_ptr, window_ptr); wlmtk_window_destroy(window_ptr); } /* ------------------------------------------------------------------------- */ /** Exercises workspace create & destroy methods. */ void test_create_destroy(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 100, .height = 200, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, -10, -20); wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr); BS_TEST_VERIFY_EQ( test_ptr, wlr_output_layout_ptr, wlmtk_workspace_get_wlr_output_layout(workspace_ptr)); struct wlr_box box = { .x = -10, .y = -20, .width = 100, .height = 200 }; workspace_ptr->x1 = -10; workspace_ptr->y1 = -20; workspace_ptr->x2 = 90; workspace_ptr->y2 = 180; box = wlmtk_workspace_get_maximize_extents(workspace_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, -10, box.x); BS_TEST_VERIFY_EQ(test_ptr, -20, box.y); BS_TEST_VERIFY_EQ(test_ptr, 36, box.width); BS_TEST_VERIFY_EQ(test_ptr, 136, box.height); box = wlmtk_workspace_get_fullscreen_extents(workspace_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, -10, box.x); BS_TEST_VERIFY_EQ(test_ptr, -20, box.y); BS_TEST_VERIFY_EQ(test_ptr, 100, box.width); BS_TEST_VERIFY_EQ(test_ptr, 200, box.height); const char *name_ptr; int index; wlmtk_workspace_set_details(workspace_ptr, 42); wlmtk_workspace_get_details(workspace_ptr, &name_ptr, &index); BS_TEST_VERIFY_STREQ(test_ptr, "t", name_ptr); BS_TEST_VERIFY_EQ(test_ptr, 42, index); wlmtk_workspace_destroy(workspace_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Verifies that mapping and unmapping windows works. */ void test_map_unmap(bs_test_t *test_ptr) { struct wlr_scene *wlr_scene_ptr = wlr_scene_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_scene_ptr); struct wl_display *wl_display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(wl_display_ptr); wlmtk_root_t *root_ptr = wlmtk_root_create( wlr_scene_ptr, wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_ptr); wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "test", &_wlmtk_workspace_test_tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr); wlmtk_root_add_workspace(root_ptr, workspace_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 20, 10); wlmtk_util_test_listener_t mapped, unmapped; wlmtk_util_connect_test_listener( &wlmtk_root_events(root_ptr)->window_mapped, &mapped); wlmtk_util_connect_test_listener( &wlmtk_root_events(root_ptr)->window_unmapped, &unmapped); bs_dllist_t *wdl_ptr = wlmtk_workspace_get_windows_dllist(workspace_ptr); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(wdl_ptr)); wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_element(w)->visible); wlmtk_workspace_map_window(workspace_ptr, w); BS_TEST_VERIFY_NEQ( test_ptr, NULL, wlmtk_window_element(w)->wlr_scene_node_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_element(w)->visible); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(wdl_ptr)); BS_TEST_VERIFY_EQ(test_ptr, 1, mapped.calls); BS_TEST_VERIFY_EQ(test_ptr, w, mapped.last_data_ptr); int x, y; wlmtk_element_get_position(wlmtk_window_element(w), &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 0, x); BS_TEST_VERIFY_EQ(test_ptr, 0, y); wlmtk_workspace_unmap_window(workspace_ptr, w); BS_TEST_VERIFY_EQ( test_ptr, NULL, wlmtk_window_element(w)->wlr_scene_node_ptr); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_element(w)->visible); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(wdl_ptr)); BS_TEST_VERIFY_EQ(test_ptr, 1, unmapped.calls); BS_TEST_VERIFY_EQ(test_ptr, w, mapped.last_data_ptr); wlmtk_util_disconnect_test_listener(&mapped); wlmtk_util_disconnect_test_listener(&unmapped); wlmtk_window_destroy(w); wlmtk_root_remove_workspace(root_ptr, workspace_ptr); wlmtk_workspace_destroy(workspace_ptr); wlmtk_root_destroy(root_ptr); wl_display_destroy(wl_display_ptr); wlr_scene_node_destroy(&wlr_scene_ptr->tree.node); } /* ------------------------------------------------------------------------- */ /** Tests moving a window. */ void test_move(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_element_set_visible(wlmtk_workspace_element(ws_ptr), true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_fake_element_set_dimensions(fe_ptr, 10, 2); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_workspace_map_window(ws_ptr, w); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(w)->y); wlmtk_pointer_motion_event_t mev = { .x = 0, .y = 0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(wlmtk_workspace_element(ws_ptr), &mev)); // Starts a move for the window. Will move it... wlmtk_workspace_begin_window_move(ws_ptr, w); mev = (wlmtk_pointer_motion_event_t){ .x = 1, .y = 2 }; wlmtk_element_pointer_motion(wlmtk_workspace_element(ws_ptr), &mev); BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(w)->y); // Releases the button. Should end the move. wlmtk_button_event_t button_event = { .button = BTN_LEFT, .type = WLMTK_BUTTON_UP, .time_msec = 44, }; wlmtk_element_pointer_button( wlmtk_workspace_element(ws_ptr), &button_event); BS_TEST_VERIFY_EQ(test_ptr, NULL, ws_ptr->grabbed_window_ptr); // More motion, no longer updates the position. mev = (wlmtk_pointer_motion_event_t){ .x = 3, .y = 4 }; wlmtk_element_pointer_motion(wlmtk_workspace_element(ws_ptr), &mev); BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(w)->y); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests moving a window that unmaps during the move. */ void test_unmap_during_move(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_element_set_visible(wlmtk_workspace_element(ws_ptr), true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_fake_element_set_dimensions(fe_ptr, 40, 20); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_workspace_map_window(ws_ptr, w); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(w)->y); wlmtk_pointer_motion_event_t mev = { .x = 0, .y = 0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); // Starts a move for the window. Will move it... wlmtk_workspace_begin_window_move(ws_ptr, w); mev = (wlmtk_pointer_motion_event_t){ .x = 1, .y = 2 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(w)->y); wlmtk_workspace_unmap_window(ws_ptr, w); BS_TEST_VERIFY_EQ(test_ptr, NULL, ws_ptr->grabbed_window_ptr); // More motion, no longer updates the position. mev = (wlmtk_pointer_motion_event_t){ .x = 3, .y = 4 }; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(w)->y); // More motion, no longer updates the position. BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); BS_TEST_VERIFY_EQ(test_ptr, 1, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 2, wlmtk_window_element(w)->y); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests resizing a window. */ void test_resize(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_element_set_visible(wlmtk_workspace_element(ws_ptr), true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_fake_element_set_dimensions(fe_ptr, 40, 20); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_workspace_map_window(ws_ptr, w); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(w)->y); wlmtk_util_test_listener_t l; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->request_size, &l); wlmtk_pointer_motion_event_t mev = { .x = 0, .y = 0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 40, 20, wlmtk_element_get_dimensions_box(wlmtk_window_element(w))); // Starts a resize for the window. Must call the listener. wlmtk_workspace_begin_window_resize(ws_ptr, w, WLR_EDGE_TOP|WLR_EDGE_LEFT); mev = (wlmtk_pointer_motion_event_t){ .x = 1, .y = 2 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 40, 20, wlmtk_element_get_dimensions_box(wlmtk_window_element(w))); // Now, apply the dimension. wlmtk_fake_element_set_dimensions(fe_ptr, 39, 18); BS_TEST_VERIFY_TRUE( test_ptr, ws_ptr->super_container.invalidated_layout); wlmtk_element_layout(wlmtk_workspace_element(ws_ptr)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 39, 18, wlmtk_element_get_dimensions_box(wlmtk_window_element(w))); int x, y; wlmtk_element_get_position(wlmtk_window_element(w), &x, &y); BS_TEST_VERIFY_EQ(test_ptr, 1, x); BS_TEST_VERIFY_EQ(test_ptr, 2, y); // Releases the button. Should end the move. wlmtk_button_event_t button_event = { .button = BTN_LEFT, .type = WLMTK_BUTTON_UP, .time_msec = 44, }; wlmtk_element_pointer_button( wlmtk_workspace_element(ws_ptr), &button_event); BS_TEST_VERIFY_EQ(test_ptr, NULL, ws_ptr->grabbed_window_ptr); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests enabling or disabling the workspace. */ void test_enable(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, false); // Map while disabled: Not activated. wlmtk_window_t *w1 = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w1); wlmtk_workspace_map_window(ws_ptr, w1); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w1)); // Enable: Activates the earlier-mapped window. wlmtk_workspace_enable(ws_ptr, true); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w1)); // Disable: De-activate the window wlmtk_workspace_enable(ws_ptr, false); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w1)); // Maps another window, while de-activated: Also don't activate. wlmtk_window_t *w2 = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w2); wlmtk_workspace_map_window(ws_ptr, w2); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w2)); // Enable: Activates the window just mapped before. wlmtk_workspace_enable(ws_ptr, true); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w2)); // Unmaps while de-activated. Enabling after should still activate fw1. wlmtk_workspace_enable(ws_ptr, false); wlmtk_workspace_unmap_window(ws_ptr, w2); wlmtk_window_destroy(w2); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w1)); wlmtk_workspace_enable(ws_ptr, true); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w1)); wlmtk_workspace_unmap_window(ws_ptr, w1); wlmtk_window_destroy(w1); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests window activation. */ void test_activate(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_element_set_visible(wlmtk_workspace_element(ws_ptr), true); wlmtk_workspace_enable(ws_ptr, true); // Window 1: from (0, 0) to (100, 100) wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1_ptr); wlmtk_fake_element_set_dimensions(fe1_ptr, 100, 100); wlmtk_window_t *w1 = wlmtk_test_window_create(&fe1_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w1); wlmtk_workspace_set_window_position(ws_ptr, w1, 0, 0); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w1)); // Window 1 is mapped => it's activated. wlmtk_workspace_map_window(ws_ptr, w1); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w1)); // Window 2: from (200, 0) to (300, 100). // Window 2 is mapped: Will get activated, and 1st one de-activated. wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2_ptr); wlmtk_fake_element_set_dimensions(fe2_ptr, 100, 100); wlmtk_window_t *w2 = wlmtk_test_window_create(&fe2_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w2); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w2)); wlmtk_workspace_map_window(ws_ptr, w2); wlmtk_workspace_set_window_position(ws_ptr, w2, 200, 0); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w1)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w2)); // Pointer move, over window 1. Nothing happens: We have click-to-focus. wlmtk_pointer_motion_event_t mev = { .x = 50, .y = 50 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(wlmtk_workspace_element(ws_ptr), &mev)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w1)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w2)); // Click on window 1: Gets activated. wlmtk_button_event_t bev = { .button = BTN_RIGHT, .type = WLMTK_BUTTON_DOWN, }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(wlmtk_workspace_element(ws_ptr), &bev)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w1)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w2)); // Unmap window 1. Now window 2 gets activated. wlmtk_workspace_unmap_window(ws_ptr, w1); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w1)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w2)); // Unmap the remaining window 2. Nothing is activated. wlmtk_workspace_unmap_window(ws_ptr, w2); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w2)); wlmtk_window_destroy(w2); wlmtk_element_destroy(&fe2_ptr->element); wlmtk_window_destroy(w1); wlmtk_element_destroy(&fe1_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests cycling through windows. */ void test_activate_cycling(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, true); bs_dllist_t *windows_ptr = wlmtk_workspace_get_windows_dllist( ws_ptr); // Window 1 gets mapped: Activated and on top. wlmtk_window_t *w1 = wlmtk_test_window_create(NULL); wlmtk_workspace_map_window(ws_ptr, w1); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w1)); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_window(w1), windows_ptr->head_ptr); // Window 2 gets mapped: Activated and on top. wlmtk_window_t *w2 = wlmtk_test_window_create(NULL); wlmtk_workspace_map_window(ws_ptr, w2); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w2)); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_window(w2), windows_ptr->head_ptr); // Window 3 gets mapped: Activated and on top. wlmtk_window_t *w3 = wlmtk_test_window_create(NULL); wlmtk_workspace_map_window(ws_ptr, w3); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w3)); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_window(w3), windows_ptr->head_ptr); // From mapping sequence: We have 3 -> 2 -> 1. Cycling brings us to // window 2, but must not change the top window. wlmtk_workspace_activate_next_window(ws_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w2)); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_window(w3), windows_ptr->head_ptr); // One more cycle: 1. wlmtk_workspace_activate_next_window(ws_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w1)); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_window(w3), windows_ptr->head_ptr); // One more cycle: Back at 3. wlmtk_workspace_activate_next_window(ws_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w3)); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_window(w3), windows_ptr->head_ptr); // Cycle backward: Gets us to 1. wlmtk_workspace_activate_previous_window(ws_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w1)); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_window(w3), windows_ptr->head_ptr); // Raise: Must come to top. wlmtk_workspace_raise_window(ws_ptr, w1); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_window(w1), windows_ptr->head_ptr); wlmtk_workspace_unmap_window(ws_ptr, w3); wlmtk_workspace_unmap_window(ws_ptr, w2); wlmtk_workspace_unmap_window(ws_ptr, w1); wlmtk_window_destroy(w3); wlmtk_window_destroy(w2); wlmtk_window_destroy(w1); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests extents with multiple outputs. */ void test_multi_output_extents(bs_test_t *test_ptr) { struct wlr_box result; struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); struct wlr_output o1 = { .width = 100, .height = 200, .scale = 1 }; wlmtk_test_wlr_output_init(&o1); struct wlr_output o2 = { .width = 300, .height = 250, .scale = 1 }; wlmtk_test_wlr_output_init(&o2); // (1): Get extents without any output. Must be empty. result = wlmtk_workspace_get_maximize_extents(ws_ptr, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 0, 0, result); result = wlmtk_workspace_get_maximize_extents(ws_ptr, &o1); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 0, 0, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 0, 0, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, &o1); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 0, 0, result); // (2): Add one output. Return extents on NULL or for &o1. wlr_output_layout_add(wlr_output_layout_ptr, &o1, -10, -20); result = wlmtk_workspace_get_maximize_extents(ws_ptr, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, -10, -20, 36, 136, result); result = wlmtk_workspace_get_maximize_extents(ws_ptr, &o1); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, -10, -20, 36, 136, result); result = wlmtk_workspace_get_maximize_extents(ws_ptr, &o2); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 0, 0, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, -10, -20, 100, 200, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, &o1); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, -10, -20, 100, 200, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, &o2); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 0, 0, result); // (3): Add second output. Must return extents on all. wlr_output_layout_add(wlr_output_layout_ptr, &o2, 400, 0); result = wlmtk_workspace_get_maximize_extents(ws_ptr, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, -10, -20, 36, 136, result); result = wlmtk_workspace_get_maximize_extents(ws_ptr, &o1); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, -10, -20, 36, 136, result); result = wlmtk_workspace_get_maximize_extents(ws_ptr, &o2); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 400, 0, 236, 186, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, -10, -20, 100, 200, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, &o1); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, -10, -20, 100, 200, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, &o2); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 400, 0, 300, 250, result); // (4): Remove first output. Must now default to 2nd output. wlr_output_layout_remove(wlr_output_layout_ptr, &o1); result = wlmtk_workspace_get_maximize_extents(ws_ptr, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 400, 0, 236, 186, result); result = wlmtk_workspace_get_maximize_extents(ws_ptr, &o1); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 0, 0, result); result = wlmtk_workspace_get_maximize_extents(ws_ptr, &o2); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 400, 0, 236, 186, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 400, 0, 300, 250, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, &o1); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 0, 0, result); result = wlmtk_workspace_get_fullscreen_extents(ws_ptr, &o2); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 400, 0, 300, 250, result); wlmtk_workspace_destroy(ws_ptr); wlr_output_layout_destroy(wlr_output_layout_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Verifies that windows are re-positioned when output is removed. */ void test_multi_output_reposition(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &_wlmtk_workspace_test_tile_style); struct wlr_output o1 = { .width = 100, .height = 200, .scale = 1 }; wlmtk_test_wlr_output_init(&o1); wlr_output_layout_add(wlr_output_layout_ptr, &o1, -10, -20); struct wlr_output o2 = { .width = 300, .height = 250, .scale = 1 }; wlmtk_test_wlr_output_init(&o2); wlr_output_layout_add(wlr_output_layout_ptr, &o2, 400, 0); // A fullscreen window w1, on o1. wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1_ptr); wlmtk_window_t *w1 = wlmtk_test_window_create(&fe1_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w1); wlmtk_workspace_map_window(ws_ptr, w1); wlmtk_window_commit_fullscreen(w1, true); wlmtk_fake_element_set_dimensions(fe1_ptr, 100, 200); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, -10, -20, 100, 200, wlmtk_window_get_bounding_box(w1)); wlmtk_util_test_wlr_box_listener_t l1 = {}; wlmtk_util_connect_test_wlr_box_listener( &wlmtk_window_events(w1)->request_size, &l1); // A normal window w2, on o1. wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2_ptr); wlmtk_fake_element_set_dimensions(fe2_ptr, 30, 40); wlmtk_window_t *w2 = wlmtk_test_window_create(&fe2_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w2); wlmtk_workspace_map_window(ws_ptr, w2); wlmtk_workspace_set_window_position(ws_ptr, w2, 10, 20); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 10, 20, 30, 40, wlmtk_window_get_bounding_box(w2)); // A maximized window w3, on o1. wlmtk_fake_element_t *fe3_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2_ptr); wlmtk_window_t *w3 = wlmtk_test_window_create(&fe3_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w3); wlmtk_workspace_map_window(ws_ptr, w3); wlmtk_window_commit_maximized(w3, true); wlmtk_fake_element_set_dimensions(fe3_ptr, 100, 200); WLMTK_TEST_VERIFY_WLRBOX_EQ( // FIXME: but.. why? test_ptr, -10, -20, 100, 200, wlmtk_window_get_bounding_box(w3)); wlmtk_util_test_wlr_box_listener_t l3 = {}; wlmtk_util_connect_test_wlr_box_listener( &wlmtk_window_events(w3)->request_size, &l3); // Remove o1. wlr_output_layout_remove(wlr_output_layout_ptr, &o1); // The fullscreen window must have received a `request_size` call. Verify, // then let element take that dimension BS_TEST_VERIFY_EQ(test_ptr, 1, l1.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 400, 0, 300, 250, l1.box); wlmtk_fake_element_set_dimensions(fe1_ptr, l1.box.width, l1.box.height); // Also, the maximimzed window must have receied a request_size call; now // suitable for o2. And be placed there. BS_TEST_VERIFY_EQ(test_ptr, 1, l3.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 400, 0, 236, 186, l3.box); wlmtk_fake_element_set_dimensions(fe3_ptr, l3.box.width, l3.box.height); // Now both windows must be on o2. w1 must have a new size. WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 400, 0, 300, 250, wlmtk_window_get_bounding_box(w1)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 400, 0, 30, 40, wlmtk_window_get_bounding_box(w2)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 400, 0, 236, 186, wlmtk_window_get_bounding_box(w3)); // Remove the other layout. Extents now 0x0 -- no re-sizing expected. wlmtk_util_clear_test_wlr_box_listener(&l1); wlmtk_util_clear_test_wlr_box_listener(&l3); wlr_output_layout_remove(wlr_output_layout_ptr, &o2); BS_TEST_VERIFY_EQ(test_ptr, 0, l1.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, l3.calls); wlmtk_workspace_unmap_window(ws_ptr, w3); wlmtk_window_destroy(w3); wlmtk_element_destroy(&fe3_ptr->element); wlmtk_workspace_unmap_window(ws_ptr, w2); wlmtk_window_destroy(w2); wlmtk_element_destroy(&fe2_ptr->element); wlmtk_workspace_unmap_window(ws_ptr, w1); wlmtk_window_destroy(w1); wlmtk_element_destroy(&fe1_ptr->element); wlmtk_workspace_destroy(ws_ptr); wlr_output_layout_destroy(wlr_output_layout_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests window placement. */ void test_window_position(bs_test_t *test_ptr) { struct _wlmtk_workspace_test_context *c_ptr = bs_test_context(test_ptr); struct wlr_output o1 = { .width = 800, .height = 600, .scale = 1 }; wlmtk_test_wlr_output_init(&o1); BS_TEST_VERIFY_NEQ_OR_RETURN( test_ptr, NULL, wlr_output_layout_add(c_ptr->wlr_output_layout_ptr, &o1, 0, 0)); wlmtk_fake_element_t *fe[5] = {}; wlmtk_window_t *w[5] = {}; for (int i = 0; i < 5; ++i) { fe[i] = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ(test_ptr, NULL, fe[i]); if (bs_test_failed(test_ptr)) break; wlmtk_fake_element_set_dimensions(fe[i], 360, 240); w[i] = wlmtk_test_window_create(&fe[i]->element); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w[i]); if (bs_test_failed(test_ptr)) break; wlmtk_workspace_map_window(c_ptr->workspace_ptr, w[i]); } WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 360, 240, wlmtk_window_get_bounding_box(w[0])); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 360, 0, 360, 240, wlmtk_window_get_bounding_box(w[1])); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 240, 360, 240, wlmtk_window_get_bounding_box(w[2])); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 360, 240, 360, 240, wlmtk_window_get_bounding_box(w[3])); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 24, 24, 360, 240, wlmtk_window_get_bounding_box(w[4])); for (int i = 0; i < 5; ++i) { if (NULL != w[i]) { wlmtk_workspace_unmap_window(c_ptr->workspace_ptr, w[i]); wlmtk_window_destroy(w[i]); } if (NULL != fe[i]) wlmtk_element_destroy(&fe[i]->element); } wlr_output_layout_remove(c_ptr->wlr_output_layout_ptr, &o1); } /* == End of workspace.c =================================================== */ wlmaker-0.8.1/src/toolkit/container.c0000644000175100017510000023404115222270710017174 0ustar runnerrunner/* ========================================================================= */ /** * @file container.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "container.h" #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include #include "input.h" /* == Declarations ========================================================= */ static void _wlmtk_container_element_dlnode_destroy( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static struct wlr_scene_node *_wlmtk_container_element_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr); static void _wlmtk_container_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr); static bool _wlmtk_container_element_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr); static bool _wlmtk_container_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static bool _wlmtk_container_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); static void _wlmtk_container_element_pointer_grab_cancel( wlmtk_element_t *element_ptr); static void _wlmtk_container_element_keyboard_blur( wlmtk_element_t *element_ptr); static bool _wlmtk_container_element_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr); static bool _wlmtk_container_element_keyboard_sym( wlmtk_element_t *element_ptr, xkb_keysym_t keysym, enum xkb_key_direction direction, uint32_t modifiers); static void _wlmtk_container_element_layout( wlmtk_element_t *element_ptr); static void _wlmtk_container_handle_wlr_scene_tree_node_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr); static void _wlmtk_container_handle_element_pointer_leave( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr); /** Virtual method table for the container's super class: Element. */ static const wlmtk_element_vmt_t container_element_vmt = { .create_scene_node = _wlmtk_container_element_create_scene_node, .get_dimensions = _wlmtk_container_element_get_dimensions, .pointer_accepts_motion = _wlmtk_container_element_pointer_accepts_motion, .pointer_button = _wlmtk_container_element_pointer_button, .pointer_axis = _wlmtk_container_element_pointer_axis, .pointer_grab_cancel = _wlmtk_container_element_pointer_grab_cancel, .keyboard_blur = _wlmtk_container_element_keyboard_blur, .keyboard_event = _wlmtk_container_element_keyboard_event, .keyboard_sym = _wlmtk_container_element_keyboard_sym, .layout = _wlmtk_container_element_layout, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_container_init(wlmtk_container_t *container_ptr) { BS_ASSERT(NULL != container_ptr); *container_ptr = (wlmtk_container_t){}; if (!wlmtk_element_init(&container_ptr->super_element)) { return false; } container_ptr->orig_super_element_vmt = wlmtk_element_extend( &container_ptr->super_element, &container_element_vmt); wlmtk_util_connect_listener_signal( &container_ptr->super_element.events.pointer_leave, &container_ptr->element_pointer_leave_listener, _wlmtk_container_handle_element_pointer_leave); return true; } /* ------------------------------------------------------------------------- */ bool wlmtk_container_init_attached( wlmtk_container_t *container_ptr, struct wlr_scene_tree *root_wlr_scene_tree_ptr) { if (!wlmtk_container_init(container_ptr)) return false; container_ptr->super_element.wlr_scene_node_ptr = _wlmtk_container_element_create_scene_node( &container_ptr->super_element, root_wlr_scene_tree_ptr); if (NULL == container_ptr->super_element.wlr_scene_node_ptr) { wlmtk_container_fini(container_ptr); return false; } BS_ASSERT(NULL != container_ptr->super_element.wlr_scene_node_ptr); return true; } /* ------------------------------------------------------------------------- */ void wlmtk_container_fini(wlmtk_container_t *container_ptr) { bs_dllist_for_each( &container_ptr->elements, _wlmtk_container_element_dlnode_destroy, container_ptr); // For containers created with wlmtk_container_init_attached(): We also // need to remove references to the WLR scene tree. if (NULL != container_ptr->wlr_scene_tree_ptr) { BS_ASSERT(NULL == container_ptr->super_element.parent_container_ptr); wlr_scene_node_destroy(&container_ptr->wlr_scene_tree_ptr->node); container_ptr->wlr_scene_tree_ptr = NULL; container_ptr->super_element.wlr_scene_node_ptr = NULL; } wlmtk_util_disconnect_listener( &container_ptr->element_pointer_leave_listener); wlmtk_element_fini(&container_ptr->super_element); *container_ptr = (wlmtk_container_t){}; } /* ------------------------------------------------------------------------- */ void wlmtk_container_add_element( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr) { BS_ASSERT(NULL == element_ptr->parent_container_ptr); BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); // Before adding the element: Clear potentially set grabs in the child. wlmtk_element_pointer_grab_cancel(element_ptr); bs_dllist_push_front( &container_ptr->elements, wlmtk_dlnode_from_element(element_ptr)); wlmtk_element_set_parent_container(element_ptr, container_ptr); wlmtk_container_invalidate_layout(container_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_container_add_element_atop( wlmtk_container_t *container_ptr, wlmtk_element_t *reference_element_ptr, wlmtk_element_t *element_ptr) { BS_ASSERT(NULL == element_ptr->parent_container_ptr); BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); BS_ASSERT( NULL == reference_element_ptr || container_ptr == reference_element_ptr->parent_container_ptr); if (NULL == reference_element_ptr) { bs_dllist_push_back( &container_ptr->elements, wlmtk_dlnode_from_element(element_ptr)); } else { bs_dllist_insert_node_before( &container_ptr->elements, wlmtk_dlnode_from_element(reference_element_ptr), wlmtk_dlnode_from_element(element_ptr)); } wlmtk_element_set_parent_container(element_ptr, container_ptr); if (NULL != element_ptr->wlr_scene_node_ptr) { if (NULL == reference_element_ptr) { wlr_scene_node_lower_to_bottom(element_ptr->wlr_scene_node_ptr); } else { BS_ASSERT(NULL != reference_element_ptr->wlr_scene_node_ptr); wlr_scene_node_place_above( element_ptr->wlr_scene_node_ptr, reference_element_ptr->wlr_scene_node_ptr); } } wlmtk_container_invalidate_layout(container_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_container_remove_element( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr) { BS_ASSERT(element_ptr->parent_container_ptr == container_ptr); wlmtk_element_pointer_blur(element_ptr); if (container_ptr->pointer_grab_element_ptr == element_ptr) { _wlmtk_container_element_pointer_grab_cancel( &container_ptr->super_element); if (NULL != container_ptr->super_element.parent_container_ptr) { wlmtk_container_pointer_grab_release( container_ptr->super_element.parent_container_ptr, &container_ptr->super_element); } } if (container_ptr->left_button_element_ptr == element_ptr) { container_ptr->left_button_element_ptr = NULL; } if (container_ptr->keyboard_focus_element_ptr == element_ptr) { wlmtk_container_set_keyboard_focus_element( container_ptr, element_ptr, false); } wlmtk_element_set_parent_container(element_ptr, NULL); bs_dllist_remove( &container_ptr->elements, wlmtk_dlnode_from_element(element_ptr)); wlmtk_container_invalidate_layout(container_ptr); BS_ASSERT(element_ptr != container_ptr->pointer_focus_element_ptr); BS_ASSERT(element_ptr != container_ptr->keyboard_focus_element_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_container_raise_element_to_top( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr) { BS_ASSERT(element_ptr->parent_container_ptr == container_ptr); // Already at the top? Nothing to do. if (wlmtk_dlnode_from_element(element_ptr) == container_ptr->elements.head_ptr) return; bs_dllist_remove( &container_ptr->elements, wlmtk_dlnode_from_element(element_ptr)); bs_dllist_push_front( &container_ptr->elements, wlmtk_dlnode_from_element(element_ptr)); if (NULL != element_ptr->wlr_scene_node_ptr) { wlr_scene_node_raise_to_top(element_ptr->wlr_scene_node_ptr); } wlmtk_container_invalidate_layout(container_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_container_request_pointer_focus( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr) { BS_ASSERT(NULL != element_ptr); BS_ASSERT(element_ptr->parent_container_ptr == container_ptr); // Element already has focus. Nothing to do. if (container_ptr->pointer_focus_element_ptr == element_ptr) { BS_ASSERT(container_ptr->super_element.pointer_inside); return true; } // A different element had focus. Blur it first. But prevent our element // from accepting that blur -- we retain pointer focus for element_ptr. if (container_ptr->pointer_focus_element_ptr != element_ptr && NULL != container_ptr->pointer_focus_element_ptr) { container_ptr->super_element.inhibit_pointer_blur = true; wlmtk_element_pointer_blur( container_ptr->pointer_focus_element_ptr); container_ptr->super_element.inhibit_pointer_blur = false; } container_ptr->pointer_focus_element_ptr = element_ptr; bool rv = wlmtk_element_pointer_focus( &container_ptr->super_element, motion_event_ptr); if (!rv) container_ptr->pointer_focus_element_ptr = NULL; return rv; } /* ------------------------------------------------------------------------- */ void wlmtk_container_pointer_grab( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr) { BS_ASSERT(NULL != element_ptr); BS_ASSERT(container_ptr == element_ptr->parent_container_ptr); // We only accept elements that have a grab_cancel method. BS_ASSERT(NULL != element_ptr->vmt.pointer_grab_cancel); if (container_ptr->pointer_grab_element_ptr == element_ptr) return; // Cancel a currently-held grab. _wlmtk_container_element_pointer_grab_cancel( &container_ptr->super_element); // Then, setup the grab. container_ptr->pointer_grab_element_ptr = element_ptr; if (NULL != container_ptr->super_element.parent_container_ptr) { wlmtk_container_pointer_grab( container_ptr->super_element.parent_container_ptr, &container_ptr->super_element); } if (NULL != container_ptr->pointer_focus_element_ptr && container_ptr->pointer_focus_element_ptr != element_ptr) { wlmtk_element_pointer_blur(container_ptr->pointer_focus_element_ptr); } } /* ------------------------------------------------------------------------- */ void wlmtk_container_pointer_grab_release( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr) { BS_ASSERT(NULL != element_ptr); BS_ASSERT(container_ptr == element_ptr->parent_container_ptr); if (container_ptr->pointer_grab_element_ptr != element_ptr) return; container_ptr->pointer_grab_element_ptr = NULL; if (NULL != container_ptr->super_element.parent_container_ptr) { wlmtk_container_pointer_grab_release( container_ptr->super_element.parent_container_ptr, &container_ptr->super_element); } else { // Re-trigger focus computation, from top-level. wlmtk_container_invalidate_layout(container_ptr); } } /* ------------------------------------------------------------------------- */ void wlmtk_container_set_keyboard_focus_element( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr, bool enabled) { BS_ASSERT(NULL != element_ptr); if (enabled) { // Guard clause: Nothing to do, if that element already has focus. if (container_ptr->keyboard_focus_element_ptr == element_ptr) return; if (NULL != container_ptr->keyboard_focus_element_ptr) { wlmtk_element_keyboard_blur (container_ptr->keyboard_focus_element_ptr); } container_ptr->keyboard_focus_element_ptr = element_ptr; } else { // Guard clause: Nothing to do if that element does NOT have focus. if (container_ptr->keyboard_focus_element_ptr != element_ptr) return; container_ptr->keyboard_focus_element_ptr = NULL; } if (NULL != container_ptr->super_element.parent_container_ptr) { wlmtk_container_set_keyboard_focus_element( container_ptr->super_element.parent_container_ptr, &container_ptr->super_element, enabled); } } /* ------------------------------------------------------------------------- */ void wlmtk_container_invalidate_layout( wlmtk_container_t *container_ptr) { container_ptr->invalidated_layout = true; wlmtk_element_invalidate_parent_layout(&container_ptr->super_element); } /* ------------------------------------------------------------------------- */ struct wlr_scene_tree *wlmtk_container_wlr_scene_tree( wlmtk_container_t *container_ptr) { return container_ptr->wlr_scene_tree_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Calls dtor for @ref wlmtk_element_t at `dlnode_ptr` in `ud_ptr`. */ void _wlmtk_container_element_dlnode_destroy( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); wlmtk_container_t *container_ptr = ud_ptr; wlmtk_container_remove_element(container_ptr, element_ptr); wlmtk_element_destroy(element_ptr); } /* ------------------------------------------------------------------------- */ /** * Implementation of the superclass wlmtk_element_t::create_scene_node method. * * Creates the wlroots scene graph tree for the container, and will attach all * already-contained elements to the scene graph, as well. * * @param element_ptr * @param wlr_scene_tree_ptr * * @return Pointer to the scene graph API node. */ struct wlr_scene_node *_wlmtk_container_element_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); BS_ASSERT(NULL == container_ptr->wlr_scene_tree_ptr); container_ptr->wlr_scene_tree_ptr = wlr_scene_tree_create( wlr_scene_tree_ptr); BS_ASSERT(NULL != container_ptr->wlr_scene_tree_ptr); // Build the nodes from tail to head: Adding an element to the scene graph // will always put it on top, so this adds the elements in desired order. for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.tail_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->prev_ptr) { wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); BS_ASSERT(NULL == element_ptr->wlr_scene_node_ptr); wlmtk_element_attach_to_scene_graph(element_ptr); } wlmtk_util_connect_listener_signal( &container_ptr->wlr_scene_tree_ptr->node.events.destroy, &container_ptr->wlr_scene_tree_node_destroy_listener, _wlmtk_container_handle_wlr_scene_tree_node_destroy); return &container_ptr->wlr_scene_tree_ptr->node; } /* ------------------------------------------------------------------------- */ /** * Implementation of the element's get_dimensions method: Return dimensions. * * @param element_ptr * @param left_ptr Leftmost position. May be NULL. * @param top_ptr Topmost position. May be NULL. * @param right_ptr Rightmost position. Ma be NULL. * @param bottom_ptr Bottommost position. May be NULL. */ void _wlmtk_container_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); int left = INT32_MAX, top = INT32_MAX; int right = INT32_MIN, bottom = INT32_MIN; for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); if (!element_ptr->visible) continue; int x_pos, y_pos; wlmtk_element_get_position(element_ptr, &x_pos, &y_pos); int x1, y1, x2, y2; wlmtk_element_get_dimensions(element_ptr, &x1, &y1, &x2, &y2); left = BS_MIN(left, x_pos + x1); top = BS_MIN(top, y_pos + y1); right = BS_MAX(right, x_pos + x2); bottom = BS_MAX(bottom, y_pos + y2); } if (left >= right) { left = 0; right = 0; } if (top >= bottom) { top = 0; bottom = 0; } if (NULL != left_ptr) *left_ptr = left; if (NULL != top_ptr) *top_ptr = top; if (NULL != right_ptr) *right_ptr = right; if (NULL != bottom_ptr) *bottom_ptr = bottom; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::pointer_accepts_motion. * * @param element_ptr * @param motion_event_ptr * * @return Whether this container has an element that accepts the emotion. */ bool _wlmtk_container_element_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); wlmtk_pointer_motion_event_t child_motion = *motion_event_ptr; int x_pos, y_pos; if (NULL != container_ptr->pointer_grab_element_ptr) { child_motion = *motion_event_ptr; wlmtk_element_get_position( container_ptr->pointer_grab_element_ptr, &x_pos, &y_pos); child_motion.x = motion_event_ptr->x - x_pos; child_motion.y = motion_event_ptr->y - y_pos; return wlmtk_element_pointer_motion( container_ptr->pointer_grab_element_ptr, &child_motion); } for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { wlmtk_element_t *child_element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); wlmtk_element_get_position(child_element_ptr, &x_pos, &y_pos); child_motion.x = motion_event_ptr->x - x_pos; child_motion.y = motion_event_ptr->y - y_pos; container_ptr->super_element.inhibit_pointer_blur = true; bool rv = wlmtk_element_pointer_motion(child_element_ptr, &child_motion); container_ptr->super_element.inhibit_pointer_blur = false; if (rv) { if (NULL == container_ptr->pointer_focus_element_ptr) { // TODO(kaeser@gubbe.ch): This should no longer be needed when // pointer grab and pointer_focus are unified. return false; } BS_ASSERT(NULL != container_ptr->pointer_focus_element_ptr); BS_ASSERT(container_ptr->super_element.pointer_inside); return true; } } container_ptr->pointer_focus_element_ptr = NULL; wlmtk_element_pointer_blur(element_ptr); return false; } /* ------------------------------------------------------------------------- */ /** * Implementation of the element's pointer_button() method. Forwards it to the * element currently having pointer focus. * * @param element_ptr * @param button_event_ptr * * @return true if the button was handled. */ bool _wlmtk_container_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); bool accepted = false; if (NULL != container_ptr->pointer_grab_element_ptr) { return wlmtk_element_pointer_button( container_ptr->pointer_grab_element_ptr, button_event_ptr); } // TODO: Generalize this for non-LEFT buttons. if (BTN_LEFT == button_event_ptr->button) { switch (button_event_ptr->type) { case WLMTK_BUTTON_DOWN: // Forward to the pointer focus element, if any. If it // was accepted: remember the element. if (NULL != container_ptr->pointer_focus_element_ptr) { accepted = wlmtk_element_pointer_button( container_ptr->pointer_focus_element_ptr, button_event_ptr); if (accepted) { container_ptr->left_button_element_ptr = container_ptr->pointer_focus_element_ptr; } else { container_ptr->left_button_element_ptr = NULL; } } break; case WLMTK_BUTTON_UP: // Forward to the element that received the DOWN, if any. if (NULL != container_ptr->left_button_element_ptr) { accepted = wlmtk_element_pointer_button( container_ptr->left_button_element_ptr, button_event_ptr); } break; case WLMTK_BUTTON_CLICK: case WLMTK_BUTTON_DOUBLE_CLICK: // Will only be forwarded, if the element still (or again) // has pointer focus. if (NULL != container_ptr->left_button_element_ptr && container_ptr->left_button_element_ptr == container_ptr->pointer_focus_element_ptr) { accepted = wlmtk_element_pointer_button( container_ptr->left_button_element_ptr, button_event_ptr); } break; default: // Uh, don't know about this... bs_log(BS_FATAL, "Unhandled button type %d", button_event_ptr->type); } return accepted; } if (NULL == container_ptr->pointer_focus_element_ptr) return false; return wlmtk_element_pointer_button( container_ptr->pointer_focus_element_ptr, button_event_ptr); } /* ------------------------------------------------------------------------- */ /** * Implementation of the element's axis method: Handles axis events, by * forwarding it to the element having pointer focus. * * @param element_ptr * @param wlr_pointer_axis_event_ptr * * @return true if the axis event was handled. */ bool _wlmtk_container_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); if (NULL != container_ptr->pointer_grab_element_ptr) { return wlmtk_element_pointer_axis( container_ptr->pointer_grab_element_ptr, wlr_pointer_axis_event_ptr); } if (NULL != container_ptr->pointer_focus_element_ptr) { return wlmtk_element_pointer_axis( container_ptr->pointer_focus_element_ptr, wlr_pointer_axis_event_ptr); } else { return false; } } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::pointer_grab_cancel. * * Cancels an existing pointer grab. * * @param element_ptr */ void _wlmtk_container_element_pointer_grab_cancel( wlmtk_element_t *element_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); if (NULL == container_ptr->pointer_grab_element_ptr) return; wlmtk_element_pointer_grab_cancel( container_ptr->pointer_grab_element_ptr); container_ptr->pointer_grab_element_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_element_vmt_t::keyboard_blur. Blurs all children. */ void _wlmtk_container_element_keyboard_blur(wlmtk_element_t *element_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); // Guard clause: No elements having keyboard focus, return right away. if (NULL == container_ptr->keyboard_focus_element_ptr) return; wlmtk_element_keyboard_blur(container_ptr->keyboard_focus_element_ptr); container_ptr->keyboard_focus_element_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Handler for keyboard events: Pass to keyboard-focussed element, if any. */ bool _wlmtk_container_element_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); // Guard clause: No focus here, return right away. if (NULL == container_ptr->keyboard_focus_element_ptr) return false; return wlmtk_element_keyboard_event( container_ptr->keyboard_focus_element_ptr, wlr_keyboard_key_event_ptr); } /* ------------------------------------------------------------------------- */ /** Handler for translated keyboard events: To keyboard-focussed, if any. */ bool _wlmtk_container_element_keyboard_sym( wlmtk_element_t *element_ptr, xkb_keysym_t keysym, enum xkb_key_direction direction, uint32_t modifiers) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); // Guard clause: No focus here, return right away. if (NULL == container_ptr->keyboard_focus_element_ptr) return false; return wlmtk_element_keyboard_sym( container_ptr->keyboard_focus_element_ptr, keysym, direction, modifiers); } /* ------------------------------------------------------------------------- */ /** Runs @ref wlmtk_element_layout for each node element. */ void _wlmtk_container_element_run_layout( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); wlmtk_element_layout(element_ptr); } /* ------------------------------------------------------------------------- */ /** Imlements @ref wlmtk_element_vmt_t::layout. Calls layout for each child. */ void _wlmtk_container_element_layout(wlmtk_element_t *element_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_container_t, super_element); if (!container_ptr->invalidated_layout) return; container_ptr->invalidated_layout = false; bs_dllist_for_each( &container_ptr->elements, _wlmtk_container_element_run_layout, NULL); } /* ------------------------------------------------------------------------- */ /** * Handles the 'destroy' callback of wlr_scene_tree_ptr->node. * * Will also detach (but not destroy) each of the still-contained elements. * * @param listener_ptr * @param data_ptr */ void _wlmtk_container_handle_wlr_scene_tree_node_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_container_t, wlr_scene_tree_node_destroy_listener); container_ptr->wlr_scene_tree_ptr = NULL; for (bs_dllist_node_t *dlnode_ptr = container_ptr->elements.head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); // Will read the parent container's wlr_scene_tree_ptr == NULL. wlmtk_element_attach_to_scene_graph(element_ptr); } // Since this is a callback from the tree node dtor, the tree is going to // be destroyed. We are using this to reset the container's reference. wlmtk_util_disconnect_listener(&container_ptr->wlr_scene_tree_node_destroy_listener); } /* ------------------------------------------------------------------------- */ /** Handles 'pointer_leave' events: Blurs element currently having focus. */ void _wlmtk_container_handle_element_pointer_leave( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_container_t *container_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_container_t, element_pointer_leave_listener); if (NULL != container_ptr->pointer_focus_element_ptr) { wlmtk_element_pointer_blur( container_ptr->pointer_focus_element_ptr); container_ptr->pointer_focus_element_ptr = NULL; } } /* == Helper for unit tests: A fake container with a tree, as parent ======= */ /** State of the "fake" parent container. Refers to a scene graph. */ typedef struct { /** The actual container */ wlmtk_container_t container; /** A scene graph. Not attached to any output, */ struct wlr_scene *wlr_scene_ptr; } fake_parent_container_t; /* ------------------------------------------------------------------------- */ wlmtk_container_t *wlmtk_container_create_fake_parent(void) { fake_parent_container_t *fake_parent_container_ptr = logged_calloc( 1, sizeof(fake_parent_container_t)); if (NULL == fake_parent_container_ptr) return NULL; fake_parent_container_ptr->wlr_scene_ptr = wlr_scene_create(); if (NULL == fake_parent_container_ptr->wlr_scene_ptr) { wlmtk_container_destroy_fake_parent( &fake_parent_container_ptr->container); return NULL; } if (!wlmtk_container_init_attached( &fake_parent_container_ptr->container, &fake_parent_container_ptr->wlr_scene_ptr->tree)) { wlmtk_container_destroy_fake_parent( &fake_parent_container_ptr->container); return NULL; } return &fake_parent_container_ptr->container; } /* ------------------------------------------------------------------------- */ void wlmtk_container_destroy_fake_parent(wlmtk_container_t *container_ptr) { fake_parent_container_t *fake_parent_container_ptr = BS_CONTAINER_OF( container_ptr, fake_parent_container_t, container); wlmtk_container_fini(&fake_parent_container_ptr->container); if (NULL != fake_parent_container_ptr->wlr_scene_ptr) { wlr_scene_node_destroy( &fake_parent_container_ptr->wlr_scene_ptr->tree.node); fake_parent_container_ptr->wlr_scene_ptr = NULL; } free(fake_parent_container_ptr); } /* == Unit tests =========================================================== */ static void test_init_fini(bs_test_t *test_ptr); static void test_add_remove(bs_test_t *test_ptr); static void test_add_remove_with_scene_graph(bs_test_t *test_ptr); static void test_add_with_raise(bs_test_t *test_ptr); static void test_pointer_button(bs_test_t *test_ptr); static void test_pointer_grab(bs_test_t *test_ptr); static void test_pointer_grab_events(bs_test_t *test_ptr); static void test_keyboard_event(bs_test_t *test_ptr); static void test_keyboard_focus(bs_test_t *test_ptr); static void test_pointer_axis(bs_test_t *test_ptr); static void test_pointer_focus_with_parent(bs_test_t *test_ptr); static void test_pointer_focus_children(bs_test_t *test_ptr); static void test_pointer_focus_order(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_container_test_cases[] = { { 1, "init_fini", test_init_fini }, { 1, "add_remove", test_add_remove }, { 1, "add_remove_with_scene_graph", test_add_remove_with_scene_graph }, { 1, "add_with_raise", test_add_with_raise }, { 1, "pointer_button", test_pointer_button }, { 1, "pointer_grab", test_pointer_grab }, { 1, "pointer_grab_events", test_pointer_grab_events }, { 1, "keyboard_event", test_keyboard_event }, { 1, "keyboard_focus", test_keyboard_focus }, { 1, "pointer_axis", test_pointer_axis }, { 1, "pointer_focus_with_parent", test_pointer_focus_with_parent }, { 1, "pointer_focus_children", test_pointer_focus_children }, { 1, "test_pointer_focus_order", test_pointer_focus_order }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_container_test_set = BS_TEST_SET( true, "container", _wlmtk_container_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises init() and fini() methods, verifies dtor forwarding. */ void test_init_fini(bs_test_t *test_ptr) { wlmtk_container_t container; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_container_init(&container)); // Also expect the super element to be initialized. BS_TEST_VERIFY_NEQ( test_ptr, NULL, container.super_element.vmt.pointer_accepts_motion); wlmtk_container_fini(&container); // Also expect the super element to be un-initialized. BS_TEST_VERIFY_EQ( test_ptr, NULL, container.super_element.vmt.pointer_accepts_motion); } /* ------------------------------------------------------------------------- */ /** Exercises adding and removing elements, verifies destruction on fini. */ void test_add_remove(bs_test_t *test_ptr) { wlmtk_container_t container; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_container_init(&container)); wlmtk_fake_element_t *elem1_ptr, *elem2_ptr, *elem3_ptr; elem1_ptr = wlmtk_fake_element_create(); BS_ASSERT(NULL != elem1_ptr); elem2_ptr = wlmtk_fake_element_create(); BS_ASSERT(NULL != elem2_ptr); elem3_ptr = wlmtk_fake_element_create(); BS_ASSERT(NULL != elem3_ptr); // Build sequence: 3 -> 2 -> 1. wlmtk_container_add_element(&container, &elem1_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, &container, elem1_ptr->element.parent_container_ptr); wlmtk_container_add_element(&container, &elem2_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, &container, elem2_ptr->element.parent_container_ptr); wlmtk_container_add_element(&container, &elem3_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, &container, elem3_ptr->element.parent_container_ptr); // Remove 2, then add at the bottom: 3 -> 1 -> 2. wlmtk_container_remove_element(&container, &elem2_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, NULL, elem2_ptr->element.parent_container_ptr); wlmtk_container_add_element_atop(&container, NULL, &elem2_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, &container, elem2_ptr->element.parent_container_ptr); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_element(&elem1_ptr->element)->next_ptr, wlmtk_dlnode_from_element(&elem2_ptr->element)); // Remove elem3 and add atop elem2: 1 -> 3 -> 2. wlmtk_container_remove_element(&container, &elem3_ptr->element); wlmtk_container_add_element_atop( &container, &elem2_ptr->element, &elem3_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, wlmtk_dlnode_from_element(&elem3_ptr->element)->next_ptr, wlmtk_dlnode_from_element(&elem2_ptr->element)); wlmtk_container_remove_element(&container, &elem2_ptr->element); wlmtk_element_destroy(&elem2_ptr->element); // Will destroy contained elements. wlmtk_container_fini(&container); } /* ------------------------------------------------------------------------- */ /** Tests that elements are attached, resp. detached from scene graph. */ void test_add_remove_with_scene_graph(bs_test_t *test_ptr) { wlmtk_container_t *fake_parent_ptr = wlmtk_container_create_fake_parent(); BS_TEST_VERIFY_NEQ(test_ptr, NULL, fake_parent_ptr); wlmtk_container_t container; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_container_init(&container)); wlmtk_fake_element_t *fe3_ptr = wlmtk_fake_element_create(); wlmtk_container_add_element(&container, &fe3_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, NULL, fe3_ptr->element.wlr_scene_node_ptr); wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); wlmtk_container_add_element(&container, &fe2_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, NULL, fe2_ptr->element.wlr_scene_node_ptr); wlmtk_element_set_parent_container( &container.super_element, fake_parent_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, fe3_ptr->element.wlr_scene_node_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, fe2_ptr->element.wlr_scene_node_ptr); BS_TEST_VERIFY_EQ( test_ptr, container.elements.head_ptr, &fe2_ptr->element.dlnode); BS_TEST_VERIFY_EQ( test_ptr, container.elements.tail_ptr, &fe3_ptr->element.dlnode); // The top is at parent->children.prev (see wlr_scene_node_raise_to_top). // Seems counter-intuitive, since wayhland-util.h denotes `prev` to refer // to the last element in the list. BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev, &fe2_ptr->element.wlr_scene_node_ptr->link); BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev->prev, &fe3_ptr->element.wlr_scene_node_ptr->link); // Want to have the node. BS_TEST_VERIFY_NEQ( test_ptr, NULL, container.super_element.wlr_scene_node_ptr); // Fresh element: No scene graph node yet. wlmtk_fake_element_t *fe0_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_EQ(test_ptr, NULL, fe0_ptr->element.wlr_scene_node_ptr); // Add to container with attached graph: Element now has a graph node. wlmtk_container_add_element(&container, &fe0_ptr->element); BS_TEST_VERIFY_NEQ(test_ptr, NULL, fe0_ptr->element.wlr_scene_node_ptr); // Now fe0 has to be on top, followed by fe2 and fe3. BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev, &fe0_ptr->element.wlr_scene_node_ptr->link); BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev->prev, &fe2_ptr->element.wlr_scene_node_ptr->link); BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev->prev->prev, &fe3_ptr->element.wlr_scene_node_ptr->link); // One more element, but we add this atop of fe2. wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_EQ(test_ptr, NULL, fe1_ptr->element.wlr_scene_node_ptr); wlmtk_container_add_element_atop( &container, &fe2_ptr->element, &fe1_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev, &fe0_ptr->element.wlr_scene_node_ptr->link); BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev->prev, &fe1_ptr->element.wlr_scene_node_ptr->link); BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev->prev->prev, &fe2_ptr->element.wlr_scene_node_ptr->link); BS_TEST_VERIFY_EQ( test_ptr, container.wlr_scene_tree_ptr->children.prev->prev->prev->prev, &fe3_ptr->element.wlr_scene_node_ptr->link); // Remove: The element's graph node must be destroyed & cleared.. wlmtk_container_remove_element(&container, &fe0_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, NULL, fe0_ptr->element.wlr_scene_node_ptr); wlmtk_element_destroy(&fe0_ptr->element); wlmtk_element_set_parent_container(&container.super_element, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, fe3_ptr->element.wlr_scene_node_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, fe2_ptr->element.wlr_scene_node_ptr); wlmtk_container_remove_element(&container, &fe3_ptr->element); wlmtk_element_destroy(&fe3_ptr->element); wlmtk_container_remove_element(&container, &fe2_ptr->element); wlmtk_element_destroy(&fe2_ptr->element); wlmtk_container_fini(&container); wlmtk_container_destroy_fake_parent(fake_parent_ptr); } /* ------------------------------------------------------------------------- */ /** Tests that elements inserted at position are also placed in scene graph. */ void test_add_with_raise(bs_test_t *test_ptr) { wlmtk_container_t *c_ptr = wlmtk_container_create_fake_parent(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, c_ptr); wlmtk_element_set_visible(&c_ptr->super_element, true); // fe1 added. Sole element, is the top. wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&fe1_ptr->element, true); wlmtk_container_add_element(c_ptr, &fe1_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, c_ptr->wlr_scene_tree_ptr->children.prev, &fe1_ptr->element.wlr_scene_node_ptr->link); wlmtk_pointer_motion_event_t e = { .x = 0, .y = 0, .time_msec = 7 }; wlmtk_element_pointer_motion(&c_ptr->super_element, &e); BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_accepts_motion_called); fe1_ptr->pointer_accepts_motion_called = false; BS_TEST_VERIFY_EQ( test_ptr, &fe1_ptr->element, c_ptr->pointer_focus_element_ptr); // fe2 placed atop 'NULL', goes to back. wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&fe2_ptr->element, true); wlmtk_container_add_element_atop(c_ptr, NULL, &fe2_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, c_ptr->wlr_scene_tree_ptr->children.prev->prev, &fe2_ptr->element.wlr_scene_node_ptr->link); // Raise fe2. wlmtk_container_raise_element_to_top(c_ptr, &fe2_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, c_ptr->wlr_scene_tree_ptr->children.prev, &fe2_ptr->element.wlr_scene_node_ptr->link); BS_TEST_VERIFY_EQ( test_ptr, c_ptr->wlr_scene_tree_ptr->children.prev->prev, &fe1_ptr->element.wlr_scene_node_ptr->link); // Must also update pointer focus. wlmtk_element_pointer_motion(&c_ptr->super_element, &e); BS_TEST_VERIFY_EQ( test_ptr, &fe2_ptr->element, c_ptr->pointer_focus_element_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_accepts_motion_called); fe2_ptr->pointer_accepts_motion_called = false; // Now remove fe1 and add on top of fe2. Ensure scene graph has fe1 on top // and pointer focus is on it, too. wlmtk_container_remove_element(c_ptr, &fe1_ptr->element); wlmtk_container_add_element_atop(c_ptr, &fe2_ptr->element, &fe1_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, c_ptr->wlr_scene_tree_ptr->children.prev, &fe1_ptr->element.wlr_scene_node_ptr->link); BS_TEST_VERIFY_EQ( test_ptr, c_ptr->wlr_scene_tree_ptr->children.prev->prev, &fe2_ptr->element.wlr_scene_node_ptr->link); wlmtk_element_pointer_motion(&c_ptr->super_element, &e); BS_TEST_VERIFY_EQ( test_ptr, &fe1_ptr->element, c_ptr->pointer_focus_element_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_accepts_motion_called); wlmtk_container_remove_element(c_ptr, &fe2_ptr->element); wlmtk_element_destroy(&fe2_ptr->element); wlmtk_container_remove_element(c_ptr, &fe1_ptr->element); wlmtk_element_destroy(&fe1_ptr->element); wlmtk_container_destroy_fake_parent(c_ptr); } /* ------------------------------------------------------------------------- */ /** Tests that pointer DOWN is forwarded to element with pointer focus. */ void test_pointer_button(bs_test_t *test_ptr) { wlmtk_container_t container; BS_ASSERT(wlmtk_container_init(&container)); wlmtk_element_set_visible(&container.super_element, true); wlmtk_fake_element_t *elem1_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&elem1_ptr->element, true); elem1_ptr->dimensions.width = 1; elem1_ptr->dimensions.height = 1; wlmtk_container_add_element(&container, &elem1_ptr->element); wlmtk_fake_element_t *elem2_ptr = wlmtk_fake_element_create(); wlmtk_element_set_position(&elem2_ptr->element, 10, 10); wlmtk_element_set_visible(&elem2_ptr->element, true); wlmtk_container_add_element_atop(&container, NULL, &elem2_ptr->element); wlmtk_button_event_t button = { .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN }; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_button(&container.super_element, &button)); // DOWN events go to the focussed element. wlmtk_pointer_motion_event_t e = { .x = 0, .y = 0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&container.super_element, &e)); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(&container.super_element, &button)); BS_TEST_VERIFY_EQ( test_ptr, &elem1_ptr->element, container.left_button_element_ptr); BS_TEST_VERIFY_TRUE( test_ptr, elem1_ptr->pointer_button_called); // Moves, pointer focus is now on elem2. e = (wlmtk_pointer_motion_event_t){ .x = 10, .y = 10 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&container.super_element, &e)) BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->element.pointer_inside); // The UP event is still received by elem1. elem1_ptr->pointer_button_called = false; button.type = WLMTK_BUTTON_UP; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(&container.super_element, &button)); BS_TEST_VERIFY_TRUE( test_ptr, elem1_ptr->pointer_button_called); // Click will be ignored button.type = WLMTK_BUTTON_CLICK; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_button(&container.super_element, &button)); // New DOWN event goes to elem2, though. elem2_ptr->pointer_button_called = false; button.type = WLMTK_BUTTON_DOWN; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(&container.super_element, &button)); BS_TEST_VERIFY_TRUE( test_ptr, elem2_ptr->pointer_button_called); // And UP event now goes to elem2. elem2_ptr->pointer_button_called = false; button.type = WLMTK_BUTTON_UP; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(&container.super_element, &button)); BS_TEST_VERIFY_TRUE( test_ptr, elem2_ptr->pointer_button_called); // Here, CLICK goes to elem2. elem2_ptr->pointer_button_called = false; button.type = WLMTK_BUTTON_CLICK; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(&container.super_element, &button)); BS_TEST_VERIFY_TRUE( test_ptr, elem2_ptr->pointer_button_called); // After removing, further UP events won't be accidentally sent there. wlmtk_container_remove_element(&container, &elem1_ptr->element); wlmtk_container_remove_element(&container, &elem2_ptr->element); button.type = WLMTK_BUTTON_UP; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_button(&container.super_element, &button)); BS_TEST_VERIFY_EQ( test_ptr, NULL, container.left_button_element_ptr); wlmtk_element_destroy(&elem2_ptr->element); wlmtk_element_destroy(&elem1_ptr->element); wlmtk_container_fini(&container); } /* ------------------------------------------------------------------------- */ /** * Tests @ref wlmtk_container_pointer_grab and * @ref wlmtk_container_pointer_grab_release. */ void test_pointer_grab(bs_test_t *test_ptr) { wlmtk_container_t c, p; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&c)); BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&p)); wlmtk_container_add_element(&p, &c.super_element); wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1_ptr); wlmtk_container_add_element(&c, &fe1_ptr->element); wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2_ptr); wlmtk_container_add_element(&c, &fe2_ptr->element); fe1_ptr->pointer_grab_cancel_called = false; fe2_ptr->pointer_grab_cancel_called = false; // Basic grab/release flow: Will not call pointer_grab_cancel(). wlmtk_container_pointer_grab(&c, &fe1_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, &fe1_ptr->element, c.pointer_grab_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, &c.super_element, p.pointer_grab_element_ptr); wlmtk_container_pointer_grab_release(&c, &fe1_ptr->element); BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_grab_cancel_called); BS_TEST_VERIFY_FALSE(test_ptr, fe2_ptr->pointer_grab_cancel_called); BS_TEST_VERIFY_EQ(test_ptr, NULL, c.pointer_grab_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, p.pointer_grab_element_ptr); // Grab that is taken over by the other element: Must be cancelled. wlmtk_container_pointer_grab(&c, &fe1_ptr->element); wlmtk_container_pointer_grab(&c, &fe2_ptr->element); BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_grab_cancel_called); BS_TEST_VERIFY_FALSE(test_ptr, fe2_ptr->pointer_grab_cancel_called); BS_TEST_VERIFY_EQ(test_ptr, &fe2_ptr->element, c.pointer_grab_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, &c.super_element, p.pointer_grab_element_ptr); // When removing element with the grab: Call cancel first. wlmtk_container_remove_element(&c, &fe2_ptr->element); BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_grab_cancel_called); wlmtk_element_destroy(&fe2_ptr->element); BS_TEST_VERIFY_EQ( test_ptr, NULL, c.pointer_grab_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, p.pointer_grab_element_ptr); wlmtk_container_remove_element(&p, &c.super_element); wlmtk_container_fini(&p); wlmtk_container_fini(&c); } /* ------------------------------------------------------------------------- */ /** Tests that element with the pointer grab receives pointer events. */ void test_pointer_grab_events(bs_test_t *test_ptr) { wlmtk_util_test_listener_t enter1 = {}, enter2 = {}; wlmtk_util_test_listener_t leave1 = {}, leave2 = {}; wlmtk_container_t c; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&c)); wlmtk_element_set_visible(&c.super_element, true); wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1_ptr); wlmtk_element_set_visible(&fe1_ptr->element, true); fe1_ptr->dimensions.width = 10; fe1_ptr->dimensions.height = 10; wlmtk_container_add_element(&c, &fe1_ptr->element); wlmtk_util_connect_test_listener(&fe1_ptr->element.events.pointer_enter, &enter1); wlmtk_util_connect_test_listener(&fe1_ptr->element.events.pointer_leave, &leave1); wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2_ptr); wlmtk_element_set_visible(&fe2_ptr->element, true); wlmtk_element_set_position(&fe2_ptr->element, 10, 0); fe2_ptr->dimensions.width = 10; fe2_ptr->dimensions.height = 10; wlmtk_container_add_element(&c, &fe2_ptr->element); wlmtk_util_connect_test_listener(&fe2_ptr->element.events.pointer_enter, &enter2); wlmtk_util_connect_test_listener(&fe2_ptr->element.events.pointer_leave, &leave2); // Move pointer into first element: Must see 'enter' and 'motion'. wlmtk_pointer_motion_event_t e = { .x = 5, .y = 5 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&c.super_element, &e)); BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_accepts_motion_called); fe1_ptr->pointer_accepts_motion_called = false; BS_TEST_VERIFY_EQ(test_ptr, 1, enter1.calls); wlmtk_util_clear_test_listener(&enter1); // 2nd element grabs pointer. Axis and button events must go there. wlmtk_container_pointer_grab(&c, &fe2_ptr->element); // 1st element must get notified to no longer have pointer focus. BS_TEST_VERIFY_EQ(test_ptr, 1, leave1.calls); wlmtk_util_clear_test_listener(&leave1); fe1_ptr->pointer_accepts_motion_called = false; wlmtk_button_event_t button_event = { .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN }; wlmtk_element_pointer_button(&c.super_element, &button_event); BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_button_called); BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_button_called); struct wlr_pointer_axis_event axis_event = {}; wlmtk_element_pointer_axis(&c.super_element, &axis_event); BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_axis_called); BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_axis_called); // A motion within the 1st element: Trigger an out-of-area motion // event to 2nd element. e = (wlmtk_pointer_motion_event_t){ .x = 8, .y = 5 }; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_motion(&c.super_element, &e)); BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_accepts_motion_called); BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_accepts_motion_called); fe2_ptr->pointer_accepts_motion_called = false; // A motion into the 2nd element: Trigger motion and enter(). e = (wlmtk_pointer_motion_event_t){ .x = 13, .y = 5 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&c.super_element, &e)); BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_accepts_motion_called); BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_accepts_motion_called); fe2_ptr->pointer_accepts_motion_called = false; BS_TEST_VERIFY_EQ(test_ptr, 1, enter2.calls); wlmtk_util_clear_test_listener(&enter2); // A motion back into the 2nd element: Trigger motion and leave(). e = (wlmtk_pointer_motion_event_t){ .x = 8, .y = 5 }; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_motion(&c.super_element, &e)); BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->pointer_accepts_motion_called); BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->pointer_accepts_motion_called); fe2_ptr->pointer_accepts_motion_called = false; BS_TEST_VERIFY_EQ(test_ptr, 1, leave2.calls); wlmtk_util_clear_test_listener(&leave2); // Second element releases the grab. 1st element must receive enter(). wlmtk_container_pointer_grab_release(&c, &fe2_ptr->element); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&c.super_element, &e)); BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->pointer_accepts_motion_called); BS_TEST_VERIFY_EQ(test_ptr, 1, enter1.calls); wlmtk_container_fini(&c); } /* ------------------------------------------------------------------------- */ /** Tests that keyboard event are forwarded to element with keyboard focus. */ void test_keyboard_event(bs_test_t *test_ptr) { wlmtk_container_t container; BS_ASSERT(wlmtk_container_init(&container)); wlmtk_container_t parent; BS_ASSERT(wlmtk_container_init(&parent)); wlmtk_container_add_element(&parent, &container.super_element); struct wlr_keyboard_key_event event = {}; wlmtk_element_t *parent_elptr = &parent.super_element; wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); wlmtk_container_add_element(&container, &fe_ptr->element); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_keyboard_event(parent_elptr, &event)); BS_TEST_VERIFY_FALSE(test_ptr, fe_ptr->keyboard_event_called); // Obtains keyboard focus for the fake element. wlmtk_fake_element_grab_keyboard(fe_ptr); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_event(parent_elptr, &event)); BS_TEST_VERIFY_TRUE(test_ptr, fe_ptr->keyboard_event_called); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(parent_elptr, 0, 0, 0)); BS_TEST_VERIFY_TRUE(test_ptr, fe_ptr->keyboard_sym_called); BS_TEST_VERIFY_EQ( test_ptr, &fe_ptr->element, container.keyboard_focus_element_ptr); BS_TEST_VERIFY_EQ( test_ptr, &container.super_element, parent.keyboard_focus_element_ptr); // Release keyboard focus for *not* this element: Retains focus. fe_ptr->keyboard_event_called = false; fe_ptr->keyboard_sym_called = false; wlmtk_container_set_keyboard_focus_element(&container, parent_elptr, false); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_event(parent_elptr, &event)); BS_TEST_VERIFY_TRUE(test_ptr, fe_ptr->keyboard_event_called); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(parent_elptr, 0, 0, 0)); BS_TEST_VERIFY_TRUE(test_ptr, fe_ptr->keyboard_sym_called); BS_TEST_VERIFY_EQ( test_ptr, &fe_ptr->element, container.keyboard_focus_element_ptr); BS_TEST_VERIFY_EQ( test_ptr, &container.super_element, parent.keyboard_focus_element_ptr); // Release keyboard focus for *this* element: Releases focus. fe_ptr->keyboard_event_called = false; fe_ptr->keyboard_sym_called = false; wlmtk_container_set_keyboard_focus_element(&container, &fe_ptr->element, false); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_keyboard_event(parent_elptr, &event)); BS_TEST_VERIFY_FALSE(test_ptr, fe_ptr->keyboard_event_called); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_keyboard_sym(parent_elptr, 0, 0, 0)); BS_TEST_VERIFY_FALSE(test_ptr, fe_ptr->keyboard_sym_called); BS_TEST_VERIFY_EQ(test_ptr, NULL, container.keyboard_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, parent.keyboard_focus_element_ptr); wlmtk_container_remove_element(&container, &fe_ptr->element); wlmtk_element_destroy(&fe_ptr->element); wlmtk_container_remove_element(&parent, &container.super_element); wlmtk_container_fini(&parent); wlmtk_container_fini(&container); } /* ------------------------------------------------------------------------- */ /** Test that keyboard focus is propagated and respects element removal. */ void test_keyboard_focus(bs_test_t *test_ptr) { wlmtk_container_t c, p; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&c)); BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&p)); wlmtk_container_add_element(&p, &c.super_element); // Two child elements to c. wlmtk_fake_element_t *fe1_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1_ptr); wlmtk_container_add_element(&c, &fe1_ptr->element); // One extra child element to p. wlmtk_fake_element_t *fe2_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2_ptr); wlmtk_container_add_element(&p, &fe2_ptr->element); // fe1 of c grabs focus. Ensure it is propagated. wlmtk_fake_element_grab_keyboard(fe1_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->has_keyboard_focus); BS_TEST_VERIFY_EQ( test_ptr, &fe1_ptr->element, c.keyboard_focus_element_ptr); BS_TEST_VERIFY_EQ( test_ptr, &c.super_element, p.keyboard_focus_element_ptr); // fe2 of p sets focus. Must disable focus for c. wlmtk_fake_element_grab_keyboard(fe2_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fe2_ptr->has_keyboard_focus); BS_TEST_VERIFY_FALSE(test_ptr, fe1_ptr->has_keyboard_focus); BS_TEST_VERIFY_EQ( test_ptr, &fe2_ptr->element, p.keyboard_focus_element_ptr); BS_TEST_VERIFY_EQ( test_ptr, NULL, c.keyboard_focus_element_ptr); // fe1 of c re-gains focus. Must disable focus for fe2. wlmtk_fake_element_grab_keyboard(fe1_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fe1_ptr->has_keyboard_focus); BS_TEST_VERIFY_FALSE(test_ptr, fe2_ptr->has_keyboard_focus); BS_TEST_VERIFY_EQ( test_ptr, &c.super_element, p.keyboard_focus_element_ptr); BS_TEST_VERIFY_EQ( test_ptr, &fe1_ptr->element, c.keyboard_focus_element_ptr); // Remove fe1. There is no more keyboard focus to fall back to. wlmtk_container_remove_element(&c, &fe1_ptr->element); wlmtk_element_destroy(&fe1_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, NULL, c.keyboard_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, p.keyboard_focus_element_ptr); wlmtk_container_remove_element(&p, &c.super_element); wlmtk_container_fini(&c); // fe2 is collected during cleanup of &p. wlmtk_container_fini(&p); } /* ------------------------------------------------------------------------- */ /** Tests that axis events are forwarded to element with pointer focus. */ void test_pointer_axis(bs_test_t *test_ptr) { struct wlr_pointer_axis_event event = {}; wlmtk_container_t container; BS_ASSERT(wlmtk_container_init(&container)); wlmtk_element_set_visible(&container.super_element, true); wlmtk_fake_element_t *elem1_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&elem1_ptr->element, true); elem1_ptr->dimensions.width = 1; elem1_ptr->dimensions.height = 1; wlmtk_container_add_element(&container, &elem1_ptr->element); wlmtk_fake_element_t *elem2_ptr = wlmtk_fake_element_create(); wlmtk_element_set_position(&elem2_ptr->element, 10, 10); wlmtk_element_set_visible(&elem2_ptr->element, true); wlmtk_container_add_element_atop(&container, NULL, &elem2_ptr->element); // Pointer on elem1, axis goes there. wlmtk_pointer_motion_event_t e = { .x = 0, .y = 0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&container.super_element, &e)); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_axis(&container.super_element, &event)); BS_TEST_VERIFY_TRUE(test_ptr, elem1_ptr->pointer_axis_called); elem1_ptr->pointer_axis_called = false; BS_TEST_VERIFY_FALSE(test_ptr, elem2_ptr->pointer_axis_called); // Pointer on elem2, axis goes there. e = (wlmtk_pointer_motion_event_t){ .x = 10, .y = 10 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&container.super_element, &e)); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_axis(&container.super_element, &event)); BS_TEST_VERIFY_FALSE(test_ptr, elem1_ptr->pointer_axis_called); BS_TEST_VERIFY_TRUE(test_ptr, elem2_ptr->pointer_axis_called); wlmtk_container_remove_element(&container, &elem1_ptr->element); wlmtk_container_remove_element(&container, &elem2_ptr->element); wlmtk_element_destroy(&elem2_ptr->element); wlmtk_element_destroy(&elem1_ptr->element); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_axis(&container.super_element, &event)); wlmtk_container_fini(&container); } /* ------------------------------------------------------------------------- */ /** Tests pointer focus. */ static void test_pointer_focus_with_parent(bs_test_t *test_ptr) { wlmtk_container_t p, c; wlmtk_util_test_listener_t p_enter, p_leave, c_enter, c_leave, fe_enter, fe_leave; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&p)); wlmtk_util_connect_test_listener( &p.super_element.events.pointer_enter, &p_enter); wlmtk_util_connect_test_listener( &p.super_element.events.pointer_leave, &p_leave); BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&c)); wlmtk_util_connect_test_listener( &c.super_element.events.pointer_enter, &c_enter); wlmtk_util_connect_test_listener( &c.super_element.events.pointer_leave, &c_leave); wlmtk_element_set_position(&c.super_element, 10, 0); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_util_connect_test_listener( &fe_ptr->element.events.pointer_enter, &fe_enter); wlmtk_util_connect_test_listener( &fe_ptr->element.events.pointer_leave, &fe_leave); wlmtk_element_set_position(&fe_ptr->element, 0, 20); wlmtk_element_set_visible(&fe_ptr->element, true); wlmtk_element_set_visible(&c.super_element, true); wlmtk_element_set_visible(&p.super_element, true); wlmtk_container_add_element(&p, &c.super_element); wlmtk_container_add_element(&c, &fe_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, 0, fe_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, fe_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, c_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, p_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, p_leave.calls); // Motion into fe_ptr. Must trigger focus throughout. wlmtk_pointer_motion_event_t e = { .x = 11.0, .y = 22.0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&p.super_element, &e)); BS_TEST_VERIFY_TRUE(test_ptr, fe_ptr->element.pointer_inside); BS_TEST_VERIFY_TRUE(test_ptr, c.super_element.pointer_inside); BS_TEST_VERIFY_EQ( test_ptr, &fe_ptr->element, c.pointer_focus_element_ptr); BS_TEST_VERIFY_TRUE(test_ptr, p.super_element.pointer_inside); BS_TEST_VERIFY_EQ( test_ptr, &c.super_element, p.pointer_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, fe_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, fe_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, c_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, p_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, p_leave.calls); // Blurring the container must blur throughout. wlmtk_element_pointer_blur(&p.super_element); BS_TEST_VERIFY_FALSE(test_ptr, fe_ptr->element.pointer_inside); BS_TEST_VERIFY_FALSE(test_ptr, c.super_element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, NULL, c.pointer_focus_element_ptr); BS_TEST_VERIFY_FALSE(test_ptr, p.super_element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, NULL, p.pointer_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, fe_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, fe_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, p_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, p_leave.calls); // Focus it once more, verify. wlmtk_element_pointer_focus(&fe_ptr->element, &e); BS_TEST_VERIFY_TRUE(test_ptr, fe_ptr->element.pointer_inside); BS_TEST_VERIFY_TRUE(test_ptr, c.super_element.pointer_inside); BS_TEST_VERIFY_EQ( test_ptr, &fe_ptr->element, c.pointer_focus_element_ptr); BS_TEST_VERIFY_TRUE(test_ptr, p.super_element.pointer_inside); BS_TEST_VERIFY_EQ( test_ptr, &c.super_element, p.pointer_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, 2, fe_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, fe_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, p_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, p_leave.calls); // Remove the element. It had focus, so blur throughout. wlmtk_container_remove_element(&c, &fe_ptr->element); BS_TEST_VERIFY_FALSE(test_ptr, fe_ptr->element.pointer_inside); BS_TEST_VERIFY_FALSE(test_ptr, c.super_element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, NULL, c.pointer_focus_element_ptr); BS_TEST_VERIFY_FALSE(test_ptr, p.super_element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, NULL, p.pointer_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, 2, fe_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, fe_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, c_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, p_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, p_leave.calls); // TODO(kaeser@gubbe.ch): Remove post the pointer migration. p.super_element.last_pointer_motion_event.x = NAN; p.super_element.last_pointer_motion_event.y = NAN; // Add it back. Container didn't have focus, so won't do anything. wlmtk_container_add_element(&c, &fe_ptr->element); BS_TEST_VERIFY_FALSE(test_ptr, fe_ptr->element.pointer_inside); BS_TEST_VERIFY_FALSE(test_ptr, c.super_element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, NULL, c.pointer_focus_element_ptr); BS_TEST_VERIFY_FALSE(test_ptr, p.super_element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, NULL, p.pointer_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, 2, fe_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, fe_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, c_leave.calls); // Send in a motion. Must enable the element. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&p.super_element, &e)); BS_TEST_VERIFY_TRUE(test_ptr, fe_ptr->element.pointer_inside); BS_TEST_VERIFY_TRUE(test_ptr, c.super_element.pointer_inside); BS_TEST_VERIFY_EQ( test_ptr, &fe_ptr->element, c.pointer_focus_element_ptr); BS_TEST_VERIFY_TRUE(test_ptr, p.super_element.pointer_inside); BS_TEST_VERIFY_EQ( test_ptr, &c.super_element, p.pointer_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, 3, fe_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, fe_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 3, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, c_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 3, p_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, p_leave.calls); // Remove the c. Must send LEAVE to all. wlmtk_container_remove_element(&p, &c.super_element); BS_TEST_VERIFY_FALSE(test_ptr, fe_ptr->element.pointer_inside); BS_TEST_VERIFY_FALSE(test_ptr, c.super_element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, NULL, c.pointer_focus_element_ptr); BS_TEST_VERIFY_FALSE(test_ptr, p.super_element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, NULL, p.pointer_focus_element_ptr); BS_TEST_VERIFY_EQ(test_ptr, 3, fe_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 3, fe_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 3, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 3, c_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 3, p_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 3, p_leave.calls); wlmtk_container_remove_element(&c, &fe_ptr->element); wlmtk_element_destroy(&fe_ptr->element); wlmtk_container_fini(&p); wlmtk_container_fini(&c); } /* ------------------------------------------------------------------------- */ /** Tests moving pointer focus between children elements. */ void test_pointer_focus_children(bs_test_t *test_ptr) { wlmtk_util_test_listener_t c_enter, c_leave, e1_enter, e1_leave, e2_enter, e2_leave; wlmtk_container_t c; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&c)); wlmtk_element_set_visible(&c.super_element, true); wlmtk_util_connect_test_listener( &c.super_element.events.pointer_enter, &c_enter); wlmtk_util_connect_test_listener( &c.super_element.events.pointer_leave, &c_leave); wlmtk_fake_element_t *e1_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, e1_ptr); wlmtk_element_set_position(&e1_ptr->element, 1, 1); wlmtk_util_connect_test_listener( &e1_ptr->element.events.pointer_enter, &e1_enter); wlmtk_util_connect_test_listener( &e1_ptr->element.events.pointer_leave, &e1_leave); wlmtk_element_set_visible(&e1_ptr->element, true); wlmtk_container_add_element(&c, &e1_ptr->element); // 1. Move pointer onto the one element: Receives focus. wlmtk_pointer_motion_event_t m = { .x = 0.0, .y = 0.0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&c.super_element, &m)); BS_TEST_VERIFY_TRUE(test_ptr, e1_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 1, e1_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_enter.calls); // 2. Add second element, atop: Receives focus, no 'leave' for container. wlmtk_fake_element_t *e2_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, e2_ptr); wlmtk_util_connect_test_listener( &e2_ptr->element.events.pointer_enter, &e2_enter); wlmtk_util_connect_test_listener( &e2_ptr->element.events.pointer_leave, &e2_leave); wlmtk_element_set_visible(&e2_ptr->element, true); wlmtk_container_add_element(&c, &e2_ptr->element); wlmtk_element_pointer_motion(&c.super_element, &m); BS_TEST_VERIFY_FALSE(test_ptr, e1_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 1, e1_leave.calls); BS_TEST_VERIFY_TRUE(test_ptr, e2_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 1, e2_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_enter.calls); // 3. Make second element invisible. Blur, E1 focus, no container leave. wlmtk_element_set_visible(&e2_ptr->element, false); wlmtk_element_pointer_motion(&c.super_element, &m); BS_TEST_VERIFY_TRUE(test_ptr, e1_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 2, e1_enter.calls); BS_TEST_VERIFY_FALSE(test_ptr, e2_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 1, e2_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_enter.calls); // 4. Make it visible again. Receives focus. wlmtk_element_set_visible(&e2_ptr->element, true); wlmtk_element_pointer_motion(&c.super_element, &m); BS_TEST_VERIFY_FALSE(test_ptr, e1_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 2, e1_leave.calls); BS_TEST_VERIFY_TRUE(test_ptr, e2_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 2, e1_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_enter.calls); // 5. Move pointer to where only e1 is. E2 blurs, E1 focus, no C leave. m = (wlmtk_pointer_motion_event_t ){ .x = 3.0, .y = 4.0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&c.super_element, &m)); BS_TEST_VERIFY_TRUE(test_ptr, e1_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 3, e1_enter.calls); BS_TEST_VERIFY_FALSE(test_ptr, e2_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 2, e2_leave.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, c_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 0, c_leave.calls); wlmtk_container_remove_element(&c, &e2_ptr->element); wlmtk_element_destroy(&e2_ptr->element); wlmtk_container_remove_element(&c, &e1_ptr->element); wlmtk_element_destroy(&e1_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, 1, c_leave.calls); wlmtk_container_fini(&c); } /* ------------------------------------------------------------------------- */ /** Tests pointer focus when moving elements order (and position). */ void test_pointer_focus_order(bs_test_t *test_ptr) { wlmtk_util_test_listener_t e1_enter, e1_leave, e2_enter, e2_leave; wlmtk_container_t c; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_container_init(&c)); wlmtk_element_set_visible(&c.super_element, true); wlmtk_fake_element_t *e1_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, e1_ptr); wlmtk_element_set_position(&e1_ptr->element, 1, 1); wlmtk_util_connect_test_listener( &e1_ptr->element.events.pointer_enter, &e1_enter); wlmtk_util_connect_test_listener( &e1_ptr->element.events.pointer_leave, &e1_leave); wlmtk_element_set_visible(&e1_ptr->element, true); wlmtk_container_add_element(&c, &e1_ptr->element); // 1. Initial motion. Focus to e1. wlmtk_pointer_motion_event_t m = { .x = 0.0, .y = 0.0 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(&c.super_element, &m)); BS_TEST_VERIFY_EQ(test_ptr, 1, e1_enter.calls); BS_TEST_VERIFY_TRUE(test_ptr, e1_ptr->element.pointer_inside); // 2. Add e2, on top e1. Focus to e2. wlmtk_fake_element_t *e2_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, e2_ptr); wlmtk_util_connect_test_listener( &e2_ptr->element.events.pointer_enter, &e2_enter); wlmtk_util_connect_test_listener( &e2_ptr->element.events.pointer_leave, &e2_leave); wlmtk_element_set_visible(&e2_ptr->element, true); wlmtk_container_add_element_atop(&c, &e1_ptr->element, &e2_ptr->element); wlmtk_element_pointer_motion(&c.super_element, &m); BS_TEST_VERIFY_EQ(test_ptr, 1, e1_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, e1_leave.calls); BS_TEST_VERIFY_FALSE(test_ptr, e1_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 1, e2_enter.calls); BS_TEST_VERIFY_TRUE(test_ptr, e2_ptr->element.pointer_inside); // 3. Raise e1. Must re-gain focus. wlmtk_container_raise_element_to_top(&c, &e1_ptr->element); wlmtk_element_pointer_motion(&c.super_element, &m); BS_TEST_VERIFY_EQ(test_ptr, 2, e1_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, e1_leave.calls); BS_TEST_VERIFY_TRUE(test_ptr, e1_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 1, e2_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, e2_leave.calls); BS_TEST_VERIFY_FALSE(test_ptr, e2_ptr->element.pointer_inside); // 4. Move e1 away. Must re-trigger focus computation, e2 gets it. wlmtk_element_set_position(&e1_ptr->element, 20, 0); wlmtk_element_pointer_motion(&c.super_element, &m); BS_TEST_VERIFY_EQ(test_ptr, 2, e1_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 2, e1_leave.calls); BS_TEST_VERIFY_FALSE(test_ptr, e1_ptr->element.pointer_inside); BS_TEST_VERIFY_EQ(test_ptr, 2, e2_enter.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, e2_leave.calls); BS_TEST_VERIFY_TRUE(test_ptr, e2_ptr->element.pointer_inside); wlmtk_container_remove_element(&c, &e2_ptr->element); wlmtk_element_destroy(&e2_ptr->element); wlmtk_container_remove_element(&c, &e1_ptr->element); wlmtk_element_destroy(&e1_ptr->element); wlmtk_container_fini(&c); } /* == End of container.c =================================================== */ wlmaker-0.8.1/src/toolkit/bordered.c0000644000175100017510000002671615222270710017010 0ustar runnerrunner/* ========================================================================= */ /** * @file bordered.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "bordered.h" #include #include "libbase/libbase.h" /* == Declarations ========================================================= */ static void _wlmtk_bordered_element_layout(wlmtk_element_t *element_ptr); static wlmtk_rectangle_t * _wlmtk_bordered_create_border_rectangle( wlmtk_bordered_t *bordered_ptr); static void _wlmtk_bordered_destroy_border_rectangle( wlmtk_bordered_t *bordered_ptr, wlmtk_rectangle_t **rectangle_ptr_ptr); static void _wlmtk_bordered_set_positions(wlmtk_bordered_t *bordered_ptr); /* == Data ================================================================= */ /** Virtual method table: @ref wlmtk_element_t at @ref wlmtk_bordered_t. */ static const wlmtk_element_vmt_t bordered_element_vmt = { .layout = _wlmtk_bordered_element_layout, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_bordered_init(wlmtk_bordered_t *bordered_ptr, wlmtk_element_t *element_ptr, const struct wlmtk_margin_style *style_ptr) { BS_ASSERT(NULL != bordered_ptr); *bordered_ptr = (wlmtk_bordered_t){ .style = *style_ptr }; if (!wlmtk_container_init(&bordered_ptr->super_container)) { return false; } bordered_ptr->orig_super_element_vmt = wlmtk_element_extend( wlmtk_bordered_element(bordered_ptr), &bordered_element_vmt); bordered_ptr->element_ptr = element_ptr; wlmtk_container_add_element(&bordered_ptr->super_container, bordered_ptr->element_ptr); bordered_ptr->northern_border_rectangle_ptr = _wlmtk_bordered_create_border_rectangle(bordered_ptr); bordered_ptr->eastern_border_rectangle_ptr = _wlmtk_bordered_create_border_rectangle(bordered_ptr); bordered_ptr->southern_border_rectangle_ptr = _wlmtk_bordered_create_border_rectangle(bordered_ptr); bordered_ptr->western_border_rectangle_ptr = _wlmtk_bordered_create_border_rectangle(bordered_ptr); if (NULL == bordered_ptr->northern_border_rectangle_ptr || NULL == bordered_ptr->eastern_border_rectangle_ptr || NULL == bordered_ptr->southern_border_rectangle_ptr || NULL == bordered_ptr->western_border_rectangle_ptr) { wlmtk_bordered_fini(bordered_ptr); return false; } _wlmtk_bordered_set_positions(bordered_ptr); return true; } /* ------------------------------------------------------------------------- */ void wlmtk_bordered_fini(wlmtk_bordered_t *bordered_ptr) { _wlmtk_bordered_destroy_border_rectangle( bordered_ptr, &bordered_ptr->western_border_rectangle_ptr); _wlmtk_bordered_destroy_border_rectangle( bordered_ptr, &bordered_ptr->southern_border_rectangle_ptr); _wlmtk_bordered_destroy_border_rectangle( bordered_ptr, &bordered_ptr->eastern_border_rectangle_ptr); _wlmtk_bordered_destroy_border_rectangle( bordered_ptr, &bordered_ptr->northern_border_rectangle_ptr); wlmtk_container_remove_element(&bordered_ptr->super_container, bordered_ptr->element_ptr); wlmtk_container_fini(&bordered_ptr->super_container); *bordered_ptr = (wlmtk_bordered_t){}; } /* ------------------------------------------------------------------------- */ void wlmtk_bordered_set_style(wlmtk_bordered_t *bordered_ptr, const struct wlmtk_margin_style *style_ptr) { bordered_ptr->style = *style_ptr; wlmtk_element_layout(wlmtk_bordered_element(bordered_ptr)); wlmtk_element_invalidate_parent_layout( wlmtk_bordered_element(bordered_ptr)); // Guard clause. Actually, if *any* of the rectangles was not created. if (NULL == bordered_ptr->western_border_rectangle_ptr) return; wlmtk_rectangle_set_color( bordered_ptr->northern_border_rectangle_ptr, style_ptr->color); wlmtk_rectangle_set_color( bordered_ptr->eastern_border_rectangle_ptr, style_ptr->color); wlmtk_rectangle_set_color( bordered_ptr->southern_border_rectangle_ptr, style_ptr->color); wlmtk_rectangle_set_color( bordered_ptr->western_border_rectangle_ptr, style_ptr->color); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_bordered_element(wlmtk_bordered_t *bordered_ptr) { return &bordered_ptr->super_container.super_element; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Updates the layout of the bordered element. * * @param element_ptr */ void _wlmtk_bordered_element_layout(wlmtk_element_t *element_ptr) { wlmtk_bordered_t *bordered_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_bordered_t, super_container.super_element); bordered_ptr->orig_super_element_vmt.layout(element_ptr); _wlmtk_bordered_set_positions(bordered_ptr); } /* ------------------------------------------------------------------------- */ /** Creates a border rectangle and adds it to `bordered_ptr`. */ wlmtk_rectangle_t * _wlmtk_bordered_create_border_rectangle( wlmtk_bordered_t *bordered_ptr) { wlmtk_rectangle_t *rectangle_ptr = wlmtk_rectangle_create( 0, 0, bordered_ptr->style.color); if (NULL == rectangle_ptr) return NULL; wlmtk_element_set_visible(wlmtk_rectangle_element(rectangle_ptr), true); wlmtk_container_add_element_atop( &bordered_ptr->super_container, NULL, wlmtk_rectangle_element(rectangle_ptr)); return rectangle_ptr; } /* ------------------------------------------------------------------------- */ /** Removes the rectangle from `bordered_ptr`, destroys it and NULLs it. */ void _wlmtk_bordered_destroy_border_rectangle( wlmtk_bordered_t *bordered_ptr, wlmtk_rectangle_t **rectangle_ptr_ptr) { if (NULL == *rectangle_ptr_ptr) return; wlmtk_container_remove_element( &bordered_ptr->super_container, wlmtk_rectangle_element(*rectangle_ptr_ptr)); wlmtk_rectangle_destroy(*rectangle_ptr_ptr); *rectangle_ptr_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** * Updates the position of all 4 border elements. * * Retrieves the position and dimensions of @ref wlmtk_bordered_t::element_ptr * and arranges the 4 border elements around it. * * @param bordered_ptr */ void _wlmtk_bordered_set_positions(wlmtk_bordered_t *bordered_ptr) { int x1, y1, x2, y2; int x_pos, y_pos; if (NULL == bordered_ptr->western_border_rectangle_ptr) return; int margin = bordered_ptr->style.width; wlmtk_element_get_dimensions( bordered_ptr->element_ptr, &x1, &y1, &x2, &y2); x_pos = -x1 + margin; y_pos = -y1 + margin; int width = x2 - x1; int height = y2 - y1; wlmtk_element_set_position(bordered_ptr->element_ptr, x_pos, y_pos); wlmtk_element_set_position( wlmtk_rectangle_element(bordered_ptr->northern_border_rectangle_ptr), x_pos - margin, y_pos - margin); wlmtk_rectangle_set_size( bordered_ptr->northern_border_rectangle_ptr, width + 2 * margin, margin); wlmtk_element_set_position( wlmtk_rectangle_element(bordered_ptr->eastern_border_rectangle_ptr), x_pos + width, y_pos); wlmtk_rectangle_set_size( bordered_ptr->eastern_border_rectangle_ptr, margin, height); wlmtk_element_set_position( wlmtk_rectangle_element(bordered_ptr->southern_border_rectangle_ptr), x_pos - margin, y_pos + height); wlmtk_rectangle_set_size( bordered_ptr->southern_border_rectangle_ptr, width + 2 * margin, margin); wlmtk_element_set_position( wlmtk_rectangle_element(bordered_ptr->western_border_rectangle_ptr), x_pos - margin, y_pos); wlmtk_rectangle_set_size( bordered_ptr->western_border_rectangle_ptr, margin, height); } /* == Unit tests =========================================================== */ static void test_init_fini(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_bordered_test_cases[] = { { 1, "init_fini", test_init_fini }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_bordered_test_set = BS_TEST_SET( true, "bordered", _wlmtk_bordered_test_cases); /** Style used for tests. */ static const struct wlmtk_margin_style test_style = { .width = 2, .color = 0xff000000 }; /** Helper: Tests that the rectangle is positioned as specified. */ void test_rectangle_pos(bs_test_t *test_ptr, wlmtk_rectangle_t *rect_ptr, int x, int y, int width, int height) { wlmtk_element_t *elem_ptr = wlmtk_rectangle_element(rect_ptr); BS_TEST_VERIFY_EQ(test_ptr, x, elem_ptr->x); BS_TEST_VERIFY_EQ(test_ptr, y, elem_ptr->y); int x1, y1, x2, y2; wlmtk_element_get_dimensions(elem_ptr, &x1, &y1, &x2, &y2); BS_TEST_VERIFY_EQ(test_ptr, 0, x1); BS_TEST_VERIFY_EQ(test_ptr, 0, y1); BS_TEST_VERIFY_EQ(test_ptr, width, x2 - x1); BS_TEST_VERIFY_EQ(test_ptr, height, y2 - y1); } /* ------------------------------------------------------------------------- */ /** Exercises setup and teardown. */ void test_init_fini(bs_test_t *test_ptr) { wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); fe_ptr->dimensions.width = 100; fe_ptr->dimensions.height = 20; wlmtk_element_set_position(&fe_ptr->element, -10, -4); wlmtk_bordered_t bordered; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_bordered_init( &bordered, &fe_ptr->element, &test_style)); // Positions of border elements. test_rectangle_pos( test_ptr, bordered.northern_border_rectangle_ptr, 0, 0, 104, 2); test_rectangle_pos( test_ptr, bordered.eastern_border_rectangle_ptr, 102, 2, 2, 20); test_rectangle_pos( test_ptr, bordered.southern_border_rectangle_ptr, 0, 22, 104, 2); test_rectangle_pos( test_ptr, bordered.western_border_rectangle_ptr, 0, 2, 2, 20); // Update layout, test updated positions. wlmtk_fake_element_set_dimensions(fe_ptr, 200, 120); wlmtk_element_layout(wlmtk_bordered_element(&bordered)); test_rectangle_pos( test_ptr, bordered.northern_border_rectangle_ptr, 0, 0, 204, 2); test_rectangle_pos( test_ptr, bordered.eastern_border_rectangle_ptr, 202, 2, 2, 120); test_rectangle_pos( test_ptr, bordered.southern_border_rectangle_ptr, 0, 122, 204, 2); test_rectangle_pos( test_ptr, bordered.western_border_rectangle_ptr, 0, 2, 2, 120); wlmtk_bordered_fini(&bordered); wlmtk_element_destroy(&fe_ptr->element); } /* == End of bordered.c ==================================================== */ wlmaker-0.8.1/src/toolkit/base.c0000644000175100017510000001511615222270710016124 0ustar runnerrunner/* ========================================================================= */ /** * @file base.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "base.h" #include #include #include "test.h" // IWYU pragma: keep /* == Declarations ========================================================= */ static void _wlmtk_base_element_get_dimensions( wlmtk_element_t *element_ptr, int *x1_ptr, int *y1_ptr, int *x2_ptr, int *y2_ptr); /* == Data ================================================================= */ /** Virtual method table for the base's elemnt superclass. */ static const wlmtk_element_vmt_t _wlmtk_base_element_vmt = { .get_dimensions = _wlmtk_base_element_get_dimensions }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_base_init( wlmtk_base_t *base_ptr, wlmtk_element_t *element_ptr) { *base_ptr = (wlmtk_base_t){}; if (!wlmtk_container_init(&base_ptr->super_container)) return false; base_ptr->orig_super_element_vmt = wlmtk_element_extend( &base_ptr->super_container.super_element, &_wlmtk_base_element_vmt); wlmtk_element_set_visible(wlmtk_base_element(base_ptr), true); if (NULL != element_ptr) { wlmtk_base_set_content_element(base_ptr, element_ptr); } return true; } /* ------------------------------------------------------------------------- */ void wlmtk_base_fini(wlmtk_base_t *base_ptr) { wlmtk_base_set_content_element(base_ptr, NULL); wlmtk_container_fini(&base_ptr->super_container); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_base_element(wlmtk_base_t *base_ptr) { return &base_ptr->super_container.super_element; } /* ------------------------------------------------------------------------- */ void wlmtk_base_set_content_element( wlmtk_base_t *base_ptr, wlmtk_element_t *content_element_ptr) { if (NULL != base_ptr->content_element_ptr) { wlmtk_container_remove_element( &base_ptr->super_container, base_ptr->content_element_ptr); wlmtk_element_destroy(base_ptr->content_element_ptr); base_ptr->content_element_ptr = NULL; } if (NULL != content_element_ptr) { wlmtk_container_add_element_atop( &base_ptr->super_container, NULL, content_element_ptr); base_ptr->content_element_ptr = content_element_ptr; } } /* ------------------------------------------------------------------------- */ void wlmtk_base_push_element( wlmtk_base_t *base_ptr, wlmtk_element_t *element_ptr) { if (NULL != base_ptr->content_element_ptr) { BS_ASSERT(base_ptr->content_element_ptr != element_ptr); } wlmtk_container_add_element( &base_ptr->super_container, element_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_base_pop_element( wlmtk_base_t *base_ptr, wlmtk_element_t *element_ptr) { if (NULL != base_ptr->content_element_ptr) { BS_ASSERT(base_ptr->content_element_ptr != element_ptr); } wlmtk_container_remove_element( &base_ptr->super_container, element_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Gets the base's dimensions: Relays it to the base element. */ void _wlmtk_base_element_get_dimensions( wlmtk_element_t *element_ptr, int *x1_ptr, int *y1_ptr, int *x2_ptr, int *y2_ptr) { wlmtk_base_t *base_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_base_t, super_container.super_element); if (NULL == base_ptr->content_element_ptr) { if (NULL != x1_ptr) *x1_ptr = 0; if (NULL != y1_ptr) *y1_ptr = 0; if (NULL != x2_ptr) *x2_ptr = 0; if (NULL != y2_ptr) *y2_ptr = 0; } else { wlmtk_element_get_dimensions( base_ptr->content_element_ptr, x1_ptr, y1_ptr, x2_ptr, y2_ptr); } } /* == Unit Tests =========================================================== */ static void test_init_fini(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_base_test_cases[] = { { 1, "init_fini", test_init_fini }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_base_test_set = BS_TEST_SET( true, "base", _wlmtk_base_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises setup and teardown. */ void test_init_fini(bs_test_t *test_ptr) { wlmtk_base_t base; wlmtk_fake_element_t *fe1, *fe2; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_base_init(&base, NULL)); BS_TEST_VERIFY_EQ( test_ptr, &base.super_container.super_element, wlmtk_base_element(&base)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 0, 0, wlmtk_element_get_dimensions_box(wlmtk_base_element(&base))); fe1 = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1); wlmtk_fake_element_set_dimensions(fe1, 20, 10); wlmtk_base_set_content_element(&base, &fe1->element); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 20, 10, wlmtk_element_get_dimensions_box(wlmtk_base_element(&base))); // Push an element. It spans beyond the content. Must not show in // get_dimensions. fe2 = wlmtk_fake_element_create(); wlmtk_fake_element_set_dimensions(fe2, 100, 200); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2); wlmtk_base_push_element(&base, &fe2->element); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 20, 10, wlmtk_element_get_dimensions_box(wlmtk_base_element(&base))); wlmtk_base_pop_element(&base, &fe2->element); wlmtk_element_destroy(&fe2->element); wlmtk_base_fini(&base); } /* == End of base.c ======================================================== */ wlmaker-0.8.1/src/toolkit/button.c0000644000175100017510000003564015222270710016531 0ustar runnerrunner/* ========================================================================= */ /** * @file button.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "button.h" #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "gfxbuf.h" // IWYU pragma: keep #include "input.h" #include "util.h" #include "libbase/libbase.h" /* == Declarations ========================================================= */ static void _wlmtk_button_clicked(wlmtk_button_t *button_ptr); static bool _wlmtk_button_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static void _wlmtk_button_handle_pointer_enter( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_button_handle_pointer_leave( struct wl_listener *listener_ptr, void *data_ptr); static void apply_state(wlmtk_button_t *button_ptr); /* == Data ================================================================= */ /** Virtual method table for the button's element super class. */ static const wlmtk_element_vmt_t button_element_vmt = { .pointer_button = _wlmtk_button_element_pointer_button, }; /** Virtual method table for the button. */ static const wlmtk_button_vmt_t button_vmt = { .clicked = _wlmtk_button_clicked, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_button_init(wlmtk_button_t *button_ptr) { BS_ASSERT(NULL != button_ptr); *button_ptr = (wlmtk_button_t){ .vmt = button_vmt }; if (!wlmtk_buffer_init(&button_ptr->super_buffer)) { wlmtk_button_fini(button_ptr); return false; } button_ptr->orig_super_element_vmt = wlmtk_element_extend( &button_ptr->super_buffer.super_element, &button_element_vmt); wlmtk_util_connect_listener_signal( &button_ptr->super_buffer.super_element.events.pointer_enter, &button_ptr->pointer_enter_listener, _wlmtk_button_handle_pointer_enter); wlmtk_util_connect_listener_signal( &button_ptr->super_buffer.super_element.events.pointer_leave, &button_ptr->pointer_leave_listener, _wlmtk_button_handle_pointer_leave); return true; } /* ------------------------------------------------------------------------- */ wlmtk_button_vmt_t wlmtk_button_extend( wlmtk_button_t *button_ptr, const wlmtk_button_vmt_t *button_vmt_ptr) { wlmtk_button_vmt_t orig_vmt = button_ptr->vmt; if (NULL != button_vmt_ptr->clicked) { button_ptr->vmt.clicked = button_vmt_ptr->clicked; } return orig_vmt; } /* ------------------------------------------------------------------------- */ void wlmtk_button_fini(wlmtk_button_t *button_ptr) { wlmtk_util_disconnect_listener(&button_ptr->pointer_leave_listener); wlmtk_util_disconnect_listener(&button_ptr->pointer_enter_listener); wlmtk_util_disconnect_listener(&button_ptr->pointer_leave_listener); wlmtk_util_disconnect_listener(&button_ptr->pointer_enter_listener); if (NULL != button_ptr->pressed_wlr_buffer_ptr) { wlr_buffer_unlock(button_ptr->pressed_wlr_buffer_ptr); button_ptr->pressed_wlr_buffer_ptr = NULL; } if (NULL != button_ptr->released_wlr_buffer_ptr) { wlr_buffer_unlock(button_ptr->released_wlr_buffer_ptr); button_ptr->released_wlr_buffer_ptr = NULL; } wlmtk_buffer_fini(&button_ptr->super_buffer); } /* ------------------------------------------------------------------------- */ void wlmtk_button_set( wlmtk_button_t *button_ptr, struct wlr_buffer *released_wlr_buffer_ptr, struct wlr_buffer *pressed_wlr_buffer_ptr ) { if (NULL == released_wlr_buffer_ptr) { BS_ASSERT(NULL == pressed_wlr_buffer_ptr); } else { BS_ASSERT(released_wlr_buffer_ptr->width == pressed_wlr_buffer_ptr->width); BS_ASSERT(released_wlr_buffer_ptr->height == pressed_wlr_buffer_ptr->height); } if (NULL != button_ptr->released_wlr_buffer_ptr) { wlr_buffer_unlock(button_ptr->released_wlr_buffer_ptr); } button_ptr->released_wlr_buffer_ptr = wlr_buffer_lock( released_wlr_buffer_ptr); if (NULL != button_ptr->pressed_wlr_buffer_ptr) { wlr_buffer_unlock(button_ptr->pressed_wlr_buffer_ptr); } button_ptr->pressed_wlr_buffer_ptr = wlr_buffer_lock( pressed_wlr_buffer_ptr); apply_state(button_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Default implementation of @ref wlmtk_button_vmt_t::clicked. Nothing. */ void _wlmtk_button_clicked(__UNUSED__ wlmtk_button_t *button_ptr) { // Nothing. } /* ------------------------------------------------------------------------- */ /** See @ref wlmtk_element_vmt_t::pointer_button. */ bool _wlmtk_button_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_button_t *button_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_button_t, super_buffer.super_element); if (button_event_ptr->button != BTN_LEFT) return true; switch (button_event_ptr->type) { case WLMTK_BUTTON_DOWN: button_ptr->pressed = true; apply_state(button_ptr); break; case WLMTK_BUTTON_UP: button_ptr->pressed = false; apply_state(button_ptr); break; case WLMTK_BUTTON_CLICK: button_ptr->vmt.clicked(button_ptr); break; default: break; } return true; } /* ------------------------------------------------------------------------- */ /** Pointer enters the area: We may need to update visualization. */ void _wlmtk_button_handle_pointer_enter( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_button_t *button_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_button_t, pointer_enter_listener); apply_state(button_ptr); } /* ------------------------------------------------------------------------- */ /** Pointer leaves the area: We may need to update visualization. */ void _wlmtk_button_handle_pointer_leave( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_button_t *button_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_button_t, pointer_leave_listener); apply_state(button_ptr); } /* ------------------------------------------------------------------------- */ /** Sets the appropriate texture for the button. */ void apply_state(wlmtk_button_t *button_ptr) { if (button_ptr->super_buffer.super_element.pointer_inside && button_ptr->pressed) { wlmtk_buffer_set( &button_ptr->super_buffer, button_ptr->pressed_wlr_buffer_ptr); } else { wlmtk_buffer_set( &button_ptr->super_buffer, button_ptr->released_wlr_buffer_ptr); } } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); static void test_press_release(bs_test_t *test_ptr); static void test_press_release_outside(bs_test_t *test_ptr); static void test_press_right(bs_test_t *test_ptr); /** Test case definition. */ /** Test cases */ static const bs_test_case_t _wlmtk_button_test_cases[] = { { 1, "create_destroy", test_create_destroy }, { 1, "press_release", test_press_release }, { 1, "press_release_outside", test_press_release_outside }, { 1, "press_right", test_press_right }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_button_test_set = BS_TEST_SET( true, "button", _wlmtk_button_test_cases); /** Test outcome: Whether 'clicked' was called. */ static bool fake_button_got_clicked = false; /** Fake 'clicked' handler. */ static void fake_button_clicked(__UNUSED__ wlmtk_button_t *button_ptr) { fake_button_got_clicked = true; } /** Virtual method table of fake button. */ static const wlmtk_button_vmt_t fake_button_vmt = { .clicked = fake_button_clicked, }; /* ------------------------------------------------------------------------- */ /** Exercises @ref wlmtk_button_init and @ref wlmtk_button_fini. */ void test_create_destroy(bs_test_t *test_ptr) { wlmtk_button_t button; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_button_init(&button)); wlmtk_button_fini(&button); } /* ------------------------------------------------------------------------- */ /** Tests button pressing & releasing. */ void test_press_release(bs_test_t *test_ptr) { wlmtk_button_t button; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_button_init(&button)); wlmtk_button_extend(&button, &fake_button_vmt); wlmtk_element_set_visible(&button.super_buffer.super_element, true); struct wlr_buffer *p_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); struct wlr_buffer *r_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); wlmtk_button_set(&button, r_ptr, p_ptr); wlmtk_button_event_t event = { .button = BTN_LEFT, .time_msec = 42 }; wlmtk_element_t *element_ptr = &button.super_buffer.super_element; fake_button_got_clicked = false; // Initial state: released. BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); wlmtk_pointer_motion_event_t e = { .x = 0, .y = 0, .time_msec = 41 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(element_ptr, &e)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); // Button down: pressed. event.type = WLMTK_BUTTON_DOWN; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &event)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, p_ptr); // Pointer leaves the area: released. e = (wlmtk_pointer_motion_event_t){ .x = NAN, .y = NAN, .time_msec = 41 }; wlmtk_element_pointer_motion(element_ptr, &e); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); // Pointer re-enters the area: pressed. e = (wlmtk_pointer_motion_event_t){ .x = 0, .y = 0, .time_msec = 41 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(element_ptr, &e)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, p_ptr); // Button up: released. event.type = WLMTK_BUTTON_UP; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &event)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); event.type = WLMTK_BUTTON_CLICK; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &event)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); BS_TEST_VERIFY_TRUE(test_ptr, fake_button_got_clicked); wlr_buffer_drop(r_ptr); wlr_buffer_drop(p_ptr); wlmtk_button_fini(&button); } /* ------------------------------------------------------------------------- */ /** Tests button when releasing outside the pointer focus. */ void test_press_release_outside(bs_test_t *test_ptr) { wlmtk_button_t button; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_button_init(&button)); wlmtk_element_set_visible(&button.super_buffer.super_element, true); struct wlr_buffer *p_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); struct wlr_buffer *r_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); wlmtk_button_set(&button, r_ptr, p_ptr); wlmtk_button_event_t event = { .button = BTN_LEFT, .time_msec = 42 }; wlmtk_element_t *element_ptr = &button.super_buffer.super_element; // Enter the ara. Released. wlmtk_pointer_motion_event_t e = { .x = 0, .y = 0, .time_msec = 41 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(element_ptr, &e)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); // Button down: pressed. event.type = WLMTK_BUTTON_DOWN; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &event)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, p_ptr); // Pointer leaves the area: released. e = (wlmtk_pointer_motion_event_t){ .x = NAN, .y = NAN, .time_msec = 41 }; BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_pointer_motion(element_ptr, &e)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); // Button up, outside the area. Then, re-enter: Still released. event.type = WLMTK_BUTTON_UP; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &event)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); e = (wlmtk_pointer_motion_event_t){ .x = 0, .y = 0, .time_msec = 41 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(element_ptr, &e)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); wlr_buffer_drop(r_ptr); wlr_buffer_drop(p_ptr); wlmtk_button_fini(&button); } /* ------------------------------------------------------------------------- */ /** Tests button when releasing outside the pointer focus. */ void test_press_right(bs_test_t *test_ptr) { wlmtk_button_t button; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_button_init(&button)); wlmtk_element_set_visible(&button.super_buffer.super_element, true); struct wlr_buffer *p_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); struct wlr_buffer *r_ptr = bs_gfxbuf_create_wlr_buffer(1, 1); wlmtk_button_set(&button, r_ptr, p_ptr); wlmtk_button_event_t event = { .button = BTN_RIGHT, .type = WLMTK_BUTTON_DOWN, .time_msec = 42, }; wlmtk_element_t *element_ptr = &button.super_buffer.super_element; // Enter the ara. Released. wlmtk_pointer_motion_event_t e = { .x = 0, .y = 0, .time_msec = 41 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(element_ptr, &e)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); // Right button down: Remains released, reports claimed. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &event)); BS_TEST_VERIFY_EQ(test_ptr, button.super_buffer.wlr_buffer_ptr, r_ptr); wlr_buffer_drop(r_ptr); wlr_buffer_drop(p_ptr); wlmtk_button_fini(&button); } /* == End of button.c ====================================================== */ wlmaker-0.8.1/src/toolkit/window.c0000644000175100017510000025670015222270710016527 0ustar runnerrunner/* ========================================================================= */ /** * @file window.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "window.h" #include #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #undef WLR_USE_UNSTABLE #include "bordered.h" #include "box.h" #include "container.h" #include "input.h" #include "menu_item.h" #include "resizebar.h" #include "test.h" // IWYU pragma: keep #include "tile.h" #include "titlebar.h" /* == Declarations ========================================================= */ /** Window handle. */ struct _wlmtk_window_t { /** Bordered, wraps around the box. */ wlmtk_bordered_t bordered; /** Original virtual method table of the window's element superclass. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Composed of a box: Holds decoration, popup container and content. */ wlmtk_box_t box; /** Element in @ref wlmtk_workspace_t::windows, when mapped. */ bs_dllist_node_t dlnode; /** Events for this window. */ wlmtk_window_events_t events; /** Client information. See @ref wlmtk_window_set_client. */ wlmtk_util_client_t client; /** Container for the content. */ wlmtk_container_t content_container; /** The content. */ wlmtk_element_t *content_element_ptr; /** The workspace, when mapped to a workspace. NULL otherwise. */ wlmtk_workspace_t *workspace_ptr; /** Preferred output. See @ref wlmtk_window_set_preferred_wlr_output. */ struct wlr_output *preferred_wlr_output_ptr; /** Reference to the menu's style. */ wlmtk_menu_style_ref_t *menu_style_ref_ptr; /** Reference to the window's style. */ wlmtk_window_style_ref_t *style_ref_ptr; /** The window's style. Implies holding a reference. */ const struct wlmtk_window_style *style_ptr; /** The titlebar, when server-side decorated. */ wlmtk_titlebar_t *titlebar_ptr; /** The resize-bar, when server-side decorated. */ wlmtk_resizebar_t *resizebar_ptr; /** The window's title. */ char *title_ptr; /** Properties of the window. See @ref wlmtk_window_property_t. */ uint32_t properties; /** Modifier to trigger moving the window, on pointer drag. */ uint32_t move_modifier; /** * Position of the window, and size of the content window when not in * fullscreen or maximized state. */ struct wlr_box organic_bounding_box; /** * Committed content size. That may differ from the dimensions of * the content element. See @ref wlmtk_window_t::committed_size and * @ref wlmtk_window_get_size. */ struct wlr_box committed_size; /** Edges to anchor on when resizing. */ uint32_t resize_edges; /** Current box size when resizing. */ struct wlr_box old_box; /** The window menu. */ wlmtk_menu_t *window_menu_ptr; /** Listener for then the popup menu requests to be closed. */ struct wl_listener menu_request_close_listener; /** * Whether an "inorganic" sizing operation is in progress, and thus size * changes should not record to @ref wlmtk_window_t::organic_bounding_box. * * This is eg. between @ref wlmtk_window_request_fullscreen and * @ref wlmtk_window_commit_fullscreen. */ bool inorganic_sizing; /** Whether this window has server-side decorations. */ bool server_side_decorated; /** Whether this windows is currently in fullscreen mode. */ bool fullscreen; /** Whether this window is currently in maximized state. */ bool maximized; /** Whether this window is currently shaded. */ bool shaded; /** Whether this window is currently activated (has keyboard focus). */ bool activated; }; /** Type-safe holder for the window style's reference counter. */ struct _wlmtk_window_style_ref_t { /** Actual reference counter. */ bs_ref_t reference; }; /** Holds the reference and the style. */ struct wlmtk_window_style_holder { /** Reference counter */ struct _wlmtk_window_style_ref_t wsr; /** Style */ struct wlmtk_window_style style; }; static bool _wlmtk_window_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static void _wlmtk_window_element_layout( wlmtk_element_t *element_ptr); static void _wlmtk_window_container_element_get_dimensions( wlmtk_element_t *element_ptr, int *x1_ptr, int *y1_ptr, int *x2_ptr, int *y2_ptr); static void _wlmtk_window_apply_decoration(wlmtk_window_t *window_ptr); static void _wlmtk_window_create_titlebar(wlmtk_window_t *window_ptr); static void _wlmtk_window_create_resizebar(wlmtk_window_t *window_ptr); static void _wlmtk_window_destroy_titlebar(wlmtk_window_t *window_ptr); static void _wlmtk_window_destroy_resizebar(wlmtk_window_t *window_ptr); static void _wlmtk_window_set_decoration_width(wlmtk_window_t *window_ptr); static void _wlmtk_window_menu_request_close_handler( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_window_style_destroy(bs_ref_t *ref_ptr); static void _wlmtk_window_request_fullscreen_size( wlmtk_window_t *window_ptr, bool fullscreen); static void _wlmtk_window_request_maximized_size( wlmtk_window_t *window_ptr, bool maximized); /* == Data ================================================================= */ /** Virtual method table for the window's element superclass. */ static const wlmtk_element_vmt_t window_element_vmt = { .pointer_button = _wlmtk_window_element_pointer_button, .layout = _wlmtk_window_element_layout, }; /** Virtual method table for the window's content superclass element. */ static const wlmtk_element_vmt_t _wlmtk_window_content_container_element_vmt = { .get_dimensions = _wlmtk_window_container_element_get_dimensions, }; /** Descriptor for decoding the "Window" dictionary. */ static const bspl_desc_t _wlmtk_window_style_desc[] = { BSPL_DESC_DICT( "TitleBar", true, struct wlmtk_window_style, titlebar, titlebar, wlmtk_titlebar_style_desc), BSPL_DESC_DICT( "ResizeBar", true, struct wlmtk_window_style, resizebar, resizebar, wlmtk_resizebar_style_desc), BSPL_DESC_DICT( "Border", true, struct wlmtk_window_style, border, border, wlmtk_style_margin_desc), BSPL_DESC_DICT( "Margin", true, struct wlmtk_window_style, margin, margin, wlmtk_style_margin_desc), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_window_t *wlmtk_window_create( wlmtk_element_t *content_element_ptr, wlmtk_window_style_ref_t *style_ref_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr) { wlmtk_window_t *window_ptr = logged_calloc(1, sizeof(wlmtk_window_t)); if (NULL == window_ptr) return NULL; window_ptr->style_ref_ptr = style_ref_ptr; window_ptr->style_ptr = wlmtk_window_style_ref_retain(style_ref_ptr); window_ptr->menu_style_ref_ptr = menu_style_ref_ptr; wlmtk_menu_style_ref_retain(menu_style_ref_ptr); wl_signal_init(&window_ptr->events.state_changed); wl_signal_init(&window_ptr->events.request_close); wl_signal_init(&window_ptr->events.set_activated); wl_signal_init(&window_ptr->events.request_size); wl_signal_init(&window_ptr->events.request_fullscreen); wl_signal_init(&window_ptr->events.request_maximized); window_ptr->move_modifier = WLR_MODIFIER_ALT; if (!wlmtk_window_set_title(window_ptr, NULL)) goto error; if (!wlmtk_container_init(&window_ptr->content_container)) goto error; if (!wlmtk_box_init( &window_ptr->box, WLMTK_BOX_VERTICAL, &window_ptr->style_ptr->margin)) { goto error; } if (!wlmtk_bordered_init( &window_ptr->bordered, wlmtk_box_element(&window_ptr->box), &window_ptr->style_ptr->border)) { goto error; } window_ptr->orig_super_element_vmt = wlmtk_element_extend( &window_ptr->bordered.super_container.super_element, &window_element_vmt); wlmtk_element_extend( &window_ptr->content_container.super_element, &_wlmtk_window_content_container_element_vmt); wlmtk_element_set_visible( &window_ptr->content_container.super_element, true); if (NULL != content_element_ptr) { wlmtk_element_set_visible(content_element_ptr, true); wlmtk_container_add_element( &window_ptr->content_container, content_element_ptr); window_ptr->content_element_ptr = content_element_ptr; } wlmtk_box_add_element_front( &window_ptr->box, &window_ptr->content_container.super_element); wlmtk_element_set_visible(wlmtk_box_element(&window_ptr->box), true); // Create the window menu. It is kept hidden until invoked. window_ptr->window_menu_ptr = wlmtk_menu_create(menu_style_ref_ptr); if (NULL == window_ptr->window_menu_ptr) goto error; wlmtk_container_add_element( &window_ptr->content_container, wlmtk_menu_element(window_ptr->window_menu_ptr)); wlmtk_util_connect_listener_signal( &wlmtk_menu_events(window_ptr->window_menu_ptr)->request_close, &window_ptr->menu_request_close_listener, _wlmtk_window_menu_request_close_handler); _wlmtk_window_apply_decoration(window_ptr); return window_ptr; error: wlmtk_window_destroy(window_ptr); return NULL; } /* ------------------------------------------------------------------------- */ void wlmtk_window_destroy(wlmtk_window_t *window_ptr) { wlmtk_window_set_server_side_decorated(window_ptr, false); if (NULL != window_ptr->window_menu_ptr) { wlmtk_util_disconnect_listener( &window_ptr->menu_request_close_listener); wlmtk_container_remove_element( &window_ptr->content_container, wlmtk_menu_element(window_ptr->window_menu_ptr)); wlmtk_menu_destroy(window_ptr->window_menu_ptr); window_ptr->window_menu_ptr = NULL; } if (window_ptr->content_container.super_element.parent_container_ptr) { wlmtk_box_remove_element( &window_ptr->box, &window_ptr->content_container.super_element); } if (NULL != window_ptr->content_element_ptr) { wlmtk_container_remove_element( &window_ptr->content_container, window_ptr->content_element_ptr); window_ptr->content_element_ptr = NULL; } wlmtk_bordered_fini(&window_ptr->bordered); wlmtk_box_fini(&window_ptr->box); wlmtk_container_fini(&window_ptr->content_container); if (NULL != window_ptr->title_ptr) { free(window_ptr->title_ptr); window_ptr->title_ptr = NULL; } if (NULL != window_ptr->style_ref_ptr) { wlmtk_window_style_ref_release(window_ptr->style_ref_ptr); window_ptr->style_ref_ptr = NULL; } if (NULL != window_ptr->menu_style_ref_ptr) { wlmtk_menu_style_ref_release(window_ptr->menu_style_ref_ptr); window_ptr->menu_style_ref_ptr = NULL; } free(window_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_window_events_t *wlmtk_window_events(wlmtk_window_t *window_ptr) { return &window_ptr->events; } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_window_element(wlmtk_window_t *window_ptr) { return &window_ptr->bordered.super_container.super_element; } /* ------------------------------------------------------------------------- */ wlmtk_window_t *wlmtk_window_from_element(wlmtk_element_t *element_ptr) { return BS_CONTAINER_OF( element_ptr, wlmtk_window_t, bordered.super_container.super_element); } /* ------------------------------------------------------------------------- */ struct wlr_box wlmtk_window_get_bounding_box(wlmtk_window_t *window_ptr) { struct wlr_box wbox = wlmtk_element_get_dimensions_box( wlmtk_window_element(window_ptr)); int x, y; wlmtk_element_get_position(wlmtk_window_element(window_ptr), &x, &y); wbox.x += x; wbox.y += y; return wbox; } /* ------------------------------------------------------------------------- */ void wlmtk_window_position_changed(wlmtk_window_t *window_ptr) { if (window_ptr->inorganic_sizing) return; wlmtk_element_get_position( wlmtk_window_element(window_ptr), &window_ptr->organic_bounding_box.x, &window_ptr->organic_bounding_box.y); } /* ------------------------------------------------------------------------- */ void wlmtk_window_set_properties( wlmtk_window_t *window_ptr, uint32_t properties) { if (window_ptr->properties == properties) return; window_ptr->properties = properties; _wlmtk_window_apply_decoration(window_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_window_set_client( wlmtk_window_t *window_ptr, const wlmtk_util_client_t *client_ptr) { window_ptr->client = *client_ptr; } /* ------------------------------------------------------------------------- */ const wlmtk_util_client_t *wlmtk_window_get_client_ptr( __UNUSED__ wlmtk_window_t *window_ptr) { return &window_ptr->client; } /* ------------------------------------------------------------------------- */ void wlmtk_window_set_preferred_wlr_output( wlmtk_window_t *window_ptr, struct wlr_output *wlr_output_ptr) { window_ptr->preferred_wlr_output_ptr = wlr_output_ptr; } /* ------------------------------------------------------------------------- */ struct wlr_output *wlmtk_window_get_wlr_output(wlmtk_window_t *window_ptr) { // Preference had been indicated earlier. Use that. if (NULL != window_ptr->preferred_wlr_output_ptr) { return window_ptr->preferred_wlr_output_ptr; } wlmtk_workspace_t *ws = wlmtk_window_get_workspace(window_ptr); if (NULL == ws) return NULL; struct wlr_output_layout *ol = wlmtk_workspace_get_wlr_output_layout(ws); if (NULL == ol) return NULL; struct wlr_box wbox = wlmtk_window_get_bounding_box(window_ptr); double layout_x, layout_y; wlr_output_layout_closest_point( ol, NULL, // struct wlr_output* reference. We don't need a reference. wbox.x + wbox.width / 2, wbox.y + wbox.height / 2, &layout_x, &layout_y); return wlr_output_layout_output_at(ol, layout_x, layout_y); } /* ------------------------------------------------------------------------- */ bool wlmtk_window_set_title( wlmtk_window_t *window_ptr, const char *title_ptr) { char *new_title_ptr = NULL; if (NULL != title_ptr) { new_title_ptr = logged_strdup(title_ptr); } else { char buf[64]; snprintf(buf, sizeof(buf), "Unnamed window %p", window_ptr); new_title_ptr = logged_strdup(buf); } if (NULL == new_title_ptr) return false; if (NULL != window_ptr->title_ptr) { if (0 == strcmp(window_ptr->title_ptr, new_title_ptr)) { free(new_title_ptr); return true; } free(window_ptr->title_ptr); } window_ptr->title_ptr = new_title_ptr; if (NULL != window_ptr->titlebar_ptr) { wlmtk_titlebar_set_title(window_ptr->titlebar_ptr, window_ptr->title_ptr); } return true; } /* ------------------------------------------------------------------------- */ const char *wlmtk_window_get_title(wlmtk_window_t *window_ptr) { BS_ASSERT(NULL != window_ptr->title_ptr); return window_ptr->title_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_window_set_activated( wlmtk_window_t *window_ptr, bool activated) { if (window_ptr->activated == activated) return; window_ptr->activated = activated; wl_signal_emit(&window_ptr->events.set_activated, NULL); if (NULL != window_ptr->titlebar_ptr) { wlmtk_titlebar_set_activated(window_ptr->titlebar_ptr, activated); } if (!activated) { wlmtk_window_menu_set_enabled(window_ptr, false); // TODO(kaeser@gubbe.ch): Should test this behaviour. if (NULL != window_ptr->workspace_ptr) { wlmtk_workspace_activate_window(window_ptr->workspace_ptr, NULL); } } } /* ------------------------------------------------------------------------- */ bool wlmtk_window_is_activated(wlmtk_window_t *window_ptr) { return window_ptr->activated; } /* ------------------------------------------------------------------------- */ void wlmtk_window_request_size( wlmtk_window_t *window_ptr, const struct wlr_box *box_ptr) { if (window_ptr->fullscreen) return; if (window_ptr->maximized) { static bool not_maximized = false; wl_signal_emit(&window_ptr->events.request_maximized, ¬_maximized); } wl_signal_emit(&window_ptr->events.request_size, (void*)box_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_window_commit_size( wlmtk_window_t *window_ptr, int width, int height) { window_ptr->committed_size = (struct wlr_box){ .width = width, .height = height }; // Store organic size. If we're in an organic mode. if (!window_ptr->inorganic_sizing && !window_ptr->fullscreen && !window_ptr->maximized) { window_ptr->organic_bounding_box.width = width; window_ptr->organic_bounding_box.height = height; } wlmtk_element_layout(wlmtk_window_element(window_ptr)); wlmtk_element_invalidate_parent_layout(wlmtk_window_element(window_ptr)); } /* ------------------------------------------------------------------------- */ struct wlr_box wlmtk_window_get_size(wlmtk_window_t *window_ptr) { return window_ptr->committed_size; } /* ------------------------------------------------------------------------- */ void wlmtk_window_set_resize_edges( wlmtk_window_t *window_ptr, uint32_t edges) { window_ptr->resize_edges = edges; window_ptr->old_box = wlmtk_element_get_dimensions_box( wlmtk_bordered_element(&window_ptr->bordered)); } /* ------------------------------------------------------------------------- */ uint32_t wlmtk_window_get_resize_edges(wlmtk_window_t *window_ptr) { return window_ptr->resize_edges; } /* ------------------------------------------------------------------------- */ void wlmtk_window_request_close(wlmtk_window_t *window_ptr) { if (!(window_ptr->properties & WLMTK_WINDOW_PROPERTY_CLOSABLE)) return; wl_signal_emit(&window_ptr->events.request_close, NULL); } /* ------------------------------------------------------------------------- */ void wlmtk_window_request_minimize(wlmtk_window_t *window_ptr) { bs_log(BS_ERROR, "TODO: Request minimize for window %p", window_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_window_request_fullscreen( wlmtk_window_t *window_ptr, bool fullscreen) { window_ptr->inorganic_sizing = fullscreen; // If a window is not mapped, nor does have an output, we cannot make it // fullscreen. We wouldn't have the dimensions needed. struct wlr_output *wlr_o = wlmtk_window_get_wlr_output(window_ptr); if (NULL == wlr_o) { static bool not_fullscreen = false; wl_signal_emit(&window_ptr->events.request_fullscreen, ¬_fullscreen); return; } wl_signal_emit(&window_ptr->events.request_fullscreen, &fullscreen); _wlmtk_window_request_fullscreen_size(window_ptr, fullscreen); } /* ------------------------------------------------------------------------- */ bool wlmtk_window_is_fullscreen(wlmtk_window_t *window_ptr) { return window_ptr->fullscreen; } /* ------------------------------------------------------------------------- */ void wlmtk_window_commit_fullscreen( wlmtk_window_t *window_ptr, bool fullscreen) { // Guard clause: Nothing to do if already in expected state. if (window_ptr->fullscreen == fullscreen ) return; window_ptr->fullscreen = fullscreen; _wlmtk_window_apply_decoration(window_ptr); if (NULL != window_ptr->workspace_ptr) { wlmtk_workspace_window_to_fullscreen( window_ptr->workspace_ptr, window_ptr, fullscreen); struct wlr_box fsbox = wlmtk_workspace_get_fullscreen_extents( window_ptr->workspace_ptr, wlmtk_window_get_wlr_output(window_ptr)); wlmtk_workspace_set_window_position( window_ptr->workspace_ptr, window_ptr, fullscreen ? fsbox.x : window_ptr->organic_bounding_box.x, fullscreen ? fsbox.y : window_ptr->organic_bounding_box.y); } _wlmtk_window_request_fullscreen_size(window_ptr, fullscreen); window_ptr->inorganic_sizing = false; wl_signal_emit(&window_ptr->events.state_changed, window_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_window_request_maximized( wlmtk_window_t *window_ptr, bool maximized) { // Guard clause: Maximizing is refused if on fullscreen or neither mapped // nor having an output. Signal the window that it shall not be maximized. if (window_ptr->fullscreen || NULL == wlmtk_window_get_wlr_output(window_ptr)) { static bool not_maximized = false; wl_signal_emit(&window_ptr->events.request_maximized, ¬_maximized); return; } window_ptr->inorganic_sizing = maximized; wl_signal_emit(&window_ptr->events.request_maximized, &maximized); _wlmtk_window_request_maximized_size(window_ptr, maximized); } /* ------------------------------------------------------------------------- */ bool wlmtk_window_is_maximized(wlmtk_window_t *window_ptr) { return window_ptr->maximized; } /* ------------------------------------------------------------------------- */ void wlmtk_window_commit_maximized( wlmtk_window_t *window_ptr, bool maximized) { if (window_ptr->maximized == maximized || NULL == window_ptr->workspace_ptr) return; window_ptr->maximized = maximized; struct wlr_box max_box = wlmtk_workspace_get_maximize_extents( window_ptr->workspace_ptr, wlmtk_window_get_wlr_output(window_ptr)); wlmtk_workspace_set_window_position( window_ptr->workspace_ptr, window_ptr, maximized ? max_box.x : window_ptr->organic_bounding_box.x, maximized ? max_box.y : window_ptr->organic_bounding_box.y); _wlmtk_window_request_maximized_size(window_ptr, maximized); window_ptr->inorganic_sizing = false; wl_signal_emit(&window_ptr->events.state_changed, window_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_window_request_shaded(wlmtk_window_t *window_ptr, bool shaded) { if (window_ptr->fullscreen || !window_ptr->server_side_decorated || window_ptr->shaded == shaded) return; wlmtk_element_set_visible(window_ptr->content_element_ptr, !shaded); if (NULL != window_ptr->resizebar_ptr) { wlmtk_element_set_visible( wlmtk_resizebar_element(window_ptr->resizebar_ptr), !shaded); } window_ptr->shaded = shaded; wl_signal_emit(&window_ptr->events.state_changed, window_ptr); wlmtk_element_layout(wlmtk_window_element(window_ptr)); wlmtk_element_invalidate_parent_layout(wlmtk_window_element(window_ptr)); } /* ------------------------------------------------------------------------- */ bool wlmtk_window_is_shaded(wlmtk_window_t *window_ptr) { return window_ptr->shaded; } /* ------------------------------------------------------------------------- */ void wlmtk_window_menu_set_enabled(wlmtk_window_t *window_ptr, bool enabled) { if (!window_ptr->activated) enabled = false; // For convenience: Get the menu's element. Note: It must have a parent, // since it's contained within the window. wlmtk_element_t *menu_element_ptr = wlmtk_menu_element( window_ptr->window_menu_ptr); BS_ASSERT(NULL != menu_element_ptr->parent_container_ptr); wlmtk_menu_set_open(window_ptr->window_menu_ptr, enabled); if (enabled) { int x_pos = 0; // Grabbing the element will clear the focus. if (wlmtk_window_element(window_ptr)->pointer_inside) { x_pos = wlmtk_window_element( window_ptr)->last_pointer_motion_event.x; } wlmtk_menu_set_mode( window_ptr->window_menu_ptr, WLMTK_MENU_MODE_RIGHTCLICK); wlmtk_container_raise_element_to_top( menu_element_ptr->parent_container_ptr, menu_element_ptr); wlmtk_container_pointer_grab( menu_element_ptr->parent_container_ptr, menu_element_ptr); wlmtk_element_set_position(menu_element_ptr, x_pos, 0); } else { wlmtk_container_pointer_grab_release( menu_element_ptr->parent_container_ptr, menu_element_ptr); } } /* ------------------------------------------------------------------------- */ wlmtk_menu_t *wlmtk_window_menu(wlmtk_window_t *window_ptr) { return window_ptr->window_menu_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_window_set_server_side_decorated( wlmtk_window_t *window_ptr, bool decorated) { if (window_ptr->server_side_decorated == decorated) return; window_ptr->server_side_decorated = decorated; _wlmtk_window_apply_decoration(window_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_window_set_options( wlmtk_window_t *window_ptr, uint32_t move_modifier) { window_ptr->move_modifier = move_modifier; } /* ------------------------------------------------------------------------- */ void wlmtk_window_set_workspace( wlmtk_window_t *window_ptr, wlmtk_workspace_t *workspace_ptr) { if (window_ptr->workspace_ptr == workspace_ptr) return; window_ptr->workspace_ptr = workspace_ptr; wl_signal_emit(&window_ptr->events.state_changed, window_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_workspace_t *wlmtk_window_get_workspace(wlmtk_window_t *window_ptr) { return window_ptr->workspace_ptr; } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *wlmtk_dlnode_from_window(wlmtk_window_t *window_ptr) { return &window_ptr->dlnode; } /* ------------------------------------------------------------------------- */ wlmtk_window_t *wlmtk_window_from_dlnode(bs_dllist_node_t *dlnode_ptr) { return BS_CONTAINER_OF(dlnode_ptr, wlmtk_window_t, dlnode); } /* ------------------------------------------------------------------------- */ bool wlmtk_window_set_style( wlmtk_window_t *window_ptr, wlmtk_window_style_ref_t *style_ref_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr) { // Cautiously keep the reference until style update is done. wlmtk_window_style_ref_t *old_ref_ptr = window_ptr->style_ref_ptr; window_ptr->style_ref_ptr = style_ref_ptr; window_ptr->style_ptr = wlmtk_window_style_ref_retain(style_ref_ptr); wlmtk_menu_style_ref_t *old_menu_ref_ptr = window_ptr->menu_style_ref_ptr; window_ptr->menu_style_ref_ptr = menu_style_ref_ptr; wlmtk_menu_style_ref_retain(menu_style_ref_ptr); bool rv = true; wlmtk_bordered_set_style( &window_ptr->bordered, &window_ptr->style_ptr->border); wlmtk_box_set_style( &window_ptr->box, &window_ptr->style_ptr->margin); if (NULL != window_ptr->resizebar_ptr) { rv &= wlmtk_resizebar_set_style(window_ptr->resizebar_ptr, &window_ptr->style_ptr->resizebar); } if (NULL != window_ptr->titlebar_ptr) { rv &= wlmtk_titlebar_set_style(window_ptr->titlebar_ptr, &window_ptr->style_ptr->titlebar); } if (NULL != window_ptr->window_menu_ptr) { rv &= wlmtk_menu_set_style(window_ptr->window_menu_ptr, menu_style_ref_ptr); } wlmtk_menu_style_ref_release(old_menu_ref_ptr); wlmtk_window_style_ref_release(old_ref_ptr); return rv; } /* ------------------------------------------------------------------------- */ struct wlmtk_window_style *wlmtk_window_style_create(void) { struct wlmtk_window_style_holder *sh = logged_calloc(1, sizeof(*sh)); if (NULL == sh) return NULL; bs_ref_init(&sh->wsr.reference, _wlmtk_window_style_destroy); return &sh->style; } /* ------------------------------------------------------------------------- */ wlmtk_window_style_ref_t *wlmtk_window_style_to_ref( struct wlmtk_window_style *window_style_ptr) { // Guard clause. if (NULL == window_style_ptr) return NULL; struct wlmtk_window_style_holder *sh = BS_CONTAINER_OF( window_style_ptr, struct wlmtk_window_style_holder, style); return &sh->wsr; } /* ------------------------------------------------------------------------- */ const struct wlmtk_window_style *wlmtk_window_style_ref_retain( wlmtk_window_style_ref_t *ref_ptr) { bs_ref_retain(&ref_ptr->reference); struct wlmtk_window_style_holder *sh = BS_CONTAINER_OF( ref_ptr, struct wlmtk_window_style_holder, wsr.reference); return &sh->style; } /* ------------------------------------------------------------------------- */ void wlmtk_window_style_ref_release(wlmtk_window_style_ref_t *ref_ptr) { bs_ref_release(&ref_ptr->reference); } /* ------------------------------------------------------------------------- */ bool wlmtk_window_style_decode( bspl_object_t *object_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { struct wlmtk_window_style **s = value_ptr; return bspl_decode_dict( bspl_dict_from_object(object_ptr), _wlmtk_window_style_desc, *s); } /* ------------------------------------------------------------------------- */ bool wlmtk_window_style_decode_init(void *dst_ptr) { struct wlmtk_window_style **s = dst_ptr; *s = wlmtk_window_style_create(); return *s != NULL; } /* ------------------------------------------------------------------------- */ void wlmtk_window_style_decode_fini(void *dst_ptr) { struct wlmtk_window_style **s = dst_ptr; if (NULL != *s) { wlmtk_window_style_ref_release(wlmtk_window_style_to_ref(*s)); *s = NULL; } } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Activates window on button press, and calls the parent's implementation. */ bool _wlmtk_window_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_window_t *window_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_window_t, bordered.super_container.super_element); // In right-click mode: Any out-of-window action will close it. // TODO(kaeser@gubbe.ch): This should be a specific window mode, and should // have a handler method when leaving that mode (eg. left release within // the window). // Also TODO(kaeser@gubbe.ch): Test this. if (window_ptr->properties & WLMTK_WINDOW_PROPERTY_RIGHTCLICK) { bool rv = window_ptr->orig_super_element_vmt.pointer_button( element_ptr, button_event_ptr); if (BTN_RIGHT == button_event_ptr->button && WLMTK_BUTTON_UP == button_event_ptr->type && !rv) { wlmtk_window_request_close(window_ptr); return true; } return rv; } // Modifier-click initiates move. if (0 != window_ptr->move_modifier && BTN_LEFT == button_event_ptr->button && WLMTK_BUTTON_DOWN == button_event_ptr->type && window_ptr->move_modifier == button_event_ptr->keyboard_modifiers && NULL != wlmtk_window_get_workspace(window_ptr)) { wlmtk_workspace_begin_window_move( wlmtk_window_get_workspace(window_ptr), window_ptr); return true; } // We shouldn't receive buttons when not mapped. wlmtk_workspace_t *workspace_ptr = wlmtk_window_get_workspace(window_ptr); wlmtk_workspace_activate_window(workspace_ptr, window_ptr); if (!window_ptr->fullscreen) { wlmtk_workspace_raise_window(workspace_ptr, window_ptr); } return window_ptr->orig_super_element_vmt.pointer_button( element_ptr, button_event_ptr); } /* ------------------------------------------------------------------------- */ /** Gets dimensions of the content container: Forward to only the content. */ void _wlmtk_window_container_element_get_dimensions( wlmtk_element_t *element_ptr, int *x1_ptr, int *y1_ptr, int *x2_ptr, int *y2_ptr) { wlmtk_window_t *window_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_window_t, content_container.super_element); if (NULL == window_ptr->content_element_ptr || !window_ptr->content_element_ptr->visible) { if (NULL != x1_ptr) *x1_ptr = 0; if (NULL != y1_ptr) *y1_ptr = 0; if (NULL != x2_ptr) *x2_ptr = 0; if (NULL != y2_ptr) *y2_ptr = 0; } else { wlmtk_element_get_dimensions( window_ptr->content_element_ptr, x1_ptr, y1_ptr, x2_ptr, y2_ptr); } } /* ------------------------------------------------------------------------- */ /** * Implementation of @ref wlmtk_element_vmt_t::layout. * * Invoked when the window's contained elements triggered a layout update, * and will use this to trigger (potential) size updates to the window * decorations. * * @param element_ptr */ void _wlmtk_window_element_layout( wlmtk_element_t *element_ptr) { wlmtk_window_t *window_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_window_t, bordered.super_container.super_element); _wlmtk_window_set_decoration_width(window_ptr); window_ptr->orig_super_element_vmt.layout(element_ptr); // No updates if there is no content element (or not in the container). if (NULL == window_ptr->content_element_ptr || NULL == window_ptr->content_element_ptr->parent_container_ptr) { return; } // new_box includes potential decorations. struct wlr_box new_box = wlmtk_element_get_dimensions_box( wlmtk_bordered_element(&window_ptr->bordered)); int x, y; wlmtk_element_get_position( wlmtk_bordered_element(&window_ptr->bordered), &x, &y); if (window_ptr->resize_edges & WLR_EDGE_LEFT) { x += window_ptr->old_box.width - new_box.width; } if (window_ptr->resize_edges & WLR_EDGE_TOP) { y += window_ptr->old_box.height - new_box.height; } // TODO: Should we rather use wlmtk_workspace_set_window_position ? wlmtk_element_set_position( wlmtk_bordered_element(&window_ptr->bordered), x, y); window_ptr->old_box = new_box; } /* ------------------------------------------------------------------------- */ /** Applies window decoration depending on current state. */ void _wlmtk_window_apply_decoration(wlmtk_window_t *window_ptr) { struct wlmtk_margin_style bstyle = window_ptr->style_ptr->border; if (window_ptr->server_side_decorated && !window_ptr->fullscreen) { _wlmtk_window_create_titlebar(window_ptr); } else { bstyle.width = 0; _wlmtk_window_destroy_titlebar(window_ptr); } if (NULL != window_ptr->titlebar_ptr) { uint32_t properties = 0; if (window_ptr->properties & WLMTK_WINDOW_PROPERTY_ICONIFIABLE) { properties |= WLMTK_TITLEBAR_PROPERTY_ICONIFY; } if (window_ptr->properties & WLMTK_WINDOW_PROPERTY_CLOSABLE) { properties |= WLMTK_TITLEBAR_PROPERTY_CLOSE; } wlmtk_titlebar_set_properties(window_ptr->titlebar_ptr, properties); wlmtk_titlebar_set_activated( window_ptr->titlebar_ptr, window_ptr->activated); } if (window_ptr->server_side_decorated && !window_ptr->fullscreen && (window_ptr->properties & WLMTK_WINDOW_PROPERTY_RESIZABLE)) { _wlmtk_window_create_resizebar(window_ptr); } else { _wlmtk_window_destroy_resizebar(window_ptr); } wlmtk_bordered_set_style(&window_ptr->bordered, &bstyle); _wlmtk_window_set_decoration_width(window_ptr); wlmtk_element_layout(wlmtk_window_element(window_ptr)); wlmtk_element_invalidate_parent_layout(wlmtk_window_element(window_ptr)); } /* ------------------------------------------------------------------------- */ /** Creates the titlebar. */ void _wlmtk_window_create_titlebar(wlmtk_window_t *window_ptr) { BS_ASSERT(window_ptr->server_side_decorated && !window_ptr->fullscreen); // Guard clause: Don't add decoration. if (NULL != window_ptr->titlebar_ptr) return; // Create decoration. window_ptr->titlebar_ptr = wlmtk_titlebar_create( window_ptr, &window_ptr->style_ptr->titlebar); BS_ASSERT(NULL != window_ptr->titlebar_ptr); wlmtk_element_set_visible( wlmtk_titlebar_element(window_ptr->titlebar_ptr), true); wlmtk_titlebar_set_activated( window_ptr->titlebar_ptr, window_ptr->activated); // Hm, if the content has a popup that extends over the titlebar area, // it'll be partially obscured. That will look odd... Well, let's // address that problem once there's a situation. wlmtk_box_add_element_front( &window_ptr->box, wlmtk_titlebar_element(window_ptr->titlebar_ptr)); } /* ------------------------------------------------------------------------- */ /** Destroys the titlebar. */ void _wlmtk_window_destroy_titlebar(wlmtk_window_t *window_ptr) { BS_ASSERT(!window_ptr->server_side_decorated || window_ptr->fullscreen); if (NULL == window_ptr->titlebar_ptr) return; wlmtk_box_remove_element( &window_ptr->box, wlmtk_titlebar_element(window_ptr->titlebar_ptr)); wlmtk_titlebar_destroy(window_ptr->titlebar_ptr); window_ptr->titlebar_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Creates the resizebar. */ void _wlmtk_window_create_resizebar(wlmtk_window_t *window_ptr) { BS_ASSERT(window_ptr->server_side_decorated && !window_ptr->fullscreen); // Guard clause: Don't add decoration. if (NULL != window_ptr->resizebar_ptr) return; window_ptr->resizebar_ptr = wlmtk_resizebar_create( window_ptr, &window_ptr->style_ptr->resizebar); BS_ASSERT(NULL != window_ptr->resizebar_ptr); wlmtk_element_set_visible( wlmtk_resizebar_element(window_ptr->resizebar_ptr), true); wlmtk_box_add_element_back( &window_ptr->box, wlmtk_resizebar_element(window_ptr->resizebar_ptr)); } /* ------------------------------------------------------------------------- */ /** Destroys the resizebar. */ void _wlmtk_window_destroy_resizebar(wlmtk_window_t *window_ptr) { BS_ASSERT(!window_ptr->server_side_decorated || window_ptr->fullscreen || !(window_ptr->properties & WLMTK_WINDOW_PROPERTY_RESIZABLE)); if (NULL == window_ptr->resizebar_ptr) return; wlmtk_box_remove_element( &window_ptr->box, wlmtk_resizebar_element(window_ptr->resizebar_ptr)); wlmtk_resizebar_destroy(window_ptr->resizebar_ptr); window_ptr->resizebar_ptr = NULL; } /* ------------------------------------------------------------------------- */ /** Sets the decoration's width to match the content. */ void _wlmtk_window_set_decoration_width(wlmtk_window_t *window_ptr) { if (NULL == window_ptr->content_element_ptr) return; struct wlr_box box = wlmtk_element_get_dimensions_box( window_ptr->content_element_ptr); if (NULL != window_ptr->titlebar_ptr) { wlmtk_titlebar_set_width(window_ptr->titlebar_ptr, box.width); } if (NULL != window_ptr->resizebar_ptr) { wlmtk_resizebar_set_width(window_ptr->resizebar_ptr, box.width); } } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_menu_events_t::request_close. */ void _wlmtk_window_menu_request_close_handler( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_window_t *window_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_window_t, menu_request_close_listener); wlmtk_window_menu_set_enabled(window_ptr, false); } /* ------------------------------------------------------------------------- */ /** Destroys the window style. */ void _wlmtk_window_style_destroy(bs_ref_t *ref_ptr) { struct wlmtk_window_style_holder *sh = BS_CONTAINER_OF( ref_ptr, struct wlmtk_window_style_holder, wsr.reference); free(sh); } /* ------------------------------------------------------------------------- */ /** Requests the size appropriate for the maximized state. */ void _wlmtk_window_request_maximized_size( wlmtk_window_t *window_ptr, bool maximized) { struct wlr_box desired_size = window_ptr->organic_bounding_box; desired_size.x = 0; desired_size.y = 0; if (maximized) { // For decorated windows, we need to subtract the decorations size. // Since maximization will be requested from an organic size, we can // derive this from the delta of the "bordered" element, minus the // dimensions of the "content" element. // That will be zero for non-decorated windows. struct wlr_box wbox = wlmtk_element_get_dimensions_box( wlmtk_bordered_element(&window_ptr->bordered)); struct wlr_box cbox = wlmtk_element_get_dimensions_box( window_ptr->content_element_ptr); struct wlr_output *wo_ptr = wlmtk_window_get_wlr_output(window_ptr); if (NULL != window_ptr->workspace_ptr) { desired_size = wlmtk_workspace_get_maximize_extents( window_ptr->workspace_ptr, wo_ptr); } else if (NULL != wo_ptr) { desired_size.width = wo_ptr->width; desired_size.height = wo_ptr->height; } else { // Not mappend nor a specified output -- no size to set. return; } desired_size.width -= wbox.width - cbox.width; desired_size.height -= wbox.height - cbox.height; } wl_signal_emit(&window_ptr->events.request_size, &desired_size); } /* ------------------------------------------------------------------------- */ /** Requests size appropriate for the fullscreen state. */ void _wlmtk_window_request_fullscreen_size(wlmtk_window_t *window_ptr, bool fullscreen) { struct wlr_box desired_size = window_ptr->organic_bounding_box; desired_size.x = 0; desired_size.y = 0; if (fullscreen) { struct wlr_output *wo_ptr = wlmtk_window_get_wlr_output(window_ptr); if (NULL != window_ptr->workspace_ptr) { desired_size = wlmtk_workspace_get_fullscreen_extents( window_ptr->workspace_ptr, wo_ptr); } else if (NULL != wo_ptr) { desired_size = (struct wlr_box){ .width = wo_ptr->width, .height = wo_ptr->height }; } else { // No output and not mapped? No size to set. return; } } wl_signal_emit(&window_ptr->events.request_size, &desired_size); } /* == Unit Tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); static void test_decoration(bs_test_t *test_ptr); static void test_events(bs_test_t *test_ptr); static void test_set_activated(bs_test_t *test_ptr); static void test_resize(bs_test_t *test_ptr); static void test_fullscreen(bs_test_t *test_ptr); static void test_fullscreen_preferred_output(bs_test_t *test_ptr); static void test_fullscreen_unmap(bs_test_t *test_ptr); static void test_maximized(bs_test_t *test_ptr); static void test_shaded(bs_test_t *test_ptr); static void test_modifier_move(bs_test_t *test_ptr); static void test_menu(bs_test_t *test_ptr); static void test_style(bs_test_t *test_ptr); static void test_style_decode(bs_test_t *test_ptr); // TODO(kaeser@gubbe.ch): Add tests for .. // * storing organic_bounding_box /** Test cases */ static const bs_test_case_t _wlmtk_window_test_cases[] = { { 1, "create_destroy", test_create_destroy }, { 1, "decoration", test_decoration }, { 1, "events", test_events }, { 1, "set_activated", test_set_activated }, { 1, "resize", test_resize }, { 1, "fullscreen", test_fullscreen }, { 1, "fullscreen_preferred_output", test_fullscreen_preferred_output }, { 1, "fullscreen_unmap", test_fullscreen_unmap }, { 1, "maximized", test_maximized }, { 1, "shaded", test_shaded }, { 1, "modifier_move", test_modifier_move }, { 1, "menu", test_menu }, { 1, "style", test_style }, { 1, "style_decode", test_style_decode }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_window_test_set = BS_TEST_SET( true, "window", _wlmtk_window_test_cases); /** Window style used as default for testing, including a reference. */ static struct wlmtk_window_style_holder _wlmtk_window_test_style_holder = { .wsr = { .reference = { .count = 1 } }, .style = { .titlebar = { .height = 10 }, .resizebar = { .height = 5 }, .margin = { .width = 1 }, .border = { .width = 2 }, } }; /** Menu style used as default for testing windows. */ static const struct wlmtk_menu_style _wlmtk_window_test_menu_style = { .item = { .height = 20, .width = 100 } }; /* ------------------------------------------------------------------------- */ wlmtk_window_t *wlmtk_test_window_create( wlmtk_element_t *content_element_ptr) { struct wlmtk_menu_style *ms = wlmtk_menu_style_create(); if (NULL == ms) return NULL; *ms = _wlmtk_window_test_menu_style; wlmtk_window_t *window_ptr = wlmtk_window_create( content_element_ptr, &_wlmtk_window_test_style_holder.wsr, wlmtk_menu_style_to_ref(ms)); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(ms)); return window_ptr; } /* ------------------------------------------------------------------------- */ /** Exercises ctor and dtor and some accessors. */ void test_create_destroy(bs_test_t *test_ptr) { wlmtk_fake_element_t *fe = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe); wlmtk_window_t *w = wlmtk_test_window_create(&fe->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); // Title. Must be set when unnamed, and override otherwise. BS_TEST_VERIFY_STRMATCH( test_ptr, wlmtk_window_get_title(w), "Unnamed window .*"); wlmtk_window_set_title(w, "Title"); BS_TEST_VERIFY_STREQ(test_ptr, "Title", wlmtk_window_get_title(w)); // Transform to element and dlnode and back. wlmtk_element_t *e = wlmtk_window_element(w); BS_TEST_VERIFY_EQ(test_ptr, w, wlmtk_window_from_element(e)); bs_dllist_node_t *dlnode_ptr = wlmtk_dlnode_from_window(w); BS_TEST_VERIFY_EQ(test_ptr, w, wlmtk_window_from_dlnode(dlnode_ptr)); // set_workspace and get_workspace. struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, workspace_ptr); wlmtk_util_test_listener_t l; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->state_changed, &l); wlmtk_window_set_workspace(w, workspace_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); BS_TEST_VERIFY_EQ(test_ptr, w, l.last_data_ptr); BS_TEST_VERIFY_EQ( test_ptr, workspace_ptr, wlmtk_window_get_workspace(w)); wlmtk_util_clear_test_listener(&l); wlmtk_window_set_workspace(w, workspace_ptr); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_get_wlr_output(w)); struct wlr_output empty = {}; wlmtk_window_set_preferred_wlr_output(w, &empty); BS_TEST_VERIFY_EQ(test_ptr, &empty, wlmtk_window_get_wlr_output(w)); wlmtk_workspace_destroy(workspace_ptr); wl_display_destroy(display_ptr); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe->element); } /* ------------------------------------------------------------------------- */ /** Tests server-side decoration. */ void test_decoration(bs_test_t *test_ptr) { wlmtk_fake_element_t *fe = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe); wlmtk_fake_element_set_dimensions(fe, 42, 20); wlmtk_window_t *w = wlmtk_test_window_create(&fe->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); // Initially: no decoration. BS_TEST_VERIFY_EQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, w->resizebar_ptr); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 42, 20, wlmtk_window_get_bounding_box(w)); // Enable: Default property is not resizable, so we only get titlebar. wlmtk_window_set_server_side_decorated(w, true); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, w->resizebar_ptr); // Set property. Now we must have resize bar. wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_RESIZABLE); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->resizebar_ptr); // Disable. Decoration vanishes. wlmtk_window_set_server_side_decorated(w, false); BS_TEST_VERIFY_EQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, w->resizebar_ptr); // Re-enable. Properties are sticky, so we want all. wlmtk_window_set_server_side_decorated(w, true); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->resizebar_ptr); // Verify width: Must match the fake element's dimensions. wlmtk_element_t *te = wlmtk_titlebar_element(w->titlebar_ptr); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 42, 10, wlmtk_element_get_dimensions_box(te)); wlmtk_element_t *re = wlmtk_resizebar_element(w->resizebar_ptr); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 42, 5, wlmtk_element_get_dimensions_box(re)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 46, 41, wlmtk_window_get_bounding_box(w)); // An update in the element would call the parent's update_layout. wlmtk_fake_element_set_dimensions(fe, 52, 20); wlmtk_element_layout(wlmtk_window_element(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 52, 10, wlmtk_element_get_dimensions_box(te)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 52, 5, wlmtk_element_get_dimensions_box(re)); // Update again, with top-left edges resizing. wlmtk_window_set_resize_edges(w, WLR_EDGE_TOP | WLR_EDGE_LEFT); wlmtk_fake_element_set_dimensions(fe, 32, 10); wlmtk_element_layout(wlmtk_window_element(w)); BS_TEST_VERIFY_TRUE( test_ptr, w->bordered.super_container.invalidated_layout); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 32, 10, wlmtk_element_get_dimensions_box(te)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 32, 5, wlmtk_element_get_dimensions_box(re)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 36, 31, wlmtk_window_get_bounding_box(w)); BS_TEST_VERIFY_EQ( test_ptr, WLR_EDGE_TOP | WLR_EDGE_LEFT, wlmtk_window_get_resize_edges(w)); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe->element); } /* ------------------------------------------------------------------------- */ /** Tests that desired events are firing. */ void test_events(bs_test_t *test_ptr) { wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_util_test_listener_t l; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->request_close, &l); wlmtk_window_request_close(w); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_CLOSABLE); wlmtk_window_request_close(w); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); wlmtk_window_destroy(w); } /* ------------------------------------------------------------------------- */ /** Tests that activation updates decoration and callback. */ void test_set_activated(bs_test_t *test_ptr) { wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_util_test_listener_t l; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->set_activated, &l); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w)); wlmtk_window_set_activated(w, true); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w)); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); wlmtk_window_set_server_side_decorated(w, true); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_titlebar_is_activated(w->titlebar_ptr)); wlmtk_util_clear_test_listener(&l); wlmtk_window_set_activated(w, false); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w)); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_titlebar_is_activated(w->titlebar_ptr)); wlmtk_window_destroy(w); } /* ------------------------------------------------------------------------- */ /** Tests resize request: Ignore when fullscreen, unmaximize when maximized. */ void test_resize(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_RESIZABLE); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_workspace_map_window(ws_ptr, w); wlmtk_util_test_wlr_box_listener_t l = {}; wlmtk_util_connect_test_wlr_box_listener( &wlmtk_window_events(w)->request_size, &l); wlmtk_util_test_listener_t maxl = {}; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->request_maximized, &maxl); // Request a size change on normal window: Accepted, forward to listener. struct wlr_box size = { .width = 100, .height = 50 }; wlmtk_window_request_size(w, &size); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 100, 50, l.box); // Request a size change on a fullscreen window: Not accepted. wlmtk_window_commit_fullscreen(w, true); wlmtk_util_clear_test_wlr_box_listener(&l); wlmtk_window_request_size(w, &size); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); wlmtk_window_commit_fullscreen(w, false); // Request size change on maximized window: Accept, but request unmaximize. wlmtk_window_commit_maximized(w, true); wlmtk_util_clear_test_wlr_box_listener(&l); wlmtk_util_clear_test_listener(&maxl); wlmtk_window_request_size(w, &size); BS_TEST_VERIFY_EQ(test_ptr, 1, maxl.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 0, 0, 100, 50, l.box); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_util_disconnect_listener(&l.listener); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests fullscreen mode of a window. */ void test_fullscreen(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_RESIZABLE); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_util_test_listener_t state_changed_listener; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->state_changed, &state_changed_listener); wlmtk_util_test_listener_t request_fullscreen_listener; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->request_fullscreen, &request_fullscreen_listener); wlmtk_util_test_wlr_box_listener_t request_size_listener; wlmtk_util_connect_test_wlr_box_listener( &wlmtk_window_events(w)->request_size, &request_size_listener); wlmtk_util_test_listener_t request_maximized_listener; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->request_maximized, &request_maximized_listener); // Map the window. Must be activated. wlmtk_window_set_server_side_decorated(w, true); wlmtk_workspace_map_window(ws_ptr, w); BS_TEST_VERIFY_EQ(test_ptr, 1, state_changed_listener.calls); BS_TEST_VERIFY_EQ(test_ptr, w, state_changed_listener.last_data_ptr); wlmtk_util_clear_test_listener(&state_changed_listener); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w)); // Setup initial size and position. Verify. wlmtk_fake_element_set_dimensions(fe_ptr, 200, 100); wlmtk_window_commit_size(w, 200, 100); wlmtk_workspace_set_window_position(ws_ptr, w, 20, 10); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 204, 121, wlmtk_window_get_bounding_box(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 200, 100, wlmtk_window_get_size(w)); // Request fullscreen. Size yet unchanged, but requests issued. wlmtk_window_request_fullscreen(w, true); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 204, 121, wlmtk_window_get_bounding_box(w)); BS_TEST_VERIFY_EQ(test_ptr, 1, request_fullscreen_listener.calls); wlmtk_util_clear_test_listener(&request_fullscreen_listener); BS_TEST_VERIFY_EQ(test_ptr, 1, request_size_listener.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, request_size_listener.box); // Only after commit: Be fullscreen. wlmtk_fake_element_set_dimensions(fe_ptr, 1024, 768); wlmtk_window_commit_fullscreen(w, true); wlmtk_window_commit_size(w, 1024, 768); BS_TEST_VERIFY_EQ(test_ptr, 1, state_changed_listener.calls); BS_TEST_VERIFY_EQ(test_ptr, w, state_changed_listener.last_data_ptr); wlmtk_util_clear_test_listener(&state_changed_listener); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, wlmtk_window_get_bounding_box(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, wlmtk_window_get_size(w)); BS_TEST_VERIFY_TRUE(test_ptr, w->server_side_decorated); BS_TEST_VERIFY_EQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, w->resizebar_ptr); // No maximization permitted. wlmtk_util_clear_test_wlr_box_listener(&request_size_listener); wlmtk_window_request_maximized(w, true); BS_TEST_VERIFY_EQ(test_ptr, 1, request_maximized_listener.calls); BS_TEST_VERIFY_FALSE( test_ptr, *((bool*)request_maximized_listener.last_data_ptr)); BS_TEST_VERIFY_EQ(test_ptr, 0, request_size_listener.calls); // Request to end fullscreen. Also not taking immediate effect. wlmtk_window_request_fullscreen(w, false); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, wlmtk_window_get_bounding_box(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, wlmtk_window_get_size(w)); BS_TEST_VERIFY_EQ(test_ptr, 1, request_fullscreen_listener.calls); wlmtk_util_clear_test_listener(&request_fullscreen_listener); BS_TEST_VERIFY_EQ(test_ptr, 1, request_size_listener.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 200, 100, request_size_listener.box); wlmtk_util_clear_test_wlr_box_listener(&request_size_listener); // Only after commit: Be fullscreen. wlmtk_fake_element_set_dimensions(fe_ptr, 200, 100); wlmtk_window_commit_fullscreen(w, false); BS_TEST_VERIFY_EQ(test_ptr, 1, state_changed_listener.calls); BS_TEST_VERIFY_EQ(test_ptr, w, state_changed_listener.last_data_ptr); wlmtk_util_clear_test_listener(&state_changed_listener); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 204, 121, wlmtk_window_get_bounding_box(w)); BS_TEST_VERIFY_TRUE(test_ptr, w->server_side_decorated); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->resizebar_ptr); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_util_disconnect_test_listener(&request_maximized_listener); wlmtk_util_disconnect_test_listener(&request_fullscreen_listener); wlmtk_util_disconnect_test_listener(&state_changed_listener); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests fullscreen mode of a window onto preferred output . */ void test_fullscreen_preferred_output(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output1 = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output1); wlr_output_layout_add(wlr_output_layout_ptr, &output1, 0, 0); struct wlr_output output2 = { .width = 800, .height = 600, .scale = 1 }; wlmtk_test_wlr_output_init(&output2); wlr_output_layout_add(wlr_output_layout_ptr, &output2, 1024, 0); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_RESIZABLE); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_workspace_map_window(ws_ptr, w); wlmtk_util_test_wlr_box_listener_t request_size_listener; wlmtk_util_connect_test_wlr_box_listener( &wlmtk_window_events(w)->request_size, &request_size_listener); // Setup initial size and position. Verify. wlmtk_fake_element_set_dimensions(fe_ptr, 200, 100); wlmtk_window_commit_size(w, 200, 100); wlmtk_workspace_set_window_position(ws_ptr, w, 1400, 10); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 1400, 10, 200, 100, wlmtk_window_get_bounding_box(w)); // Enable fullscreen. Must happen on output2. BS_TEST_VERIFY_EQ(test_ptr, &output2, wlmtk_window_get_wlr_output(w)); wlmtk_window_request_fullscreen(w, true); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 1024, 0, 800, 600, request_size_listener.box); wlmtk_fake_element_set_dimensions(fe_ptr, 800, 600); wlmtk_window_commit_fullscreen(w, true); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 1024, 0, 800, 600, wlmtk_window_get_bounding_box(w)); // Disable fullscreen. wlmtk_window_request_fullscreen(w, false); wlmtk_fake_element_set_dimensions(fe_ptr, 200, 100); wlmtk_window_commit_fullscreen(w, false); // Enable fullscreen, with preference for output1. BS_TEST_VERIFY_EQ(test_ptr, &output2, wlmtk_window_get_wlr_output(w)); wlmtk_window_set_preferred_wlr_output(w, &output1); BS_TEST_VERIFY_EQ(test_ptr, &output1, wlmtk_window_get_wlr_output(w)); wlmtk_window_request_fullscreen(w, true); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, request_size_listener.box); wlmtk_fake_element_set_dimensions(fe_ptr, 1024, 768); wlmtk_window_commit_fullscreen(w, true); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, wlmtk_window_get_bounding_box(w)); wlmtk_util_disconnect_listener(&request_size_listener.listener); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests that unmapping a fullscreen window works. */ void test_fullscreen_unmap(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_RESIZABLE); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_util_test_listener_t state_changed_listener; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->state_changed, &state_changed_listener); wlmtk_util_test_listener_t request_fullscreen_listener; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->request_fullscreen, &request_fullscreen_listener); // Map the window. Must be activated. wlmtk_window_set_server_side_decorated(w, true); wlmtk_workspace_map_window(ws_ptr, w); BS_TEST_VERIFY_EQ(test_ptr, 1, state_changed_listener.calls); wlmtk_util_clear_test_listener(&state_changed_listener); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w)); // Setup initial size and position. Verify. wlmtk_fake_element_set_dimensions(fe_ptr, 200, 100); wlmtk_element_layout(wlmtk_window_element(w)); wlmtk_workspace_set_window_position(ws_ptr, w, 20, 10); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 204, 121, wlmtk_window_get_bounding_box(w)); // Request fullscreen. Verify the signals to request size and fullscreen. wlmtk_window_request_fullscreen(w, true); BS_TEST_VERIFY_EQ(test_ptr, 1, request_fullscreen_listener.calls); wlmtk_util_clear_test_listener(&request_fullscreen_listener); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 204, 121, wlmtk_window_get_bounding_box(w)); // Only after commit: Be back at organic size. wlmtk_fake_element_set_dimensions(fe_ptr, 1024, 768); wlmtk_window_commit_fullscreen(w, true); BS_TEST_VERIFY_EQ(test_ptr, 1, state_changed_listener.calls); wlmtk_util_clear_test_listener(&state_changed_listener); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, wlmtk_window_get_bounding_box(w)); BS_TEST_VERIFY_TRUE(test_ptr, w->server_side_decorated); BS_TEST_VERIFY_EQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, w->resizebar_ptr); wlmtk_workspace_unmap_window(ws_ptr, w); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_activated(w)); wlmtk_util_disconnect_test_listener(&request_fullscreen_listener); wlmtk_util_disconnect_test_listener(&state_changed_listener); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests maximizing a window. */ void test_maximized(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_RESIZABLE); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_util_test_listener_t state_changed_listener; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->state_changed, &state_changed_listener); wlmtk_util_test_listener_t request_maximized_listener; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->request_maximized, &request_maximized_listener); wlmtk_util_test_wlr_box_listener_t request_size_listener; wlmtk_util_connect_test_wlr_box_listener( &wlmtk_window_events(w)->request_size, &request_size_listener); // Before mapped: Refuses. wlmtk_window_request_maximized(w, false); wlmtk_window_request_maximized(w, true); BS_TEST_VERIFY_EQ(test_ptr, 2, request_maximized_listener.calls); wlmtk_util_clear_test_listener(&request_maximized_listener); // Map the window. Must be activated. wlmtk_window_set_server_side_decorated(w, true); wlmtk_workspace_map_window(ws_ptr, w); BS_TEST_VERIFY_EQ(test_ptr, 1, state_changed_listener.calls); BS_TEST_VERIFY_EQ(test_ptr, w, state_changed_listener.last_data_ptr); wlmtk_util_clear_test_listener(&state_changed_listener); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_activated(w)); // Setup initial size and position. Verify. wlmtk_fake_element_set_dimensions(fe_ptr, 200, 100); wlmtk_window_commit_size(w, 150, 80); wlmtk_workspace_set_window_position(ws_ptr, w, 20, 10); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 204, 121, wlmtk_window_get_bounding_box(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 150, 80, wlmtk_window_get_size(w)); wlmtk_util_clear_test_listener(&state_changed_listener); wlmtk_window_request_maximized(w, false); BS_TEST_VERIFY_EQ(test_ptr, 1, request_maximized_listener.calls); // Request maximized. Size yet unchanged, but requests issued. wlmtk_util_clear_test_listener(&request_maximized_listener); wlmtk_util_clear_test_wlr_box_listener(&request_size_listener); wlmtk_window_request_maximized(w, true); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 204, 121, wlmtk_window_get_bounding_box(w)); BS_TEST_VERIFY_EQ(test_ptr, 1, request_maximized_listener.calls); wlmtk_util_clear_test_listener(&request_maximized_listener); BS_TEST_VERIFY_EQ(test_ptr, 1, request_size_listener.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1020, 747, request_size_listener.box); // Only after commit: Be maximized. wlmtk_fake_element_set_dimensions(fe_ptr, 1020, 747); wlmtk_window_commit_maximized(w, true); wlmtk_window_commit_size(w, 1010, 737); BS_TEST_VERIFY_EQ(test_ptr, 1, state_changed_listener.calls); BS_TEST_VERIFY_EQ(test_ptr, w, state_changed_listener.last_data_ptr); wlmtk_util_clear_test_listener(&state_changed_listener); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, wlmtk_window_get_bounding_box(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1010, 737, wlmtk_window_get_size(w)); BS_TEST_VERIFY_TRUE(test_ptr, w->server_side_decorated); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->resizebar_ptr); // A further "request_maximized" call will be refued. wlmtk_window_request_maximized(w, true); BS_TEST_VERIFY_EQ(test_ptr, 1, request_maximized_listener.calls); // But, if the workspace extents are different, it will accept and request // resizing.. wlmtk_util_clear_test_listener(&request_maximized_listener); wlmtk_util_clear_test_wlr_box_listener(&request_size_listener); output.width = 800; output.height = 600; wlmtk_window_request_maximized(w, true); BS_TEST_VERIFY_EQ(test_ptr, 1, request_maximized_listener.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, request_size_listener.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 796, 579, request_size_listener.box); // Request to end maximized. Also not taking immediate effect. wlmtk_util_clear_test_listener(&request_maximized_listener); wlmtk_util_clear_test_wlr_box_listener(&request_size_listener); wlmtk_window_request_maximized(w, false); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1024, 768, wlmtk_window_get_bounding_box(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 1010, 737, wlmtk_window_get_size(w)); BS_TEST_VERIFY_EQ(test_ptr, 1, request_maximized_listener.calls); BS_TEST_VERIFY_EQ(test_ptr, 1, request_size_listener.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 150, 80, request_size_listener.box); // Only after commit: Be back at organic size. wlmtk_fake_element_set_dimensions(fe_ptr, 200, 100); wlmtk_window_commit_size(w, 160, 80); wlmtk_window_commit_maximized(w, false); BS_TEST_VERIFY_EQ(test_ptr, 1, state_changed_listener.calls); wlmtk_util_clear_test_listener(&state_changed_listener); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 20, 10, 204, 121, wlmtk_window_get_bounding_box(w)); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 160, 80, wlmtk_window_get_size(w)); BS_TEST_VERIFY_TRUE(test_ptr, w->server_side_decorated); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->titlebar_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, w->resizebar_ptr); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_util_disconnect_test_listener(&request_maximized_listener); wlmtk_util_disconnect_test_listener(&state_changed_listener); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests shading of a window. */ void test_shaded(bs_test_t *test_ptr) { wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_fake_element_set_dimensions(fe_ptr, 42, 20); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_RESIZABLE); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_util_test_listener_t l; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->state_changed, &l); // Not decorated: Will not be shaded. wlmtk_window_set_server_side_decorated(w, false); wlmtk_window_request_shaded(w, true); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(w)); wlmtk_window_set_server_side_decorated(w, true); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 46, 41, wlmtk_window_get_bounding_box(w)); // Fullscreen: Will not be shaded. wlmtk_window_commit_fullscreen(w, true); wlmtk_util_clear_test_listener(&l); wlmtk_window_request_shaded(w, true); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(w)); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); wlmtk_window_commit_fullscreen(w, false); // Now a regular, decorated window: Will be shaded. wlmtk_util_clear_test_listener(&l); wlmtk_window_request_shaded(w, true); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_shaded(w)); BS_TEST_VERIFY_FALSE(test_ptr, fe_ptr->element.visible); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_resizebar_element(w->resizebar_ptr)->visible); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 46, 15, wlmtk_window_get_bounding_box(w)); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); BS_TEST_VERIFY_EQ(test_ptr, w, l.last_data_ptr); // And verify that unshading works. wlmtk_util_clear_test_listener(&l); wlmtk_window_request_shaded(w, false); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(w)); BS_TEST_VERIFY_TRUE(test_ptr, fe_ptr->element.visible); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_resizebar_element(w->resizebar_ptr)->visible); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 46, 41, wlmtk_window_get_bounding_box(w)); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); BS_TEST_VERIFY_EQ(test_ptr, w, l.last_data_ptr); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); } /* ------------------------------------------------------------------------- */ /** Tests that modifer-motion moves the window. */ void test_modifier_move(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_element_set_visible(wlmtk_workspace_element(ws_ptr), true); wlmtk_workspace_enable(ws_ptr, true); wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe_ptr); wlmtk_fake_element_set_dimensions(fe_ptr, 40, 20); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_workspace_map_window(ws_ptr, w); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_window_element(w)->y); // Move pointer over the window. wlmtk_pointer_motion_event_t mev = { .x = 20, .y = 10 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); // Drag: Button down, and have both modifiers pressed, then move. wlmtk_button_event_t bev = { .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN, .keyboard_modifiers = WLR_MODIFIER_ALT }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(wlmtk_workspace_element(ws_ptr), &bev)); mev = (wlmtk_pointer_motion_event_t){ .x = 24, .y = 13 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); // Now must be at a new position. BS_TEST_VERIFY_EQ(test_ptr, 4, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 3, wlmtk_window_element(w)->y); // Change modifier. Move again, with ALT -- must not move the window. bev.type = WLMTK_BUTTON_UP; wlmtk_element_pointer_button(wlmtk_workspace_element(ws_ptr), &bev); bev.type = WLMTK_BUTTON_DOWN; wlmtk_window_set_options(w, WLR_MODIFIER_LOGO); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(wlmtk_workspace_element(ws_ptr), &bev)); mev = (wlmtk_pointer_motion_event_t){ .x = 22, .y = 13 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); BS_TEST_VERIFY_EQ(test_ptr, 4, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 3, wlmtk_window_element(w)->y); // Now, move with the correct modifier. Moves by another bit to the right. bev.type = WLMTK_BUTTON_UP; wlmtk_element_pointer_button(wlmtk_workspace_element(ws_ptr), &bev); bev.keyboard_modifiers = WLR_MODIFIER_LOGO; bev.type = WLMTK_BUTTON_DOWN; wlmtk_window_set_options(w, WLR_MODIFIER_LOGO); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(wlmtk_workspace_element(ws_ptr), &bev)); mev = (wlmtk_pointer_motion_event_t){ .x = 24, .y = 13 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion( wlmtk_workspace_element(ws_ptr), &mev)); BS_TEST_VERIFY_EQ(test_ptr, 6, wlmtk_window_element(w)->x); BS_TEST_VERIFY_EQ(test_ptr, 3, wlmtk_window_element(w)->y); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests enabling and disabling the window menu. */ void test_menu(bs_test_t *test_ptr) { wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_menu_item_t *i = wlmtk_menu_item_create(w->menu_style_ref_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i); wlmtk_menu_add_item(wlmtk_window_menu(w), i); // No menu shown when not activated. wlmtk_window_menu_set_enabled(w, true); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_menu_element(wlmtk_window_menu(w))->visible); // Show when activated. wlmtk_window_set_activated(w, true); wlmtk_window_menu_set_enabled(w, true); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_menu_element(wlmtk_window_menu(w))->visible); // Bounding box still none: The menu's dimensions not included. WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 0, 0, wlmtk_window_get_bounding_box(w)); // Hide. _wlmtk_window_menu_request_close_handler( &w->menu_request_close_listener, NULL); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_menu_element(wlmtk_window_menu(w))->visible); wlmtk_window_destroy(w); } /* ------------------------------------------------------------------------- */ /** Tests style reference counter. */ void test_style(bs_test_t *test_ptr) { struct wlmtk_window_style *s = wlmtk_window_style_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, s); wlmtk_window_style_ref_t *r = wlmtk_window_style_to_ref(s); BS_TEST_VERIFY_EQ(test_ptr, s, wlmtk_window_style_ref_retain(r)); wlmtk_window_style_ref_release(r); wlmtk_window_style_ref_release(r); BS_TEST_VERIFY_EQ(test_ptr, NULL, wlmtk_window_style_to_ref(NULL)); } /* ------------------------------------------------------------------------- */ /** Tests decoding the window style from a plist dict. */ void test_style_decode(bs_test_t *test_ptr) { static const char *s = ("{" " TitleBar = {" " FocussedFill = {" " Color = \"argb32:ff000000\";" " Type = SOLID;" " };" " FocussedTextColor = \"argb32:ffffffff\";" " BlurredFill = {" " Color = \"argb32:ff666666\";" " Type = SOLID;" " };" " BlurredTextColor = \"argb32:ff000000\";" " Height = 22;" " BezelWidth = 1;" " Margin = {" " Width = 1;" " Color = \"argb32:ff000000\";" " };" " Font = {" " Face = Helvetica;" " Weight = Bold;" " Size = 15;" " };" " };" " ResizeBar = {" " Fill = { Color = \"argb32:ffaaaaaa\"; Type = SOLID; };" " Height = 7; CornerWidth = 29; BezelWidth = 1;" " };" " Border = { Width = 1; Color = \"argb32:10203040\"; };" " Margin = { Width = 2; Color = \"argb32:01020304\"; }" "}"); struct wlmtk_window_style *ws = wlmtk_window_style_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws); bspl_dict_t *d = bspl_dict_from_object( bspl_create_object_from_plist_string(s)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, d); BS_TEST_VERIFY_TRUE( test_ptr, bspl_decode_dict(d, _wlmtk_window_style_desc, ws)); BS_TEST_VERIFY_EQ(test_ptr, 0xffffffff, ws->titlebar.focussed_text_color); BS_TEST_VERIFY_EQ(test_ptr, 15, ws->titlebar.font.size); BS_TEST_VERIFY_EQ(test_ptr, WLMTK_STYLE_COLOR_SOLID, ws->resizebar.fill.type); BS_TEST_VERIFY_EQ(test_ptr, 0xffaaaaaa, ws->resizebar.fill.param.solid.color); BS_TEST_VERIFY_EQ(test_ptr, 7, ws->resizebar.height); BS_TEST_VERIFY_EQ(test_ptr, 29, ws->resizebar.corner_width); BS_TEST_VERIFY_EQ(test_ptr, 1, ws->resizebar.bezel_width); BS_TEST_VERIFY_EQ(test_ptr, 1, ws->border.width); BS_TEST_VERIFY_EQ(test_ptr, 0x10203040, ws->border.color); BS_TEST_VERIFY_EQ(test_ptr, 2, ws->margin.width); BS_TEST_VERIFY_EQ(test_ptr, 0x01020304, ws->margin.color); bspl_dict_unref(d); wlmtk_window_style_ref_release(wlmtk_window_style_to_ref(ws)); } /* == End of window.c ===================================================== */ wlmaker-0.8.1/src/toolkit/image.c0000644000175100017510000001615215222270710016275 0ustar runnerrunner/* ========================================================================= */ /** * @file image.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "image.h" #include #include #include #include #include "buffer.h" #include "gfxbuf.h" // IWYU pragma: keep /* == Declarations ========================================================= */ /** State of the image. */ struct _wlmtk_image_t { /** The image's superclass: A buffer. */ wlmtk_buffer_t super_buffer; /** The superclass' virtual method table. */ wlmtk_element_vmt_t orig_element_vmt; }; struct wlr_buffer *_wlmtk_image_create_wlr_buffer_from_image( const char *path_ptr, int width, int height); static void _wlmtk_image_element_destroy(wlmtk_element_t *element_ptr); /* == Data ================================================================= */ /** The imag'es virtual method table for @ref wlmtk_element_t superclass. */ static const wlmtk_element_vmt_t _wlmtk_image_element_vmt = { .destroy = _wlmtk_image_element_destroy, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_image_t *wlmtk_image_create(const char *image_path_ptr) { return wlmtk_image_create_scaled(image_path_ptr, 0, 0); } /* ------------------------------------------------------------------------- */ wlmtk_image_t *wlmtk_image_create_scaled( const char *image_path_ptr, int width, int height) { wlmtk_image_t *image_ptr = logged_calloc(1, sizeof(wlmtk_image_t)); if (NULL == image_ptr) return NULL; if (!wlmtk_buffer_init(&image_ptr->super_buffer)) { wlmtk_image_destroy(image_ptr); return NULL; } image_ptr->orig_element_vmt = wlmtk_element_extend( wlmtk_image_element(image_ptr), &_wlmtk_image_element_vmt); struct wlr_buffer *wlr_buffer_ptr = _wlmtk_image_create_wlr_buffer_from_image( image_path_ptr, width, height); if (NULL == wlr_buffer_ptr) { wlmtk_image_destroy(image_ptr); return NULL; } wlmtk_buffer_set(&image_ptr->super_buffer, wlr_buffer_ptr); wlr_buffer_drop(wlr_buffer_ptr); return image_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_image_destroy(wlmtk_image_t *image_ptr) { wlmtk_buffer_fini(&image_ptr->super_buffer); free(image_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_image_element(wlmtk_image_t *image_ptr) { return wlmtk_buffer_element(&image_ptr->super_buffer); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Creates a wlr_buffer that holds the image loaded from path, at that image's * size. * * @param path_ptr * @param width Desired width of the image. 0 0r negative to use * the image's native width. * @param height Desired height of the image. 0 0r negative to use * the image's native height. * * @return the wlr_buffer or NULL on error. */ struct wlr_buffer *_wlmtk_image_create_wlr_buffer_from_image( const char *path_ptr, int width, int height) { cairo_surface_t *icon_surface_ptr = cairo_image_surface_create_from_png( path_ptr); if (NULL == icon_surface_ptr) { bs_log(BS_ERROR, "Failed cairo_image_surface_create_from_png(%s).", path_ptr); return false; } if (CAIRO_STATUS_SUCCESS != cairo_surface_status(icon_surface_ptr)) { bs_log(BS_ERROR, "Bad surface after cairo_image_surface_create_from_png(%s): %s", path_ptr, cairo_status_to_string(cairo_surface_status(icon_surface_ptr))); cairo_surface_destroy(icon_surface_ptr); return NULL; } int w = width; if (0 >= w) { w = cairo_image_surface_get_width(icon_surface_ptr); } int h = height; if (0 >= h) { h = cairo_image_surface_get_height(icon_surface_ptr); } struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer(w, h); if (NULL == wlr_buffer_ptr) { cairo_surface_destroy(icon_surface_ptr); return NULL; } cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); cairo_surface_destroy(icon_surface_ptr); return NULL; } cairo_surface_set_device_scale( icon_surface_ptr, (double)cairo_image_surface_get_width(icon_surface_ptr) / w, (double)cairo_image_surface_get_height(icon_surface_ptr) / h); cairo_set_source_surface(cairo_ptr, icon_surface_ptr, 0, 0); cairo_rectangle(cairo_ptr, 0, 0, w, h); cairo_fill(cairo_ptr); cairo_stroke(cairo_ptr); cairo_destroy(cairo_ptr); cairo_surface_destroy(icon_surface_ptr); return wlr_buffer_ptr; } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_element_vmt_t::destroy -- virtual dtor. */ void _wlmtk_image_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_image_t *image_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_image_t, super_buffer.super_element); wlmtk_image_destroy(image_ptr); } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_image_test_cases[] = { { 1, "create_destroy", test_create_destroy }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_image_test_set = BS_TEST_SET( true, "image", _wlmtk_image_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises ctor and dtor. */ void test_create_destroy(bs_test_t *test_ptr) { wlmtk_image_t *image_ptr = wlmtk_image_create( bs_test_data_path(test_ptr, "toolkit/test_icon.png")); BS_TEST_VERIFY_NEQ(test_ptr, NULL, image_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(image_ptr->super_buffer.wlr_buffer_ptr), "toolkit/test_icon.png"); BS_TEST_VERIFY_EQ( test_ptr, &image_ptr->super_buffer.super_element, wlmtk_image_element(image_ptr)); wlmtk_image_destroy(image_ptr); } /* == End of image.c ======================================================= */ wlmaker-0.8.1/src/toolkit/output_tracker.c0000644000175100017510000003753415222270710020275 0ustar runnerrunner/* ========================================================================= */ /** * @file output_tracker.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "output_tracker.h" #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include "test.h" // IWYU pragma: keep #include "util.h" struct wl_list; /* == Declarations ========================================================= */ /** State of the output tracker. */ struct _wlmtk_output_tracker_t { /** The output layout tracked here. */ struct wlr_output_layout *wlr_output_layout_ptr; /** Listener for wlr_output_layout::events.change. */ struct wl_listener output_layout_change_listener; /** Callback for when an output is added ("created"). */ wlmtk_output_tracker_output_create_callback_t create_fn; /** Callback for when the layout changes, but the output remains. */ wlmtk_output_tracker_output_update_callback_t update_fn; /** Callback for when the output is removed ("destroyed"). */ wlmtk_output_tracker_output_destroy_callback_t destroy_fn; /** The userdata provided to @ref wlmtk_output_tracker_create. */ void *userdata_ptr; /** All outputs, through @ref wlmtk_output_tracker_output_t::avlnode. */ bs_avltree_t *output_tree_ptr; }; /** A tracked output. */ typedef struct { /** Tree node, from @ref wlmtk_output_tracker_t::output_tree_ptr. */ bs_avltree_node_t avlnode; /** Back-link to the tracker. */ wlmtk_output_tracker_t *tracker_ptr; /** The WLR output tracked. */ struct wlr_output *wlr_output_ptr; /** Holds the return value of @ref wlmtk_output_tracker_t::create_fn. */ void *create_fn_retval_ptr; } wlmtk_output_tracker_output_t; /** Arguments to @ref _wlmtk_output_tracker_output_handle_change. */ struct wlmtk_output_tracker_update_arg { /** Tracker. */ wlmtk_output_tracker_t *tracker_ptr; /** The former output tree. */ bs_avltree_t *former_output_tree_ptr; }; static void _wlmtk_output_tracker_tree_node_destroy( bs_avltree_node_t *avlnode_ptr); static int _wlmtk_output_tracker_tree_node_cmp( const bs_avltree_node_t *avlnode_ptr, const void *key_ptr); static void _wlmtk_output_tracker_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr); static bool _wlmtk_output_tracker_output_handle_change( struct wl_list *link_ptr, void *ud_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_output_tracker_t *wlmtk_output_tracker_create( struct wlr_output_layout *wlr_output_layout_ptr, void *userdata_ptr, wlmtk_output_tracker_output_create_callback_t create_fn, wlmtk_output_tracker_output_update_callback_t update_fn, wlmtk_output_tracker_output_destroy_callback_t destroy_fn) { wlmtk_output_tracker_t *tracker_ptr = logged_calloc( 1, sizeof(wlmtk_output_tracker_t)); if (NULL == tracker_ptr) return NULL; tracker_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr; tracker_ptr->userdata_ptr = userdata_ptr; tracker_ptr->create_fn = create_fn; tracker_ptr->update_fn = update_fn; tracker_ptr->destroy_fn = destroy_fn; tracker_ptr->output_tree_ptr = bs_avltree_create( _wlmtk_output_tracker_tree_node_cmp, _wlmtk_output_tracker_tree_node_destroy); if (NULL == tracker_ptr->output_tree_ptr) { wlmtk_output_tracker_destroy(tracker_ptr); return NULL; } wlmtk_util_connect_listener_signal( &tracker_ptr->wlr_output_layout_ptr->events.change, &tracker_ptr->output_layout_change_listener, _wlmtk_output_tracker_handle_output_layout_change); _wlmtk_output_tracker_handle_output_layout_change( &tracker_ptr->output_layout_change_listener, tracker_ptr->wlr_output_layout_ptr); return tracker_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_output_tracker_destroy(wlmtk_output_tracker_t *tracker_ptr) { wlmtk_util_disconnect_listener( &tracker_ptr->output_layout_change_listener); if (NULL != tracker_ptr->output_tree_ptr) { bs_avltree_destroy(tracker_ptr->output_tree_ptr); tracker_ptr->output_tree_ptr = NULL; } free(tracker_ptr); } /* ------------------------------------------------------------------------- */ void *wlmtk_output_tracker_get_output( wlmtk_output_tracker_t *tracker_ptr, struct wlr_output *wlr_output_ptr) { bs_avltree_node_t *avlnode_ptr = bs_avltree_lookup( tracker_ptr->output_tree_ptr, wlr_output_ptr); if (NULL == avlnode_ptr) return NULL; wlmtk_output_tracker_output_t *output_ptr = BS_CONTAINER_OF( avlnode_ptr, wlmtk_output_tracker_output_t, avlnode); return output_ptr->create_fn_retval_ptr; } /* ------------------------------------------------------------------------- */ struct wlr_output_layout *wlmtk_output_tracker_get_layout( wlmtk_output_tracker_t *tracker_ptr) { return tracker_ptr->wlr_output_layout_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Creates state for tracking an output. */ wlmtk_output_tracker_output_t *wlmtk_output_tracker_output_create( wlmtk_output_tracker_t *tracker_ptr, struct wlr_output *wlr_output_ptr) { wlmtk_output_tracker_output_t *output_ptr = logged_calloc( 1, sizeof(wlmtk_output_tracker_output_t)); if (NULL == output_ptr) return NULL; output_ptr->wlr_output_ptr = wlr_output_ptr; output_ptr->tracker_ptr = tracker_ptr; if (NULL != tracker_ptr->create_fn) { output_ptr->create_fn_retval_ptr = tracker_ptr->create_fn( wlr_output_ptr, tracker_ptr->userdata_ptr); if (NULL == output_ptr->create_fn_retval_ptr) { _wlmtk_output_tracker_tree_node_destroy( &output_ptr->avlnode); return NULL; } } return output_ptr; } /* ------------------------------------------------------------------------- */ /** Destroys the tracker state for an output, by the tree node. */ void _wlmtk_output_tracker_tree_node_destroy( bs_avltree_node_t *avlnode_ptr) { wlmtk_output_tracker_output_t *output_ptr = BS_CONTAINER_OF( avlnode_ptr, wlmtk_output_tracker_output_t, avlnode); if (NULL != output_ptr->tracker_ptr->destroy_fn && NULL != output_ptr->create_fn_retval_ptr) { output_ptr->tracker_ptr->destroy_fn( output_ptr->wlr_output_ptr, output_ptr->tracker_ptr->userdata_ptr, output_ptr->create_fn_retval_ptr); output_ptr->create_fn_retval_ptr = NULL; } free(output_ptr); } /* ------------------------------------------------------------------------- */ /** Compares @ref wlmtk_output_tracker_output_t::wlr_output_ptr. */ int _wlmtk_output_tracker_tree_node_cmp( const bs_avltree_node_t *avlnode_ptr, const void *key_ptr) { const wlmtk_output_tracker_output_t *output_ptr = BS_CONTAINER_OF( avlnode_ptr, const wlmtk_output_tracker_output_t, avlnode); return bs_avltree_cmp_ptr(output_ptr->wlr_output_ptr, key_ptr); } /* ------------------------------------------------------------------------- */ /** Output layout changed: Iterate over all outputs. */ void _wlmtk_output_tracker_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_output_tracker_t *tracker_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_output_tracker_t, output_layout_change_listener); struct wlr_output_layout *wlr_output_layout_ptr = data_ptr; struct wlmtk_output_tracker_update_arg arg = { .tracker_ptr = tracker_ptr, .former_output_tree_ptr = tracker_ptr->output_tree_ptr }; tracker_ptr->output_tree_ptr = BS_ASSERT_NOTNULL( bs_avltree_create( _wlmtk_output_tracker_tree_node_cmp, _wlmtk_output_tracker_tree_node_destroy)); BS_ASSERT(wlmtk_util_wl_list_for_each( &wlr_output_layout_ptr->outputs, _wlmtk_output_tracker_output_handle_change, &arg)); // This will destroy any output no longer in the layout. bs_avltree_destroy(arg.former_output_tree_ptr); } /* ------------------------------------------------------------------------- */ /** Handles change for one output of the layout: Calls create or update. */ bool _wlmtk_output_tracker_output_handle_change( struct wl_list *link_ptr, void *ud_ptr) { struct wlr_output_layout_output *wlr_output_layout_output_ptr = BS_CONTAINER_OF(link_ptr, struct wlr_output_layout_output, link); struct wlr_output *wlr_output_ptr = wlr_output_layout_output_ptr->output; struct wlmtk_output_tracker_update_arg *arg_ptr = ud_ptr; wlmtk_output_tracker_output_t *output_ptr = NULL; bs_avltree_node_t *avlnode_ptr = bs_avltree_delete( arg_ptr->former_output_tree_ptr, wlr_output_ptr); if (NULL != avlnode_ptr) { output_ptr = BS_CONTAINER_OF( avlnode_ptr, wlmtk_output_tracker_output_t, avlnode); if (NULL != arg_ptr->tracker_ptr->update_fn) { arg_ptr->tracker_ptr->update_fn( wlr_output_ptr, arg_ptr->tracker_ptr->userdata_ptr, output_ptr->create_fn_retval_ptr); } } else { output_ptr = wlmtk_output_tracker_output_create( arg_ptr->tracker_ptr, wlr_output_ptr); if (NULL == output_ptr) return false; } return bs_avltree_insert( arg_ptr->tracker_ptr->output_tree_ptr, wlr_output_ptr, &output_ptr->avlnode, false); } /* ------------------------------------------------------------------------- */ void wlmtk_output_tracker_for_each( wlmtk_output_tracker_t *tracker_ptr, void (*callback)(struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr, void *arg_ptr), void *arg_ptr) { bs_avltree_node_t *avlnode_ptr = bs_avltree_min( tracker_ptr->output_tree_ptr); while (NULL != avlnode_ptr) { bs_avltree_node_t *next_avlnode_ptr = bs_avltree_node_next( tracker_ptr->output_tree_ptr, avlnode_ptr); wlmtk_output_tracker_output_t *output_ptr = BS_CONTAINER_OF( avlnode_ptr, wlmtk_output_tracker_output_t, avlnode); callback(output_ptr->wlr_output_ptr, tracker_ptr->userdata_ptr, output_ptr->create_fn_retval_ptr, arg_ptr); avlnode_ptr = next_avlnode_ptr; } } /* == Unit Tests =========================================================== */ static void _wlmtk_output_tracker_test(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_output_tracker_test_cases[] = { { 1, "test", _wlmtk_output_tracker_test }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_output_tracker_test_set = BS_TEST_SET( true, "output_tracker", _wlmtk_output_tracker_test_cases); /** For tests: State of tracked outputs. */ struct _wlmtk_output_tracker_test_state { /** Test. */ bs_test_t *test_ptr; /** Stores the outputs */ struct wlr_output *outputs[3]; /** Counts @ref wlmtk_output_tracker_output_create_callback_t calls. */ intptr_t create_calls; /** Counts @ref wlmtk_output_tracker_output_destroy_callback_t calls. */ intptr_t destroy_calls; /** Counts @ref wlmtk_output_tracker_output_update_callback_t calls. */ intptr_t update_calls; }; /** For tests: Creates a tracked output. */ static void *_wlmtk_output_tracker_test_create_output( struct wlr_output *wlr_output_ptr, void *ud_ptr) { struct _wlmtk_output_tracker_test_state *state_ptr = ud_ptr; BS_ASSERT(state_ptr->create_calls < 3); state_ptr->outputs[state_ptr->create_calls++] = wlr_output_ptr; return (void*)(state_ptr->create_calls); } /** For tests: Update to a tracked output. */ static void _wlmtk_output_tracker_test_update_output( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr) { struct _wlmtk_output_tracker_test_state *state_ptr = ud_ptr; BS_TEST_VERIFY_EQ( state_ptr->test_ptr, state_ptr->outputs[(intptr_t)(output_ptr) - 1], wlr_output_ptr); state_ptr->update_calls++; } /** For tests: Destroys a tracked output. */ static void _wlmtk_output_tracker_test_destroy_output( __UNUSED__ struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr) { struct _wlmtk_output_tracker_test_state *state_ptr = ud_ptr; BS_TEST_VERIFY_EQ( state_ptr->test_ptr, state_ptr->outputs[(intptr_t)(output_ptr) - 1], wlr_output_ptr); state_ptr->destroy_calls++; } /* ------------------------------------------------------------------------- */ /** Tests output tracker. */ void _wlmtk_output_tracker_test(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); // Setup layout, initially with o1 and o2. struct wlr_output o1 = { .width = 1024, .height = 768, .scale = 1 }; struct wlr_output o2 = o1; struct wlr_output o3 = o1; wlmtk_test_wlr_output_init(&o1); wlmtk_test_wlr_output_init(&o2); wlmtk_test_wlr_output_init(&o3); wlr_output_layout_add(wlr_output_layout_ptr, &o1, 0, 0); wlr_output_layout_add(wlr_output_layout_ptr, &o2, 0, 0); struct _wlmtk_output_tracker_test_state state = { .test_ptr = test_ptr }; wlmtk_output_tracker_t *t = wlmtk_output_tracker_create( wlr_output_layout_ptr, &state, _wlmtk_output_tracker_test_create_output, _wlmtk_output_tracker_test_update_output, _wlmtk_output_tracker_test_destroy_output); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, t); BS_TEST_VERIFY_EQ(test_ptr, 2, state.create_calls); BS_TEST_VERIFY_EQ(test_ptr, 0, state.update_calls); BS_TEST_VERIFY_EQ( test_ptr, (void*)1, wlmtk_output_tracker_get_output(t, &o1)); BS_TEST_VERIFY_EQ( test_ptr, (void*)2, wlmtk_output_tracker_get_output(t, &o2)); wlr_output_layout_add(wlr_output_layout_ptr, &o3, 0, 0); BS_TEST_VERIFY_EQ(test_ptr, 3, state.create_calls); BS_TEST_VERIFY_EQ(test_ptr, 2, state.update_calls); wl_signal_emit( &wlr_output_layout_ptr->events.change, wlr_output_layout_ptr); BS_TEST_VERIFY_EQ(test_ptr, 5, state.update_calls); wlr_output_layout_remove(wlr_output_layout_ptr, &o2); BS_TEST_VERIFY_EQ(test_ptr, 1, state.destroy_calls); wlmtk_output_tracker_destroy(t); BS_TEST_VERIFY_EQ(test_ptr, 3, state.destroy_calls); wlr_output_layout_destroy(wlr_output_layout_ptr); wl_display_destroy(display_ptr); } /* == End of output_tracker.c ============================================== */ wlmaker-0.8.1/src/toolkit/test.c0000644000175100017510000000405215222270710016166 0ustar runnerrunner/* ========================================================================= */ /** * @file test.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "test.h" #include #include #define WLR_USE_UNSTABLE #include #include #include #undef WLR_USE_UNSTABLE /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ void wlmtk_test_wlr_output_init(struct wlr_output *wlr_output_ptr) { wlr_addon_set_init(&wlr_output_ptr->addons); wlr_addon_set_init(&wlr_output_ptr->addons); #if WLR_VERSION_NUM >= (19 << 8) wl_list_init(&wlr_output_ptr->WLR_PRIVATE.display_destroy.link); #else // WLR_VERSION_NUM >= (19 << 8) wl_list_init(&wlr_output_ptr->display_destroy.link); #endif // WLR_VERSION_NUM >= (19 << 8) wl_list_init(&wlr_output_ptr->modes); wl_list_init(&wlr_output_ptr->resources); wl_signal_init(&wlr_output_ptr->events.commit); wl_signal_init(&wlr_output_ptr->events.damage); wl_signal_init(&wlr_output_ptr->events.destroy); wl_signal_init(&wlr_output_ptr->events.frame); wl_signal_init(&wlr_output_ptr->events.needs_frame); wl_signal_init(&wlr_output_ptr->events.present); wl_signal_init(&wlr_output_ptr->events.request_state); } /* == End of test.c ======================================================== */ wlmaker-0.8.1/src/toolkit/popup.c0000644000175100017510000000664615222270710016365 0ustar runnerrunner/* ========================================================================= */ /** * @file popup.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "popup.h" #include #include /* == Declarations ========================================================= */ /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_popup_init( wlmtk_popup_t *popup_ptr, wlmtk_element_t *element_ptr) { *popup_ptr = (wlmtk_popup_t){}; if (!wlmtk_container_init(&popup_ptr->super_container)) { return false; } if (!wlmtk_container_init(&popup_ptr->popup_container)) { wlmtk_popup_fini(popup_ptr); return false; } wlmtk_container_add_element( &popup_ptr->super_container, &popup_ptr->popup_container.super_element); wlmtk_element_set_visible(&popup_ptr->popup_container.super_element, true); if (NULL != element_ptr) { popup_ptr->element_ptr = element_ptr; wlmtk_container_add_element( &popup_ptr->super_container, popup_ptr->element_ptr); } return true; } /* ------------------------------------------------------------------------- */ void wlmtk_popup_fini(wlmtk_popup_t *popup_ptr) { if (NULL != wlmtk_popup_element(popup_ptr)->parent_container_ptr) { wlmtk_container_remove_element( wlmtk_popup_element(popup_ptr)->parent_container_ptr, wlmtk_popup_element(popup_ptr)); } if (NULL != popup_ptr->element_ptr) { wlmtk_container_remove_element( &popup_ptr->super_container, popup_ptr->element_ptr); popup_ptr->element_ptr = NULL; } if (popup_ptr->popup_container.super_element.parent_container_ptr) { wlmtk_container_remove_element( &popup_ptr->super_container, &popup_ptr->popup_container.super_element); } wlmtk_container_fini(&popup_ptr->popup_container); wlmtk_container_fini(&popup_ptr->super_container); } /* ------------------------------------------------------------------------- */ void wlmtk_popup_add_popup(wlmtk_popup_t *popup_ptr, wlmtk_popup_t *further_popup_ptr) { BS_ASSERT(!wlmtk_popup_element(further_popup_ptr)->parent_container_ptr); wlmtk_container_add_element( &popup_ptr->popup_container, wlmtk_popup_element(further_popup_ptr)); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_popup_element(wlmtk_popup_t *popup_ptr) { return &popup_ptr->super_container.super_element; } /* == Local (static) methods =============================================== */ /* == End of popup.c ======================================================= */ wlmaker-0.8.1/src/toolkit/titlebar.c0000644000175100017510000005565615222270710017035 0ustar runnerrunner/* ========================================================================= */ /** * @file titlebar.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "titlebar.h" #include #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "box.h" #include "primitives.h" #include "style.h" #include "titlebar_button.h" #include "titlebar_title.h" /* == Declarations ========================================================= */ /** State of the title bar. */ struct _wlmtk_titlebar_t { /** Superclass: Box. */ wlmtk_box_t super_box; /** Link to the titlebar's title. */ const char *title_ptr; /** Title element of the title bar. */ wlmtk_titlebar_title_t *titlebar_title_ptr; /** Minimize button. */ wlmtk_titlebar_button_t *minimize_button_ptr; /** Close button. */ wlmtk_titlebar_button_t *close_button_ptr; /** Titlebar background, when focussed. */ bs_gfxbuf_t *focussed_gfxbuf_ptr; /** Titlebar background, when blurred. */ bs_gfxbuf_t *blurred_gfxbuf_ptr; /** Current width of the title bar. */ unsigned width; /** Position of the close button. */ int close_position; /** Position of the title element. */ int title_position; /** Width of the title element. */ int title_width; /** Whether the title bar is currently displayed as activated. */ bool activated; /** Properties of the title bar. */ uint32_t properties; /** Title bar style. */ const struct wlmtk_titlebar_style *style_ptr; }; static void _wlmtk_titlebar_element_destroy(wlmtk_element_t *element_ptr); static void _wlmtk_titlebar_compute_positions( wlmtk_titlebar_t *titlebar_ptr, const struct wlmtk_titlebar_style *style_ptr); static bool _wlmtk_titlebar_redraw_buffers( wlmtk_titlebar_t *titlebar_ptr, const struct wlmtk_titlebar_style *style_ptr, unsigned width); static bool _wlmtk_titlebar_redraw( wlmtk_titlebar_t *titlebar_ptr, const struct wlmtk_titlebar_style *style_ptr); /* == Data ================================================================= */ const bspl_desc_t wlmtk_titlebar_style_desc[] = { BSPL_DESC_CUSTOM( "FocussedFill", true, struct wlmtk_titlebar_style, focussed_fill, focussed_fill, wlmtk_style_decode_fill, NULL, NULL, NULL), BSPL_DESC_ARGB32( "FocussedTextColor", true, struct wlmtk_titlebar_style, focussed_text_color, focussed_text_color, 0), BSPL_DESC_CUSTOM( "BlurredFill", true, struct wlmtk_titlebar_style, blurred_fill, blurred_fill, wlmtk_style_decode_fill, NULL, NULL, NULL), BSPL_DESC_ARGB32( "BlurredTextColor", true, struct wlmtk_titlebar_style, blurred_text_color, blurred_text_color, 0), BSPL_DESC_UINT64( "Height", true, struct wlmtk_titlebar_style, height, height, 22), BSPL_DESC_UINT64( "BezelWidth", true, struct wlmtk_titlebar_style, bezel_width, bezel_width, 1), BSPL_DESC_DICT( "Margin", true, struct wlmtk_titlebar_style, margin, margin, wlmtk_style_margin_desc), BSPL_DESC_DICT( "Font", true, struct wlmtk_titlebar_style, font, font, wlmtk_style_font_desc), BSPL_DESC_SENTINEL() }; /** Virtual method table extension for the titlebar's element superclass. */ static const wlmtk_element_vmt_t titlebar_element_vmt = { .destroy = _wlmtk_titlebar_element_destroy }; /** Default properties: All buttons shown. */ static const uint32_t _wlmtk_titlebar_default_properties = WLMTK_TITLEBAR_PROPERTY_ICONIFY | WLMTK_TITLEBAR_PROPERTY_CLOSE; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_titlebar_t *wlmtk_titlebar_create( wlmtk_window_t *window_ptr, const struct wlmtk_titlebar_style *style_ptr) { wlmtk_titlebar_t *titlebar_ptr = logged_calloc( 1, sizeof(wlmtk_titlebar_t)); if (NULL == titlebar_ptr) return NULL; titlebar_ptr->style_ptr = style_ptr; titlebar_ptr->title_ptr = wlmtk_window_get_title(window_ptr); if (!wlmtk_box_init(&titlebar_ptr->super_box, WLMTK_BOX_HORIZONTAL, &titlebar_ptr->style_ptr->margin)) { wlmtk_titlebar_destroy(titlebar_ptr); return NULL; } wlmtk_element_extend( &titlebar_ptr->super_box.super_container.super_element, &titlebar_element_vmt); titlebar_ptr->titlebar_title_ptr = wlmtk_titlebar_title_create(window_ptr); if (NULL == titlebar_ptr->titlebar_title_ptr) { wlmtk_titlebar_destroy(titlebar_ptr); return NULL; } wlmtk_box_add_element_front( &titlebar_ptr->super_box, wlmtk_titlebar_title_element(titlebar_ptr->titlebar_title_ptr)); titlebar_ptr->minimize_button_ptr = wlmtk_titlebar_button_create( wlmtk_window_request_minimize, window_ptr, wlmaker_primitives_draw_minimize_icon); if (NULL == titlebar_ptr->minimize_button_ptr) { wlmtk_titlebar_destroy(titlebar_ptr); return NULL; } wlmtk_box_add_element_front( &titlebar_ptr->super_box, wlmtk_titlebar_button_element(titlebar_ptr->minimize_button_ptr)); titlebar_ptr->close_button_ptr = wlmtk_titlebar_button_create( wlmtk_window_request_close, window_ptr, wlmaker_primitives_draw_close_icon); if (NULL == titlebar_ptr->close_button_ptr) { wlmtk_titlebar_destroy(titlebar_ptr); return NULL; } wlmtk_box_add_element_back( &titlebar_ptr->super_box, wlmtk_titlebar_button_element(titlebar_ptr->close_button_ptr)); wlmtk_titlebar_set_properties( titlebar_ptr, _wlmtk_titlebar_default_properties); return titlebar_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_titlebar_destroy(wlmtk_titlebar_t *titlebar_ptr) { if (NULL != titlebar_ptr->close_button_ptr) { wlmtk_box_remove_element( &titlebar_ptr->super_box, wlmtk_titlebar_button_element(titlebar_ptr->close_button_ptr)); wlmtk_titlebar_button_destroy(titlebar_ptr->close_button_ptr); titlebar_ptr->close_button_ptr = NULL; } if (NULL != titlebar_ptr->minimize_button_ptr) { wlmtk_box_remove_element( &titlebar_ptr->super_box, wlmtk_titlebar_button_element(titlebar_ptr->minimize_button_ptr)); wlmtk_titlebar_button_destroy(titlebar_ptr->minimize_button_ptr); titlebar_ptr->minimize_button_ptr = NULL; } if (NULL != titlebar_ptr->titlebar_title_ptr) { wlmtk_box_remove_element( &titlebar_ptr->super_box, wlmtk_titlebar_title_element(titlebar_ptr->titlebar_title_ptr)); wlmtk_titlebar_title_destroy(titlebar_ptr->titlebar_title_ptr); titlebar_ptr->titlebar_title_ptr = NULL; } if (NULL != titlebar_ptr->blurred_gfxbuf_ptr) { bs_gfxbuf_destroy(titlebar_ptr->blurred_gfxbuf_ptr); titlebar_ptr->blurred_gfxbuf_ptr = NULL; } if (NULL != titlebar_ptr->focussed_gfxbuf_ptr) { bs_gfxbuf_destroy(titlebar_ptr->focussed_gfxbuf_ptr); titlebar_ptr->focussed_gfxbuf_ptr = NULL; } wlmtk_box_fini(&titlebar_ptr->super_box); free(titlebar_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_titlebar_set_width( wlmtk_titlebar_t *tb, unsigned width) { if (tb->width == width) return true; if (!_wlmtk_titlebar_redraw_buffers(tb, tb->style_ptr, width)) return false; BS_ASSERT(width == tb->width); return _wlmtk_titlebar_redraw(tb, tb->style_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_titlebar_set_style( wlmtk_titlebar_t *tb, const struct wlmtk_titlebar_style *style_ptr) { wlmtk_box_set_style(&tb->super_box, &style_ptr->margin); if (!_wlmtk_titlebar_redraw_buffers(tb, style_ptr, tb->width) || !_wlmtk_titlebar_redraw(tb, style_ptr)) { return false; } tb->style_ptr = style_ptr; return true; } /* ------------------------------------------------------------------------- */ void wlmtk_titlebar_set_properties( wlmtk_titlebar_t *titlebar_ptr, uint32_t properties) { if (titlebar_ptr->properties == properties) return; titlebar_ptr->properties = properties; (void)_wlmtk_titlebar_redraw(titlebar_ptr, titlebar_ptr->style_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_titlebar_set_activated( wlmtk_titlebar_t *titlebar_ptr, bool activated) { if (titlebar_ptr->activated == activated) return; titlebar_ptr->activated = activated; wlmtk_titlebar_button_set_activated( titlebar_ptr->minimize_button_ptr, titlebar_ptr->activated); wlmtk_titlebar_title_set_activated( titlebar_ptr->titlebar_title_ptr, titlebar_ptr->activated); wlmtk_titlebar_button_set_activated( titlebar_ptr->close_button_ptr, titlebar_ptr->activated); } /* ------------------------------------------------------------------------- */ bool wlmtk_titlebar_is_activated(wlmtk_titlebar_t *titlebar_ptr) { return titlebar_ptr->activated; } /* ------------------------------------------------------------------------- */ void wlmtk_titlebar_set_title( wlmtk_titlebar_t *titlebar_ptr, const char *title_ptr) { if (titlebar_ptr->title_ptr == title_ptr) return; titlebar_ptr->title_ptr = title_ptr; _wlmtk_titlebar_redraw(titlebar_ptr, titlebar_ptr->style_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_titlebar_element(wlmtk_titlebar_t *titlebar_ptr) { return &titlebar_ptr->super_box.super_container.super_element; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Virtual destructor, wraps to our dtor. */ void _wlmtk_titlebar_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_titlebar_t *titlebar_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_titlebar_t, super_box.super_container.super_element); wlmtk_titlebar_destroy(titlebar_ptr); } /* ------------------------------------------------------------------------- */ /** * Compute positions of the titlebar elements, if configured. * * This method updates @ref wlmtk_titlebar_t::close_position, @ref * wlmtk_titlebar_t::title_position and @ref wlmtk_titlebar_t::title_width. * * @param titlebar_ptr * @param style_ptr */ void _wlmtk_titlebar_compute_positions( wlmtk_titlebar_t *titlebar_ptr, const struct wlmtk_titlebar_style *style_ptr) { titlebar_ptr->title_width = titlebar_ptr->width; // Room for a close button? titlebar_ptr->close_position = titlebar_ptr->width; if (3 * style_ptr->height < titlebar_ptr->width && (titlebar_ptr->properties & WLMTK_TITLEBAR_PROPERTY_CLOSE)) { titlebar_ptr->close_position = titlebar_ptr->width - style_ptr->height; titlebar_ptr->title_width -= style_ptr->height + style_ptr->margin.width; } titlebar_ptr->title_position = 0; // Also having room for a minimize button? if (4 * style_ptr->height < titlebar_ptr->width && (titlebar_ptr->properties & WLMTK_TITLEBAR_PROPERTY_ICONIFY)) { titlebar_ptr->title_position = style_ptr->height + style_ptr->margin.width; titlebar_ptr->title_width -= style_ptr->height + style_ptr->margin.width; } } /* ------------------------------------------------------------------------- */ /** Redraws the titlebar's background in appropriate size. */ bool _wlmtk_titlebar_redraw_buffers( wlmtk_titlebar_t *titlebar_ptr, const struct wlmtk_titlebar_style *style_ptr, unsigned width) { cairo_t *cairo_ptr; bs_gfxbuf_t *focussed_gfxbuf_ptr = bs_gfxbuf_create( width, style_ptr->height); if (NULL == focussed_gfxbuf_ptr) return false; cairo_ptr = cairo_create_from_bs_gfxbuf(focussed_gfxbuf_ptr); if (NULL == cairo_ptr) { bs_gfxbuf_destroy(focussed_gfxbuf_ptr); return false; } wlmaker_primitives_cairo_fill(cairo_ptr, &style_ptr->focussed_fill); cairo_destroy(cairo_ptr); bs_gfxbuf_t *blurred_gfxbuf_ptr = bs_gfxbuf_create( width, style_ptr->height); if (NULL == blurred_gfxbuf_ptr) return false; cairo_ptr = cairo_create_from_bs_gfxbuf(blurred_gfxbuf_ptr); if (NULL == cairo_ptr) { bs_gfxbuf_destroy(blurred_gfxbuf_ptr); bs_gfxbuf_destroy(focussed_gfxbuf_ptr); return false; } wlmaker_primitives_cairo_fill(cairo_ptr, &style_ptr->blurred_fill); cairo_destroy(cairo_ptr); if (NULL != titlebar_ptr->focussed_gfxbuf_ptr) { bs_gfxbuf_destroy(titlebar_ptr->focussed_gfxbuf_ptr); } titlebar_ptr->focussed_gfxbuf_ptr = focussed_gfxbuf_ptr; if (NULL != titlebar_ptr->blurred_gfxbuf_ptr) { bs_gfxbuf_destroy(titlebar_ptr->blurred_gfxbuf_ptr); } titlebar_ptr->blurred_gfxbuf_ptr = blurred_gfxbuf_ptr; titlebar_ptr->width = width; return true; } /* ------------------------------------------------------------------------- */ /** Redraws the titlebar elements. */ bool _wlmtk_titlebar_redraw( wlmtk_titlebar_t *titlebar_ptr, const struct wlmtk_titlebar_style *style_ptr) { // Guard clause: Nothing to do... yet. if (0 >= titlebar_ptr->width) return true; _wlmtk_titlebar_compute_positions(titlebar_ptr, style_ptr); if (!wlmtk_titlebar_title_redraw( titlebar_ptr->titlebar_title_ptr, titlebar_ptr->focussed_gfxbuf_ptr, titlebar_ptr->blurred_gfxbuf_ptr, titlebar_ptr->title_position, titlebar_ptr->title_width, titlebar_ptr->activated, titlebar_ptr->title_ptr, style_ptr)) { return false; } wlmtk_element_set_visible( wlmtk_titlebar_title_element(titlebar_ptr->titlebar_title_ptr), true); if (0 < titlebar_ptr->title_position) { if (!wlmtk_titlebar_button_redraw( titlebar_ptr->minimize_button_ptr, titlebar_ptr->focussed_gfxbuf_ptr, titlebar_ptr->blurred_gfxbuf_ptr, 0, style_ptr)) { return false; } wlmtk_element_set_visible( wlmtk_titlebar_button_element(titlebar_ptr->minimize_button_ptr), true); } else { wlmtk_element_set_visible( wlmtk_titlebar_button_element(titlebar_ptr->minimize_button_ptr), false); } if (titlebar_ptr->close_position < (int)titlebar_ptr->width) { if (!wlmtk_titlebar_button_redraw( titlebar_ptr->close_button_ptr, titlebar_ptr->focussed_gfxbuf_ptr, titlebar_ptr->blurred_gfxbuf_ptr, titlebar_ptr->close_position, style_ptr)) { return false; } wlmtk_element_set_visible( wlmtk_titlebar_button_element(titlebar_ptr->close_button_ptr), true); } else { wlmtk_element_set_visible( wlmtk_titlebar_button_element(titlebar_ptr->close_button_ptr), false); } // Don't forget to re-position the elements. wlmtk_element_layout(wlmtk_box_element(&titlebar_ptr->super_box)); wlmtk_element_invalidate_parent_layout( wlmtk_box_element(&titlebar_ptr->super_box)); return true; } /* == Unit tests =========================================================== */ static void test_variable_width(bs_test_t *test_ptr); static void test_properties(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_titlebar_test_cases[] = { { 1, "variable_width", test_variable_width }, { 1, "properties", test_properties }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_titlebar_test_set = BS_TEST_SET( true, "titlebar", _wlmtk_titlebar_test_cases); /* ------------------------------------------------------------------------- */ /** Tests titlebar with variable width. */ void test_variable_width(bs_test_t *test_ptr) { wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); struct wlmtk_titlebar_style style = { .height = 22, .margin = { .width = 2 } }; wlmtk_titlebar_t *titlebar_ptr = wlmtk_titlebar_create(w, &style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, titlebar_ptr); // Short names, for improved readability. wlmtk_element_t *title_elem_ptr = wlmtk_titlebar_title_element( titlebar_ptr->titlebar_title_ptr); wlmtk_element_t *minimize_elem_ptr = wlmtk_titlebar_button_element( titlebar_ptr->minimize_button_ptr); wlmtk_element_t *close_elem_ptr = wlmtk_titlebar_button_element( titlebar_ptr->close_button_ptr); int width; // Created with zero width: All invisible. */ BS_TEST_VERIFY_FALSE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, close_elem_ptr->visible); // Width sufficient for all: All elements visible and placed. BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_titlebar_set_width(titlebar_ptr, 89)); BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, close_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 24, title_elem_ptr->x); wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); BS_TEST_VERIFY_EQ(test_ptr, 41, width); BS_TEST_VERIFY_EQ(test_ptr, 67, close_elem_ptr->x); // Width sufficient only for 1 button. BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_titlebar_set_width(titlebar_ptr, 67)); BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, close_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 0, title_elem_ptr->x); wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); BS_TEST_VERIFY_EQ(test_ptr, 43, width); // Width doesn't permit any button. BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_titlebar_set_width(titlebar_ptr, 66)); BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, close_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 0, title_elem_ptr->x); wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); BS_TEST_VERIFY_EQ(test_ptr, 66, width); wlmtk_element_destroy(wlmtk_titlebar_element(titlebar_ptr)); wlmtk_window_destroy(w); } /* ------------------------------------------------------------------------- */ /** Tests titlebar with configured properties. */ void test_properties(bs_test_t *test_ptr) { wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); struct wlmtk_titlebar_style style = { .height = 22, .margin = { .width = 2 } }; wlmtk_titlebar_t *titlebar_ptr = wlmtk_titlebar_create(w, &style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, titlebar_ptr); // Short names, for improved readability. wlmtk_element_t *title_elem_ptr = wlmtk_titlebar_title_element( titlebar_ptr->titlebar_title_ptr); wlmtk_element_t *minimize_elem_ptr = wlmtk_titlebar_button_element( titlebar_ptr->minimize_button_ptr); wlmtk_element_t *close_elem_ptr = wlmtk_titlebar_button_element( titlebar_ptr->close_button_ptr); int width; // Width sufficient for all: All elements visible and placed. BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_titlebar_set_width(titlebar_ptr, 89)); BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, close_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 24, title_elem_ptr->x); wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); BS_TEST_VERIFY_EQ(test_ptr, 41, width); BS_TEST_VERIFY_EQ(test_ptr, 67, close_elem_ptr->x); // Properties disabling the close button. wlmtk_titlebar_set_properties( titlebar_ptr, WLMTK_TITLEBAR_PROPERTY_ICONIFY); BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, close_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 24, title_elem_ptr->x); wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); BS_TEST_VERIFY_EQ(test_ptr, 65, width); // Properties disabling the iconify button. wlmtk_titlebar_set_properties( titlebar_ptr, WLMTK_TITLEBAR_PROPERTY_CLOSE); BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, close_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 0, title_elem_ptr->x); wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); BS_TEST_VERIFY_EQ(test_ptr, 65, width); BS_TEST_VERIFY_EQ(test_ptr, 67, close_elem_ptr->x); // Disable all of them. wlmtk_titlebar_set_properties(titlebar_ptr, 0); BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, close_elem_ptr->visible); wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); BS_TEST_VERIFY_EQ(test_ptr, 89, width); // Re-enable all of them. wlmtk_titlebar_set_properties( titlebar_ptr, _wlmtk_titlebar_default_properties); BS_TEST_VERIFY_TRUE(test_ptr, title_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, minimize_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, close_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 24, title_elem_ptr->x); wlmtk_element_get_dimensions(title_elem_ptr, NULL, NULL, &width, NULL); BS_TEST_VERIFY_EQ(test_ptr, 41, width); BS_TEST_VERIFY_EQ(test_ptr, 67, close_elem_ptr->x); wlmtk_element_destroy(wlmtk_titlebar_element(titlebar_ptr)); wlmtk_window_destroy(w); } /* == End of titlebar.c ==================================================== */ wlmaker-0.8.1/src/toolkit/fsm.c0000644000175100017510000000702615222270710016000 0ustar runnerrunner/* ========================================================================= */ /** * @file fsm.c * Event-driven finite state machine. * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "fsm.h" #include /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ void wlmtk_fsm_init( wlmtk_fsm_t *fsm_ptr, const wlmtk_fsm_transition_t *transitions, int initial_state) { fsm_ptr->transitions = transitions; fsm_ptr->state = initial_state; } /* ------------------------------------------------------------------------- */ bool wlmtk_fsm_event( wlmtk_fsm_t *fsm_ptr, int event, void *ud_ptr) { for (const wlmtk_fsm_transition_t *transition_ptr = fsm_ptr->transitions; 0 <= transition_ptr->state; ++transition_ptr) { if (transition_ptr->state == fsm_ptr->state && transition_ptr->event == event) { bool rv = true; if (NULL != transition_ptr->handler) { rv = transition_ptr->handler(fsm_ptr, ud_ptr); } fsm_ptr->state = transition_ptr->to_state; return rv; } } return false; } /* == Unit tests =========================================================== */ static void test_event(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_fsm_test_cases[] = { { 1, "event", test_event }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_fsm_test_set = BS_TEST_SET( true, "fsm", _wlmtk_fsm_test_cases); /** Test handler for the FSM unit test: Sets the bool to true. */ static bool test_fsm_handler(__UNUSED__ wlmtk_fsm_t *fsm_ptr, void *ud_ptr) { *((bool*)ud_ptr) = true; return true; } /** Test transition table for the FSM unit test. */ static const wlmtk_fsm_transition_t test_transitions[] = { { 1, 100, 2, test_fsm_handler }, { 2, 101, 3, NULL }, WLMTK_FSM_TRANSITION_SENTINEL }; /* ------------------------------------------------------------------------- */ /** Tests FSM. */ void test_event(bs_test_t *test_ptr) { wlmtk_fsm_t fsm; bool called = false; wlmtk_fsm_init(&fsm, test_transitions, 1); BS_TEST_VERIFY_EQ(test_ptr, 1, fsm.state); // (1, 100) should trigger call to handler and move to (2). BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_fsm_event(&fsm, 100, &called)); BS_TEST_VERIFY_EQ(test_ptr, 2, fsm.state); BS_TEST_VERIFY_TRUE(test_ptr, called); called = false; // (2, 100) is not defined. return false. BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_fsm_event(&fsm, 100, &called)); // (2, 101) is defined. No handler == no crash. moves to (3). BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_fsm_event(&fsm, 101, &called)); BS_TEST_VERIFY_EQ(test_ptr, 3, fsm.state); BS_TEST_VERIFY_FALSE(test_ptr, called); } /* == End of fsm.c ========================================================= */ wlmaker-0.8.1/src/toolkit/panel.c0000644000175100017510000004321215222270710016307 0ustar runnerrunner/* ========================================================================= */ /** * @file panel.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "panel.h" #include #include #include #include #include "test.h" // IWYU pragma: keep /* == Declarations ========================================================= */ /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_panel_init( wlmtk_panel_t *panel_ptr, const wlmtk_panel_positioning_t *positioning_ptr) { *panel_ptr = (wlmtk_panel_t){}; if (!wlmtk_container_init(&panel_ptr->super_container)) { wlmtk_panel_fini(panel_ptr); return false; } panel_ptr->positioning = *positioning_ptr; if (!wlmtk_container_init(&panel_ptr->popup_container)) { wlmtk_panel_fini(panel_ptr); return false; } wlmtk_container_add_element( &panel_ptr->super_container, &panel_ptr->popup_container.super_element); wlmtk_element_set_visible(&panel_ptr->popup_container.super_element, true); return true; } /* ------------------------------------------------------------------------- */ void wlmtk_panel_fini(wlmtk_panel_t *panel_ptr) { if (panel_ptr->popup_container.super_element.parent_container_ptr) { wlmtk_container_remove_element( &panel_ptr->super_container, &panel_ptr->popup_container.super_element); } wlmtk_container_fini(&panel_ptr->popup_container); wlmtk_container_fini(&panel_ptr->super_container); } /* ------------------------------------------------------------------------- */ wlmtk_panel_vmt_t wlmtk_panel_extend( wlmtk_panel_t *panel_ptr, const wlmtk_panel_vmt_t *panel_vmt_ptr) { wlmtk_panel_vmt_t orig_panel_vmt = panel_ptr->vmt; if (NULL != panel_vmt_ptr->request_size) { panel_ptr->vmt.request_size = panel_vmt_ptr->request_size; } return orig_panel_vmt; } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_panel_element(wlmtk_panel_t *panel_ptr) { return &panel_ptr->super_container.super_element; } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *wlmtk_dlnode_from_panel(wlmtk_panel_t *panel_ptr) { if (NULL == panel_ptr) return NULL; return &panel_ptr->dlnode; } /* ------------------------------------------------------------------------- */ wlmtk_panel_t *wlmtk_panel_from_dlnode(bs_dllist_node_t *dlnode_ptr) { if (NULL == dlnode_ptr) return NULL; return BS_CONTAINER_OF(dlnode_ptr, wlmtk_panel_t, dlnode); } /* ------------------------------------------------------------------------- */ void wlmtk_panel_set_layer(wlmtk_panel_t *panel_ptr, wlmtk_layer_t *layer_ptr) { // Guard condition: Permit setting layer only if none set. And clearing // only if one is set. BS_ASSERT((NULL == layer_ptr) != (NULL == panel_ptr->layer_ptr)); panel_ptr->layer_ptr = layer_ptr; } /* ------------------------------------------------------------------------- */ wlmtk_layer_t *wlmtk_panel_get_layer(wlmtk_panel_t *panel_ptr) { return panel_ptr->layer_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_panel_set_layer_output( wlmtk_panel_t *panel_ptr, wlmtk_layer_output_t *layer_output_ptr) { panel_ptr->layer_output_ptr = layer_output_ptr; } /* ------------------------------------------------------------------------- */ wlmtk_layer_output_t *wlmtk_panel_get_layer_output(wlmtk_panel_t *panel_ptr) { return panel_ptr->layer_output_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_panel_commit( wlmtk_panel_t *panel_ptr, __UNUSED__ uint32_t serial, const wlmtk_panel_positioning_t *positioning_ptr) { // TODO(kaeser@gubbe.ch): Make use of `serial` and only update the // element's position once this matches the corresponding call to // @ref wlmtk_panel_request_size. // Guard clause: No updates, nothing more to do. if (0 == memcmp( &panel_ptr->positioning, positioning_ptr, sizeof(wlmtk_panel_positioning_t))) return; panel_ptr->positioning = *positioning_ptr; if (NULL != panel_ptr->layer_output_ptr) { wlmtk_layer_output_reconfigure(panel_ptr->layer_output_ptr); } } /* ------------------------------------------------------------------------- */ struct wlr_box wlmtk_panel_compute_dimensions( const wlmtk_panel_t *panel_ptr, const struct wlr_box *full_area_ptr, struct wlr_box *usable_area_ptr) { // Copied for readability. uint32_t anchor = panel_ptr->positioning.anchor; int margin_left = panel_ptr->positioning.margin_left; int margin_right = panel_ptr->positioning.margin_right; int margin_top = panel_ptr->positioning.margin_top; int margin_bottom = panel_ptr->positioning.margin_bottom; // Negative 'exclusive_zone' values mean to ignore other panels. struct wlr_box max_dims = *usable_area_ptr; if (0 > panel_ptr->positioning.exclusive_zone) { max_dims = *full_area_ptr; } struct wlr_box dims = { .width = panel_ptr->positioning.desired_width, .height = panel_ptr->positioning.desired_height }; // Set horizontal position and width. if (0 == dims.width) { // Width not given. Protocol requires the anchor to be set on left & // right edges, and translates to full width (minus margins). dims.x = max_dims.x + margin_left; dims.width = max_dims.width - margin_left - margin_right; } else if (anchor & WLR_EDGE_LEFT && !(anchor & WLR_EDGE_RIGHT)) { // Width given, anchored only on the left: At margin. dims.x = max_dims.x + margin_left; } else if (anchor & WLR_EDGE_RIGHT && !(anchor & WLR_EDGE_LEFT)) { // Width given, anchored only on the right: At margin minus width. dims.x = max_dims.x + max_dims.width - margin_right - dims.width; } else { // There was a width, and no one-sided anchoring: Center it. dims.x = max_dims.x + max_dims.width / 2 - dims.width / 2; } // Set vertical position and height. if (0 == dims.height) { // Height not given. Protocol requires the anchor to be set on top & // bottom edges, and translates to full height (minus margins). dims.y = max_dims.y + margin_top; dims.height = max_dims.height - margin_top - margin_bottom; } else if (anchor & WLR_EDGE_TOP && !(anchor & WLR_EDGE_BOTTOM)) { // Height given, anchored only on the top: At margin. dims.y = max_dims.y + margin_top; } else if (anchor & WLR_EDGE_BOTTOM && !(anchor & WLR_EDGE_TOP)) { // Height given, anchored only on the bottom: At margin minus height. dims.y = max_dims.y + max_dims.height - margin_bottom - dims.height; } else { // There was a height, and no vertical anchoring: Center it. dims.y = max_dims.y + max_dims.height / 2 - dims.height / 2; } // Update the usable area, if there is an exclusive zone. int exclusive_zone = panel_ptr->positioning.exclusive_zone; if (0 < exclusive_zone) { if (anchor == WLR_EDGE_LEFT || anchor == (WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) { usable_area_ptr->x += exclusive_zone + margin_left; usable_area_ptr->width -= exclusive_zone + margin_left; } if (anchor == WLR_EDGE_RIGHT || anchor == (WLR_EDGE_RIGHT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) { usable_area_ptr->width -= exclusive_zone + margin_right; } if (anchor == WLR_EDGE_TOP || anchor == (WLR_EDGE_TOP | WLR_EDGE_LEFT | WLR_EDGE_RIGHT)) { usable_area_ptr->y += exclusive_zone + margin_top; usable_area_ptr->height -= exclusive_zone + margin_top; } if (anchor == WLR_EDGE_BOTTOM || anchor == (WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT)) { usable_area_ptr->height -= exclusive_zone + margin_bottom; } } return dims; } /* == Methods for @ref wlmtk_fake_panel_t ================================== */ static void _wlmtk_fake_panel_element_destroy( wlmtk_element_t *element_ptr); static uint32_t _wlmtk_fake_panel_request_size( wlmtk_panel_t *panel_ptr, int width, int height); /** Virtual methods of the fake panel's element superclass. */ static const wlmtk_element_vmt_t _wlmtk_fake_panel_element_vmt = { .destroy = _wlmtk_fake_panel_element_destroy }; /** Virtual methods of the fake panel. */ static const wlmtk_panel_vmt_t _wlmtk_fake_panel_vmt = { .request_size = _wlmtk_fake_panel_request_size }; /* ------------------------------------------------------------------------- */ wlmtk_fake_panel_t *wlmtk_fake_panel_create( const wlmtk_panel_positioning_t *positioning_ptr) { wlmtk_fake_panel_t *fake_panel_ptr = logged_calloc( 1, sizeof(wlmtk_fake_panel_t)); if (NULL == fake_panel_ptr) return NULL; if (!wlmtk_panel_init(&fake_panel_ptr->panel, positioning_ptr)) { wlmtk_fake_panel_destroy(fake_panel_ptr); return NULL; } wlmtk_element_extend( &fake_panel_ptr->panel.super_container.super_element, &_wlmtk_fake_panel_element_vmt); wlmtk_panel_extend(&fake_panel_ptr->panel, &_wlmtk_fake_panel_vmt); return fake_panel_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_fake_panel_destroy(wlmtk_fake_panel_t *fake_panel_ptr) { wlmtk_panel_fini(&fake_panel_ptr->panel); free(fake_panel_ptr); } /** Implements @ref wlmtk_element_vmt_t::destroy for the fake panel. */ void _wlmtk_fake_panel_element_destroy( wlmtk_element_t *element_ptr) { wlmtk_fake_panel_t *fake_panel_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_fake_panel_t, panel.super_container.super_element); wlmtk_fake_panel_destroy(fake_panel_ptr); } /** Fake implementation of @ref wlmtk_panel_vmt_t::request_size. */ uint32_t _wlmtk_fake_panel_request_size( wlmtk_panel_t *panel_ptr, int width, int height) { wlmtk_fake_panel_t *fake_panel_ptr = BS_CONTAINER_OF( panel_ptr, wlmtk_fake_panel_t, panel); fake_panel_ptr->requested_width = width; fake_panel_ptr->requested_height = height; return fake_panel_ptr->serial; } /* == Unit tests =========================================================== */ static void test_init_fini(bs_test_t *test_ptr); static void test_compute_dimensions(bs_test_t *test_ptr); static void test_compute_dimensions_exclusive(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_panel_test_cases[] = { { 1, "init_fini", test_init_fini }, { 1, "compute_dimensions", test_compute_dimensions }, { 1, "compute_dimensions_exclusive", test_compute_dimensions_exclusive }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_panel_test_set = BS_TEST_SET( true, "panel", _wlmtk_panel_test_cases); /* ------------------------------------------------------------------------- */ /** Tests setup, teardown and some accessors. */ void test_init_fini(bs_test_t *test_ptr) { wlmtk_panel_t p; wlmtk_panel_positioning_t pos = {}; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_panel_init(&p, &pos)); bs_dllist_node_t *dlnode_ptr = wlmtk_dlnode_from_panel(&p); BS_TEST_VERIFY_EQ(test_ptr, &p.dlnode, dlnode_ptr); BS_TEST_VERIFY_EQ(test_ptr, &p, wlmtk_panel_from_dlnode(dlnode_ptr)); wlmtk_panel_fini(&p); } /* ------------------------------------------------------------------------- */ /** Verifies wlmtk_panel_compute_dimensions. */ void test_compute_dimensions(bs_test_t *test_ptr) { wlmtk_panel_positioning_t pos = { .desired_width = 100, .desired_height = 50 }; wlmtk_fake_panel_t *fake_panel_ptr = BS_ASSERT_NOTNULL( wlmtk_fake_panel_create(&pos)); wlmtk_panel_t *p_ptr = &fake_panel_ptr->panel; struct wlr_box extents = { .x = 0, .y = 0, .width = 200, .height = 100 }; struct wlr_box usable = extents; struct wlr_box dims; p_ptr->positioning.margin_left = 10; p_ptr->positioning.margin_right = 20; p_ptr->positioning.margin_top = 8; p_ptr->positioning.margin_bottom = 4; // Not anchored: Keep proposed dimensions. p_ptr->positioning.anchor = 0; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 50, 25, 100, 50, dims); // Anchored left or right: Respect margin. p_ptr->positioning.anchor = WLR_EDGE_LEFT; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 10, 25, 100, 50, dims); p_ptr->positioning.anchor = WLR_EDGE_RIGHT; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 80, 25, 100, 50, dims); // Anchored left & right: Centered, and keep proposed dimensions. p_ptr->positioning.anchor = WLR_EDGE_LEFT | WLR_EDGE_RIGHT; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 50, 25, 100, 50, dims); // Anchored top or bottom: Respect margin. p_ptr->positioning.anchor = WLR_EDGE_TOP; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 50, 8, 100, 50, dims); p_ptr->positioning.anchor = WLR_EDGE_BOTTOM; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 50, 46, 100, 50, dims); // Anchored top and bottom: Centered. p_ptr->positioning.anchor = WLR_EDGE_TOP | WLR_EDGE_BOTTOM; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 50, 25, 100, 50, dims); // Anchored all around, and no size proposed: Use full extents, // while respecting margins. p_ptr->positioning.anchor = WLR_EDGE_LEFT | WLR_EDGE_RIGHT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM; p_ptr->positioning.desired_height = 0; p_ptr->positioning.desired_width = 0; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 10, 8, 170, 88, dims); wlmtk_fake_panel_destroy(fake_panel_ptr); } /* ------------------------------------------------------------------------- */ /** Verifies dimension computation with an exclusive_zone. */ void test_compute_dimensions_exclusive(bs_test_t *test_ptr) { wlmtk_panel_positioning_t pos = { .exclusive_zone = 16, .anchor = (WLR_EDGE_LEFT | WLR_EDGE_RIGHT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM), .margin_left = 40, .margin_right = 30, .margin_top = 20, .margin_bottom = 10 }; wlmtk_fake_panel_t *fake_panel_ptr = BS_ASSERT_NOTNULL( wlmtk_fake_panel_create(&pos)); wlmtk_panel_t *p_ptr = &fake_panel_ptr->panel; struct wlr_box extents = { .x = 0, .y = 0, .width = 200, .height = 100 }; struct wlr_box usable = { .x = 1, .y = 2, .width = 195, .height = 90 }; struct wlr_box dims; // Use full extents on negative exclusive_zone value. p_ptr->positioning.exclusive_zone = -1; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 40, 20, 130, 70, dims); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 1, 2, 195, 90, usable); // Respect the usable area, for non-negative exclusive zone. p_ptr->positioning.exclusive_zone = 0; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 41, 22, 125, 60, dims); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 1, 2, 195, 90, usable); // Respect the usable area, for non-negative exclusive zone. Do not // update the usable zone, since anchored not appropriately. p_ptr->positioning.exclusive_zone = 7; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 41, 22, 125, 60, dims); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 1, 2, 195, 90, usable); // Respect usable zone, and update, since anchored left and full-height. p_ptr->positioning.desired_width = 20; p_ptr->positioning.exclusive_zone = 7; p_ptr->positioning.anchor = WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 41, 22, 20, 60, dims); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 48, 2, 148, 90, usable); // Check for usable zone at the bottom. usable.x = 1; usable.y = 2; usable.width = 195; usable.height = 90; p_ptr->positioning.desired_width = 100; p_ptr->positioning.desired_height = 20; p_ptr->positioning.exclusive_zone = 7; p_ptr->positioning.anchor = WLR_EDGE_BOTTOM; dims = wlmtk_panel_compute_dimensions(p_ptr, &extents, &usable); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 48, 62, 100, 20, dims); WLMTK_TEST_VERIFY_WLRBOX_EQ(test_ptr, 1, 2, 195, 73, usable); wlmtk_fake_panel_destroy(fake_panel_ptr); } /* == End of panel.c ======================================================= */ wlmaker-0.8.1/src/toolkit/gfxbuf.c0000644000175100017510000001461315222270710016474 0ustar runnerrunner/* ========================================================================= */ /** * @file gfxbuf.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "gfxbuf.h" #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include #include #include #include /* == Declarations ========================================================= */ /** State of the wrapped graphics buffer. */ typedef struct { /** The wlroots buffer. */ struct wlr_buffer wlr_buffer; /** The actual graphics buffer. */ bs_gfxbuf_t *gfxbuf_ptr; } wlmaker_gfxbuf_t; static wlmaker_gfxbuf_t *wlmaker_gfxbuf_from_wlr_buffer( struct wlr_buffer *wlr_buffer_ptr); static void wlmaker_gfxbuf_impl_destroy( struct wlr_buffer *wlr_buffer_ptr); static bool wlmaker_gfxbuf_impl_begin_data_ptr_access( struct wlr_buffer *wlr_buffer_ptr, uint32_t flags, void **data_ptr_ptr, uint32_t *format_ptr, size_t *stride_ptr); static void wlmaker_gfxbuf_impl_end_data_ptr_access( struct wlr_buffer *wlr_buffer_ptr); /** Implementation callbacks for wlroots' `struct wlr_buffer`. */ static const struct wlr_buffer_impl wlmaker_gfxbuf_impl = { .destroy = wlmaker_gfxbuf_impl_destroy, .begin_data_ptr_access = wlmaker_gfxbuf_impl_begin_data_ptr_access, .end_data_ptr_access = wlmaker_gfxbuf_impl_end_data_ptr_access }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ struct wlr_buffer *bs_gfxbuf_create_wlr_buffer( unsigned width, unsigned height) { wlmaker_gfxbuf_t *gfxbuf_ptr = logged_calloc(1, sizeof(wlmaker_gfxbuf_t)); if (NULL == gfxbuf_ptr) return NULL; wlr_buffer_init( &gfxbuf_ptr->wlr_buffer, &wlmaker_gfxbuf_impl, width, height); gfxbuf_ptr->gfxbuf_ptr = bs_gfxbuf_create(width, height); if (NULL == gfxbuf_ptr->gfxbuf_ptr) { wlmaker_gfxbuf_impl_destroy(&gfxbuf_ptr->wlr_buffer); return NULL; } return &gfxbuf_ptr->wlr_buffer; } /* ------------------------------------------------------------------------- */ void wlr_buffer_drop_nullify(struct wlr_buffer **wlr_buffer_ptr_ptr) { if (NULL == *wlr_buffer_ptr_ptr) return; wlr_buffer_drop(*wlr_buffer_ptr_ptr); *wlr_buffer_ptr_ptr = NULL; } /* ------------------------------------------------------------------------- */ bs_gfxbuf_t *bs_gfxbuf_from_wlr_buffer( struct wlr_buffer *wlr_buffer_ptr) { wlmaker_gfxbuf_t *gfxbuf_ptr = wlmaker_gfxbuf_from_wlr_buffer( wlr_buffer_ptr); return gfxbuf_ptr->gfxbuf_ptr; } /* ------------------------------------------------------------------------- */ cairo_t *cairo_create_from_wlr_buffer(struct wlr_buffer *wlr_buffer_ptr) { return cairo_create_from_bs_gfxbuf( bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr)); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Returns the @ref wlmaker_gfxbuf_t for `wlr_buffer_ptr`. * * @param wlr_buffer_ptr Pointer to a `struct wlr_buffer`. Must be a * `wlr_buffer` that was previously created by * @ref bs_gfxbuf_create_wlr_buffer. * * @return A pointer to the @ref wlmaker_gfxbuf_t. */ wlmaker_gfxbuf_t *wlmaker_gfxbuf_from_wlr_buffer( struct wlr_buffer *wlr_buffer_ptr) { // Verifies this is indeed a graphics-buffer-backed WLR buffer. */ BS_ASSERT(0 == memcmp( wlr_buffer_ptr->impl, &wlmaker_gfxbuf_impl, sizeof(struct wlr_buffer_impl))); return BS_CONTAINER_OF(wlr_buffer_ptr, wlmaker_gfxbuf_t, wlr_buffer); } /* ------------------------------------------------------------------------- */ /** * `struct wlr_buffer_impl` callback: Destroys the graphics buffer. * * This function Will be called only once producer and all consumers of the * corresponding wlr_buffer have lifted their locks (references). * * @param wlr_buffer_ptr */ void wlmaker_gfxbuf_impl_destroy( struct wlr_buffer *wlr_buffer_ptr) { wlmaker_gfxbuf_t *gfxbuf_ptr = wlmaker_gfxbuf_from_wlr_buffer( wlr_buffer_ptr); if (NULL != gfxbuf_ptr->gfxbuf_ptr) { bs_gfxbuf_destroy(gfxbuf_ptr->gfxbuf_ptr); gfxbuf_ptr->gfxbuf_ptr = NULL; } free(gfxbuf_ptr); } /* ------------------------------------------------------------------------- */ /** * `struct wlr_buffer_impl` callback: Set up for data access. * * @param wlr_buffer_ptr * @param flags unused. * @param data_ptr_ptr Output argument, point to the surface data. * @param format_ptr Output argument, set to the surface's format. * @param stride_ptr Output argument, set to the surface's stride. */ bool wlmaker_gfxbuf_impl_begin_data_ptr_access( struct wlr_buffer *wlr_buffer_ptr, __UNUSED__ uint32_t flags, void **data_ptr_ptr, uint32_t *format_ptr, size_t *stride_ptr) { wlmaker_gfxbuf_t *gfxbuf_ptr = wlmaker_gfxbuf_from_wlr_buffer( wlr_buffer_ptr); *data_ptr_ptr = gfxbuf_ptr->gfxbuf_ptr->data_ptr; *format_ptr = DRM_FORMAT_ARGB8888; // Equivalent to CAIRO ARGB32. *stride_ptr =gfxbuf_ptr->gfxbuf_ptr->pixels_per_line * sizeof(uint32_t); return true; } /* ------------------------------------------------------------------------- */ /** `struct wlr_buffer_impl` callback: End data access. Here, a no-op. */ void wlmaker_gfxbuf_impl_end_data_ptr_access( __UNUSED__ struct wlr_buffer *wlr_buffer_ptr) { // Nothing to do. } /* == End of gfxbuf.c ====================================================== */ wlmaker-0.8.1/src/toolkit/titlebar_button.c0000644000175100017510000003367515222270710020425 0ustar runnerrunner/* ========================================================================= */ /** * @file titlebar_button.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "titlebar_button.h" #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "buffer.h" #include "button.h" #include "gfxbuf.h" // IWYU pragma: keep #include "input.h" #include "primitives.h" #include "style.h" #include "util.h" /* == Declarations ========================================================= */ /** State of a titlebar button. */ struct _wlmtk_titlebar_button_t { /** Superclass: Button. */ wlmtk_button_t super_button; /** Whether the titlebar button is activated (focussed). */ bool activated; /** Callback for when the button is clicked. */ void (*click_handler2)(wlmtk_window_t *window_ptr); /** Points to the @ref wlmtk_window_t that carries this titlebar. */ wlmtk_window_t *window_ptr; /** For drawing the button contents. */ wlmtk_titlebar_button_draw_t draw; /** WLR buffer of the button when focussed & released. */ struct wlr_buffer *focussed_released_wlr_buffer_ptr; /** WLR buffer of the button when focussed & pressed. */ struct wlr_buffer *focussed_pressed_wlr_buffer_ptr; /** WLR buffer of the button when blurred. */ struct wlr_buffer *blurred_wlr_buffer_ptr; }; static void titlebar_button_element_destroy(wlmtk_element_t *element_ptr); static void titlebar_button_clicked(wlmtk_button_t *button_ptr); static void update_buffers(wlmtk_titlebar_button_t *titlebar_button_ptr); static struct wlr_buffer *create_buf( bs_gfxbuf_t *gfxbuf_ptr, int position, bool pressed, bool focussed, const struct wlmtk_titlebar_style *style_ptr, wlmtk_titlebar_button_draw_t draw); /* == Data ================================================================= */ /** Extension to the superclass element's virtual method table. */ static const wlmtk_element_vmt_t titlebar_button_element_vmt = { .destroy = titlebar_button_element_destroy, }; /** Extension to the parent button class' virtual methods. */ static const wlmtk_button_vmt_t titlebar_button_vmt = { .clicked = titlebar_button_clicked, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_titlebar_button_t *wlmtk_titlebar_button_create( void (*click_handler)(wlmtk_window_t *window_ptr), wlmtk_window_t *window_ptr, wlmtk_titlebar_button_draw_t draw) { BS_ASSERT(NULL != window_ptr); BS_ASSERT(NULL != click_handler); BS_ASSERT(NULL != draw); wlmtk_titlebar_button_t *titlebar_button_ptr = logged_calloc( 1, sizeof(wlmtk_titlebar_button_t)); if (NULL == titlebar_button_ptr) return NULL; titlebar_button_ptr->click_handler2 = click_handler; titlebar_button_ptr->window_ptr = window_ptr; titlebar_button_ptr->draw = draw; if (!wlmtk_button_init(&titlebar_button_ptr->super_button)) { wlmtk_titlebar_button_destroy(titlebar_button_ptr); return NULL; } wlmtk_element_extend( &titlebar_button_ptr->super_button.super_buffer.super_element, &titlebar_button_element_vmt); wlmtk_button_extend( &titlebar_button_ptr->super_button, &titlebar_button_vmt); return titlebar_button_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_titlebar_button_destroy( wlmtk_titlebar_button_t *titlebar_button_ptr) { wlr_buffer_drop_nullify( &titlebar_button_ptr->focussed_released_wlr_buffer_ptr); wlr_buffer_drop_nullify( &titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr); wlr_buffer_drop_nullify( &titlebar_button_ptr->blurred_wlr_buffer_ptr); wlmtk_button_fini(&titlebar_button_ptr->super_button); free(titlebar_button_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_titlebar_button_set_activated( wlmtk_titlebar_button_t *titlebar_button_ptr, bool activated) { if (titlebar_button_ptr->activated == activated) return; titlebar_button_ptr->activated = activated; update_buffers(titlebar_button_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_titlebar_button_redraw( wlmtk_titlebar_button_t *titlebar_button_ptr, bs_gfxbuf_t *focussed_gfxbuf_ptr, bs_gfxbuf_t *blurred_gfxbuf_ptr, int position, const struct wlmtk_titlebar_style *style_ptr) { BS_ASSERT(focussed_gfxbuf_ptr->width == blurred_gfxbuf_ptr->width); BS_ASSERT(focussed_gfxbuf_ptr->height == blurred_gfxbuf_ptr->height); BS_ASSERT(style_ptr->height == focussed_gfxbuf_ptr->height); BS_ASSERT(position + style_ptr->height <= focussed_gfxbuf_ptr->width); struct wlr_buffer *focussed_released_ptr = create_buf( focussed_gfxbuf_ptr, position, false, true, style_ptr, titlebar_button_ptr->draw); struct wlr_buffer *focussed_pressed_ptr = create_buf( focussed_gfxbuf_ptr, position, true, true, style_ptr, titlebar_button_ptr->draw); struct wlr_buffer *blurred_ptr = create_buf( blurred_gfxbuf_ptr, position, false, false, style_ptr, titlebar_button_ptr->draw); if (NULL != focussed_released_ptr && NULL != focussed_pressed_ptr && NULL != blurred_ptr) { wlr_buffer_drop_nullify( &titlebar_button_ptr->focussed_released_wlr_buffer_ptr); wlr_buffer_drop_nullify( &titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr); wlr_buffer_drop_nullify( &titlebar_button_ptr->blurred_wlr_buffer_ptr); titlebar_button_ptr->focussed_released_wlr_buffer_ptr = focussed_released_ptr; titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr = focussed_pressed_ptr; titlebar_button_ptr->blurred_wlr_buffer_ptr = blurred_ptr; update_buffers(titlebar_button_ptr); return true; } wlr_buffer_drop_nullify(&focussed_released_ptr); wlr_buffer_drop_nullify(&focussed_pressed_ptr); wlr_buffer_drop_nullify(&blurred_ptr); return false; } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_titlebar_button_element( wlmtk_titlebar_button_t *titlebar_button_ptr) { return &titlebar_button_ptr->super_button.super_buffer.super_element; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Virtual destructor, wraps to @ref wlmtk_titlebar_button_destroy. */ void titlebar_button_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_titlebar_button_t *titlebar_button_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_titlebar_button_t, super_button.super_buffer.super_element); wlmtk_titlebar_button_destroy(titlebar_button_ptr); } /* ------------------------------------------------------------------------- */ /** Handles button clicks: Passes the request to the window. */ void titlebar_button_clicked(wlmtk_button_t *button_ptr) { wlmtk_titlebar_button_t *titlebar_button_ptr = BS_CONTAINER_OF( button_ptr, wlmtk_titlebar_button_t, super_button); titlebar_button_ptr->click_handler2(titlebar_button_ptr->window_ptr); } /* ------------------------------------------------------------------------- */ /** Updates the button's buffer depending on activation status. */ void update_buffers(wlmtk_titlebar_button_t *titlebar_button_ptr) { // No buffer: Nothing to update. if (NULL == titlebar_button_ptr->focussed_released_wlr_buffer_ptr || NULL == titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr || NULL == titlebar_button_ptr->blurred_wlr_buffer_ptr) return; if (titlebar_button_ptr->activated) { wlmtk_button_set( &titlebar_button_ptr->super_button, titlebar_button_ptr->focussed_released_wlr_buffer_ptr, titlebar_button_ptr->focussed_pressed_wlr_buffer_ptr); } else { wlmtk_button_set( &titlebar_button_ptr->super_button, titlebar_button_ptr->blurred_wlr_buffer_ptr, titlebar_button_ptr->blurred_wlr_buffer_ptr); } } /* ------------------------------------------------------------------------- */ /** Helper: Creates a WLR buffer for the button. */ struct wlr_buffer *create_buf( bs_gfxbuf_t *gfxbuf_ptr, int position, bool pressed, bool focussed, const struct wlmtk_titlebar_style *style_ptr, wlmtk_titlebar_button_draw_t draw) { struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( style_ptr->height, style_ptr->height); if (NULL == wlr_buffer_ptr) return NULL; bs_gfxbuf_copy_area( bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), 0, 0, gfxbuf_ptr, position, 0, style_ptr->height, style_ptr->height); cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); return NULL; } wlmaker_primitives_draw_bezel( cairo_ptr, style_ptr->bezel_width, !pressed); uint32_t color = style_ptr->focussed_text_color; if (!focussed) color = style_ptr->blurred_text_color; draw(cairo_ptr, style_ptr->height, color); cairo_destroy(cairo_ptr); return wlr_buffer_ptr; } /* == Unit tests =========================================================== */ static void test_button(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_titlebar_button_test_cases[] = { { 1, "button", test_button }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_titlebar_button_test_set = BS_TEST_SET( true, "titlebar_button", _wlmtk_titlebar_button_test_cases); /* ------------------------------------------------------------------------- */ /** Tests button visualization. */ void test_button(bs_test_t *test_ptr) { wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_window_set_properties(w, WLMTK_WINDOW_PROPERTY_CLOSABLE); wlmtk_util_test_listener_t l; wlmtk_util_connect_test_listener( &wlmtk_window_events(w)->request_close, &l); wlmtk_titlebar_button_t *button_ptr = wlmtk_titlebar_button_create( wlmtk_window_request_close, w, wlmaker_primitives_draw_close_icon); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, button_ptr); wlmtk_titlebar_button_set_activated(button_ptr, true); // For improved readability. wlmtk_buffer_t *super_buffer_ptr = &button_ptr->super_button.super_buffer; wlmtk_element_t *element_ptr = wlmtk_titlebar_button_element(button_ptr); wlmtk_element_set_visible(element_ptr, true); // Draw contents. struct wlmtk_titlebar_style style = { .height = 22, .focussed_text_color = 0xffffffff, .blurred_text_color = 0xffe0c0a0, .bezel_width = 1 }; bs_gfxbuf_t *f_ptr = bs_gfxbuf_create(100, 22); bs_gfxbuf_clear(f_ptr, 0xff4040c0); bs_gfxbuf_t *b_ptr = bs_gfxbuf_create(100, 22); bs_gfxbuf_clear(b_ptr, 0xff303030); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_titlebar_button_redraw(button_ptr, f_ptr, b_ptr, 30, &style)); bs_gfxbuf_destroy(b_ptr); bs_gfxbuf_destroy(f_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), "toolkit/title_button_focussed_released.png"); // Pointer must be inside the button for accepting DOWN. wlmtk_pointer_motion_event_t mev = { .x = 11, .y = 11 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(element_ptr, &mev)); // Button down: pressed. wlmtk_button_event_t button = { .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &button)); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), "toolkit/title_button_focussed_pressed.png"); button.type = WLMTK_BUTTON_UP; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &button)); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), "toolkit/title_button_focussed_released.png"); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); // Click: To be passed along, no change to visual. button.type = WLMTK_BUTTON_CLICK; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &button)); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), "toolkit/title_button_focussed_released.png"); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); // De-activate: Show as blurred. wlmtk_titlebar_button_set_activated(button_ptr, false); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), "toolkit/title_button_blurred.png"); wlmtk_element_destroy(element_ptr); wlmtk_window_destroy(w); } /* == End of titlebar_button.c ============================================= */ wlmaker-0.8.1/src/toolkit/style.c0000644000175100017510000002150415222270710016350 0ustar runnerrunner/* ========================================================================= */ /** * @file style.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include /* == Data ================================================================= */ const bspl_desc_t wlmtk_style_margin_desc[] = { BSPL_DESC_UINT64( "Width", true, struct wlmtk_margin_style, width, width, 0), BSPL_DESC_ARGB32( "Color", true, struct wlmtk_margin_style, color, color, 0xff000000), BSPL_DESC_SENTINEL() }; /** Plist decoding descriptor for font weight. */ static const bspl_enum_desc_t _wlmtk_style_font_weight_desc[] = { BSPL_ENUM("Normal", WLMTK_FONT_WEIGHT_NORMAL), BSPL_ENUM("Bold", WLMTK_FONT_WEIGHT_BOLD), BSPL_ENUM_SENTINEL() }; const bspl_desc_t wlmtk_style_font_desc[] = { BSPL_DESC_CHARBUF( "Face", true, wlmtk_style_font_t, face, face, WLMTK_STYLE_FONT_FACE_LENGTH, NULL), BSPL_DESC_ENUM( "Weight", true, wlmtk_style_font_t, weight, weight, WLMTK_FONT_WEIGHT_NORMAL, _wlmtk_style_font_weight_desc), BSPL_DESC_UINT64( "Size", true, wlmtk_style_font_t, size, size, 10), BSPL_DESC_SENTINEL() }; /** Plist decoding descriptor of the fill type. */ static const bspl_enum_desc_t _wlmtk_style_fill_type_desc[] = { BSPL_ENUM("SOLID", WLMTK_STYLE_COLOR_SOLID), BSPL_ENUM("HGRADIENT", WLMTK_STYLE_COLOR_HGRADIENT), BSPL_ENUM("VGRADIENT", WLMTK_STYLE_COLOR_VGRADIENT), BSPL_ENUM("DGRADIENT", WLMTK_STYLE_COLOR_DGRADIENT), BSPL_ENUM("ADGRADIENT", WLMTK_STYLE_COLOR_ADGRADIENT), BSPL_ENUM_SENTINEL() }; /** Plist decoding descriptor of the fill style. */ static const bspl_desc_t _wlmtk_style_fill_style_desc[] = { BSPL_DESC_ENUM("Type", true, wlmtk_style_fill_t, type, type, WLMTK_STYLE_COLOR_SOLID, _wlmtk_style_fill_type_desc), BSPL_DESC_SENTINEL() }; /** Plist decoding descriptor of the solid color. */ static const bspl_desc_t _wlmtk_style_fill_color_solid_desc[] = { BSPL_DESC_ARGB32( "Color", true, wlmtk_style_color_solid_data_t, color, color, 0), BSPL_DESC_SENTINEL() }; /** Plist decoding descriptor of a color gradient. */ static const bspl_desc_t _wlmtk_style_fill_color_gradient_desc[] = { BSPL_DESC_ARGB32( "From", true, wlmtk_style_color_gradient_data_t, from, from, 0), BSPL_DESC_ARGB32( "To", true, wlmtk_style_color_gradient_data_t, to, to, 0), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ cairo_font_weight_t wlmtk_style_font_weight_cairo_from_wlmtk( wlmtk_style_font_weight_t weight) { switch (weight) { case WLMTK_FONT_WEIGHT_NORMAL: return CAIRO_FONT_WEIGHT_NORMAL; case WLMTK_FONT_WEIGHT_BOLD: return CAIRO_FONT_WEIGHT_BOLD; default: bs_log(BS_FATAL, "Unhandled font weight %d", weight); BS_ABORT(); } return CAIRO_FONT_WEIGHT_NORMAL; } /* ------------------------------------------------------------------------- */ bool wlmtk_style_decode_fill( bspl_object_t *object_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { wlmtk_style_fill_t *fill_ptr = value_ptr; bspl_dict_t *dict_ptr = bspl_dict_from_object(object_ptr); if (NULL == dict_ptr) return false; if (!bspl_decode_dict( dict_ptr, _wlmtk_style_fill_style_desc, fill_ptr)) return false; switch (fill_ptr->type) { case WLMTK_STYLE_COLOR_SOLID: return bspl_decode_dict( dict_ptr, _wlmtk_style_fill_color_solid_desc, &fill_ptr->param.solid); case WLMTK_STYLE_COLOR_HGRADIENT: return bspl_decode_dict( dict_ptr, _wlmtk_style_fill_color_gradient_desc, &fill_ptr->param.hgradient); case WLMTK_STYLE_COLOR_VGRADIENT: return bspl_decode_dict( dict_ptr, _wlmtk_style_fill_color_gradient_desc, &fill_ptr->param.vgradient); case WLMTK_STYLE_COLOR_DGRADIENT: return bspl_decode_dict( dict_ptr, _wlmtk_style_fill_color_gradient_desc, &fill_ptr->param.dgradient); case WLMTK_STYLE_COLOR_ADGRADIENT: return bspl_decode_dict( dict_ptr, _wlmtk_style_fill_color_gradient_desc, &fill_ptr->param.adgradient); default: bs_log(BS_ERROR, "Unhandled fill type %d", fill_ptr->type); return false; } bs_log(BS_FATAL, "Uh... no idea how this got here."); return false; } /* == Unit Tests =========================================================== */ static void _wlmtk_style_test_decode_fill(bs_test_t *test_ptr); static void _wlmtk_style_test_decode_font(bs_test_t *test_ptr); /** Unit test cases. */ /** Test cases */ static const bs_test_case_t _wlmtk_style_test_cases[] = { { true, "decode_fill", _wlmtk_style_test_decode_fill }, { true, "decode_font", _wlmtk_style_test_decode_font }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_style_test_set = BS_TEST_SET( true, "style", _wlmtk_style_test_cases); /* ------------------------------------------------------------------------- */ /** Tests the decoder for the fill style. */ void _wlmtk_style_test_decode_fill(bs_test_t *test_ptr) { const char *s = ("{" "Type = DGRADIENT;" "From = \"argb32:0x01020304\";" "To = \"argb32:0x0204080c\"" "}"); wlmtk_style_fill_t fill; bspl_object_t *object_ptr; object_ptr = bspl_create_object_from_plist_string(s); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, object_ptr); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_style_decode_fill(object_ptr, NULL, &fill)); BS_TEST_VERIFY_EQ(test_ptr, WLMTK_STYLE_COLOR_DGRADIENT, fill.type); BS_TEST_VERIFY_EQ(test_ptr, 0x01020304, fill.param.dgradient.from); BS_TEST_VERIFY_EQ(test_ptr, 0x0204080c, fill.param.dgradient.to); bspl_object_unref(object_ptr); s = ("{" "Type = HGRADIENT;" "From = \"argb32:0x04030201\";" "To = \"argb32:0x40302010\"" "}"); object_ptr = bspl_create_object_from_plist_string(s); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, object_ptr); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_style_decode_fill(object_ptr, NULL, &fill)); BS_TEST_VERIFY_EQ(test_ptr, WLMTK_STYLE_COLOR_HGRADIENT, fill.type); BS_TEST_VERIFY_EQ(test_ptr, 0x04030201, fill.param.hgradient.from); BS_TEST_VERIFY_EQ(test_ptr, 0x40302010, fill.param.hgradient.to); bspl_object_unref(object_ptr); s = ("{" "Type = SOLID;" "Color = \"argb32:0x11223344\"" "}"); object_ptr = bspl_create_object_from_plist_string(s); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, object_ptr); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_style_decode_fill(object_ptr, NULL, &fill)); BS_TEST_VERIFY_EQ(test_ptr, WLMTK_STYLE_COLOR_SOLID, fill.type); BS_TEST_VERIFY_EQ(test_ptr, 0x11223344, fill.param.solid.color); bspl_object_unref(object_ptr); } /* ------------------------------------------------------------------------- */ /** Tests the decoder for a font descriptor. */ void _wlmtk_style_test_decode_font(bs_test_t *test_ptr) { bspl_object_t *object_ptr; const char *s = ("{" "Face = Helvetica;" "Weight = Bold;" "Size = 12;" "}"); object_ptr = bspl_create_object_from_plist_string(s); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, object_ptr); bspl_dict_t *dict_ptr = bspl_dict_from_object(object_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dict_ptr); wlmtk_style_font_t font = {}; BS_TEST_VERIFY_TRUE( test_ptr, bspl_decode_dict(dict_ptr, wlmtk_style_font_desc, &font)); BS_TEST_VERIFY_STREQ(test_ptr, "Helvetica", font.face); BS_TEST_VERIFY_EQ(test_ptr, WLMTK_FONT_WEIGHT_BOLD, font.weight); BS_TEST_VERIFY_EQ(test_ptr, 12, font.size); bspl_object_unref(object_ptr); } /* == End of style.c ======================================================= */ wlmaker-0.8.1/src/toolkit/layer.c0000644000175100017510000004307415222270710016332 0ustar runnerrunner/* ========================================================================= */ /** * @file layer.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "layer.h" #include #include #include #include // IWYU pragma: keep #define WLR_USE_UNSTABLE #include #include #include #undef WLR_USE_UNSTABLE #include "container.h" #include "output_tracker.h" #include "panel.h" #include "test.h" // IWYU pragma: keep #include "tile.h" #include "workspace.h" /* == Declarations ========================================================= */ /** State of a layer, covering multiple outputs. */ struct _wlmtk_layer_t { /** Super class of the layer. */ wlmtk_container_t super_container; /** Workspace that the layer belongs to. */ wlmtk_workspace_t *workspace_ptr; /** Tracks the outputs. */ wlmtk_output_tracker_t *output_tracker_ptr; }; /** State of the layer on the given output. */ struct _wlmtk_layer_output_t { /** Extents and position of the output in the layout. */ struct wlr_box extents; /** Panels. Holds nodes at @ref wlmtk_panel_t::dlnode. */ bs_dllist_t panels; }; static void _wlmtk_layer_output_remove_dlnode_panel( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmtk_layer_output_add_panel( wlmtk_layer_output_t *layer_output_ptr, wlmtk_panel_t *panel_ptr); static void _wlmtk_layer_output_remove_panel( wlmtk_layer_output_t *layer_output_ptr, wlmtk_panel_t *panel_ptr); static void *_wlmtk_layer_output_create( struct wlr_output *wlr_output_ptr, void *ud_ptr); static void _wlmtk_layer_output_update( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr); static void _wlmtk_layer_output_destroy( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_layer_t *wlmtk_layer_create( struct wlr_output_layout *wlr_output_layout_ptr) { wlmtk_layer_t *layer_ptr = logged_calloc(1, sizeof(wlmtk_layer_t)); if (NULL == layer_ptr) return NULL; if (!wlmtk_container_init(&layer_ptr->super_container)) { wlmtk_layer_destroy(layer_ptr); return NULL; } layer_ptr->output_tracker_ptr = wlmtk_output_tracker_create( wlr_output_layout_ptr, wlr_output_layout_ptr, _wlmtk_layer_output_create, _wlmtk_layer_output_update, _wlmtk_layer_output_destroy); return layer_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_layer_destroy(wlmtk_layer_t *layer_ptr) { if (NULL != layer_ptr->output_tracker_ptr) { wlmtk_output_tracker_destroy(layer_ptr->output_tracker_ptr); layer_ptr->output_tracker_ptr = NULL; } BS_ASSERT(bs_dllist_empty(&layer_ptr->super_container.elements)); wlmtk_container_fini(&layer_ptr->super_container); free(layer_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_layer_element(wlmtk_layer_t *layer_ptr) { return &layer_ptr->super_container.super_element; } /* ------------------------------------------------------------------------- */ bool wlmtk_layer_add_panel( wlmtk_layer_t *layer_ptr, wlmtk_panel_t *panel_ptr, struct wlr_output *wlr_output_ptr) { BS_ASSERT(NULL == wlmtk_panel_get_layer(panel_ptr)); wlmtk_layer_output_t *layer_output_ptr = wlmtk_output_tracker_get_output( layer_ptr->output_tracker_ptr, wlr_output_ptr); if (NULL == layer_output_ptr) { bs_log(BS_WARNING, "Layer %p does not contain output %p", layer_ptr, wlr_output_ptr); return false; } wlmtk_container_add_element( &layer_ptr->super_container, wlmtk_panel_element(panel_ptr)); wlmtk_panel_set_layer(panel_ptr, layer_ptr); _wlmtk_layer_output_add_panel(layer_output_ptr, panel_ptr); wlmtk_layer_output_reconfigure(layer_output_ptr); return true; } /* ------------------------------------------------------------------------- */ void wlmtk_layer_remove_panel(wlmtk_layer_t *layer_ptr, wlmtk_panel_t *panel_ptr) { BS_ASSERT(layer_ptr == wlmtk_panel_get_layer(panel_ptr)); wlmtk_layer_output_t *layer_output_ptr = BS_ASSERT_NOTNULL( wlmtk_panel_get_layer_output(panel_ptr)); _wlmtk_layer_output_remove_panel(layer_output_ptr, panel_ptr); wlmtk_panel_set_layer(panel_ptr, NULL); wlmtk_container_remove_element( &layer_ptr->super_container, wlmtk_panel_element(panel_ptr)); wlmtk_layer_output_reconfigure(layer_output_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_layer_output_reconfigure( wlmtk_layer_output_t *layer_output_ptr) { struct wlr_box usable_area = layer_output_ptr->extents; for (bs_dllist_node_t *dlnode_ptr = layer_output_ptr->panels.head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { wlmtk_panel_t *panel_ptr = wlmtk_panel_from_dlnode(dlnode_ptr); struct wlr_box new_usable_area = usable_area; struct wlr_box panel_dimensions = wlmtk_panel_compute_dimensions( panel_ptr, &layer_output_ptr->extents, &new_usable_area); if (wlmtk_panel_element(panel_ptr)->visible) { usable_area = new_usable_area; } wlmtk_panel_request_size( panel_ptr, panel_dimensions.width, panel_dimensions.height); wlmtk_element_set_position( wlmtk_panel_element(panel_ptr), panel_dimensions.x, panel_dimensions.y); } } /* ------------------------------------------------------------------------- */ void wlmtk_layer_set_workspace(wlmtk_layer_t *layer_ptr, wlmtk_workspace_t *workspace_ptr) { layer_ptr->workspace_ptr = workspace_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Removes `dlnode_ptr`'s panel from the layer output and destroys it. */ void _wlmtk_layer_output_remove_dlnode_panel( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { wlmtk_panel_t *panel_ptr = wlmtk_panel_from_dlnode(dlnode_ptr); wlmtk_layer_remove_panel( BS_ASSERT_NOTNULL(wlmtk_panel_get_layer(panel_ptr)), panel_ptr); wlmtk_element_destroy(wlmtk_panel_element(panel_ptr)); } /* ------------------------------------------------------------------------- */ /** Adds the panel to the output. */ void _wlmtk_layer_output_add_panel( wlmtk_layer_output_t *layer_output_ptr, wlmtk_panel_t *panel_ptr) { wlmtk_panel_set_layer_output(panel_ptr, layer_output_ptr); bs_dllist_push_back( &layer_output_ptr->panels, wlmtk_dlnode_from_panel(panel_ptr)); } /* ------------------------------------------------------------------------- */ /** Removes the panel from the output. */ void _wlmtk_layer_output_remove_panel( wlmtk_layer_output_t *layer_output_ptr, wlmtk_panel_t *panel_ptr) { BS_ASSERT(layer_output_ptr == wlmtk_panel_get_layer_output(panel_ptr)); bs_dllist_remove( &layer_output_ptr->panels, wlmtk_dlnode_from_panel(panel_ptr)); wlmtk_panel_set_layer_output(panel_ptr, NULL); } /* ------------------------------------------------------------------------- */ /** Creates a layer output for `wlr_output_ptr`. */ void *_wlmtk_layer_output_create( struct wlr_output *wlr_output_ptr, void *ud_ptr) { struct wlr_output_layout *wlr_output_layout_ptr = ud_ptr; wlmtk_layer_output_t *layer_output_ptr = logged_calloc( 1, sizeof(wlmtk_layer_output_t)); if (NULL == layer_output_ptr) return NULL; wlr_output_layout_get_box( wlr_output_layout_ptr, wlr_output_ptr, &layer_output_ptr->extents); return layer_output_ptr; } /* ------------------------------------------------------------------------- */ /** Reconfigures the layer, if the extents of this output changed. */ void _wlmtk_layer_output_update( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr) { struct wlr_output_layout *wlr_output_layout_ptr = ud_ptr; wlmtk_layer_output_t *layer_output_ptr = output_ptr; struct wlr_box new_extents; wlr_output_layout_get_box( wlr_output_layout_ptr, wlr_output_ptr, &new_extents); if (!wlr_box_equal(&new_extents, &layer_output_ptr->extents)) { layer_output_ptr->extents = new_extents; wlmtk_layer_output_reconfigure(layer_output_ptr); } } /* ------------------------------------------------------------------------- */ /** Output is removed. Unlink all panels, and destroy the layer output. */ void _wlmtk_layer_output_destroy( __UNUSED__ struct wlr_output *wlr_output_ptr, __UNUSED__ void *ud_ptr, void *output_ptr) { wlmtk_layer_output_t *layer_output_ptr = output_ptr; bs_dllist_for_each( &layer_output_ptr->panels, _wlmtk_layer_output_remove_dlnode_panel, NULL); free(layer_output_ptr); } /* == Unit tests =========================================================== */ static void test_multi_output(bs_test_t *test_ptr); static void test_layout(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_layer_test_cases[] = { { 1, "multi_output", test_multi_output }, { 1, "layout", test_layout }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_layer_test_set = BS_TEST_SET( true, "layer", _wlmtk_layer_test_cases); /* ------------------------------------------------------------------------- */ /** Tests adding + removing outputs, and updates to panel positions. */ void test_multi_output(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); wlmtk_layer_t *layer_ptr = wlmtk_layer_create(wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, layer_ptr); // First output. Add to the layout + update the layer right away. struct wlr_output o1 = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&o1); wlr_output_layout_add(wlr_output_layout_ptr, &o1, 10, 20); // Add panel to the first output, and verify global positioning. wlmtk_panel_positioning_t p1 = { .desired_width = 100, .desired_height = 50 }; wlmtk_fake_panel_t *fp1_ptr = wlmtk_fake_panel_create(&p1); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fp1_ptr); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_layer_add_panel(layer_ptr, &fp1_ptr->panel, &o1)); // Position: 10 (output) + 1024/2 (half output width) - 50 (half panel). BS_TEST_VERIFY_EQ(test_ptr, 472, wlmtk_panel_element(&fp1_ptr->panel)->x); // Position: 20 (output) + 768/2 (half output height) - 25 (half panel). BS_TEST_VERIFY_EQ(test_ptr, 379, wlmtk_panel_element(&fp1_ptr->panel)->y); // Explicitly remove first panel. wlmtk_layer_remove_panel(layer_ptr, &fp1_ptr->panel); wlmtk_fake_panel_destroy(fp1_ptr); // Second output. Do not add to layout yet. struct wlr_output o2 = { .width = 640, .height = 480, .scale = 2.0 }; wlmtk_test_wlr_output_init(&o2); // Attempt to add panel to the second output. Must fail. wlmtk_panel_positioning_t p2 = { .desired_width = 80, .desired_height = 36 }; wlmtk_fake_panel_t *fp2_ptr = wlmtk_fake_panel_create(&p2); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fp2_ptr); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_layer_add_panel(layer_ptr, &fp2_ptr->panel, &o2)); // Now: Add second output. Adding the panel must now work. wlr_output_layout_add(wlr_output_layout_ptr, &o2, 400, 200); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_layer_add_panel(layer_ptr, &fp2_ptr->panel, &o2)); // Position: 400 (output) + 640/4 (half scaled output) - 40 (half panel). BS_TEST_VERIFY_EQ(test_ptr, 520, wlmtk_panel_element(&fp2_ptr->panel)->x ); // Position: 200 (output) + 480/4 (half scaled output) - 18 (half panel). BS_TEST_VERIFY_EQ(test_ptr, 302, wlmtk_panel_element(&fp2_ptr->panel)->y); // Reposition the second output. Must reconfigure that panel's position. wlr_output_layout_add(wlr_output_layout_ptr, &o2, 500, 300); // Position: 500 (output) + 640/4 (half scaled output) - 40 (half panel). BS_TEST_VERIFY_EQ(test_ptr, 620, wlmtk_panel_element(&fp2_ptr->panel)->x ); // Position: 300 (output) + 480/4 (half scaled output) - 18 (half panel). BS_TEST_VERIFY_EQ(test_ptr, 402, wlmtk_panel_element(&fp2_ptr->panel)->y); wlr_output_layout_remove(wlr_output_layout_ptr, &o1); // We leave the second output + panel in. Must be destroyed in layer dtor. wlmtk_layer_destroy(layer_ptr); wlr_output_layout_remove(wlr_output_layout_ptr, &o2); wlr_output_layout_destroy(wlr_output_layout_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests panel layout with multiple panels. */ void test_layout(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); static const struct wlmtk_tile_style ts = { .size = 64 }; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "test", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_layer_t *layer_ptr = wlmtk_layer_create(wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, layer_ptr); wlmtk_layer_set_workspace(layer_ptr, ws_ptr); // Adds a left-bounded panel with an exclusive zone. wlmtk_panel_positioning_t pos = { .desired_width = 100, .desired_height = 50, .exclusive_zone = 40, .anchor = WLR_EDGE_LEFT }; wlmtk_fake_panel_t *fp1_ptr = wlmtk_fake_panel_create(&pos); BS_ASSERT_NOTNULL(fp1_ptr); wlmtk_element_set_visible(wlmtk_panel_element(&fp1_ptr->panel), true); wlmtk_layer_add_panel(layer_ptr, &fp1_ptr->panel, &output); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_panel_element(&fp1_ptr->panel)->x); BS_TEST_VERIFY_EQ(test_ptr, 359, wlmtk_panel_element(&fp1_ptr->panel)->y); BS_TEST_VERIFY_EQ(test_ptr, 100, fp1_ptr->requested_width); BS_TEST_VERIFY_EQ(test_ptr, 50, fp1_ptr->requested_height); // Next panel is to respect the exclusive zone. It is invisible => later // panels won't shift, but it still will be positioned. pos.anchor = WLR_EDGE_LEFT | WLR_EDGE_TOP | WLR_EDGE_BOTTOM; pos.desired_height = 0; wlmtk_fake_panel_t *fp2_ptr = wlmtk_fake_panel_create(&pos); wlmtk_element_set_visible(wlmtk_panel_element(&fp2_ptr->panel), false); BS_ASSERT_NOTNULL(fp2_ptr); wlmtk_layer_add_panel(layer_ptr, &fp2_ptr->panel, &output); BS_TEST_VERIFY_EQ(test_ptr, 40, wlmtk_panel_element(&fp2_ptr->panel)->x); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_panel_element(&fp2_ptr->panel)->y); BS_TEST_VERIFY_EQ(test_ptr, 100, fp2_ptr->requested_width); BS_TEST_VERIFY_EQ(test_ptr, 768, fp2_ptr->requested_height); // Next panel: Same size and position, since the former is invisible. wlmtk_fake_panel_t *fp3_ptr = wlmtk_fake_panel_create(&pos); wlmtk_element_set_visible(wlmtk_panel_element(&fp3_ptr->panel), true); BS_ASSERT_NOTNULL(fp3_ptr); wlmtk_layer_add_panel(layer_ptr, &fp3_ptr->panel, &output); BS_TEST_VERIFY_EQ(test_ptr, 40, wlmtk_panel_element(&fp3_ptr->panel)->x); BS_TEST_VERIFY_EQ(test_ptr, 0, wlmtk_panel_element(&fp3_ptr->panel)->y); BS_TEST_VERIFY_EQ(test_ptr, 100, fp3_ptr->requested_width); BS_TEST_VERIFY_EQ(test_ptr, 768, fp3_ptr->requested_height); wlmtk_layer_remove_panel(layer_ptr, &fp3_ptr->panel); wlmtk_fake_panel_destroy(fp3_ptr); wlmtk_layer_remove_panel(layer_ptr, &fp2_ptr->panel); wlmtk_fake_panel_destroy(fp2_ptr); wlmtk_layer_remove_panel(layer_ptr, &fp1_ptr->panel); wlmtk_fake_panel_destroy(fp1_ptr); wlmtk_layer_set_workspace(layer_ptr, NULL); wlmtk_layer_destroy(layer_ptr); wlmtk_workspace_destroy(ws_ptr); wlr_output_layout_remove(wlr_output_layout_ptr, &output); wlr_output_layout_destroy(wlr_output_layout_ptr); wl_display_destroy(display_ptr); } /* == End of layer.c ======================================================= */ wlmaker-0.8.1/src/toolkit/rectangle.c0000644000175100017510000003311715222270710017157 0ustar runnerrunner/* ========================================================================= */ /** * @file rectangle.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "rectangle.h" #include #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "container.h" #include "input.h" #include "util.h" /* == Declarations ========================================================= */ /** State of a unicolor rectangle. */ struct _wlmtk_rectangle_t { /** Superclass element. */ wlmtk_element_t super_element; /** Original virtual method table of the superclass element. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Width of the rectangle. */ int width; /** Height of the rectangle. */ int height; /** Color of the rectangle, as an ARGB8888 value. */ uint32_t color; /** WLR rectangle. */ struct wlr_scene_rect *wlr_scene_rect_ptr; /** Listener for the `destroy` signal of `wlr_rect_buffer_ptr->node`. */ struct wl_listener wlr_scene_rect_node_destroy_listener; /** Listener for @ref wlmtk_element_events_t::pointer_enter. */ struct wl_listener element_pointer_enter_listener; }; static void _wlmtk_rectangle_element_destroy(wlmtk_element_t *element_ptr); static struct wlr_scene_node *_wlmtk_rectangle_element_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr); static bool _wlmtk_rectangle_element_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr); static void _wlmtk_rectangle_get_dimensions( wlmtk_element_t *element_ptr, int *x1_ptr, int *y1_ptr, int *x2_ptr, int *y2_ptr); static void _wlmtk_rectangle_handle_wlr_scene_rect_node_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_rectangle_handle_element_pointer_enter( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Virtual method table of the rectangle, extending the element. */ static const wlmtk_element_vmt_t _wlmtk_rectangle_element_vmt = { .destroy = _wlmtk_rectangle_element_destroy, .create_scene_node = _wlmtk_rectangle_element_create_scene_node, .pointer_accepts_motion = _wlmtk_rectangle_element_pointer_accepts_motion, .get_dimensions = _wlmtk_rectangle_get_dimensions, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_rectangle_t *wlmtk_rectangle_create( int width, int height, uint32_t color) { wlmtk_rectangle_t *rectangle_ptr = logged_calloc( 1, sizeof(wlmtk_rectangle_t)); if (NULL == rectangle_ptr) return NULL; rectangle_ptr->width = width; rectangle_ptr->height = height; wlmtk_rectangle_set_color(rectangle_ptr, color); if (!wlmtk_element_init(&rectangle_ptr->super_element)) { wlmtk_rectangle_destroy(rectangle_ptr); return NULL; } rectangle_ptr->orig_super_element_vmt = wlmtk_element_extend( &rectangle_ptr->super_element, &_wlmtk_rectangle_element_vmt); wlmtk_util_connect_listener_signal( &rectangle_ptr->super_element.events.pointer_enter, &rectangle_ptr->element_pointer_enter_listener, &_wlmtk_rectangle_handle_element_pointer_enter); return rectangle_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_rectangle_destroy(wlmtk_rectangle_t *rectangle_ptr) { if (NULL != rectangle_ptr->wlr_scene_rect_ptr) { wlr_scene_node_destroy(&rectangle_ptr->wlr_scene_rect_ptr->node); rectangle_ptr->wlr_scene_rect_ptr = NULL; } wlmtk_util_disconnect_listener( &rectangle_ptr->element_pointer_enter_listener); wlmtk_element_fini(&rectangle_ptr->super_element); free(rectangle_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_rectangle_set_size( wlmtk_rectangle_t *rectangle_ptr, int width, int height) { rectangle_ptr->width = width; rectangle_ptr->height = height; if (NULL != rectangle_ptr->wlr_scene_rect_ptr) { wlr_scene_rect_set_size( rectangle_ptr->wlr_scene_rect_ptr, rectangle_ptr->width, rectangle_ptr->height); } } /* ------------------------------------------------------------------------- */ void wlmtk_rectangle_set_color( wlmtk_rectangle_t *rectangle_ptr, uint32_t color) { rectangle_ptr->color = color; if (NULL != rectangle_ptr->wlr_scene_rect_ptr) { float fcolor[4]; bs_gfxbuf_argb8888_to_floats( color, &fcolor[0], &fcolor[1], &fcolor[2], &fcolor[3]); wlr_scene_rect_set_color(rectangle_ptr->wlr_scene_rect_ptr, fcolor); } } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_rectangle_element(wlmtk_rectangle_t *rectangle_ptr) { return &rectangle_ptr->super_element; } /* ------------------------------------------------------------------------- */ wlmtk_rectangle_t *wlmtk_rectangle_from_element(wlmtk_element_t *element_ptr) { BS_ASSERT(element_ptr->vmt.destroy = _wlmtk_rectangle_element_destroy); wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_rectangle_t, super_element); return rectangle_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Virtual dtor: Invoke the rectangle's dtor. */ void _wlmtk_rectangle_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_rectangle_t, super_element); wlmtk_rectangle_destroy(rectangle_ptr); } /* ------------------------------------------------------------------------- */ /** * Implementation of the superclass wlmtk_element_t::create_scene_node method. * * Creates a `struct wlr_scene_rect` attached to `wlr_scene_tree_ptr`. * * @param element_ptr * @param wlr_scene_tree_ptr */ struct wlr_scene_node *_wlmtk_rectangle_element_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr) { wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_rectangle_t, super_element); BS_ASSERT(NULL == rectangle_ptr->wlr_scene_rect_ptr); float color[4]; bs_gfxbuf_argb8888_to_floats( rectangle_ptr->color, &color[0], &color[1], &color[2], &color[3]); rectangle_ptr->wlr_scene_rect_ptr = wlr_scene_rect_create( wlr_scene_tree_ptr, rectangle_ptr->width, rectangle_ptr->height, color); if (NULL == rectangle_ptr->wlr_scene_rect_ptr) return NULL; wlmtk_util_connect_listener_signal( &rectangle_ptr->wlr_scene_rect_ptr->node.events.destroy, &rectangle_ptr->wlr_scene_rect_node_destroy_listener, _wlmtk_rectangle_handle_wlr_scene_rect_node_destroy); return &rectangle_ptr->wlr_scene_rect_ptr->node; } /* ------------------------------------------------------------------------- */ /** See @ref wlmtk_element_vmt_t::pointer_accepts_motion. true if in area. */ bool _wlmtk_rectangle_element_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *mev_ptr) { wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_rectangle_t, super_element); return (0 <= mev_ptr->x && mev_ptr->x < rectangle_ptr->width && 0 <= mev_ptr->y && mev_ptr->y < rectangle_ptr->height); } /* ------------------------------------------------------------------------- */ /** * Implementation of the element's get_dimensions method: Return dimensions. * * @param element_ptr * @param x1_ptr 0. * @param y1_ptr 0. * @param x2_ptr Width. May be NULL. * @param y2_ptr Height. May be NULL. */ void _wlmtk_rectangle_get_dimensions( wlmtk_element_t *element_ptr, int *x1_ptr, int *y1_ptr, int *x2_ptr, int *y2_ptr) { wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_rectangle_t, super_element); if (NULL != x1_ptr) *x1_ptr = 0; if (NULL != y1_ptr) *y1_ptr = 0; if (NULL != x2_ptr) *x2_ptr = rectangle_ptr->width; if (NULL != y2_ptr) *y2_ptr = rectangle_ptr->height; } /* ------------------------------------------------------------------------- */ /** * Handles the 'destroy' callback of wlr_scene_rect_ptr->node. * * Will reset the wlr_scene_rect_ptr value. Destruction of the node had * been triggered (hence the callback). * * @param listener_ptr * @param data_ptr */ void _wlmtk_rectangle_handle_wlr_scene_rect_node_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_rectangle_t *rectangle_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_rectangle_t, wlr_scene_rect_node_destroy_listener); rectangle_ptr->wlr_scene_rect_ptr = NULL; wlmtk_util_disconnect_listener(&rectangle_ptr->wlr_scene_rect_node_destroy_listener); } /* ------------------------------------------------------------------------- */ /** Handles when we get pointer focus: Set the default cursor. */ void _wlmtk_rectangle_handle_element_pointer_enter( __UNUSED__ struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_pointer_set_cursor(data_ptr, WLMTK_POINTER_CURSOR_DEFAULT); } /* == Unit Tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); static void test_create_destroy_scene(bs_test_t *test_ptr); static void test_pointer_motion(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_rectangle_test_cases[] = { { 1, "create_destroy", test_create_destroy }, { 1, "create_destroy_scene", test_create_destroy_scene }, { 1, "pointer_motion", test_pointer_motion }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_rectangle_test_set = BS_TEST_SET( true, "rectangle", _wlmtk_rectangle_test_cases); /* ------------------------------------------------------------------------- */ /** Tests setup and teardown of rectangle. */ void test_create_destroy(bs_test_t *test_ptr) { wlmtk_rectangle_t *rectangle_ptr = wlmtk_rectangle_create( 10, 20, 0x01020304); BS_TEST_VERIFY_NEQ(test_ptr, NULL, rectangle_ptr); int x1, y1, x2, y2; wlmtk_element_get_dimensions( &rectangle_ptr->super_element, &x1, &y1, &x2, &y2); BS_TEST_VERIFY_EQ(test_ptr, 0, x1); BS_TEST_VERIFY_EQ(test_ptr, 0, y1); BS_TEST_VERIFY_EQ(test_ptr, 10, x2); BS_TEST_VERIFY_EQ(test_ptr, 20, y2); BS_TEST_VERIFY_EQ( test_ptr, &rectangle_ptr->super_element, wlmtk_rectangle_element(rectangle_ptr)); BS_TEST_VERIFY_EQ( test_ptr, rectangle_ptr, wlmtk_rectangle_from_element(&rectangle_ptr->super_element)); wlmtk_rectangle_destroy(rectangle_ptr); } /* ------------------------------------------------------------------------- */ /** Tests setup and teardown of rectangle, when attached to scene graph. */ void test_create_destroy_scene(bs_test_t *test_ptr) { wlmtk_container_t *c_ptr = wlmtk_container_create_fake_parent(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, c_ptr); wlmtk_rectangle_t *rectangle_ptr = wlmtk_rectangle_create( 10, 20, 0x01020304); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, rectangle_ptr); wlmtk_element_t *element_ptr = wlmtk_rectangle_element(rectangle_ptr); wlmtk_container_add_element(c_ptr, element_ptr); int x1, y1, x2, y2; wlmtk_element_get_dimensions(element_ptr, &x1, &y1, &x2, &y2); BS_TEST_VERIFY_EQ(test_ptr, 0, x1); BS_TEST_VERIFY_EQ(test_ptr, 0, y1); BS_TEST_VERIFY_EQ(test_ptr, 10, x2); BS_TEST_VERIFY_EQ(test_ptr, 20, y2); BS_TEST_VERIFY_NEQ(test_ptr, NULL, rectangle_ptr->wlr_scene_rect_ptr); wlmtk_container_remove_element(c_ptr, element_ptr); wlmtk_element_destroy(element_ptr); wlmtk_container_destroy_fake_parent(c_ptr); } /* ------------------------------------------------------------------------- */ /** Tests that pointer_motion returns true if pointer is within bounds. */ void test_pointer_motion(bs_test_t *test_ptr) { wlmtk_rectangle_t *rectangle_ptr = wlmtk_rectangle_create(10, 20, 0x1234); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, rectangle_ptr); wlmtk_element_t *e = wlmtk_rectangle_element(rectangle_ptr); wlmtk_element_set_visible(e, true); wlmtk_pointer_motion_event_t mev = { .x = 5, .y = 10 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); mev = (wlmtk_pointer_motion_event_t){ .x = 10, .y = 20 }; BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); wlmtk_rectangle_destroy(rectangle_ptr); } /* == End of rectangle.c =================================================== */ wlmaker-0.8.1/src/toolkit/menu.c0000644000175100017510000011061215222270710016153 0ustar runnerrunner/* ========================================================================= */ /** * @file menu.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "menu.h" #include #include #include #include #include #include #include #include "base.h" #include "input.h" #include "util.h" /* == Declarations ========================================================= */ /** State of the menu. */ struct _wlmtk_menu_t { /** Composed of a base element. */ wlmtk_base_t base; /** And, of a box, holding menu items. */ wlmtk_box_t box; /** Style of the menu. */ const struct wlmtk_menu_style *style_ptr; /** Reference to the menu style. */ wlmtk_menu_style_ref_t *style_ref_ptr; /** Signals that can be raised by the menu. */ wlmtk_menu_events_t events; /** Virtual method table of @ref wlmtk_menu_t::base, before extending. */ wlmtk_element_vmt_t orig_base_element_vmt; /** List of menu items, via @ref wlmtk_menu_item_t::dlnode. */ bs_dllist_t items; /** The currently-highlighted menu item, or NULL if none. */ wlmtk_menu_item_t *highlighted_menu_item_ptr; /** If this is a submenu, this points to the parent menu item. */ wlmtk_menu_item_t *parent_menu_item_ptr; /** Current mode of the menu. */ enum wlmtk_menu_mode mode; }; /** Type-safe holder for the menu style's reference counter. */ struct _wlmtk_menu_style_ref_t { /** Actual reference counter. */ bs_ref_t reference; }; /** Holds the reference and the style. */ struct wlmtk_menu_style_holder { /** Reference counter */ struct _wlmtk_menu_style_ref_t msr; /** Style */ struct wlmtk_menu_style style; }; static void _wlmtk_menu_eliminate_item( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static bool _wlmtk_menu_set_item_style( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmtk_menu_set_item_mode( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmtk_menu_box_element_destroy(wlmtk_element_t *element_ptr); static void _wlmtk_menu_element_destroy( wlmtk_element_t *element_ptr); static bool _wlmtk_menu_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static bool _wlmtk_menu_element_keyboard_sym( wlmtk_element_t *element_ptr, xkb_keysym_t keysym, enum xkb_key_direction direction, uint32_t modifiers); static bs_dllist_node_t *_wlmtk_menu_this_or_next_non_disabled_dlnode( bs_dllist_node_t *dlnode_ptr, bs_dllist_node_iterator_t iterator); static void _wlmtk_menu_style_destroy(bs_ref_t *ref_ptr); /* == Data ================================================================= */ /** The superclass' element virtual method table. */ static const wlmtk_element_vmt_t _wlmtk_menu_element_vmt = { .keyboard_sym = _wlmtk_menu_element_keyboard_sym, .destroy = _wlmtk_menu_element_destroy, .pointer_button = _wlmtk_menu_element_pointer_button, }; /** Extra override for the box element. Does not have a dtor by default. */ static const wlmtk_element_vmt_t _wlmtk_menu_box_element_vmt = { .destroy = _wlmtk_menu_box_element_destroy }; /** Descriptor for decoding the "Menu" dictionary. */ static const bspl_desc_t _wlmtk_menu_style_desc[] = { BSPL_DESC_DICT( "Item", true, struct wlmtk_menu_style, item, item, wlmtk_menu_item_style_desc), BSPL_DESC_DICT( "Margin", true, struct wlmtk_menu_style, margin, margin, wlmtk_style_margin_desc), BSPL_DESC_DICT( "Border", true, struct wlmtk_menu_style, border, border, wlmtk_style_margin_desc), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_menu_t *wlmtk_menu_create(wlmtk_menu_style_ref_t *style_ref_ptr) { wlmtk_menu_t *menu_ptr = logged_calloc(1, sizeof(wlmtk_menu_t)); if (NULL == menu_ptr) return NULL; menu_ptr->style_ref_ptr = style_ref_ptr; menu_ptr->style_ptr = wlmtk_menu_style_ref_retain(style_ref_ptr); if (!wlmtk_box_init( &menu_ptr->box, WLMTK_BOX_VERTICAL, &menu_ptr->style_ptr->margin)) { wlmtk_menu_destroy(menu_ptr); return NULL; } wlmtk_element_extend( wlmtk_box_element(&menu_ptr->box), &_wlmtk_menu_box_element_vmt); wlmtk_element_set_visible(wlmtk_box_element(&menu_ptr->box), true); if (!wlmtk_base_init( &menu_ptr->base, wlmtk_box_element(&menu_ptr->box))) { wlmtk_menu_destroy(menu_ptr); return NULL; } menu_ptr->orig_base_element_vmt = wlmtk_element_extend( wlmtk_base_element(&menu_ptr->base), &_wlmtk_menu_element_vmt); wlmtk_element_set_visible(wlmtk_base_element(&menu_ptr->base), false); wl_signal_init(&menu_ptr->events.open_changed); wl_signal_init(&menu_ptr->events.request_close); wl_signal_init(&menu_ptr->events.destroy); return menu_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_destroy(wlmtk_menu_t *menu_ptr) { wl_signal_emit(&menu_ptr->events.destroy, NULL); // Must destroy the items before the base. bs_dllist_for_each( &menu_ptr->items, _wlmtk_menu_eliminate_item, menu_ptr); wlmtk_base_fini(&menu_ptr->base); if (NULL != menu_ptr->style_ref_ptr) { wlmtk_menu_style_ref_release(menu_ptr->style_ref_ptr); } free(menu_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_menu_element(wlmtk_menu_t *menu_ptr) { return wlmtk_base_element(&menu_ptr->base); } /* ------------------------------------------------------------------------- */ wlmtk_base_t *wlmtk_menu_base(wlmtk_menu_t *menu_ptr) { return &menu_ptr->base; } /* ------------------------------------------------------------------------- */ wlmtk_menu_events_t *wlmtk_menu_events(wlmtk_menu_t *menu_ptr) { return &menu_ptr->events; } /* ------------------------------------------------------------------------- */ bool wlmtk_menu_set_style( wlmtk_menu_t *menu_ptr, wlmtk_menu_style_ref_t *style_ref_ptr) { wlmtk_menu_style_ref_t *old_ref_ptr = menu_ptr->style_ref_ptr; menu_ptr->style_ref_ptr = style_ref_ptr; menu_ptr->style_ptr = wlmtk_menu_style_ref_retain(style_ref_ptr); wlmtk_box_set_style(&menu_ptr->box, &menu_ptr->style_ptr->margin); bool rv = bs_dllist_all( &menu_ptr->items, _wlmtk_menu_set_item_style, style_ref_ptr); wlmtk_element_layout(wlmtk_menu_element(menu_ptr)); wlmtk_element_invalidate_parent_layout(wlmtk_menu_element(menu_ptr)); wlmtk_menu_style_ref_release(old_ref_ptr); return rv; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_set_open(wlmtk_menu_t *menu_ptr, bool opened) { if (wlmtk_menu_element(menu_ptr)->visible == opened) return; wlmtk_element_set_visible(wlmtk_menu_element(menu_ptr), opened); if (NULL != menu_ptr->highlighted_menu_item_ptr) { wlmtk_menu_item_set_highlighted( menu_ptr->highlighted_menu_item_ptr, false); menu_ptr->highlighted_menu_item_ptr = NULL; } wl_signal_emit(&menu_ptr->events.open_changed, menu_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_menu_is_open(wlmtk_menu_t *menu_ptr) { return wlmtk_menu_element(menu_ptr)->visible; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_set_mode(wlmtk_menu_t *menu_ptr, enum wlmtk_menu_mode mode) { if (menu_ptr->mode == mode) return; menu_ptr->mode = mode; bs_dllist_for_each( &menu_ptr->items, _wlmtk_menu_set_item_mode, menu_ptr); } /* ------------------------------------------------------------------------- */ enum wlmtk_menu_mode wlmtk_menu_get_mode(wlmtk_menu_t *menu_ptr) { return menu_ptr->mode; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_add_item(wlmtk_menu_t *menu_ptr, wlmtk_menu_item_t *menu_item_ptr) { bs_dllist_push_back( &menu_ptr->items, wlmtk_dlnode_from_menu_item(menu_item_ptr)); wlmtk_box_add_element_back( &menu_ptr->box, wlmtk_menu_item_element(menu_item_ptr)); wlmtk_menu_item_set_mode(menu_item_ptr, menu_ptr->mode); wlmtk_menu_item_set_parent_menu(menu_item_ptr, menu_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_menu_remove_item(wlmtk_menu_t *menu_ptr, wlmtk_menu_item_t *menu_item_ptr) { if (menu_ptr->highlighted_menu_item_ptr == menu_item_ptr) { menu_ptr->highlighted_menu_item_ptr = NULL; } wlmtk_menu_item_set_parent_menu(menu_item_ptr, NULL); wlmtk_box_remove_element( &menu_ptr->box, wlmtk_menu_item_element(menu_item_ptr)); bs_dllist_remove( &menu_ptr->items, wlmtk_dlnode_from_menu_item(menu_item_ptr)); } /* ------------------------------------------------------------------------- */ void wlmtk_menu_set_parent_item(wlmtk_menu_t *menu_ptr, wlmtk_menu_item_t *menu_item_ptr) { menu_ptr->parent_menu_item_ptr = menu_item_ptr; } /* ------------------------------------------------------------------------- */ wlmtk_menu_item_t *wlmtk_menu_get_parent_item(wlmtk_menu_t *menu_ptr) { return menu_ptr->parent_menu_item_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_request_item_highlight( wlmtk_menu_t *menu_ptr, wlmtk_menu_item_t *menu_item_ptr) { if (menu_ptr->highlighted_menu_item_ptr == menu_item_ptr) return; if (NULL != menu_ptr->highlighted_menu_item_ptr) { wlmtk_menu_item_set_highlighted( menu_ptr->highlighted_menu_item_ptr, false); menu_ptr->highlighted_menu_item_ptr = NULL; } if (NULL != menu_item_ptr && wlmtk_menu_item_set_highlighted(menu_item_ptr, true)) { menu_ptr->highlighted_menu_item_ptr = menu_item_ptr; } } /* ------------------------------------------------------------------------- */ size_t wlmtk_menu_items_size(wlmtk_menu_t *menu_ptr) { return bs_dllist_size(&menu_ptr->items); } /* ------------------------------------------------------------------------- */ wlmtk_menu_item_t *wlmtk_menu_item_at(wlmtk_menu_t *menu_ptr, size_t i) { if (i >= bs_dllist_size(&menu_ptr->items)) return NULL; bs_dllist_node_t *dlnode_ptr = menu_ptr->items.head_ptr; while (i--) dlnode_ptr = bs_dllist_node_iterator_forward(dlnode_ptr); return wlmtk_menu_item_from_dlnode(dlnode_ptr); } /* ------------------------------------------------------------------------- */ struct wlmtk_menu_style *wlmtk_menu_style_create(void) { struct wlmtk_menu_style_holder *sh = logged_calloc(1, sizeof(*sh)); if (NULL == sh) return NULL; bs_ref_init(&sh->msr.reference, _wlmtk_menu_style_destroy); return &sh->style; } /* ------------------------------------------------------------------------- */ wlmtk_menu_style_ref_t *wlmtk_menu_style_to_ref( struct wlmtk_menu_style *menu_style_ptr) { // Guard clause. if (NULL == menu_style_ptr) return NULL; struct wlmtk_menu_style_holder *sh = BS_CONTAINER_OF( menu_style_ptr, struct wlmtk_menu_style_holder, style); return &sh->msr; } /* ------------------------------------------------------------------------- */ const struct wlmtk_menu_style *wlmtk_menu_style_ref_retain( wlmtk_menu_style_ref_t *ref_ptr) { bs_ref_retain(&ref_ptr->reference); struct wlmtk_menu_style_holder *sh = BS_CONTAINER_OF( ref_ptr, struct wlmtk_menu_style_holder, msr.reference); return &sh->style; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_style_ref_release(wlmtk_menu_style_ref_t *ref_ptr) { bs_ref_release(&ref_ptr->reference); } /* ------------------------------------------------------------------------- */ bool wlmtk_menu_style_decode( bspl_object_t *object_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { struct wlmtk_menu_style **s = value_ptr; return bspl_decode_dict( bspl_dict_from_object(object_ptr), _wlmtk_menu_style_desc, *s); } /* ------------------------------------------------------------------------- */ bool wlmtk_menu_style_decode_init(void *dst_ptr) { struct wlmtk_menu_style **s = dst_ptr; *s = wlmtk_menu_style_create(); return *s != NULL; } /* ------------------------------------------------------------------------- */ void wlmtk_menu_style_decode_fini(void *dst_ptr) { struct wlmtk_menu_style **s = dst_ptr; if (NULL != *s) { wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(*s)); *s = NULL; } } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Callback for bs_dllist_for_each: Removes item from items, destroys it. */ void _wlmtk_menu_eliminate_item(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_from_dlnode(dlnode_ptr); wlmtk_menu_t *menu_ptr = ud_ptr; wlmtk_menu_remove_item(menu_ptr, item_ptr); wlmtk_element_destroy(wlmtk_menu_item_element(item_ptr)); } /* ------------------------------------------------------------------------- */ /** * Callback for bs_dllist_all: Sets the style for each menu item. * * @param dlnode_ptr * @param ud_ptr * * @return true on success. */ bool _wlmtk_menu_set_item_style(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_from_dlnode(dlnode_ptr); wlmtk_menu_style_ref_t *style_ref_ptr = ud_ptr; return wlmtk_menu_item_set_style(item_ptr, style_ref_ptr); } /* ------------------------------------------------------------------------- */ /** * Callback for bs_dllist_for_each: Sets the menu mode for the item. * * @param dlnode_ptr * @param ud_ptr */ void _wlmtk_menu_set_item_mode(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_menu_item_set_mode( wlmtk_menu_item_from_dlnode(dlnode_ptr), ((wlmtk_menu_t*)ud_ptr)->mode); } /* ------------------------------------------------------------------------- */ /** Dtor for @ref wlmtk_menu_t::box. */ void _wlmtk_menu_box_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_box_t *box_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_box_t, super_container.super_element); wlmtk_box_fini(box_ptr); } /* ------------------------------------------------------------------------- */ /** Wraps to dtor. Implements @ref wlmtk_element_vmt_t::destroy. */ void _wlmtk_menu_element_destroy( wlmtk_element_t *element_ptr) { wlmtk_menu_t *menu_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_menu_t, base.super_container.super_element); wlmtk_menu_destroy(menu_ptr); } /* ------------------------------------------------------------------------- */ /** * If the menu is in right-click mode, acts on right-button events and signals * the menu to close. * * Implementation of @ref wlmtk_element_vmt_t::pointer_button. * * @param element_ptr * @param button_event_ptr * * @return whether the button event was claimed. */ bool _wlmtk_menu_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_menu_t *menu_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_menu_t, base.super_container.super_element); bool rv = menu_ptr->orig_base_element_vmt.pointer_button( element_ptr, button_event_ptr); if (WLMTK_MENU_MODE_RIGHTCLICK == menu_ptr->mode && BTN_RIGHT == button_event_ptr->button) { wl_signal_emit(&menu_ptr->events.request_close, NULL); rv = true; } return rv; } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_element_vmt_t::keyboard_sym. Translated keys. */ bool _wlmtk_menu_element_keyboard_sym( wlmtk_element_t *element_ptr, xkb_keysym_t keysym, enum xkb_key_direction direction, uint32_t modifiers) { wlmtk_menu_t *menu_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_menu_t, base.super_container.super_element); bs_dllist_node_t *dlnode_ptr = wlmtk_dlnode_from_menu_item( menu_ptr->highlighted_menu_item_ptr); bs_dllist_node_iterator_t node_iterator; // If there is a submenu with an already highlighted item: Forward key. wlmtk_menu_t *submenu_ptr = NULL; if (NULL != menu_ptr->highlighted_menu_item_ptr) { submenu_ptr = wlmtk_menu_item_get_submenu( menu_ptr->highlighted_menu_item_ptr); if (NULL != submenu_ptr && NULL != submenu_ptr->highlighted_menu_item_ptr) { return _wlmtk_menu_element_keyboard_sym( &submenu_ptr->base.super_container.super_element, keysym, direction, modifiers); } } // Otherwise: Only interested in key press events, not release. if (direction != XKB_KEY_DOWN) return false; switch (keysym) { case XKB_KEY_Escape: if (NULL != wlmtk_menu_get_parent_item(menu_ptr)) { wlmtk_menu_request_item_highlight(menu_ptr, NULL); } else { wl_signal_emit(&menu_ptr->events.request_close, NULL); } return true; case XKB_KEY_Return: if (NULL != menu_ptr->highlighted_menu_item_ptr) { wlmtk_menu_item_trigger(menu_ptr->highlighted_menu_item_ptr); } return true; case XKB_KEY_Left: if (NULL == wlmtk_menu_get_parent_item(menu_ptr)) return false; wlmtk_menu_request_item_highlight(menu_ptr, NULL); return true; case XKB_KEY_Right: if (NULL != submenu_ptr && NULL == submenu_ptr->highlighted_menu_item_ptr) { wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_from_dlnode( _wlmtk_menu_this_or_next_non_disabled_dlnode( submenu_ptr->items.head_ptr, bs_dllist_node_iterator_forward)); if (NULL != item_ptr) { wlmtk_menu_request_item_highlight(submenu_ptr, item_ptr); return true; } } return false; case XKB_KEY_Home: node_iterator = bs_dllist_node_iterator_forward; dlnode_ptr = menu_ptr->items.head_ptr; break; case XKB_KEY_End: node_iterator = bs_dllist_node_iterator_backward; dlnode_ptr = menu_ptr->items.tail_ptr; break; case XKB_KEY_Down: node_iterator = bs_dllist_node_iterator_forward; dlnode_ptr = _wlmtk_menu_this_or_next_non_disabled_dlnode( node_iterator(dlnode_ptr), node_iterator); if (NULL == dlnode_ptr) dlnode_ptr = menu_ptr->items.head_ptr; break; case XKB_KEY_Up: node_iterator = bs_dllist_node_iterator_backward; dlnode_ptr = _wlmtk_menu_this_or_next_non_disabled_dlnode( node_iterator(dlnode_ptr), node_iterator); if (NULL == dlnode_ptr) dlnode_ptr = menu_ptr->items.tail_ptr; break; default: return false; } wlmtk_menu_item_t *item_ptr = wlmtk_menu_item_from_dlnode( _wlmtk_menu_this_or_next_non_disabled_dlnode( dlnode_ptr, node_iterator)); if (NULL != item_ptr) { wlmtk_menu_request_item_highlight(menu_ptr, item_ptr); } return true; } /* ------------------------------------------------------------------------- */ /** * Returns the given-or-next non-disabled menu item, from `dlnode_ptr`. * * @param dlnode_ptr * @param iterator Iterator to reach the "next" item. * * @return A pointer to @ref wlmtk_menu_item_t::dlnode of `dlnode_ptr` (or the * the next non-disabled item), or NULL. */ bs_dllist_node_t *_wlmtk_menu_this_or_next_non_disabled_dlnode( bs_dllist_node_t *dlnode_ptr, bs_dllist_node_iterator_t iterator) { wlmtk_menu_item_t *menu_item_ptr = wlmtk_menu_item_from_dlnode(dlnode_ptr); if (NULL == menu_item_ptr) return NULL; switch (wlmtk_menu_item_get_state(menu_item_ptr)) { case WLMTK_MENU_ITEM_ENABLED: case WLMTK_MENU_ITEM_HIGHLIGHTED: return dlnode_ptr; case WLMTK_MENU_ITEM_DISABLED: break; } return _wlmtk_menu_this_or_next_non_disabled_dlnode( iterator(dlnode_ptr), iterator); } /* ------------------------------------------------------------------------- */ /** Destroys the menu style. */ void _wlmtk_menu_style_destroy(bs_ref_t *ref_ptr) { struct wlmtk_menu_style_holder *sh = BS_CONTAINER_OF( ref_ptr, struct wlmtk_menu_style_holder, msr.reference); free(sh); } /* == Unit tests =========================================================== */ static void test_pointer_highlight(bs_test_t *test_ptr); static void test_set_mode(bs_test_t *test_ptr); static void test_keyboard_navigation(bs_test_t *test_ptr); static void test_keyboard_navigation_nested(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_menu_test_cases[] = { { 1, "pointer_highlight", test_pointer_highlight }, { 1, "set_mode", test_set_mode }, { 1, "keyboard_navigation", test_keyboard_navigation }, { 1, "keyboard_navigation_nested", test_keyboard_navigation_nested }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_menu_test_set = BS_TEST_SET( true, "menu", _wlmtk_menu_test_cases); /** For tests: Meu style to apply. */ static struct wlmtk_menu_style_holder _test_style_holder = { .msr = { .reference = { .count = 1 } }, .style = { .margin = { .width = 2 }, .border = { .width = 2 }, .item = { .height = 10, .bezel_width=1, .width = 100 } } }; /* ------------------------------------------------------------------------- */ /** Tests that pointer moves highlight the items. */ void test_pointer_highlight(bs_test_t *test_ptr) { wlmtk_menu_t *menu_ptr = wlmtk_menu_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr); wlmtk_element_t *me = wlmtk_menu_element(menu_ptr); wlmtk_element_set_visible(me, true); wlmtk_menu_item_t *i1 = wlmtk_menu_item_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i1); wlmtk_menu_add_item(menu_ptr, i1); wlmtk_menu_item_t *i2 = wlmtk_menu_item_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i2); wlmtk_menu_add_item(menu_ptr, i2); // Motion into first element: highlight it. wlmtk_pointer_motion_event_t e = { .x = 9, .y = 5 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &e)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i1)); BS_TEST_VERIFY_EQ(test_ptr, i1, menu_ptr->highlighted_menu_item_ptr); // Motion into second element: highlight that, un-highlight the other. e = (wlmtk_pointer_motion_event_t){ .x = 9, .y = 15 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &e)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(i1)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i2)); BS_TEST_VERIFY_EQ(test_ptr, i2, menu_ptr->highlighted_menu_item_ptr); // Move into the margin area: Both are un-highlighted. e = (wlmtk_pointer_motion_event_t){ .x = 9, .y = 10 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &e)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(i1)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(i2)); BS_TEST_VERIFY_EQ(test_ptr, NULL, menu_ptr->highlighted_menu_item_ptr); // Move entirely outside: Both remain just enabled. e = (wlmtk_pointer_motion_event_t){ .x = 9, .y = 55 }; BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_element_pointer_motion(me, &e)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(i1)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(i2)); BS_TEST_VERIFY_EQ(test_ptr, NULL, menu_ptr->highlighted_menu_item_ptr); // Back into second element: highlight that. e = (wlmtk_pointer_motion_event_t){ .x = 9, .y = 15 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &e)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_ENABLED, wlmtk_menu_item_get_state(i1)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_ITEM_HIGHLIGHTED, wlmtk_menu_item_get_state(i2)); // Remove one item explicitly, the other one to destroy during cleanup. wlmtk_menu_remove_item(menu_ptr, i1); wlmtk_menu_item_destroy(i1); wlmtk_menu_destroy(menu_ptr); } /* ------------------------------------------------------------------------- */ /** Tests setting the menu's mode. */ void test_set_mode(bs_test_t *test_ptr) { wlmtk_menu_t *menu_ptr = wlmtk_menu_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr); wlmtk_util_test_listener_t destroy_test_listener; wlmtk_util_connect_test_listener( &wlmtk_menu_events(menu_ptr)->destroy, &destroy_test_listener); wlmtk_menu_item_t *item1_ptr = wlmtk_menu_item_create( &_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item1_ptr); wlmtk_menu_add_item(menu_ptr, item1_ptr); // Setting the mode must propagate. BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_NORMAL, wlmtk_menu_item_get_mode(item1_ptr)); wlmtk_menu_set_mode(menu_ptr, WLMTK_MENU_MODE_RIGHTCLICK); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_RIGHTCLICK, wlmtk_menu_item_get_mode(item1_ptr)); // A new item must get the mode applied. wlmtk_menu_item_t *item2_ptr = wlmtk_menu_item_create( &_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, item2_ptr); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_NORMAL, wlmtk_menu_item_get_mode(item2_ptr)); wlmtk_menu_add_item(menu_ptr, item2_ptr); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_RIGHTCLICK, wlmtk_menu_item_get_mode(item2_ptr)); // Setting the mode must propagate to all. wlmtk_menu_set_mode(menu_ptr, WLMTK_MENU_MODE_NORMAL); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_NORMAL, wlmtk_menu_item_get_mode(item1_ptr)); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_MENU_MODE_NORMAL, wlmtk_menu_item_get_mode(item2_ptr)); BS_TEST_VERIFY_EQ(test_ptr, 0, destroy_test_listener.calls); wlmtk_menu_destroy(menu_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, destroy_test_listener.calls); } /* ------------------------------------------------------------------------- */ /** Tests keyboard navigation within the menu. */ void test_keyboard_navigation(bs_test_t *test_ptr) { static const wlmtk_menu_item_state_t HL = WLMTK_MENU_ITEM_HIGHLIGHTED; wlmtk_menu_t *menu_ptr = wlmtk_menu_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr); wlmtk_element_t *me = wlmtk_menu_element(menu_ptr); wlmtk_element_set_visible(me, true); wlmtk_util_test_listener_t request_close_test_listener; wlmtk_util_connect_test_listener( &wlmtk_menu_events(menu_ptr)->request_close, &request_close_test_listener); // Test items: disabled, enabled, enabled, disabled, enabled. struct { wlmtk_menu_item_t *item; bool enabled; wlmtk_util_test_listener_t triggered_test_listener; } items[6] = { [1] = { .enabled = true }, [2] = { .enabled = true }, [4] = { .enabled = true }, }; for (int i = 0; i < 5; ++i) { items[i].item = wlmtk_menu_item_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, items[i].item); wlmtk_util_connect_test_listener( &wlmtk_menu_item_events(items[i].item)->triggered, &items[i].triggered_test_listener); wlmtk_menu_add_item(menu_ptr, items[i].item); wlmtk_menu_item_set_enabled(items[i].item, items[i].enabled); } wlmtk_element_layout(wlmtk_menu_element(menu_ptr)); // Move pointer over items[2]. wlmtk_pointer_motion_event_t e = { .x = 9, .y = 25 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &e)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(items[2].item)); // Down key: Moves down, items[3] is disabled => land at items[4]. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Down, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(items[4].item)); // Down key once more: Wrap around, land at items[1]. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Down, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ( test_ptr, HL, wlmtk_menu_item_get_state(items[1].item)); // Up key: Wraps around, land at items[4]. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Up, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(items[4].item)); // Up key: Moves up once more, at items[2]. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Up, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(items[2].item)); // End key: Jump to items[4]. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_End, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(items[4].item)); // A motion, within items[2]. Re-gain focus there. e = (wlmtk_pointer_motion_event_t){ .x = 8, .y = 25 }; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &e)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(me, &e)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(items[2].item)); // Home key: Jump to items[1]. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Home, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(items[1].item)); // Return key: Trigger items[1]. wlmtk_util_clear_test_listener(&items[1].triggered_test_listener); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Return, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, 1, items[1].triggered_test_listener.calls); // Escape key: Request to close the menu. wlmtk_util_clear_test_listener(&request_close_test_listener); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Escape, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, 1, request_close_test_listener.calls); // Keys that were released: No trigger. BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Escape, XKB_KEY_UP, 0)); BS_TEST_VERIFY_EQ(test_ptr, 1, request_close_test_listener.calls); wlmtk_menu_destroy(menu_ptr); } /* ------------------------------------------------------------------------- */ /** Tests keyboard navigation with nested menus. */ void test_keyboard_navigation_nested(bs_test_t *test_ptr) { static const wlmtk_menu_item_state_t HL = WLMTK_MENU_ITEM_HIGHLIGHTED; // Menu m0 with two items (i00, i01). wlmtk_menu_t *m0 = wlmtk_menu_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, m0); wlmtk_menu_item_t *i00 = wlmtk_menu_item_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i00); wlmtk_menu_add_item(m0, i00); wlmtk_menu_item_t *i01 = wlmtk_menu_item_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i01); wlmtk_menu_add_item(m0, i01); wlmtk_util_test_listener_t request_close_test_listener; wlmtk_util_connect_test_listener( &wlmtk_menu_events(m0)->request_close, &request_close_test_listener); // i01 has submenu m1 with two items (i10, i11). wlmtk_menu_t *m1 = wlmtk_menu_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, m1); wlmtk_menu_item_set_submenu(i01, m1); wlmtk_menu_item_t *i10 = wlmtk_menu_item_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i10); wlmtk_menu_add_item(m1, i10); wlmtk_menu_item_t *i11 = wlmtk_menu_item_create(&_test_style_holder.msr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, i11); wlmtk_menu_add_item(m1, i11); wlmtk_element_t *me = wlmtk_menu_element(m0); wlmtk_element_set_visible(me, true); // Home key, highlights i00. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Home, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i00)); // Right key. Does not do anything. BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Right, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i00)); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_menu_is_open(m1)); // Down key. Highlights i01, opens m1, but no highlight there. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Down, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i01)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_menu_is_open(m1)); BS_TEST_VERIFY_NEQ(test_ptr, HL, wlmtk_menu_item_get_state(i10)); // Right key. Now highlights i10. i01 remains highlighted. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Right, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i01)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i10)); // Down key. Now highlights i11. i01 remains highlighted. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Down, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i01)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i11)); // Left key. Moves highlight back, but keeps submenu open. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Left, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i01)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_menu_is_open(m1)); BS_TEST_VERIFY_NEQ(test_ptr, HL, wlmtk_menu_item_get_state(i10)); BS_TEST_VERIFY_NEQ(test_ptr, HL, wlmtk_menu_item_get_state(i11)); // Right key. Highlights the submenu with i10 again. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Right, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i01)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i10)); // Esc key, while submenu has highlight. Same action as left key. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Escape, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, HL, wlmtk_menu_item_get_state(i01)); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_menu_is_open(m1)); BS_TEST_VERIFY_NEQ(test_ptr, HL, wlmtk_menu_item_get_state(i10)); BS_TEST_VERIFY_NEQ(test_ptr, HL, wlmtk_menu_item_get_state(i11)); BS_TEST_VERIFY_EQ(test_ptr, 0, request_close_test_listener.calls); // Esc key, once more. Must close. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_keyboard_sym(me, XKB_KEY_Escape, XKB_KEY_DOWN, 0)); BS_TEST_VERIFY_EQ(test_ptr, 1, request_close_test_listener.calls); wlmtk_menu_destroy(m0); } /* == End of menu.c ======================================================== */ wlmaker-0.8.1/src/toolkit/root.c0000644000175100017510000011340215222270710016172 0ustar runnerrunner/* ========================================================================= */ /** * @file root.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "root.h" #include #include #include #define WLR_USE_UNSTABLE #include #include #include #undef WLR_USE_UNSTABLE #include "container.h" #include "input.h" #include "output_tracker.h" #include "rectangle.h" #include "test.h" // IWYU pragma: keep #include "tile.h" #include "util.h" #include "window.h" #include "workspace.h" struct wlr_keyboard_key_event; /* == Declarations ========================================================= */ /** State of the root element. */ struct _wlmtk_root_t { /** The root's container: Holds workspaces and the curtain. */ wlmtk_container_t container; /** Overwritten virtual method table before extending ig. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Extents to be used by root. */ struct wlr_box extents; /** Events availabe of the root. */ wlmtk_root_events_t events; /** Whether the root is currently locked. */ bool locked; /** * The lock's element. Shown on top of * @ref wlmtk_root_t::curtain_rectangle_ptr. */ wlmtk_element_t *lock_element_ptr; /** Curtain element: Permit dimming or hiding everything. */ wlmtk_rectangle_t *curtain_rectangle_ptr; /** List of workspaces attached to root. @see wlmtk_workspace_t::dlnode. */ bs_dllist_t workspaces; /** Currently-active workspace. */ wlmtk_workspace_t *current_workspace_ptr; /** Listener for wlr_output_layout::events.change. */ struct wl_listener output_layout_change_listener; /** Output layout tracker. */ wlmtk_output_tracker_t *output_tracker_ptr; // Elements below not owned by wlmtk_root_t. /** wlroots output layout. */ struct wlr_output_layout *wlr_output_layout_ptr; /** Last recorded pointer movement. */ wlmtk_pointer_motion_event_t mev; }; /** Listeners for a specific output. */ struct wlmtk_root_output { /** Indicates this output is ready to render a frame. */ struct wl_listener frame_listener; /** Back-link to root. */ wlmtk_root_t *root_ptr; }; static void _wlmtk_root_switch_to_workspace( wlmtk_root_t *root_ptr, wlmtk_workspace_t *workspace_ptr); static void _wlmtk_root_enumerate_workspaces( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmtk_root_destroy_workspace( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static bool _wlmtk_root_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static bool _wlmtk_root_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); static bool _wlmtk_root_element_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr); static void _wlmtk_root_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr); static void *_wlmtk_root_output_tracker_create( struct wlr_output *wlr_output_ptr, void *ud_ptr); static void _wlmtk_root_output_tracker_destroy( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr); static void _wlmtk_root_output_handle_frame( struct wl_listener *listener_ptr, void *data_ptr); static bool _wlmtk_root_window_set_style( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static bool _wlmtk_root_workspace_set_style( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /** Virtual method table for the container's super class: Element. */ static const wlmtk_element_vmt_t _wlmtk_root_element_vmt = { .pointer_button = _wlmtk_root_element_pointer_button, .pointer_axis = _wlmtk_root_element_pointer_axis, .keyboard_event = _wlmtk_root_element_keyboard_event, }; /** Arg for iterators that are setting the style. */ struct _wlmtk_root_set_style_arg { /** Reference to the window's style. */ wlmtk_window_style_ref_t *window_style_ref_ptr; /** Reference to the menu's style. */ wlmtk_menu_style_ref_t *menu_style_ref_ptr; }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_root_t *wlmtk_root_create( struct wlr_scene *wlr_scene_ptr, struct wlr_output_layout *wlr_output_layout_ptr) { wlmtk_root_t *root_ptr = logged_calloc(1, sizeof(wlmtk_root_t)); if (NULL == root_ptr) return NULL; root_ptr->wlr_output_layout_ptr = wlr_output_layout_ptr; root_ptr->output_tracker_ptr = wlmtk_output_tracker_create( wlr_output_layout_ptr, root_ptr, _wlmtk_root_output_tracker_create, NULL, _wlmtk_root_output_tracker_destroy); if (NULL == root_ptr->output_tracker_ptr) { wlmtk_root_destroy(root_ptr); return NULL; } if (NULL != wlr_scene_ptr) { if (!wlmtk_container_init_attached( &root_ptr->container, &wlr_scene_ptr->tree)) { wlmtk_root_destroy(root_ptr); return NULL; } } else { if (!wlmtk_container_init(&root_ptr->container)) { wlmtk_root_destroy(root_ptr); return NULL; } } wlmtk_element_set_visible(&root_ptr->container.super_element, true); root_ptr->orig_super_element_vmt = wlmtk_element_extend( &root_ptr->container.super_element, &_wlmtk_root_element_vmt); root_ptr->curtain_rectangle_ptr = wlmtk_rectangle_create( root_ptr->extents.width, root_ptr->extents.height, 0xff000020); if (NULL == root_ptr->curtain_rectangle_ptr) { wlmtk_root_destroy(root_ptr); return NULL; } wlmtk_container_add_element( &root_ptr->container, wlmtk_rectangle_element(root_ptr->curtain_rectangle_ptr)); wlmtk_util_connect_listener_signal( &wlr_output_layout_ptr->events.change, &root_ptr->output_layout_change_listener, _wlmtk_root_handle_output_layout_change); _wlmtk_root_handle_output_layout_change( &root_ptr->output_layout_change_listener, wlr_output_layout_ptr); wl_signal_init(&root_ptr->events.workspace_changed); wl_signal_init(&root_ptr->events.unlock_event); wl_signal_init(&root_ptr->events.window_mapped); wl_signal_init(&root_ptr->events.window_unmapped); wl_signal_init(&root_ptr->events.unclaimed_button_event); return root_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_root_destroy(wlmtk_root_t *root_ptr) { wlmtk_util_disconnect_listener( &root_ptr->output_layout_change_listener); bs_dllist_for_each( &root_ptr->workspaces, _wlmtk_root_destroy_workspace, root_ptr); if (NULL != root_ptr->curtain_rectangle_ptr) { wlmtk_container_remove_element( &root_ptr->container, wlmtk_rectangle_element(root_ptr->curtain_rectangle_ptr)); wlmtk_rectangle_destroy(root_ptr->curtain_rectangle_ptr); root_ptr->curtain_rectangle_ptr = NULL; } wlmtk_container_fini(&root_ptr->container); if (NULL != root_ptr->output_tracker_ptr) { wlmtk_output_tracker_destroy(root_ptr->output_tracker_ptr); root_ptr->output_tracker_ptr = NULL; } free(root_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_root_events_t *wlmtk_root_events(wlmtk_root_t *root_ptr) { return &root_ptr->events; } /* ------------------------------------------------------------------------- */ bool wlmtk_root_pointer_motion( wlmtk_root_t *root_ptr, double x, double y, uint32_t time_msec, wlmtk_pointer_t *pointer_ptr) { root_ptr->mev = (wlmtk_pointer_motion_event_t){ .x = x, .y = y, .time_msec = time_msec, .pointer_ptr = pointer_ptr }; return wlmtk_element_pointer_motion( &root_ptr->container.super_element, &root_ptr->mev); } /* ------------------------------------------------------------------------- */ // TODO(kaeser@gubbe.ch): Improve this, has multiple bugs: It won't keep // different buttons apart, and there's currently no test associated. bool wlmtk_root_pointer_button( wlmtk_root_t *root_ptr, const struct wlr_pointer_button_event *event_ptr, uint32_t modifiers) { wlmtk_button_event_t event; bool rv; // Guard clause: nothing to pass on if no element has the focus. event.button = event_ptr->button; event.time_msec = event_ptr->time_msec; event.keyboard_modifiers = modifiers; switch (event_ptr->state) { case WL_POINTER_BUTTON_STATE_PRESSED: event.type = WLMTK_BUTTON_DOWN; rv = wlmtk_element_pointer_button( &root_ptr->container.super_element, &event); break; case WL_POINTER_BUTTON_STATE_RELEASED: event.type = WLMTK_BUTTON_UP; wlmtk_element_pointer_button( &root_ptr->container.super_element, &event); event.type = WLMTK_BUTTON_CLICK; rv = wlmtk_element_pointer_button( &root_ptr->container.super_element, &event); // Hack: Fix lost focus when a menu releases the pointer grab. This // should not be needed here, but needs a better means of updating // the pointer focus. // TODO(kaeser@gubbe.ch): Have a test for this with a window + menu, // and ensure focus computation is done well there. wlmtk_element_pointer_motion( &root_ptr->container.super_element, &root_ptr->mev); break; default: bs_log(BS_WARNING, "Root %p: Unhandled state 0x%x for button 0x%x", root_ptr, event_ptr->state, event_ptr->button); return false; } if (!rv) { wl_signal_emit(&root_ptr->events.unclaimed_button_event, &event); } return rv; } /* ------------------------------------------------------------------------- */ bool wlmtk_root_pointer_axis( wlmtk_root_t *root_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { return wlmtk_element_pointer_axis( &root_ptr->container.super_element, wlr_pointer_axis_event_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_root_add_workspace( wlmtk_root_t *root_ptr, wlmtk_workspace_t *workspace_ptr) { BS_ASSERT(NULL == wlmtk_workspace_get_root(workspace_ptr)); wlmtk_container_add_element( &root_ptr->container, wlmtk_workspace_element(workspace_ptr)); // Keep the curtain on top. wlmtk_container_raise_element_to_top( &root_ptr->container, wlmtk_rectangle_element(root_ptr->curtain_rectangle_ptr)); bs_dllist_push_back( &root_ptr->workspaces, wlmtk_dlnode_from_workspace(workspace_ptr)); wlmtk_workspace_set_details( workspace_ptr, bs_dllist_size(&root_ptr->workspaces)); wlmtk_workspace_set_root(workspace_ptr, root_ptr); if (NULL == root_ptr->current_workspace_ptr) { _wlmtk_root_switch_to_workspace(root_ptr, workspace_ptr); } else { wl_signal_emit(&root_ptr->events.workspace_changed, workspace_ptr); } } /* ------------------------------------------------------------------------- */ void wlmtk_root_remove_workspace( wlmtk_root_t *root_ptr, wlmtk_workspace_t *workspace_ptr) { BS_ASSERT(root_ptr == wlmtk_workspace_get_root(workspace_ptr)); wlmtk_workspace_set_root(workspace_ptr, NULL); bs_dllist_remove( &root_ptr->workspaces, wlmtk_dlnode_from_workspace(workspace_ptr)); wlmtk_container_remove_element( &root_ptr->container, wlmtk_workspace_element(workspace_ptr)); wlmtk_element_set_visible( wlmtk_workspace_element(workspace_ptr), false); int index = 1; bs_dllist_for_each( &root_ptr->workspaces, _wlmtk_root_enumerate_workspaces, &index); if (root_ptr->current_workspace_ptr == workspace_ptr) { _wlmtk_root_switch_to_workspace( root_ptr, wlmtk_workspace_from_dlnode(root_ptr->workspaces.head_ptr)); } else { wl_signal_emit(&root_ptr->events.workspace_changed, NULL); } } /* ------------------------------------------------------------------------- */ wlmtk_workspace_t *wlmtk_root_get_current_workspace(wlmtk_root_t *root_ptr) { return root_ptr->current_workspace_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_root_destroy_last_workspace(wlmtk_root_t *root_ptr) { wlmtk_workspace_t *ws_ptr = wlmtk_workspace_from_dlnode( root_ptr->workspaces.tail_ptr); // Guard clause: Must have further workspaces, not be current workspace, // and not have windows on that workspace. if (1 >= bs_dllist_size(&root_ptr->workspaces) || ws_ptr == root_ptr->current_workspace_ptr || !bs_dllist_empty(wlmtk_workspace_get_windows_dllist(ws_ptr))) return; wlmtk_root_remove_workspace(root_ptr, ws_ptr); wlmtk_workspace_destroy(ws_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_root_switch_to_next_workspace(wlmtk_root_t *root_ptr) { if (NULL == root_ptr->current_workspace_ptr) return; bs_dllist_node_t *dlnode_ptr = wlmtk_dlnode_from_workspace( root_ptr->current_workspace_ptr); if (NULL == dlnode_ptr->next_ptr) { dlnode_ptr = root_ptr->workspaces.head_ptr; } else { dlnode_ptr = dlnode_ptr->next_ptr; } wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_from_dlnode(dlnode_ptr); _wlmtk_root_switch_to_workspace(root_ptr, workspace_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_root_switch_to_previous_workspace(wlmtk_root_t *root_ptr) { if (NULL == root_ptr->current_workspace_ptr) return; bs_dllist_node_t *dlnode_ptr = wlmtk_dlnode_from_workspace( root_ptr->current_workspace_ptr); if (NULL == dlnode_ptr->prev_ptr) { dlnode_ptr = root_ptr->workspaces.tail_ptr; } else { dlnode_ptr = dlnode_ptr->prev_ptr; } wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_from_dlnode(dlnode_ptr); _wlmtk_root_switch_to_workspace(root_ptr, workspace_ptr); } /* ------------------------------------------------------------------------- */ void wlmtk_root_for_each_workspace( wlmtk_root_t *root_ptr, void (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr) { bs_dllist_for_each(&root_ptr->workspaces, func, ud_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_root_lock( wlmtk_root_t *root_ptr, wlmtk_element_t *element_ptr) { if (root_ptr->locked) { bs_log(BS_WARNING, "Root already locked by element %p", root_ptr->lock_element_ptr); return false; } root_ptr->lock_element_ptr = element_ptr; wlmtk_workspace_enable(root_ptr->current_workspace_ptr, false); wlmtk_rectangle_set_size( root_ptr->curtain_rectangle_ptr, root_ptr->extents.width, root_ptr->extents.height); wlmtk_element_set_visible( wlmtk_rectangle_element(root_ptr->curtain_rectangle_ptr), true); wlmtk_container_add_element( &root_ptr->container, root_ptr->lock_element_ptr); root_ptr->locked = true; return true; } /* ------------------------------------------------------------------------- */ bool wlmtk_root_unlock( wlmtk_root_t *root_ptr, wlmtk_element_t *element_ptr) { // Guard clause: Not locked => nothing to do. if (!root_ptr->locked) return false; if (element_ptr != root_ptr->lock_element_ptr) { bs_log(BS_ERROR, "Lock held by element %p, attempted to unlock by %p", root_ptr->lock_element_ptr, element_ptr); return false; } wlmtk_root_lock_unreference(root_ptr, element_ptr); root_ptr->locked = false; wlmtk_element_set_visible( wlmtk_rectangle_element(root_ptr->curtain_rectangle_ptr), false); wl_signal_emit(&root_ptr->events.unlock_event, NULL); wlmtk_workspace_enable(root_ptr->current_workspace_ptr, true); return true; } /* ------------------------------------------------------------------------- */ bool wlmtk_root_locked(wlmtk_root_t *root_ptr) { return root_ptr->locked; } /* ------------------------------------------------------------------------- */ void wlmtk_root_lock_unreference( wlmtk_root_t *root_ptr, wlmtk_element_t *element_ptr) { if (element_ptr != root_ptr->lock_element_ptr) return; wlmtk_container_remove_element( &root_ptr->container, root_ptr->lock_element_ptr); root_ptr->lock_element_ptr = NULL; } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_root_element(wlmtk_root_t *root_ptr) { return &root_ptr->container.super_element; } /* ------------------------------------------------------------------------- */ bool wlmtk_root_set_style( wlmtk_root_t *root_ptr, wlmtk_window_style_ref_t *window_style_ref_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr) { struct _wlmtk_root_set_style_arg arg = { .window_style_ref_ptr = window_style_ref_ptr, .menu_style_ref_ptr = menu_style_ref_ptr }; return bs_dllist_all( &root_ptr->workspaces, _wlmtk_root_workspace_set_style, &arg); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Switches to `workspace_ptr` as the current workspace. * * @param root_ptr * @param workspace_ptr */ void _wlmtk_root_switch_to_workspace( wlmtk_root_t *root_ptr, wlmtk_workspace_t *workspace_ptr) { if (root_ptr->current_workspace_ptr == workspace_ptr) return; if (NULL == workspace_ptr) { root_ptr->current_workspace_ptr = NULL; } else { BS_ASSERT(root_ptr = wlmtk_workspace_get_root(workspace_ptr)); wlmtk_element_set_visible( wlmtk_workspace_element(workspace_ptr), true); if (NULL != root_ptr->current_workspace_ptr) { wlmtk_element_set_visible( wlmtk_workspace_element(root_ptr->current_workspace_ptr), false); wlmtk_workspace_enable(root_ptr->current_workspace_ptr, false); } root_ptr->current_workspace_ptr = workspace_ptr; wlmtk_workspace_enable(root_ptr->current_workspace_ptr, true); } wl_signal_emit( &root_ptr->events.workspace_changed, root_ptr->current_workspace_ptr); } /* ------------------------------------------------------------------------- */ /** Callback for bs_dllist_for_each: Destroys the workspace. */ void _wlmtk_root_destroy_workspace(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_from_dlnode(dlnode_ptr); wlmtk_root_remove_workspace(ud_ptr, workspace_ptr); wlmtk_workspace_destroy(workspace_ptr); } /* ------------------------------------------------------------------------- */ /** Callback for bs_dllist_for_each: Enumerates the workspace. */ void _wlmtk_root_enumerate_workspaces( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { int *index_ptr = ud_ptr; wlmtk_workspace_set_details( wlmtk_workspace_from_dlnode(dlnode_ptr), *index_ptr); *index_ptr += 1; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::pointer_button. Handle button events. * * When locked, the root container will forward the events strictly only to * the lock container. * * @param element_ptr * @param button_event_ptr * * @return true if the button was handled. */ bool _wlmtk_root_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_root_t *root_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_root_t, container.super_element); if (!root_ptr->locked) { // TODO(kaeser@gubbe.ch): We'll want to pass this on to the non-curtain // elements only. return root_ptr->orig_super_element_vmt.pointer_button( element_ptr, button_event_ptr); } else if (NULL != root_ptr->lock_element_ptr) { return wlmtk_element_pointer_button( root_ptr->lock_element_ptr, button_event_ptr); } // Fall-through. return false; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::pointer_axis. Handle axis events. * * When locked, the root container will forward the events strictly only to * the lock container. * * @param element_ptr * @param wlr_pointer_axis_event_ptr * * @return true if the axis event was handled. */ bool _wlmtk_root_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { wlmtk_root_t *root_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_root_t, container.super_element); if (!root_ptr->locked) { // TODO(kaeser@gubbe.ch): We'll want to pass this on to the non-curtain // elements only. return root_ptr->orig_super_element_vmt.pointer_axis( element_ptr, wlr_pointer_axis_event_ptr); } else if (NULL != root_ptr->lock_element_ptr) { return wlmtk_element_pointer_axis( root_ptr->lock_element_ptr, wlr_pointer_axis_event_ptr); } // Fall-through. return false; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::keyboard_event. Handle keyboard events. * * When locked, the root container will forward the events strictly only to * the lock container. * * @param element_ptr * @param wlr_keyboard_key_event_ptr * * @return true if the axis event was handled. */ bool _wlmtk_root_element_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr) { wlmtk_root_t *root_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_root_t, container.super_element); if (!root_ptr->locked) { // TODO(kaeser@gubbe.ch): We'll want to pass this on to the non-curtain // elements only. return root_ptr->orig_super_element_vmt.keyboard_event( element_ptr, wlr_keyboard_key_event_ptr); } else if (NULL != root_ptr->lock_element_ptr) { return wlmtk_element_keyboard_event( root_ptr->lock_element_ptr, wlr_keyboard_key_event_ptr); } // Fall-through: Too bad -- the screen is locked, but the lock element // disappeared (crashed?). No more handling of keys here... return false; } /* ------------------------------------------------------------------------- */ /** * Handles wlr_output_layout::events::change. Triggers when the output layout * changes, and we use this for updating the curtain and the layout in each * workspace. * * @param listener_ptr * @param data_ptr */ void _wlmtk_root_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_root_t *root_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_root_t, output_layout_change_listener); struct wlr_output_layout *wlr_output_layout_ptr = data_ptr; wlr_output_layout_get_box(wlr_output_layout_ptr, NULL, &root_ptr->extents); wlmtk_rectangle_set_size( root_ptr->curtain_rectangle_ptr, root_ptr->extents.width, root_ptr->extents.height); wlmtk_element_set_position( wlmtk_rectangle_element(root_ptr->curtain_rectangle_ptr), root_ptr->extents.x, root_ptr->extents.y); } /* ------------------------------------------------------------------------- */ /** Ctor for struct wlmtk_root_output. */ void *_wlmtk_root_output_tracker_create( struct wlr_output *wlr_output_ptr, void *ud_ptr) { struct wlmtk_root_output *root_output_ptr = logged_calloc( 1, sizeof(struct wlmtk_root_output)); if (NULL == root_output_ptr) return NULL; root_output_ptr->root_ptr = ud_ptr; wlmtk_util_connect_listener_signal( &wlr_output_ptr->events.frame, &root_output_ptr->frame_listener, _wlmtk_root_output_handle_frame); return root_output_ptr; } /* ------------------------------------------------------------------------- */ /** Dtor for struct wlmtk_root_output. */ void _wlmtk_root_output_tracker_destroy( __UNUSED__ struct wlr_output *wlr_output_ptr, __UNUSED__ void *ud_ptr, void *output_ptr) { struct wlmtk_root_output *root_output_ptr = output_ptr; wlmtk_util_disconnect_listener(&root_output_ptr->frame_listener); free(root_output_ptr); } /* ------------------------------------------------------------------------- */ /** Handles frame: Updates layout. */ void _wlmtk_root_output_handle_frame( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmtk_root_output *root_output_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmtk_root_output, frame_listener); wlmtk_element_layout(wlmtk_root_element(root_output_ptr->root_ptr)); // Once redrawn, recompute pointer focus. wlmtk_element_pointer_motion( &root_output_ptr->root_ptr->container.super_element, &root_output_ptr->root_ptr->mev); } /* ------------------------------------------------------------------------- */ /** Calls @ref wlmtk_window_set_style for the window at `dlnode_ptr`. */ bool _wlmtk_root_window_set_style( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_window_t *window_ptr = wlmtk_window_from_dlnode(dlnode_ptr); struct _wlmtk_root_set_style_arg *arg_ptr = ud_ptr; return wlmtk_window_set_style( window_ptr, arg_ptr->window_style_ref_ptr, arg_ptr->menu_style_ref_ptr); } /* ------------------------------------------------------------------------- */ /** Sets the style for each window of the workspace. */ bool _wlmtk_root_workspace_set_style( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_from_dlnode(dlnode_ptr); struct _wlmtk_root_set_style_arg *arg_ptr = ud_ptr; return bs_dllist_all( wlmtk_workspace_get_windows_dllist(workspace_ptr), _wlmtk_root_window_set_style, arg_ptr); } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); static void test_workspaces(bs_test_t *test_ptr); static void test_pointer_button(bs_test_t *test_ptr); static void test_pointer_move(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_root_test_cases[] = { { 1, "create_destroy", test_create_destroy }, { 1, "workspaces", test_workspaces }, { 1, "pointer_button", test_pointer_button }, { 1, "pointer_move", test_pointer_move }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_root_test_set = BS_TEST_SET( true, "root", _wlmtk_root_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises ctor and dtor. */ void test_create_destroy(bs_test_t *test_ptr) { struct wlr_scene *wlr_scene_ptr = wlr_scene_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_scene_ptr); struct wl_display *wl_display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create( wl_display_ptr); wlmtk_root_t *root_ptr = wlmtk_root_create( wlr_scene_ptr, wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_ptr); BS_TEST_VERIFY_EQ( test_ptr, &root_ptr->events, wlmtk_root_events(root_ptr)); wlmtk_root_destroy(root_ptr); wl_display_destroy(wl_display_ptr); wlr_scene_node_destroy(&wlr_scene_ptr->tree.node); } /* ------------------------------------------------------------------------- */ /** Exercises workspace adding and removal. */ void test_workspaces(bs_test_t *test_ptr) { wlmtk_util_test_listener_t l; struct wlr_scene *wlr_scene_ptr = wlr_scene_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_scene_ptr); struct wl_display *wl_display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(wl_display_ptr); wlmtk_root_t *root_ptr = wlmtk_root_create( wlr_scene_ptr, wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_ptr); BS_TEST_VERIFY_EQ( test_ptr, NULL, wlmtk_root_get_current_workspace(root_ptr)); wlmtk_util_connect_test_listener( &wlmtk_root_events(root_ptr)->workspace_changed, &l); // Empty? A no-op. wlmtk_root_destroy_last_workspace(root_ptr); static const struct wlmtk_tile_style tstyle = {}; wlmtk_workspace_t *ws1_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "1", &tstyle); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws1_ptr); wlmtk_root_add_workspace(root_ptr, ws1_ptr); BS_TEST_VERIFY_EQ( test_ptr, ws1_ptr, wlmtk_root_get_current_workspace(root_ptr)); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_workspace_element(ws1_ptr)->visible); BS_TEST_VERIFY_EQ(test_ptr, ws1_ptr, l.last_data_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); wlmtk_util_clear_test_listener(&l); // Will not destroy the last workspace. wlmtk_root_destroy_last_workspace(root_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&root_ptr->workspaces)); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); wlmtk_workspace_t *ws2_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "2", &tstyle); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws2_ptr); wlmtk_root_add_workspace(root_ptr, ws2_ptr); BS_TEST_VERIFY_EQ( test_ptr, ws1_ptr, wlmtk_root_get_current_workspace(root_ptr)); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_workspace_element(ws2_ptr)->visible); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); BS_TEST_VERIFY_EQ(test_ptr, ws2_ptr, l.last_data_ptr); wlmtk_util_clear_test_listener(&l); wlmtk_root_remove_workspace(root_ptr, ws1_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); wlmtk_util_clear_test_listener(&l); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_workspace_element(ws1_ptr)->visible); BS_TEST_VERIFY_EQ( test_ptr, ws2_ptr, wlmtk_root_get_current_workspace(root_ptr)); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_workspace_element(ws2_ptr)->visible); // Again: not destroying the workspace. wlmtk_root_destroy_last_workspace(root_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&root_ptr->workspaces)); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); // Now add ws1 again. Deleting last workspace wlmtk_root_add_workspace(root_ptr, ws1_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); wlmtk_util_clear_test_listener(&l); BS_TEST_VERIFY_EQ(test_ptr, 2, bs_dllist_size(&root_ptr->workspaces)); wlmtk_root_destroy_last_workspace(root_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&root_ptr->workspaces)); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); wlmtk_util_clear_test_listener(&l); wlmtk_root_remove_workspace(root_ptr, ws2_ptr); BS_TEST_VERIFY_EQ(test_ptr, 1, l.calls); wlmtk_util_clear_test_listener(&l); wlmtk_workspace_destroy(ws2_ptr); BS_TEST_VERIFY_EQ( test_ptr, NULL, wlmtk_root_get_current_workspace(root_ptr)); BS_TEST_VERIFY_EQ(test_ptr, NULL, l.last_data_ptr); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); wlmtk_util_disconnect_test_listener(&l); wlmtk_root_destroy(root_ptr); wlr_output_layout_destroy(wlr_output_layout_ptr); wl_display_destroy(wl_display_ptr); wlr_scene_node_destroy(&wlr_scene_ptr->tree.node); } /* ------------------------------------------------------------------------- */ /** Tests wlmtk_root_pointer_button. */ void test_pointer_button(bs_test_t *test_ptr) { wlmtk_fake_element_t *fake_element_ptr = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fake_element_ptr); wlmtk_element_set_visible(&fake_element_ptr->element, true); struct wlr_scene *wlr_scene_ptr = wlr_scene_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_scene_ptr); struct wl_display *wl_display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create( wl_display_ptr); wlmtk_root_t *root_ptr = wlmtk_root_create( wlr_scene_ptr, wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_ptr); wlmtk_container_add_element( &root_ptr->container, &fake_element_ptr->element); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_root_pointer_motion(root_ptr, 0, 0, 0, NULL)); BS_TEST_VERIFY_TRUE( test_ptr, fake_element_ptr->pointer_accepts_motion_called); // Verify that a button down event is passed. struct wlr_pointer_button_event wlr_pointer_button_event = { .button = 42, .state = WL_POINTER_BUTTON_STATE_PRESSED, .time_msec = 4321, }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_root_pointer_button(root_ptr, &wlr_pointer_button_event, 0)); wlmtk_button_event_t expected_event = { .button = 42, .type = WLMTK_BUTTON_DOWN, .time_msec = 4321, }; BS_TEST_VERIFY_MEMEQ( test_ptr, &expected_event, &fake_element_ptr->pointer_button_event, sizeof(wlmtk_button_event_t)); // The button up event should trigger a click. wlr_pointer_button_event.state = WL_POINTER_BUTTON_STATE_RELEASED; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_root_pointer_button(root_ptr, &wlr_pointer_button_event, 0)); expected_event.type = WLMTK_BUTTON_CLICK; BS_TEST_VERIFY_MEMEQ( test_ptr, &expected_event, &fake_element_ptr->pointer_button_event, sizeof(wlmtk_button_event_t)); wlmtk_container_remove_element( &root_ptr->container, &fake_element_ptr->element); wlmtk_element_destroy(&fake_element_ptr->element); wlmtk_root_destroy(root_ptr); wl_display_destroy(wl_display_ptr); wlr_scene_node_destroy(&wlr_scene_ptr->tree.node); } /* ------------------------------------------------------------------------- */ /** Tests pointer moves. Finicky, because ws switch renders them invisible. */ void test_pointer_move(bs_test_t *test_ptr) { struct wlr_scene *wlr_scene_ptr = wlr_scene_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_scene_ptr); struct wl_display *wl_display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create( wl_display_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add_auto(wlr_output_layout_ptr, &output); wlmtk_root_t *root_ptr = wlmtk_root_create( wlr_scene_ptr, wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, root_ptr); static const struct wlmtk_tile_style tstyle = {}; wlmtk_workspace_t *ws1_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "1", &tstyle); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws1_ptr); wlmtk_root_add_workspace(root_ptr, ws1_ptr); wlmtk_workspace_t *ws2_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "2", &tstyle); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws2_ptr); wlmtk_root_add_workspace(root_ptr, ws2_ptr); wlmtk_fake_element_t *fe1 = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe1); wlmtk_fake_element_set_dimensions(fe1, 40, 20); wlmtk_window_t *w1 = wlmtk_test_window_create(&fe1->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w1); wlmtk_workspace_map_window(ws1_ptr, w1); wlmtk_fake_element_t *fe2 = wlmtk_fake_element_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fe2); wlmtk_fake_element_set_dimensions(fe2, 40, 20); wlmtk_window_t *w2 = wlmtk_test_window_create(&fe2->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w2); wlmtk_workspace_map_window(ws2_ptr, w2); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_root_pointer_motion(root_ptr, 5, 10, 42, NULL)); wlmtk_root_switch_to_next_workspace(root_ptr); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_root_pointer_motion(root_ptr, 6, 11, 42, NULL)); wlmtk_root_remove_workspace(root_ptr, ws2_ptr); wlmtk_root_remove_workspace(root_ptr, ws1_ptr); wlmtk_workspace_unmap_window(ws2_ptr, w2); wlmtk_window_destroy(w2); wlmtk_element_destroy(&fe2->element); wlmtk_workspace_unmap_window(ws1_ptr, w1); wlmtk_window_destroy(w1); wlmtk_element_destroy(&fe1->element); wlmtk_workspace_destroy(ws2_ptr); wlmtk_workspace_destroy(ws1_ptr); wlmtk_root_destroy(root_ptr); wl_display_destroy(wl_display_ptr); wlr_scene_node_destroy(&wlr_scene_ptr->tree.node); } /* == End of root.c ======================================================== */ wlmaker-0.8.1/src/toolkit/resizebar_area.c0000644000175100017510000003135515222270710020173 0ustar runnerrunner/* ========================================================================= */ /** * @file resizebar_area.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "resizebar_area.h" #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "buffer.h" #include "gfxbuf.h" // IWYU pragma: keep #include "input.h" #include "primitives.h" #include "resizebar.h" #include "test.h" // IWYU pragma: keep #include "tile.h" #include "util.h" #include "workspace.h" /* == Declarations ========================================================= */ /** State of an element of the resize bar. */ struct _wlmtk_resizebar_area_t { /** Superclass: Buffer. */ wlmtk_buffer_t super_buffer; /** Original virtual method table of the superclass element. */ wlmtk_element_vmt_t orig_super_element_vmt; /** WLR buffer holding the buffer in released state. */ struct wlr_buffer *released_wlr_buffer_ptr; /** WLR buffer holding the buffer in pressed state. */ struct wlr_buffer *pressed_wlr_buffer_ptr; /** Whether the area is currently pressed or not. */ bool pressed; /** Window to which the resize bar area belongs. To initiate resizing. */ wlmtk_window_t *window_ptr; /** Edges that the resizebar area controls. */ uint32_t edges; }; static void _wlmtk_resizebar_area_element_destroy( wlmtk_element_t *element_ptr); static bool _wlmtk_resizebar_area_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static void draw_state(wlmtk_resizebar_area_t *resizebar_area_ptr); static struct wlr_buffer *create_buffer( bs_gfxbuf_t *gfxbuf_ptr, unsigned position, unsigned width, const struct wlmtk_resizebar_style *style_ptr, bool pressed); /* ========================================================================= */ /** Buffer implementation for title of the title bar. */ static const wlmtk_element_vmt_t resizebar_area_element_vmt = { .destroy = _wlmtk_resizebar_area_element_destroy, .pointer_button = _wlmtk_resizebar_area_element_pointer_button, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_resizebar_area_t *wlmtk_resizebar_area_create( wlmtk_window_t *window_ptr, uint32_t edges) { wlmtk_resizebar_area_t *resizebar_area_ptr = logged_calloc( 1, sizeof(wlmtk_resizebar_area_t)); if (NULL == resizebar_area_ptr) return NULL; BS_ASSERT(NULL != window_ptr); resizebar_area_ptr->window_ptr = window_ptr; resizebar_area_ptr->edges = edges; wlmtk_pointer_cursor_t cursor = WLMTK_POINTER_CURSOR_DEFAULT; switch (resizebar_area_ptr->edges) { case WLR_EDGE_BOTTOM: cursor = WLMTK_POINTER_CURSOR_RESIZE_S; break; case WLR_EDGE_BOTTOM | WLR_EDGE_LEFT: cursor = WLMTK_POINTER_CURSOR_RESIZE_SW; break; case WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT: cursor = WLMTK_POINTER_CURSOR_RESIZE_SE; break; default: bs_log(BS_ERROR, "Unsupported edge %"PRIx32, edges); } if (!wlmtk_buffer_init(&resizebar_area_ptr->super_buffer)) { wlmtk_resizebar_area_destroy(resizebar_area_ptr); return NULL; } resizebar_area_ptr->orig_super_element_vmt = wlmtk_element_extend( &resizebar_area_ptr->super_buffer.super_element, &resizebar_area_element_vmt); resizebar_area_ptr->super_buffer.pointer_cursor = cursor; draw_state(resizebar_area_ptr); return resizebar_area_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_resizebar_area_destroy( wlmtk_resizebar_area_t *resizebar_area_ptr) { wlr_buffer_drop_nullify( &resizebar_area_ptr->released_wlr_buffer_ptr); wlr_buffer_drop_nullify( &resizebar_area_ptr->pressed_wlr_buffer_ptr); wlmtk_buffer_fini(&resizebar_area_ptr->super_buffer); free(resizebar_area_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_resizebar_area_redraw( wlmtk_resizebar_area_t *resizebar_area_ptr, bs_gfxbuf_t *gfxbuf_ptr, unsigned position, unsigned width, const struct wlmtk_resizebar_style *style_ptr) { struct wlr_buffer *released_wlr_buffer_ptr = create_buffer( gfxbuf_ptr, position, width, style_ptr, false); struct wlr_buffer *pressed_wlr_buffer_ptr = create_buffer( gfxbuf_ptr, position, width, style_ptr, true); if (NULL == released_wlr_buffer_ptr || NULL == pressed_wlr_buffer_ptr) { wlr_buffer_drop_nullify(&released_wlr_buffer_ptr); wlr_buffer_drop_nullify(&pressed_wlr_buffer_ptr); return false; } wlr_buffer_drop_nullify( &resizebar_area_ptr->released_wlr_buffer_ptr); resizebar_area_ptr->released_wlr_buffer_ptr = released_wlr_buffer_ptr; wlr_buffer_drop_nullify( &resizebar_area_ptr->pressed_wlr_buffer_ptr); resizebar_area_ptr->pressed_wlr_buffer_ptr = pressed_wlr_buffer_ptr; draw_state(resizebar_area_ptr); return true; } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_resizebar_area_element( wlmtk_resizebar_area_t *resizebar_area_ptr) { return &resizebar_area_ptr->super_buffer.super_element; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Dtor. */ void _wlmtk_resizebar_area_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_resizebar_area_t *resizebar_area_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_resizebar_area_t, super_buffer.super_element); wlmtk_resizebar_area_destroy(resizebar_area_ptr); } /* ------------------------------------------------------------------------- */ /** See @ref wlmtk_element_vmt_t::pointer_button. */ bool _wlmtk_resizebar_area_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_resizebar_area_t *resizebar_area_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_resizebar_area_t, super_buffer.super_element); if (button_event_ptr->button != BTN_LEFT) return true; switch (button_event_ptr->type) { case WLMTK_BUTTON_DOWN: resizebar_area_ptr->pressed = true; wlmtk_workspace_begin_window_resize( wlmtk_window_get_workspace(resizebar_area_ptr->window_ptr), resizebar_area_ptr->window_ptr, resizebar_area_ptr->edges); draw_state(resizebar_area_ptr); break; case WLMTK_BUTTON_UP: resizebar_area_ptr->pressed = false; draw_state(resizebar_area_ptr); break; default: break; } return true; } /* ------------------------------------------------------------------------- */ /** * Draws the buffer in current state (released or pressed). * * @param resizebar_area_ptr */ void draw_state(wlmtk_resizebar_area_t *resizebar_area_ptr) { if (!resizebar_area_ptr->pressed) { wlmtk_buffer_set( &resizebar_area_ptr->super_buffer, resizebar_area_ptr->released_wlr_buffer_ptr); } else { wlmtk_buffer_set( &resizebar_area_ptr->super_buffer, resizebar_area_ptr->pressed_wlr_buffer_ptr); } } /* ------------------------------------------------------------------------- */ /** * Creates a resizebar area texture. * * @param gfxbuf_ptr * @param position * @param width * @param style_ptr * @param pressed * * @return A pointer to a newly allocated `struct wlr_buffer`. */ struct wlr_buffer *create_buffer( bs_gfxbuf_t *gfxbuf_ptr, unsigned position, unsigned width, const struct wlmtk_resizebar_style *style_ptr, bool pressed) { struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( width, style_ptr->height); if (NULL == wlr_buffer_ptr) return NULL; bs_gfxbuf_copy_area( bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), 0, 0, gfxbuf_ptr, position, 0, width, style_ptr->height); cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); return false; } wlmaker_primitives_draw_bezel_at( cairo_ptr, 0, 0, width, style_ptr->height, style_ptr->bezel_width, !pressed); cairo_destroy(cairo_ptr); return wlr_buffer_ptr; } /* == Unit tests =========================================================== */ static void test_area(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_resizebar_area_test_cases[] = { { 1, "area", test_area }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_resizebar_area_test_set = BS_TEST_SET( true, "resizebar_area", _wlmtk_resizebar_area_test_cases); /* ------------------------------------------------------------------------- */ /** Tests the area behaviour: Must initiate resize, set the edges. */ void test_area(bs_test_t *test_ptr) { struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, true); wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_util_test_wlr_box_listener_t l = {}; wlmtk_util_connect_test_wlr_box_listener( &wlmtk_window_events(w)->request_size, &l); wlmtk_workspace_map_window(ws_ptr, w); wlmtk_resizebar_area_t *area_ptr = wlmtk_resizebar_area_create( w, WLR_EDGE_BOTTOM); BS_TEST_VERIFY_NEQ(test_ptr, NULL, area_ptr); wlmtk_element_t *element_ptr = wlmtk_resizebar_area_element(area_ptr); wlmtk_element_set_visible(element_ptr, true); // Draw and verify release state. struct wlmtk_resizebar_style style = { .height = 7, .bezel_width = 1.0 }; bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(30, 7); bs_gfxbuf_clear(gfxbuf_ptr, 0xff604020); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_resizebar_area_redraw(area_ptr, gfxbuf_ptr, 10, 12, &style)); bs_gfxbuf_destroy(gfxbuf_ptr); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(area_ptr->super_buffer.wlr_buffer_ptr), "toolkit/resizebar_area_released.png"); BS_TEST_VERIFY_EQ(test_ptr, 0, l.calls); // Pointer must be inside the button for accepting DOWN. wlmtk_pointer_motion_event_t mev = { .x = 1, .y = 1 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_motion(element_ptr, &mev)); // Button down: pressed. wlmtk_button_event_t button = { .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &button)); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(area_ptr->super_buffer.wlr_buffer_ptr), "toolkit/resizebar_area_pressed.png"); BS_TEST_VERIFY_EQ( test_ptr, WLR_EDGE_BOTTOM, wlmtk_window_get_resize_edges(w)); wlmtk_element_destroy(element_ptr); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_window_destroy(w); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* == End of resizebar_area.c ============================================== */ wlmaker-0.8.1/src/toolkit/surface.c0000644000175100017510000007564315222270710016655 0ustar runnerrunner/* ========================================================================= */ /** * @file surface.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "surface.h" #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "element.h" #include "container.h" #include "gfxbuf.h" // IWYU pragma: keep #include "input.h" #include "util.h" #include "test.h" // IWYU pragma: keep /* == Declarations ========================================================= */ /** State of a `struct wlr_surface`, encapsuled for toolkit. */ struct _wlmtk_surface_t { /** Super class of the surface: An element. */ wlmtk_element_t super_element; /** Virtual method table of the super element before extending it. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Seat. */ struct wlr_seat *wlr_seat_ptr; /** The `struct wlr_surface` wrapped. */ struct wlr_surface *wlr_surface_ptr; /** The `struct wlr_xdg_surface` wrapped. */ struct wlr_xdg_surface *wlr_xdg_surface_ptr; /** The scene API node displaying a surface and all it's sub-surfaces. */ struct wlr_scene_tree *wlr_scene_tree_ptr; /** Listener for the `destroy` signal of `wlr_scene_tree_ptr->node`. */ struct wl_listener wlr_scene_tree_node_destroy_listener; /** Committed width of the surface, in pixels. */ int committed_width; /** Committed height of the surface, in pixels. */ int committed_height; /** Listener for the `events.commit` signal of `wlr_surface`. */ struct wl_listener surface_commit_listener; /** Listener for the `map` signal of `wlr_surface`. */ struct wl_listener surface_map_listener; /** Listener for the `map` signal of `wlr_surface`. */ struct wl_listener surface_unmap_listener; /** Listener for @ref wlmtk_element_events_t::pointer_leave. */ struct wl_listener element_pointer_leave_listener; /** Listener for @ref wlmtk_element_events_t::pointer_motion. */ struct wl_listener element_pointer_motion_listener; /** Whether this surface is activated, ie. has keyboard focus. */ bool activated; }; static bool _wlmtk_surface_init( wlmtk_surface_t *surface_ptr, struct wlr_surface *wlr_surface_ptr); static bool _wlmtk_xdg_surface_init( wlmtk_surface_t *surface_ptr, struct wlr_xdg_surface *wlr_xdg_surface_ptr); static void _wlmtk_surface_fini(wlmtk_surface_t *surface_ptr); static void _wlmtk_surface_element_destroy(wlmtk_element_t *element_ptr); static struct wlr_scene_node *_wlmtk_surface_element_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr); static void _wlmtk_surface_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr); static bool _wlmtk_surface_element_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr); static bool _wlmtk_surface_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static bool _wlmtk_surface_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); static bool _wlmtk_surface_element_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr); static void _wlmtk_surface_handle_wlr_scene_tree_node_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_surface_handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_surface_handle_surface_map( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_surface_handle_surface_unmap( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_surface_handle_element_pointer_leave( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_surface_handle_element_pointer_motion( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_surface_commit_size( wlmtk_surface_t *surface_ptr, int width, int height); /* == Data ================================================================= */ /** Method table for the element's virtual methods. */ static const wlmtk_element_vmt_t surface_element_vmt = { .destroy = _wlmtk_surface_element_destroy, .create_scene_node = _wlmtk_surface_element_create_scene_node, .get_dimensions = _wlmtk_surface_element_get_dimensions, .pointer_accepts_motion = _wlmtk_surface_element_pointer_accepts_motion, .pointer_button = _wlmtk_surface_element_pointer_button, .pointer_axis = _wlmtk_surface_element_pointer_axis, .keyboard_event = _wlmtk_surface_element_keyboard_event, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_surface_t *wlmtk_surface_create( struct wlr_surface *wlr_surface_ptr, struct wlr_seat *wlr_seat_ptr) { wlmtk_surface_t *surface_ptr = logged_calloc(1, sizeof(wlmtk_surface_t)); if (NULL == surface_ptr) return NULL; if (!_wlmtk_surface_init(surface_ptr, wlr_surface_ptr)) { wlmtk_surface_destroy(surface_ptr); return NULL; } surface_ptr->wlr_seat_ptr = wlr_seat_ptr; return surface_ptr; } /* ------------------------------------------------------------------------- */ wlmtk_surface_t *wlmtk_xdg_surface_create( struct wlr_xdg_surface *wlr_xdg_surface_ptr, struct wlr_seat *wlr_seat_ptr) { wlmtk_surface_t *surface_ptr = logged_calloc(1, sizeof(wlmtk_surface_t)); if (NULL == surface_ptr) return NULL; if (!_wlmtk_xdg_surface_init(surface_ptr, wlr_xdg_surface_ptr)) { wlmtk_surface_destroy(surface_ptr); return NULL; } surface_ptr->wlr_seat_ptr = wlr_seat_ptr; return surface_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_surface_destroy(wlmtk_surface_t *surface_ptr) { _wlmtk_surface_fini(surface_ptr); free(surface_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_surface_element(wlmtk_surface_t *surface_ptr) { return &surface_ptr->super_element; } /* ------------------------------------------------------------------------- */ void wlmtk_surface_get_size( wlmtk_surface_t *surface_ptr, int *width_ptr, int *height_ptr) { if (NULL != width_ptr) *width_ptr = surface_ptr->committed_width; if (NULL != height_ptr) *height_ptr = surface_ptr->committed_height; } /* ------------------------------------------------------------------------- */ void wlmtk_surface_set_activated( wlmtk_surface_t *surface_ptr, bool activated) { if (surface_ptr->activated == activated) return; if (NULL != surface_ptr->wlr_seat_ptr) { struct wlr_keyboard *wlr_keyboard_ptr = wlr_seat_get_keyboard( surface_ptr->wlr_seat_ptr); if (activated) { if (NULL != wlr_keyboard_ptr) { wlr_seat_keyboard_notify_enter( surface_ptr->wlr_seat_ptr, surface_ptr->wlr_surface_ptr, wlr_keyboard_ptr->keycodes, wlr_keyboard_ptr->num_keycodes, &wlr_keyboard_ptr->modifiers); } } else if (surface_ptr->wlr_seat_ptr->keyboard_state.focused_surface == surface_ptr->wlr_surface_ptr) { wlr_seat_keyboard_clear_focus(surface_ptr->wlr_seat_ptr); } } if (NULL != surface_ptr->super_element.parent_container_ptr) { wlmtk_container_set_keyboard_focus_element( surface_ptr->super_element.parent_container_ptr, &surface_ptr->super_element, activated); } surface_ptr->activated = activated; } /* ------------------------------------------------------------------------- */ bool wlmtk_surface_is_activated(wlmtk_surface_t *surface_ptr) { return surface_ptr->activated; } /* ------------------------------------------------------------------------- */ void wlmtk_surface_connect_map_listener_signal( wlmtk_surface_t *surface_ptr, struct wl_listener *listener_ptr, wl_notify_func_t handler) { if (NULL == surface_ptr->wlr_surface_ptr) return; wlmtk_util_connect_listener_signal( &surface_ptr->wlr_surface_ptr->events.map, listener_ptr, handler); } /* ------------------------------------------------------------------------- */ void wlmtk_surface_connect_unmap_listener_signal( wlmtk_surface_t *surface_ptr, struct wl_listener *listener_ptr, wl_notify_func_t handler) { if (NULL == surface_ptr->wlr_surface_ptr) return; wlmtk_util_connect_listener_signal( &surface_ptr->wlr_surface_ptr->events.unmap, listener_ptr, handler); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Initializes the surface. * * @param surface_ptr * @param wlr_surface_ptr * * @return true on success. */ bool _wlmtk_surface_init( wlmtk_surface_t *surface_ptr, struct wlr_surface *wlr_surface_ptr) { BS_ASSERT(NULL != surface_ptr); *surface_ptr = (wlmtk_surface_t){}; if (!wlmtk_element_init(&surface_ptr->super_element)) { _wlmtk_surface_fini(surface_ptr); return false; } surface_ptr->orig_super_element_vmt = wlmtk_element_extend( &surface_ptr->super_element, &surface_element_vmt); wlmtk_util_connect_listener_signal( &surface_ptr->super_element.events.pointer_leave, &surface_ptr->element_pointer_leave_listener, _wlmtk_surface_handle_element_pointer_leave); wlmtk_util_connect_listener_signal( &surface_ptr->super_element.events.pointer_motion, &surface_ptr->element_pointer_motion_listener, _wlmtk_surface_handle_element_pointer_motion); surface_ptr->wlr_surface_ptr = wlr_surface_ptr; if (NULL != surface_ptr->wlr_surface_ptr) { surface_ptr->wlr_surface_ptr->data = surface_ptr; wlmtk_util_connect_listener_signal( &wlr_surface_ptr->events.commit, &surface_ptr->surface_commit_listener, _wlmtk_surface_handle_surface_commit); wlmtk_util_connect_listener_signal( &wlr_surface_ptr->events.map, &surface_ptr->surface_map_listener, _wlmtk_surface_handle_surface_map); wlmtk_util_connect_listener_signal( &wlr_surface_ptr->events.unmap, &surface_ptr->surface_unmap_listener, _wlmtk_surface_handle_surface_unmap); surface_ptr->wlr_surface_ptr->data = surface_ptr; } return true; } /* ------------------------------------------------------------------------- */ /** * Initializes the surface, based off an XDG surface. * * TODO(kaeser@gubbe.ch): Merge with _wlmtk_surface_init. * * @param surface_ptr * @param wlr_xdg_surface_ptr * * @return true on success. */ bool _wlmtk_xdg_surface_init( wlmtk_surface_t *surface_ptr, struct wlr_xdg_surface *wlr_xdg_surface_ptr) { BS_ASSERT(NULL != surface_ptr); *surface_ptr = (wlmtk_surface_t){}; if (!wlmtk_element_init(&surface_ptr->super_element)) { _wlmtk_surface_fini(surface_ptr); return false; } surface_ptr->orig_super_element_vmt = wlmtk_element_extend( &surface_ptr->super_element, &surface_element_vmt); wlmtk_util_connect_listener_signal( &surface_ptr->super_element.events.pointer_leave, &surface_ptr->element_pointer_leave_listener, _wlmtk_surface_handle_element_pointer_leave); wlmtk_util_connect_listener_signal( &surface_ptr->super_element.events.pointer_motion, &surface_ptr->element_pointer_motion_listener, _wlmtk_surface_handle_element_pointer_motion); surface_ptr->wlr_xdg_surface_ptr = wlr_xdg_surface_ptr; surface_ptr->wlr_surface_ptr = wlr_xdg_surface_ptr->surface; if (NULL != surface_ptr->wlr_surface_ptr) { surface_ptr->wlr_surface_ptr->data = surface_ptr; wlmtk_util_connect_listener_signal( &surface_ptr->wlr_surface_ptr->events.commit, &surface_ptr->surface_commit_listener, _wlmtk_surface_handle_surface_commit); wlmtk_util_connect_listener_signal( &surface_ptr->wlr_surface_ptr->events.map, &surface_ptr->surface_map_listener, _wlmtk_surface_handle_surface_map); wlmtk_util_connect_listener_signal( &surface_ptr->wlr_surface_ptr->events.unmap, &surface_ptr->surface_unmap_listener, _wlmtk_surface_handle_surface_unmap); surface_ptr->wlr_surface_ptr->data = surface_ptr; } return true; } /* ------------------------------------------------------------------------- */ /** * Un-initializes the surface. * * @param surface_ptr */ void _wlmtk_surface_fini(wlmtk_surface_t *surface_ptr) { if (NULL != surface_ptr->wlr_scene_tree_ptr) { wlmtk_util_disconnect_listener( &surface_ptr->wlr_scene_tree_node_destroy_listener); } if (NULL != surface_ptr->wlr_surface_ptr) { surface_ptr->wlr_surface_ptr->data = NULL; surface_ptr->wlr_surface_ptr = NULL; wlmtk_util_disconnect_listener(&surface_ptr->surface_commit_listener); wlmtk_util_disconnect_listener(&surface_ptr->surface_map_listener); wlmtk_util_disconnect_listener(&surface_ptr->surface_unmap_listener); } wlmtk_util_disconnect_listener( &surface_ptr->element_pointer_motion_listener); wlmtk_util_disconnect_listener( &surface_ptr->element_pointer_leave_listener); wlmtk_element_fini(&surface_ptr->super_element); *surface_ptr = (wlmtk_surface_t){}; } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_element_vmt_t::destroy. Calls the dtor. */ void _wlmtk_surface_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_surface_t, super_element); wlmtk_surface_destroy(surface_ptr); } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::create_scene_node. Creates the node. * * @param element_ptr * @param wlr_scene_tree_ptr * * @return The scene graph API node of the node displaying the surface and all * of it's sub-surfaces. Or NULL on error. */ struct wlr_scene_node *_wlmtk_surface_element_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_surface_t, super_element); BS_ASSERT(NULL == surface_ptr->wlr_scene_tree_ptr); if (NULL != surface_ptr->wlr_xdg_surface_ptr) { surface_ptr->wlr_scene_tree_ptr = wlr_scene_xdg_surface_create( wlr_scene_tree_ptr, surface_ptr->wlr_xdg_surface_ptr); } else { surface_ptr->wlr_scene_tree_ptr = wlr_scene_subsurface_tree_create( wlr_scene_tree_ptr, surface_ptr->wlr_surface_ptr); } if (NULL == surface_ptr->wlr_scene_tree_ptr) return NULL; wlmtk_util_connect_listener_signal( &surface_ptr->wlr_scene_tree_ptr->node.events.destroy, &surface_ptr->wlr_scene_tree_node_destroy_listener, _wlmtk_surface_handle_wlr_scene_tree_node_destroy); return &surface_ptr->wlr_scene_tree_ptr->node; } /* ------------------------------------------------------------------------- */ /** * Implementation of the element's get_dimensions method: Return dimensions. * * @param element_ptr * @param left_ptr Leftmost position. May be NULL. * @param top_ptr Topmost position. May be NULL. * @param right_ptr Rightmost position. Ma be NULL. * @param bottom_ptr Bottommost position. May be NULL. */ void _wlmtk_surface_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_surface_t, super_element); if (NULL != left_ptr) *left_ptr = 0; if (NULL != top_ptr) *top_ptr = 0; if (NULL != right_ptr) *right_ptr = surface_ptr->committed_width; if (NULL != bottom_ptr) *bottom_ptr = surface_ptr->committed_height; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::pointer_accepts_motion. * * @param element_ptr * @param motion_event_ptr * * @return true if there is a surface (or sub-surface) at the given coordinates * that will accept the pointer's movement. */ bool _wlmtk_surface_element_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_surface_t, super_element); if (NULL == surface_ptr->super_element.wlr_scene_node_ptr) return false; // Get the layout local coordinates of the node, so we can adjust the // node-local (x, y) for the `wlr_scene_node_at` call. int lx, ly; if (!wlr_scene_node_coords( surface_ptr->super_element.wlr_scene_node_ptr, &lx, &ly)) { return false; } // Get the node below the cursor. Return if there's no buffer node. double node_x, node_y; struct wlr_scene_node *wlr_scene_node_ptr = wlr_scene_node_at( surface_ptr->super_element.wlr_scene_node_ptr, motion_event_ptr->x + lx, motion_event_ptr->y + ly, &node_x, &node_y); return (NULL != wlr_scene_node_ptr && WLR_SCENE_NODE_BUFFER == wlr_scene_node_ptr->type); } /* ------------------------------------------------------------------------- */ /** * Passes pointer button event further to the focused surface, if any. * * The actual passing is handled by `wlr_seat`. Here we just verify that the * currently-focused surface (or sub-surface) is part of this surface. * * @param element_ptr * @param button_event_ptr * * @return Whether the button event was consumed. */ bool _wlmtk_surface_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_surface_t, super_element); // Complain if the surface isn't part of our responsibility. struct wlr_surface *focused_wlr_surface_ptr = surface_ptr->wlr_seat_ptr->pointer_state.focused_surface; if (NULL == focused_wlr_surface_ptr) return false; // TODO(kaeser@gubbe.ch): Dragging the pointer from an activated window // over to a non-activated window will trigger the condition here on the // WLMTK_BUTTON_UP event. Needs a test and fixing. // Additionally, this appears to trigger when creating a new XWL popup and // the UP event goes to the new surface. Also needs test & fixing. if (WLMTK_BUTTON_UP != button_event_ptr->type) { BS_ASSERT(surface_ptr->wlr_surface_ptr == wlr_surface_get_root_surface(focused_wlr_surface_ptr)); } // We're only forwarding PRESSED & RELEASED events. if (WLMTK_BUTTON_DOWN == button_event_ptr->type || WLMTK_BUTTON_UP == button_event_ptr->type) { enum wl_pointer_button_state state = (button_event_ptr->type == WLMTK_BUTTON_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED; wlr_seat_pointer_notify_button( surface_ptr->wlr_seat_ptr, button_event_ptr->time_msec, button_event_ptr->button, state); } return true; } /* ------------------------------------------------------------------------- */ /** * Passes pointer axis events further to the focused surface, if any. * * The actual passing is handled by `wlr_seat`. Here we just verify that the * currently-focused surface (or sub-surface) is part of this surface. * * @param element_ptr * @param wlr_pointer_axis_event_ptr * * @return Whether the axis event was consumed. */ bool _wlmtk_surface_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_surface_t, super_element); // Complain if the surface isn't part of our responsibility. struct wlr_surface *focused_wlr_surface_ptr = surface_ptr->wlr_seat_ptr->pointer_state.focused_surface; if (NULL == focused_wlr_surface_ptr) return false; wlr_seat_pointer_notify_axis( surface_ptr->wlr_seat_ptr, wlr_pointer_axis_event_ptr->time_msec, wlr_pointer_axis_event_ptr->orientation, wlr_pointer_axis_event_ptr->delta, wlr_pointer_axis_event_ptr->delta_discrete, wlr_pointer_axis_event_ptr->source , wlr_pointer_axis_event_ptr->relative_direction ); return true; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::keyboard_event. Handle keyboard events. * * Registers the surface as active, and forwards events there. * * @param element_ptr * @param wlr_keyboard_key_event_ptr * * @return true if the axis event was handled. */ bool _wlmtk_surface_element_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_surface_t, super_element); if (!surface_ptr->activated) return false; // Guard clauses. if (NULL == surface_ptr->wlr_seat_ptr) return false; wlr_seat_keyboard_notify_key( surface_ptr->wlr_seat_ptr, wlr_keyboard_key_event_ptr->time_msec, wlr_keyboard_key_event_ptr->keycode, wlr_keyboard_key_event_ptr->state); return true; } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal of `wlr_scene_tree_ptr->node`. * * We have this registered to clear out the extra pointer we're holding to * @ref wlmtk_surface_t::wlr_scene_tree_ptr. @ref wlmtk_element_t has a * separate destroy handler that will take care of actual cleanup. * */ void _wlmtk_surface_handle_wlr_scene_tree_node_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_surface_t, wlr_scene_tree_node_destroy_listener); surface_ptr->wlr_scene_tree_ptr = NULL; wlmtk_util_disconnect_listener(&surface_ptr->wlr_scene_tree_node_destroy_listener); } /* ------------------------------------------------------------------------- */ /** Handler for the `commit` signal of `wlr_surface`. */ void _wlmtk_surface_handle_surface_commit( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_surface_t, surface_commit_listener); _wlmtk_surface_commit_size( surface_ptr, surface_ptr->wlr_surface_ptr->current.width, surface_ptr->wlr_surface_ptr->current.height); } /* ------------------------------------------------------------------------- */ /** * Handles the `surface_map` signal: Makes the surface visible. * * @param listener_ptr * @param data_ptr */ void _wlmtk_surface_handle_surface_map( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_surface_t, surface_map_listener); wlmtk_element_set_visible(wlmtk_surface_element(surface_ptr), true); } /* ------------------------------------------------------------------------- */ /** * Handles the `surface_unmap` signal: Makes the surface invisible. * * @param listener_ptr * @param data_ptr */ void _wlmtk_surface_handle_surface_unmap( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_surface_t, surface_unmap_listener); wlmtk_element_set_visible( wlmtk_surface_element(surface_ptr), false); } /* ------------------------------------------------------------------------- */ /** * Handles pointer leave: If there's a WLR (sub)surface currently holding * focus, that will be cleared. * * @param listener_ptr * @param data_ptr */ void _wlmtk_surface_handle_element_pointer_leave( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_surface_t, element_pointer_leave_listener); // Guard clause. if (NULL == surface_ptr->wlr_seat_ptr) return; // If the current surface's parent is our surface: clear it. struct wlr_surface *focused_wlr_surface_ptr = surface_ptr->wlr_seat_ptr->pointer_state.focused_surface; if (NULL != focused_wlr_surface_ptr && wlr_surface_get_root_surface(focused_wlr_surface_ptr) == surface_ptr->wlr_surface_ptr) { wlr_seat_pointer_clear_focus(surface_ptr->wlr_seat_ptr); } } /* ------------------------------------------------------------------------- */ /** * Handles pointer motion: Passes the motion events to client's surface(s). * * Identifies the surface (or sub-surface) at the given coordinates, and pass * on the motion event to that surface. If needed, will update the seat's * pointer focus. * * @param listener_ptr * @param data_ptr Points to a @ref wlmtk_pointer_motion_event_t. */ void _wlmtk_surface_handle_element_pointer_motion( struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_surface_t *surface_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_surface_t, element_pointer_motion_listener); wlmtk_pointer_motion_event_t *motion_event_ptr = data_ptr; if (NULL == surface_ptr->super_element.wlr_scene_node_ptr) return; // Get the layout local coordinates of the node, so we can adjust the // node-local (x, y) for the `wlr_scene_node_at` call. int lx, ly; if (!wlr_scene_node_coords( surface_ptr->super_element.wlr_scene_node_ptr, &lx, &ly)) { return; } // Get the node below the cursor. Return if there's no buffer node. double node_x, node_y; struct wlr_scene_node *wlr_scene_node_ptr = wlr_scene_node_at( surface_ptr->super_element.wlr_scene_node_ptr, motion_event_ptr->x + lx, motion_event_ptr->y + ly, &node_x, &node_y); if (NULL == wlr_scene_node_ptr || WLR_SCENE_NODE_BUFFER != wlr_scene_node_ptr->type) { return; } struct wlr_scene_buffer *wlr_scene_buffer_ptr = wlr_scene_buffer_from_node(wlr_scene_node_ptr); struct wlr_scene_surface *wlr_scene_surface_ptr = wlr_scene_surface_try_from_buffer(wlr_scene_buffer_ptr); if (NULL == wlr_scene_surface_ptr) return; BS_ASSERT(surface_ptr->wlr_surface_ptr == wlr_surface_get_root_surface(wlr_scene_surface_ptr->surface)); if (NULL != surface_ptr->wlr_seat_ptr) { wlr_seat_pointer_notify_enter( surface_ptr->wlr_seat_ptr, wlr_scene_surface_ptr->surface, node_x, node_y); wlr_seat_pointer_notify_motion( surface_ptr->wlr_seat_ptr, motion_event_ptr->time_msec, node_x, node_y); } } /* ------------------------------------------------------------------------- */ /** * Surface commits a new size: Store the size, and update the parent's layout. * * @param surface_ptr * @param width * @param height */ void _wlmtk_surface_commit_size( wlmtk_surface_t *surface_ptr, int width, int height) { if (surface_ptr->committed_width != width || surface_ptr->committed_height != height) { surface_ptr->committed_width = width; surface_ptr->committed_height = height; } if (NULL != surface_ptr->super_element.parent_container_ptr) { wlmtk_container_invalidate_layout( surface_ptr->super_element.parent_container_ptr); } } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); static void test_dimensions(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_surface_test_cases[] = { { 1, "create_destroy", test_create_destroy }, { 1, "dimensions", test_dimensions }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_surface_test_set = BS_TEST_SET( true, "surface", _wlmtk_surface_test_cases); /* ------------------------------------------------------------------------- */ /** Tests ctor and dtor. */ void test_create_destroy(bs_test_t *test_ptr) { wlmtk_surface_t *surface_ptr = wlmtk_surface_create(NULL, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, surface_ptr); BS_TEST_VERIFY_EQ( test_ptr, &surface_ptr->super_element, wlmtk_surface_element(surface_ptr)); wlmtk_surface_destroy(surface_ptr); } /* ------------------------------------------------------------------------- */ /** Verifies that dimensions from the surface propagate thorugh. */ void test_dimensions(bs_test_t *test_ptr) { struct wlr_surface wlr_surface = {}; wl_list_init(&wlr_surface.current.subsurfaces_below); wl_list_init(&wlr_surface.current.subsurfaces_above); wl_signal_init(&wlr_surface.events.commit); wl_signal_init(&wlr_surface.events.destroy); wl_signal_init(&wlr_surface.events.map); wl_signal_init(&wlr_surface.events.unmap); wlmtk_surface_t *s = wlmtk_surface_create(&wlr_surface, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, s); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 0, 0, wlmtk_element_get_dimensions_box(wlmtk_surface_element(s))); wlr_surface.current.width = 640; wlr_surface.current.height = 480; wl_signal_emit(&wlr_surface.events.commit, NULL); WLMTK_TEST_VERIFY_WLRBOX_EQ( test_ptr, 0, 0, 640, 480, wlmtk_element_get_dimensions_box(wlmtk_surface_element(s))); wlmtk_surface_destroy(s); } /* == End of surface.c ===================================================== */ wlmaker-0.8.1/src/toolkit/box.c0000644000175100017510000003575015222270710016010 0ustar runnerrunner/* ========================================================================= */ /** * @file box.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "box.h" #include #include #include "libbase/libbase.h" #include "container.h" #include "rectangle.h" /* == Declarations ========================================================= */ static void _wlmtk_box_element_layout(wlmtk_element_t *element_ptr); static bs_dllist_node_t *create_margin(wlmtk_box_t *box_ptr); static void _wlmtk_box_margin_set_color( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /* == Data ================================================================= */ /** Virtual method table: @ref wlmtk_element_t at @ref wlmtk_box_t level. */ static const wlmtk_element_vmt_t box_element_vmt = { .layout = _wlmtk_box_element_layout, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_box_init( wlmtk_box_t *box_ptr, wlmtk_box_orientation_t orientation, const struct wlmtk_margin_style *style_ptr) { BS_ASSERT(NULL != box_ptr); *box_ptr = (wlmtk_box_t){ .style = *style_ptr }; if (!wlmtk_container_init(&box_ptr->super_container)) { return false; } box_ptr->orig_super_element_vmt = wlmtk_element_extend( wlmtk_box_element(box_ptr), &box_element_vmt); if (!wlmtk_container_init(&box_ptr->element_container)) { wlmtk_box_fini(box_ptr); return false; } wlmtk_element_set_visible(&box_ptr->element_container.super_element, true); wlmtk_container_add_element(&box_ptr->super_container, &box_ptr->element_container.super_element); if (!wlmtk_container_init(&box_ptr->margin_container)) { wlmtk_box_fini(box_ptr); return false; } wlmtk_element_set_visible(&box_ptr->margin_container.super_element, true); // Keep margins behind the box's elements. wlmtk_container_add_element_atop( &box_ptr->super_container, NULL, &box_ptr->margin_container.super_element); box_ptr->orientation = orientation; return true; } /* ------------------------------------------------------------------------- */ void wlmtk_box_fini(wlmtk_box_t *box_ptr) { if (NULL != box_ptr->element_container.super_element.parent_container_ptr) { wlmtk_container_remove_element( &box_ptr->super_container, &box_ptr->element_container.super_element); wlmtk_container_fini(&box_ptr->element_container); } if (NULL != box_ptr->margin_container.super_element.parent_container_ptr) { wlmtk_container_remove_element( &box_ptr->super_container, &box_ptr->margin_container.super_element); wlmtk_container_fini(&box_ptr->margin_container); } wlmtk_container_fini(&box_ptr->super_container); *box_ptr = (wlmtk_box_t){}; } /* ------------------------------------------------------------------------- */ void wlmtk_box_add_element_front( wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr) { wlmtk_container_add_element(&box_ptr->element_container, element_ptr); wlmtk_element_layout(wlmtk_box_element(box_ptr)); } /* ------------------------------------------------------------------------- */ void wlmtk_box_add_element_back( wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr) { wlmtk_container_add_element_atop( &box_ptr->element_container, NULL, element_ptr); wlmtk_element_layout(wlmtk_box_element(box_ptr)); } /* ------------------------------------------------------------------------- */ void wlmtk_box_remove_element(wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr) { wlmtk_container_remove_element(&box_ptr->element_container, element_ptr); wlmtk_element_layout(wlmtk_box_element(box_ptr)); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_box_element(wlmtk_box_t *box_ptr) { return &box_ptr->super_container.super_element; } /* ------------------------------------------------------------------------- */ void wlmtk_box_set_style( wlmtk_box_t *box_ptr, const struct wlmtk_margin_style *style_ptr) { box_ptr->style = *style_ptr; wlmtk_element_layout(wlmtk_box_element(box_ptr)); bs_dllist_for_each( &box_ptr->margin_container.elements, _wlmtk_box_margin_set_color, (void*)&style_ptr->color); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Lays out the elements of the box. * * Steps through all visible elements, and sets their position to be * left-to-right. Also updates and repositions all margin elements. * * @param element_ptr */ void _wlmtk_box_element_layout(wlmtk_element_t *element_ptr) { wlmtk_box_t *box_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_box_t, super_container.super_element); wlmtk_element_t *margin_element_ptr = NULL; box_ptr->orig_super_element_vmt.layout(element_ptr); int margin_x = 0; int margin_y = 0; int margin_width = box_ptr->style.width; int margin_height = box_ptr->style.width; size_t visible_elements = 0; for (bs_dllist_node_t *dlnode_ptr = box_ptr->element_container.elements.head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); if (element_ptr->visible) visible_elements++; } int position = 0; bs_dllist_node_t *margin_dlnode_ptr = box_ptr->margin_container.elements.head_ptr; for (bs_dllist_node_t *dlnode_ptr = box_ptr->element_container.elements.head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { wlmtk_element_t *element_ptr = wlmtk_element_from_dlnode(dlnode_ptr); if (!element_ptr->visible) continue; int left, top, right, bottom; wlmtk_element_get_dimensions(element_ptr, &left, &top, &right, &bottom); int x, y; wlmtk_element_get_position(element_ptr, &x, &y); switch (box_ptr->orientation) { case WLMTK_BOX_HORIZONTAL: x = position - left; margin_x = position + right - left; margin_height = bottom - top; position = margin_x + box_ptr->style.width; break; case WLMTK_BOX_VERTICAL: y = position - top; margin_y = position + bottom - top; margin_width = right - left; position = margin_y + box_ptr->style.width; break; default: bs_log(BS_FATAL, "Weird orientation %d.", box_ptr->orientation); } wlmtk_element_set_position(element_ptr, x, y); visible_elements--; // Early exit: No margin needed, if there's no next element. if (NULL == dlnode_ptr->next_ptr || 0 >= visible_elements) break; // If required: Create new margin, then position the margin element. if (NULL == margin_dlnode_ptr) { margin_dlnode_ptr = create_margin(box_ptr); } margin_element_ptr = wlmtk_element_from_dlnode(margin_dlnode_ptr); wlmtk_element_set_position(margin_element_ptr, margin_x, margin_y); wlmtk_rectangle_set_size( wlmtk_rectangle_from_element(margin_element_ptr), margin_width, margin_height); margin_dlnode_ptr = margin_dlnode_ptr->next_ptr; } // Remove excess margin nodes. while (NULL != margin_dlnode_ptr) { margin_element_ptr = wlmtk_element_from_dlnode(margin_dlnode_ptr); margin_dlnode_ptr = margin_dlnode_ptr->next_ptr; wlmtk_container_remove_element( &box_ptr->margin_container, margin_element_ptr); wlmtk_element_destroy(margin_element_ptr); } } /* ------------------------------------------------------------------------- */ /** Creates a new margin element, and returns the dlnode. */ bs_dllist_node_t *create_margin(wlmtk_box_t *box_ptr) { wlmtk_rectangle_t *rect_ptr = wlmtk_rectangle_create( 0, 0, box_ptr->style.color); BS_ASSERT(NULL != rect_ptr); wlmtk_container_add_element_atop( &box_ptr->margin_container, NULL, wlmtk_rectangle_element(rect_ptr)); wlmtk_element_set_visible(wlmtk_rectangle_element(rect_ptr), true); return wlmtk_dlnode_from_element(wlmtk_rectangle_element(rect_ptr)); } /* ------------------------------------------------------------------------- */ /** Sets the color in each margin rectangle. */ void _wlmtk_box_margin_set_color( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_element_t *e = wlmtk_element_from_dlnode(dlnode_ptr); wlmtk_rectangle_t *r = wlmtk_rectangle_from_element(e); const uint32_t *color_ptr = ud_ptr; wlmtk_rectangle_set_color(r, *color_ptr); } /* == Unit tests =========================================================== */ static void test_init_fini(bs_test_t *test_ptr); static void test_layout_horizontal(bs_test_t *test_ptr); static void test_layout_vertical(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_box_test_cases[] = { { 1, "init_fini", test_init_fini }, { 1, "layout_horizontal", test_layout_horizontal }, { 1, "layout_vertical", test_layout_vertical }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_box_test_set = BS_TEST_SET( true, "box", _wlmtk_box_test_cases); /** Style used for tests. */ static const struct wlmtk_margin_style test_style = { .width = 2, .color = 0xff000000 }; /* ------------------------------------------------------------------------- */ /** Exercises setup and teardown. */ void test_init_fini(bs_test_t *test_ptr) { wlmtk_box_t box; BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_box_init( &box, WLMTK_BOX_HORIZONTAL, &test_style)); BS_TEST_VERIFY_EQ( test_ptr, &box.super_container.super_element, wlmtk_box_element(&box)); wlmtk_box_fini(&box); } /* ------------------------------------------------------------------------- */ /** Tests layouting horizontally */ void test_layout_horizontal(bs_test_t *test_ptr) { wlmtk_box_t box; wlmtk_box_init(&box, WLMTK_BOX_HORIZONTAL, &test_style); wlmtk_fake_element_t *e1_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&e1_ptr->element, true); e1_ptr->dimensions.width = 10; e1_ptr->dimensions.height = 1; wlmtk_fake_element_t *e2_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&e2_ptr->element, false); e2_ptr->dimensions.width = 20; e1_ptr->dimensions.height = 2; wlmtk_fake_element_t *e3_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&e3_ptr->element, true); e3_ptr->dimensions.width = 40; e3_ptr->dimensions.height = 4; // Note: Elements are added "in front" == left. wlmtk_box_add_element_front(&box, &e1_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&box.margin_container.elements)); wlmtk_box_add_element_front(&box, &e2_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&box.margin_container.elements)); wlmtk_box_add_element_front(&box, &e3_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&box.margin_container.elements)); // Layout: e3 | e1 (e2 is invisible). BS_TEST_VERIFY_EQ(test_ptr, 42, e1_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 0, e1_ptr->element.y); BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.y); BS_TEST_VERIFY_EQ(test_ptr, 0, e3_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 0, e3_ptr->element.y); // Make e2 visible, now we should have: e3 | e2 | e1. wlmtk_element_set_visible(&e2_ptr->element, true); wlmtk_element_layout(wlmtk_box_element(&box)); BS_TEST_VERIFY_EQ(test_ptr, 64, e1_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 42, e2_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 0, e3_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 2, bs_dllist_size(&box.margin_container.elements)); wlmtk_element_set_visible(&e1_ptr->element, false); wlmtk_element_layout(wlmtk_box_element(&box)); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&box.margin_container.elements)); wlmtk_element_set_visible(&e1_ptr->element, true); wlmtk_element_layout(wlmtk_box_element(&box)); // Remove elements. Must update each. wlmtk_box_remove_element(&box, &e3_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, 22, e1_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&box.margin_container.elements)); wlmtk_box_remove_element(&box, &e2_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, 0, e1_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&box.margin_container.elements)); wlmtk_box_remove_element(&box, &e1_ptr->element); wlmtk_element_destroy(&e3_ptr->element); wlmtk_element_destroy(&e2_ptr->element); wlmtk_element_destroy(&e1_ptr->element); wlmtk_box_fini(&box); } /* ------------------------------------------------------------------------- */ /** Tests layouting vertically */ void test_layout_vertical(bs_test_t *test_ptr) { wlmtk_box_t box; wlmtk_box_init(&box, WLMTK_BOX_VERTICAL, &test_style); wlmtk_fake_element_t *e1_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&e1_ptr->element, true); e1_ptr->dimensions.width = 100; e1_ptr->dimensions.height = 10; wlmtk_fake_element_t *e2_ptr = wlmtk_fake_element_create(); wlmtk_element_set_visible(&e2_ptr->element, true); e2_ptr->dimensions.width = 200; e2_ptr->dimensions.height = 20; // Note: Elements are added "in front" == left. wlmtk_box_add_element_front(&box, &e1_ptr->element); wlmtk_box_add_element_front(&box, &e2_ptr->element); // Layout: e2 | e1. BS_TEST_VERIFY_EQ(test_ptr, 0, e1_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 22, e1_ptr->element.y); BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.x); BS_TEST_VERIFY_EQ(test_ptr, 0, e2_ptr->element.y); // Remove elements. Must update each. wlmtk_box_remove_element(&box, &e2_ptr->element); BS_TEST_VERIFY_EQ(test_ptr, 0, e1_ptr->element.y); wlmtk_box_remove_element(&box, &e1_ptr->element); wlmtk_element_destroy(&e2_ptr->element); wlmtk_element_destroy(&e1_ptr->element); wlmtk_box_fini(&box); } /* == End of box.c ========================================================= */ wlmaker-0.8.1/src/toolkit/dock.c0000644000175100017510000003334415222270710016135 0ustar runnerrunner/* ========================================================================= */ /** * @file dock.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "dock.h" #include #include #include #include #include #include "box.h" #include "container.h" /* == Declarations ========================================================= */ /** State of the toolkit dock. */ struct _wlmtk_dock_t { /** Parent class: The panel. */ wlmtk_panel_t super_panel; /** Original virtual method table of the panel's @ref wlmtk_element_t. */ wlmtk_element_vmt_t old_element_vmt; /** Positioning information for the panel. */ wlmtk_panel_positioning_t panel_positioning; /** Copy of the positioning information this dock was called from. */ wlmtk_dock_positioning_t dock_positioning; /** Styling info of the dock. */ struct wlmtk_dock_style style; /** Principal element of the dock is a box, holding tiles. */ wlmtk_box_t tile_box; /** Extra holder for tiles, for direct access. */ bs_dllist_t tiles; }; static uint32_t _wlmtk_dock_panel_request_size( wlmtk_panel_t *panel_ptr, int width, int height); static void _wlmtk_dock_element_layout(wlmtk_element_t *element_ptr); static wlmtk_box_orientation_t _wlmtk_dock_orientation(wlmtk_dock_t *dock_ptr); static bool _wlmtk_dock_positioning( wlmtk_dock_t *dock_ptr, wlmtk_panel_positioning_t *panel_positioning_ptr); static bool _wlmtk_dock_tile_set_style( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmtk_dock_tile_destroy( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /* == Data ================================================================= */ const bspl_desc_t wlmtk_dock_style_desc[] = { BSPL_DESC_DICT( "Margin", true, struct wlmtk_dock_style, margin, margin, wlmtk_style_margin_desc), BSPL_DESC_SENTINEL() }; /** Virtual method table of the panel. */ static const wlmtk_panel_vmt_t _wlmtk_dock_panel_vmt = { .request_size = _wlmtk_dock_panel_request_size }; /** Virtual method table of the panel's element. */ static const wlmtk_element_vmt_t _wlmtk_dock_element_vmt = { .layout = _wlmtk_dock_element_layout }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_dock_t *wlmtk_dock_create( const wlmtk_dock_positioning_t *dock_positioning_ptr, const struct wlmtk_dock_style *style_ptr) { wlmtk_dock_t *dock_ptr = logged_calloc(1, sizeof(*dock_ptr)); if (NULL == dock_ptr) return NULL; dock_ptr->dock_positioning = *dock_positioning_ptr; dock_ptr->style = *style_ptr; if (!wlmtk_box_init( &dock_ptr->tile_box, _wlmtk_dock_orientation(dock_ptr), &dock_ptr->style.margin)) { wlmtk_dock_destroy(dock_ptr); return NULL; } wlmtk_element_set_visible(wlmtk_box_element(&dock_ptr->tile_box), true); if (!_wlmtk_dock_positioning( dock_ptr, &dock_ptr->panel_positioning)) { wlmtk_dock_destroy(dock_ptr); return NULL; } if (!wlmtk_panel_init( &dock_ptr->super_panel, &dock_ptr->panel_positioning)) { bs_log(BS_ERROR, "Failed wlmtk_panel_init."); wlmtk_dock_destroy(dock_ptr); return NULL; } wlmtk_panel_extend(&dock_ptr->super_panel, &_wlmtk_dock_panel_vmt); dock_ptr->old_element_vmt = wlmtk_element_extend( wlmtk_dock_element(dock_ptr), &_wlmtk_dock_element_vmt); wlmtk_container_add_element( &dock_ptr->super_panel.super_container, wlmtk_box_element(&dock_ptr->tile_box)); return dock_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_dock_destroy(wlmtk_dock_t *dock_ptr) { bs_dllist_for_each(&dock_ptr->tiles, _wlmtk_dock_tile_destroy, dock_ptr); if (wlmtk_box_element(&dock_ptr->tile_box)->parent_container_ptr) { wlmtk_container_remove_element( &dock_ptr->super_panel.super_container, wlmtk_box_element(&dock_ptr->tile_box)); wlmtk_box_fini(&dock_ptr->tile_box); } wlmtk_panel_fini(&dock_ptr->super_panel); free(dock_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_dock_set_style( wlmtk_dock_t *dock_ptr, const struct wlmtk_dock_style *style_ptr, const struct wlmtk_tile_style *tile_style_ptr) { wlmtk_box_set_style(&dock_ptr->tile_box, &style_ptr->margin); bool rv = bs_dllist_all( &dock_ptr->tiles, _wlmtk_dock_tile_set_style, (void*)tile_style_ptr); wlmtk_element_layout(wlmtk_box_element(&dock_ptr->tile_box)); _wlmtk_dock_panel_request_size(&dock_ptr->super_panel, 0, 0); return rv; } /* ------------------------------------------------------------------------- */ void wlmtk_dock_add_tile( wlmtk_dock_t *dock_ptr, wlmtk_tile_t *tile_ptr) { BS_ASSERT(NULL == wlmtk_tile_element(tile_ptr)->parent_container_ptr); if (WLR_EDGE_TOP == dock_ptr->dock_positioning.anchor || WLR_EDGE_LEFT == dock_ptr->dock_positioning.anchor) { wlmtk_box_add_element_back( &dock_ptr->tile_box, wlmtk_tile_element(tile_ptr)); bs_dllist_push_back( &dock_ptr->tiles, wlmtk_dlnode_from_tile(tile_ptr)); } else { wlmtk_box_add_element_front( &dock_ptr->tile_box, wlmtk_tile_element(tile_ptr)); bs_dllist_push_front( &dock_ptr->tiles, wlmtk_dlnode_from_tile(tile_ptr)); } _wlmtk_dock_panel_request_size(&dock_ptr->super_panel, 0, 0); } /* ------------------------------------------------------------------------- */ void wlmtk_dock_remove_tile( wlmtk_dock_t *dock_ptr, wlmtk_tile_t *tile_ptr) { BS_ASSERT( &dock_ptr->tile_box.element_container == wlmtk_tile_element(tile_ptr)->parent_container_ptr); wlmtk_box_remove_element( &dock_ptr->tile_box, wlmtk_tile_element(tile_ptr)); bs_dllist_remove( &dock_ptr->tiles, wlmtk_dlnode_from_tile(tile_ptr)); _wlmtk_dock_panel_request_size(&dock_ptr->super_panel, 0, 0); } /* ------------------------------------------------------------------------- */ wlmtk_panel_t *wlmtk_dock_panel(wlmtk_dock_t *dock_ptr) { return &dock_ptr->super_panel; } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_dock_element(wlmtk_dock_t *dock_ptr) { return wlmtk_panel_element(wlmtk_dock_panel(dock_ptr)); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Recomputes the dock's size -- and also commits it. * * @param panel_ptr * @param width * @param height * * @return 0 */ uint32_t _wlmtk_dock_panel_request_size( wlmtk_panel_t *panel_ptr, __UNUSED__ int width, __UNUSED__ int height) { wlmtk_dock_t *dock_ptr = BS_CONTAINER_OF( panel_ptr, wlmtk_dock_t, super_panel); wlmtk_panel_positioning_t panel_positioning = {}; if (!_wlmtk_dock_positioning(dock_ptr, &panel_positioning)) { bs_log(BS_ERROR, "Panel %p invalid positioning data.", panel_ptr); return 0; } wlmtk_panel_commit(panel_ptr, 0, &panel_positioning); return 0; } /* ------------------------------------------------------------------------- */ /** Redraw the dock. We also (re)request size, ie. the positioning. */ void _wlmtk_dock_element_layout(wlmtk_element_t *element_ptr) { wlmtk_dock_t *dock_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_dock_t, super_panel.super_container.super_element); dock_ptr->old_element_vmt.layout(element_ptr); _wlmtk_dock_panel_request_size(&dock_ptr->super_panel, 0, 0); } /* ------------------------------------------------------------------------- */ /** Derives the box' orientation for the dock. */ wlmtk_box_orientation_t _wlmtk_dock_orientation(wlmtk_dock_t *dock_ptr) { switch (dock_ptr->dock_positioning.edge) { case WLR_EDGE_LEFT: case WLR_EDGE_RIGHT: return WLMTK_BOX_VERTICAL; case WLR_EDGE_TOP: case WLR_EDGE_BOTTOM: return WLMTK_BOX_HORIZONTAL; default: bs_log(BS_FATAL, "wlmtk_dock %p: Invalid edge %"PRIx32, dock_ptr, dock_ptr->dock_positioning.edge); } /* Not reached. */ return WLMTK_BOX_HORIZONTAL; } /* ------------------------------------------------------------------------- */ /** Fills the panel positioning parameters from the dock's. */ bool _wlmtk_dock_positioning( wlmtk_dock_t *dock_ptr, wlmtk_panel_positioning_t *panel_positioning_ptr) { struct wlr_box box = wlmtk_element_get_dimensions_box( wlmtk_box_element(&dock_ptr->tile_box)); switch (dock_ptr->dock_positioning.edge) { case WLR_EDGE_LEFT: case WLR_EDGE_RIGHT: panel_positioning_ptr->anchor = dock_ptr->dock_positioning.edge; panel_positioning_ptr->desired_width = BS_MAX(box.width, 1); if (dock_ptr->dock_positioning.anchor != WLR_EDGE_TOP && dock_ptr->dock_positioning.anchor != WLR_EDGE_BOTTOM) { bs_log(BS_ERROR, "wlmtk_dock_t anchor must be adjacent to edge: " "anchor %"PRIx32", edge %"PRIx32, dock_ptr->dock_positioning.anchor, dock_ptr->dock_positioning.edge); return false; } panel_positioning_ptr->anchor |= dock_ptr->dock_positioning.anchor; // The layer protocol requires a non-zero value for panels not spanning // the entire height. Go with a one-tile dimension, as long as there's // no tiles yet. panel_positioning_ptr->desired_height = BS_MAX(box.height, 1); break; case WLR_EDGE_TOP: case WLR_EDGE_BOTTOM: panel_positioning_ptr->anchor = dock_ptr->dock_positioning.edge; panel_positioning_ptr->desired_height = BS_MAX(box.height, 1); if (dock_ptr->dock_positioning.anchor != WLR_EDGE_LEFT && dock_ptr->dock_positioning.anchor != WLR_EDGE_RIGHT) { bs_log(BS_ERROR, "wlmtk_dock_t anchor must be adjacent to edge: " "anchor %"PRIx32", edge %"PRIx32, dock_ptr->dock_positioning.anchor, dock_ptr->dock_positioning.edge); return false; } panel_positioning_ptr->anchor |= dock_ptr->dock_positioning.anchor; // The layer protocol requires a non-zero value for panels not spanning // the entire width. Go with a one-tile dimension, as long as there's // no tiles yet. panel_positioning_ptr->desired_width = BS_MAX(box.width, 1); break; default: bs_log(BS_ERROR, "Unexpected wlmtk_dock_t positioning edge 0x%"PRIx32, dock_ptr->dock_positioning.edge); return false; } return true; } /* ------------------------------------------------------------------------- */ /** Iterator to bs_dllist_all(): Sets style for a tile in the iterator. */ bool _wlmtk_dock_tile_set_style(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_tile_t *tile_ptr = wlmtk_tile_from_dlnode(dlnode_ptr); const struct wlmtk_tile_style *tile_style_ptr = ud_ptr; return wlmtk_tile_set_style(tile_ptr, tile_style_ptr); } /* ------------------------------------------------------------------------- */ /** Iterator to bs_dllist_for_each(): Removes & destroys the tile at node. */ void _wlmtk_dock_tile_destroy(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_tile_t *tile_ptr = wlmtk_tile_from_dlnode(dlnode_ptr); wlmtk_dock_t *dock_ptr = ud_ptr; wlmtk_dock_remove_tile(dock_ptr, tile_ptr); wlmtk_element_destroy(wlmtk_tile_element(tile_ptr)); } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_dock_test_cases[] = { { 1, "create_destroy", test_create_destroy }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_dock_test_set = BS_TEST_SET( true, "dock", _wlmtk_dock_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises ctor and dtor. */ void test_create_destroy(bs_test_t *test_ptr) { wlmtk_dock_positioning_t pos = { .edge = WLR_EDGE_LEFT, .anchor = WLR_EDGE_BOTTOM, }; struct wlmtk_dock_style style = {}; wlmtk_dock_t *dock_ptr = wlmtk_dock_create(&pos, &style); BS_TEST_VERIFY_EQ( test_ptr, WLR_EDGE_LEFT | WLR_EDGE_BOTTOM, dock_ptr->super_panel.positioning.anchor); BS_TEST_VERIFY_EQ( test_ptr, 1, dock_ptr->super_panel.positioning.desired_width); BS_TEST_VERIFY_EQ( test_ptr, 1, dock_ptr->super_panel.positioning.desired_height); BS_TEST_VERIFY_EQ( test_ptr, WLMTK_BOX_VERTICAL, dock_ptr->tile_box.orientation); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dock_ptr); wlmtk_dock_destroy(dock_ptr); } /* == End of dock.c ======================================================== */ wlmaker-0.8.1/src/toolkit/resizebar.c0000644000175100017510000003372615222270710017207 0ustar runnerrunner/* ========================================================================= */ /** * @file resizebar.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "resizebar.h" #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include "test.h" // IWYU pragma: keep /* == Declarations ========================================================= */ /** State of the title bar. */ struct _wlmtk_resizebar_t { /** Superclass: Box. */ wlmtk_box_t super_box; /** Current width of the resize bar. */ unsigned width; /** Style of the resize bar. */ const struct wlmtk_resizebar_style *style_ptr; /** Background. */ bs_gfxbuf_t *gfxbuf_ptr; /** Left element of the resizebar. */ wlmtk_resizebar_area_t *left_area_ptr; /** Center element of the resizebar. */ wlmtk_resizebar_area_t *center_area_ptr; /** Right element of the resizebar. */ wlmtk_resizebar_area_t *right_area_ptr; }; static void _wlmtk_resizebar_element_destroy(wlmtk_element_t *element_ptr); static bool _wlmtk_resizebar_redraw( wlmtk_resizebar_t *resizebar_ptr, const struct wlmtk_resizebar_style *style_ptr, unsigned width); static bool redraw_buffers( wlmtk_resizebar_t *resizebar_ptr, const struct wlmtk_resizebar_style *style_ptr, unsigned width); /* == Data ================================================================= */ /** Virtual method table extension for the resizebar's element superclass. */ static const wlmtk_element_vmt_t resizebar_element_vmt = { .destroy = _wlmtk_resizebar_element_destroy, }; /** Descriptor for decoding the "ResizeBar" dict below "Window". */ const bspl_desc_t wlmtk_resizebar_style_desc[] = { BSPL_DESC_CUSTOM( "Fill", true, struct wlmtk_resizebar_style, fill, fill, wlmtk_style_decode_fill, NULL, NULL, NULL), BSPL_DESC_UINT64( "Height", true, struct wlmtk_resizebar_style, height, height, 7), BSPL_DESC_UINT64( "BezelWidth", true, struct wlmtk_resizebar_style, bezel_width, bezel_width, 1), BSPL_DESC_UINT64( "CornerWidth", true, struct wlmtk_resizebar_style, corner_width, corner_width, 1), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_resizebar_t *wlmtk_resizebar_create( wlmtk_window_t *window_ptr, const struct wlmtk_resizebar_style *style_ptr) { static const struct wlmtk_margin_style empty_margin_style = {}; wlmtk_resizebar_t *resizebar_ptr = logged_calloc( 1, sizeof(wlmtk_resizebar_t)); if (NULL == resizebar_ptr) return NULL; resizebar_ptr->style_ptr = style_ptr; if (!wlmtk_box_init(&resizebar_ptr->super_box, WLMTK_BOX_HORIZONTAL, &empty_margin_style)) { wlmtk_resizebar_destroy(resizebar_ptr); return NULL; } wlmtk_element_extend( &resizebar_ptr->super_box.super_container.super_element, &resizebar_element_vmt); resizebar_ptr->left_area_ptr = wlmtk_resizebar_area_create( window_ptr, WLR_EDGE_LEFT | WLR_EDGE_BOTTOM); if (NULL == resizebar_ptr->left_area_ptr) { wlmtk_resizebar_destroy(resizebar_ptr); return NULL; } wlmtk_box_add_element_front( &resizebar_ptr->super_box, wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr)); resizebar_ptr->center_area_ptr = wlmtk_resizebar_area_create( window_ptr, WLR_EDGE_BOTTOM); if (NULL == resizebar_ptr->center_area_ptr) { wlmtk_resizebar_destroy(resizebar_ptr); return NULL; } wlmtk_box_add_element_back( &resizebar_ptr->super_box, wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr)); resizebar_ptr->right_area_ptr = wlmtk_resizebar_area_create( window_ptr, WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM); if (NULL == resizebar_ptr->right_area_ptr) { wlmtk_resizebar_destroy(resizebar_ptr); return NULL; } wlmtk_box_add_element_back( &resizebar_ptr->super_box, wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr)); return resizebar_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_resizebar_destroy(wlmtk_resizebar_t *resizebar_ptr) { if (NULL != resizebar_ptr->right_area_ptr) { wlmtk_box_remove_element( &resizebar_ptr->super_box, wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr)); wlmtk_resizebar_area_destroy(resizebar_ptr->right_area_ptr); resizebar_ptr->right_area_ptr = NULL; } if (NULL != resizebar_ptr->center_area_ptr) { wlmtk_box_remove_element( &resizebar_ptr->super_box, wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr)); wlmtk_resizebar_area_destroy(resizebar_ptr->center_area_ptr); resizebar_ptr->center_area_ptr = NULL; } if (NULL != resizebar_ptr->left_area_ptr) { wlmtk_box_remove_element( &resizebar_ptr->super_box, wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr)); wlmtk_resizebar_area_destroy(resizebar_ptr->left_area_ptr); resizebar_ptr->left_area_ptr = NULL; } if (NULL != resizebar_ptr->gfxbuf_ptr) { bs_gfxbuf_destroy(resizebar_ptr->gfxbuf_ptr); resizebar_ptr->gfxbuf_ptr = NULL; } wlmtk_box_fini(&resizebar_ptr->super_box); free(resizebar_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_resizebar_set_width( wlmtk_resizebar_t *resizebar_ptr, unsigned width) { if (resizebar_ptr->width == width) return true; return _wlmtk_resizebar_redraw( resizebar_ptr, resizebar_ptr->style_ptr, width); } /* ------------------------------------------------------------------------- */ bool wlmtk_resizebar_set_style( wlmtk_resizebar_t *resizebar_ptr, const struct wlmtk_resizebar_style *style_ptr) { if (!_wlmtk_resizebar_redraw( resizebar_ptr, style_ptr, resizebar_ptr->width)) return false; resizebar_ptr->style_ptr = style_ptr; return true; } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_resizebar_element(wlmtk_resizebar_t *resizebar_ptr) { return &resizebar_ptr->super_box.super_container.super_element; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Virtual destructor: Wraps to our dtor. */ void _wlmtk_resizebar_element_destroy(wlmtk_element_t *element_ptr) { wlmtk_resizebar_t *resizebar_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_resizebar_t, super_box.super_container.super_element); wlmtk_resizebar_destroy(resizebar_ptr); } /* ------------------------------------------------------------------------- */ /** Redraws the resizebar. */ bool _wlmtk_resizebar_redraw( wlmtk_resizebar_t *resizebar_ptr, const struct wlmtk_resizebar_style *style_ptr, unsigned width) { if (!redraw_buffers(resizebar_ptr, style_ptr, width)) return false; BS_ASSERT(width == resizebar_ptr->width); BS_ASSERT(width == resizebar_ptr->gfxbuf_ptr->width); int right_corner_width = BS_MIN((int)width, (int)style_ptr->corner_width); int left_corner_width = BS_MAX(0, BS_MIN((int)width - right_corner_width, (int)style_ptr->corner_width)); int center_width = BS_MAX( 0, (int)width - right_corner_width - left_corner_width); if (!wlmtk_resizebar_area_redraw( resizebar_ptr->left_area_ptr, resizebar_ptr->gfxbuf_ptr, 0, left_corner_width, style_ptr)) { return false; } if (!wlmtk_resizebar_area_redraw( resizebar_ptr->center_area_ptr, resizebar_ptr->gfxbuf_ptr, left_corner_width, center_width, style_ptr)) { return false; } if (!wlmtk_resizebar_area_redraw( resizebar_ptr->right_area_ptr, resizebar_ptr->gfxbuf_ptr, left_corner_width + center_width, right_corner_width, style_ptr)) { return false; } wlmtk_element_set_visible( wlmtk_resizebar_area_element(resizebar_ptr->left_area_ptr), 0 < left_corner_width); wlmtk_element_set_visible( wlmtk_resizebar_area_element(resizebar_ptr->center_area_ptr), 0 < center_width); wlmtk_element_set_visible( wlmtk_resizebar_area_element(resizebar_ptr->right_area_ptr), 0 < right_corner_width); wlmtk_element_layout(wlmtk_box_element(&resizebar_ptr->super_box)); wlmtk_element_invalidate_parent_layout( wlmtk_box_element(&resizebar_ptr->super_box)); return true; } /* ------------------------------------------------------------------------- */ /** Redraws the resizebar's background in appropriate size. */ bool redraw_buffers(wlmtk_resizebar_t *resizebar_ptr, const struct wlmtk_resizebar_style *style_ptr, unsigned width) { cairo_t *cairo_ptr; bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(width, style_ptr->height); if (NULL == gfxbuf_ptr) return false; cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) { bs_gfxbuf_destroy(gfxbuf_ptr); return false; } wlmaker_primitives_cairo_fill(cairo_ptr, &style_ptr->fill); cairo_destroy(cairo_ptr); if (NULL != resizebar_ptr->gfxbuf_ptr) { bs_gfxbuf_destroy(resizebar_ptr->gfxbuf_ptr); } resizebar_ptr->gfxbuf_ptr = gfxbuf_ptr; resizebar_ptr->width = width; return true; } /* == Unit tests =========================================================== */ static void test_variable_width(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_resizebar_test_cases[] = { { 1, "variable_width", test_variable_width }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_resizebar_test_set = BS_TEST_SET( true, "resizebar", _wlmtk_resizebar_test_cases); /* ------------------------------------------------------------------------- */ /** Performs resizing and verifies the elements are shown as expected. */ void test_variable_width(bs_test_t *test_ptr) { wlmtk_window_t *w = wlmtk_test_window_create(NULL); struct wlmtk_resizebar_style style = { .height = 10, .corner_width = 17 }; wlmtk_resizebar_t *resizebar_ptr = wlmtk_resizebar_create(w, &style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, resizebar_ptr); wlmtk_element_t *left_elem_ptr = wlmtk_resizebar_area_element( resizebar_ptr->left_area_ptr); wlmtk_element_t *center_elem_ptr = wlmtk_resizebar_area_element( resizebar_ptr->center_area_ptr); wlmtk_element_t *right_elem_ptr = wlmtk_resizebar_area_element( resizebar_ptr->right_area_ptr); // Zero width. Zero visibility. BS_TEST_VERIFY_FALSE(test_ptr, left_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, right_elem_ptr->visible); // Not enough space for the center element with all margins. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 33)); BS_TEST_VERIFY_EQ(test_ptr, 10, resizebar_ptr->gfxbuf_ptr->height); BS_TEST_VERIFY_TRUE(test_ptr, left_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 16, right_elem_ptr->x); // Sufficient space for all the elements. style = (struct wlmtk_resizebar_style){ .height = 7, .corner_width = 16 }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_resizebar_set_style(resizebar_ptr, &style)); BS_TEST_VERIFY_EQ(test_ptr, 7, resizebar_ptr->gfxbuf_ptr->height); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 33)); BS_TEST_VERIFY_TRUE(test_ptr, left_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, center_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 16, center_elem_ptr->x); BS_TEST_VERIFY_EQ(test_ptr, 17, right_elem_ptr->x); // Not enough space for the center element with all margins. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 32)); BS_TEST_VERIFY_TRUE(test_ptr, left_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 16, right_elem_ptr->x); // Not enough space for center and left element. BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_resizebar_set_width(resizebar_ptr, 16)); BS_TEST_VERIFY_FALSE(test_ptr, left_elem_ptr->visible); BS_TEST_VERIFY_FALSE(test_ptr, center_elem_ptr->visible); BS_TEST_VERIFY_TRUE(test_ptr, right_elem_ptr->visible); BS_TEST_VERIFY_EQ(test_ptr, 0, right_elem_ptr->x); wlmtk_element_destroy(wlmtk_resizebar_element(resizebar_ptr)); wlmtk_window_destroy(w); } /* == End of resizebar.c =================================================== */ wlmaker-0.8.1/src/toolkit/buffer.c0000644000175100017510000002375215222270710016470 0ustar runnerrunner/* ========================================================================= */ /** * @file buffer.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "buffer.h" #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include "gfxbuf.h" // IWYU pragma: keep #include "input.h" #include "libbase/libbase.h" #include "util.h" /* == Declarations ========================================================= */ static struct wlr_scene_node *_wlmtk_buffer_element_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr); static void _wlmtk_buffer_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr); static bool _wlmtk_buffer_element_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr); static void _wlmtk_buffer_handle_wlr_scene_buffer_node_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmtk_buffer_handle_element_pointer_enter( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Method table for the buffer's virtual methods. */ static const wlmtk_element_vmt_t buffer_element_vmt = { .create_scene_node = _wlmtk_buffer_element_create_scene_node, .get_dimensions = _wlmtk_buffer_element_get_dimensions, .pointer_accepts_motion = _wlmtk_buffer_element_pointer_accepts_motion, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool wlmtk_buffer_init(wlmtk_buffer_t *buffer_ptr) { BS_ASSERT(NULL != buffer_ptr); *buffer_ptr = (wlmtk_buffer_t){}; if (!wlmtk_element_init(&buffer_ptr->super_element)) { return false; } buffer_ptr->orig_super_element_vmt = wlmtk_element_extend( &buffer_ptr->super_element, &buffer_element_vmt); wlmtk_util_connect_listener_signal( &buffer_ptr->super_element.events.pointer_enter, &buffer_ptr->element_pointer_enter_listener, &_wlmtk_buffer_handle_element_pointer_enter); return true; } /* ------------------------------------------------------------------------- */ void wlmtk_buffer_fini(wlmtk_buffer_t *buffer_ptr) { if (NULL != buffer_ptr->wlr_buffer_ptr) { wlr_buffer_unlock(buffer_ptr->wlr_buffer_ptr); buffer_ptr->wlr_buffer_ptr = NULL; } if (NULL != buffer_ptr->wlr_scene_buffer_ptr) { wlr_scene_node_destroy(&buffer_ptr->wlr_scene_buffer_ptr->node); buffer_ptr->wlr_scene_buffer_ptr = NULL; } wlmtk_util_disconnect_listener( &buffer_ptr->element_pointer_enter_listener); wlmtk_element_fini(&buffer_ptr->super_element); } /* ------------------------------------------------------------------------- */ void wlmtk_buffer_set( wlmtk_buffer_t *buffer_ptr, struct wlr_buffer *wlr_buffer_ptr) { if (wlr_buffer_ptr == buffer_ptr->wlr_buffer_ptr) return; if (NULL != buffer_ptr->wlr_buffer_ptr) { wlr_buffer_unlock(buffer_ptr->wlr_buffer_ptr); } if (NULL != wlr_buffer_ptr) { buffer_ptr->wlr_buffer_ptr = wlr_buffer_lock(wlr_buffer_ptr); } else { buffer_ptr->wlr_buffer_ptr = NULL; } if (NULL != buffer_ptr->wlr_scene_buffer_ptr) { wlr_scene_buffer_set_buffer( buffer_ptr->wlr_scene_buffer_ptr, buffer_ptr->wlr_buffer_ptr); } } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_buffer_element(wlmtk_buffer_t *buffer_ptr) { return &buffer_ptr->super_element; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Implementation of the superclass wlmtk_element_t::create_scene_node method. * * Creates a `struct wlr_scene_buffer` attached to `wlr_scene_tree_ptr`. * * @param element_ptr * @param wlr_scene_tree_ptr */ struct wlr_scene_node *_wlmtk_buffer_element_create_scene_node( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr) { wlmtk_buffer_t *buffer_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_buffer_t, super_element); BS_ASSERT(NULL == buffer_ptr->wlr_scene_buffer_ptr); buffer_ptr->wlr_scene_buffer_ptr = wlr_scene_buffer_create( wlr_scene_tree_ptr, buffer_ptr->wlr_buffer_ptr); BS_ASSERT(NULL != buffer_ptr->wlr_scene_buffer_ptr); wlmtk_util_connect_listener_signal( &buffer_ptr->wlr_scene_buffer_ptr->node.events.destroy, &buffer_ptr->wlr_scene_buffer_node_destroy_listener, _wlmtk_buffer_handle_wlr_scene_buffer_node_destroy); return &buffer_ptr->wlr_scene_buffer_ptr->node; } /* ------------------------------------------------------------------------- */ /** * Implementation of the element's get_dimensions method: Return dimensions. * * @param element_ptr * @param left_ptr Leftmost position. May be NULL. * @param top_ptr Topmost position. May be NULL. * @param right_ptr Rightmost position. Ma be NULL. * @param bottom_ptr Bottommost position. May be NULL. */ void _wlmtk_buffer_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr) { wlmtk_buffer_t *buffer_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_buffer_t, super_element); if (NULL != left_ptr) *left_ptr = 0; if (NULL != top_ptr) *top_ptr = 0; if (NULL == buffer_ptr->wlr_buffer_ptr) { if (NULL != right_ptr) *right_ptr = 0; if (NULL != bottom_ptr) *bottom_ptr = 0; return; } if (NULL != right_ptr) *right_ptr = buffer_ptr->wlr_buffer_ptr->width; if (NULL != bottom_ptr) *bottom_ptr = buffer_ptr->wlr_buffer_ptr->height; } /* ------------------------------------------------------------------------- */ /** * Returns true if a buffer is provided, and the motion happens within the * buffer's dimensions. * * @param element_ptr * @param motion_event_ptr * * @return true if (x, y) is within the buffer's dimensions. */ bool _wlmtk_buffer_element_pointer_accepts_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr) { wlmtk_buffer_t *buffer_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_buffer_t, super_element); return (NULL != buffer_ptr->wlr_buffer_ptr && 0 <= motion_event_ptr->x && motion_event_ptr->x < buffer_ptr->wlr_buffer_ptr->width && 0 <= motion_event_ptr->y && motion_event_ptr->y < buffer_ptr->wlr_buffer_ptr->height); } /* ------------------------------------------------------------------------- */ /** * Handles the 'destroy' callback of wlr_scene_buffer_ptr->node. * * Will reset the wlr_scene_buffer_ptr value. Destruction of the node had * been triggered (hence the callback). * * @param listener_ptr * @param data_ptr */ void _wlmtk_buffer_handle_wlr_scene_buffer_node_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmtk_buffer_t *buffer_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_buffer_t, wlr_scene_buffer_node_destroy_listener); buffer_ptr->wlr_scene_buffer_ptr = NULL; wlmtk_util_disconnect_listener(&buffer_ptr->wlr_scene_buffer_node_destroy_listener); } /* ------------------------------------------------------------------------- */ /** Handles when pointer enters the area: Sets the configured cursor. */ void _wlmtk_buffer_handle_element_pointer_enter( struct wl_listener *listener_ptr, void *data_ptr) { wlmtk_buffer_t *buffer_ptr = BS_CONTAINER_OF( listener_ptr, wlmtk_buffer_t, element_pointer_enter_listener); wlmtk_pointer_set_cursor(data_ptr, buffer_ptr->pointer_cursor); } /* == Unit Tests =========================================================== */ static void test_pointer_motion(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_buffer_test_cases[] = { { 1, "pointer_motion", test_pointer_motion }, BS_TEST_CASE_SENTINEL(), }; const bs_test_set_t wlmtk_buffer_test_set = BS_TEST_SET( true, "buffer", _wlmtk_buffer_test_cases); /* ------------------------------------------------------------------------- */ /** Tests behaviour of @ref _wlmtk_buffer_element_pointer_accepts_motion. */ void test_pointer_motion(bs_test_t *test_ptr) { wlmtk_buffer_t buffer; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, wlmtk_buffer_init(&buffer)); wlmtk_element_t *e = wlmtk_buffer_element(&buffer); wlmtk_element_set_visible(e, true); wlmtk_pointer_motion_event_t mev = { .x = 5, .y = 10 }; // Must not crash before a wlr_buffer is provided. BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer(10, 20); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_buffer_ptr); wlmtk_buffer_set(&buffer, wlr_buffer_ptr); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); mev = (wlmtk_pointer_motion_event_t){ .x = 10, .y = 20 }; BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_element_pointer_motion(e, &mev)); wlr_buffer_drop(wlr_buffer_ptr); wlmtk_buffer_fini(&buffer); } /* == End of buffer.c ====================================================== */ wlmaker-0.8.1/src/toolkit/CMakeLists.txt0000644000175100017510000000446415222270710017612 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) set(public_header_files base.h bordered.h box.h buffer.h button.h container.h dock.h element.h fsm.h gfxbuf.h image.h input.h layer.h menu.h menu_item.h output_tracker.h panel.h popup.h primitives.h rectangle.h resizebar.h resizebar_area.h root.h style.h surface.h test.h tile.h titlebar.h titlebar_button.h titlebar_title.h toolkit.h util.h window.h workspace.h ) add_library(toolkit STATIC) target_sources(toolkit PRIVATE base.c bordered.c box.c buffer.c button.c container.c dock.c element.c fsm.c gfxbuf.c image.c input.c layer.c menu.c menu_item.c output_tracker.c panel.c popup.c primitives.c rectangle.c resizebar.c resizebar_area.c root.c style.c surface.c test.c tile.c titlebar.c titlebar_button.c titlebar_title.c util.c window.c workspace.c ) target_include_directories( toolkit PUBLIC "${WLROOTS_INCLUDE_DIRS}" "${CAIRO_INCLUDE_DIRS}" "${PROJECT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}/third_party/protocols" ) target_include_directories( toolkit PRIVATE "${PROJECT_SOURCE_DIR}/include/toolkit" ) set_target_properties( toolkit PROPERTIES VERSION 1.0 PUBLIC_HEADER "${public_header_files}" ) add_dependencies( toolkit protocol_headers) target_compile_options( toolkit PRIVATE "${WAYLAND_SERVER_CFLAGS}" "${WAYLAND_SERVER_CFLAGS_OTHER}" ) target_link_libraries( toolkit PUBLIC libbase PkgConfig::CAIRO PkgConfig::WLROOTS PRIVATE PkgConfig::WAYLAND_SERVER libbase_plist ) if(iwyu_path_and_options) set_target_properties( toolkit PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8.1/src/toolkit/titlebar_title.c0000644000175100017510000004337615222270710020232 0ustar runnerrunner/* ========================================================================= */ /** * @file titlebar_title.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "titlebar_title.h" #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #undef WLR_USE_UNSTABLE #include "buffer.h" #include "gfxbuf.h" // IWYU pragma: keep #include "input.h" #include "menu.h" #include "primitives.h" #include "style.h" #include "test.h" // IWYU pragma: keep #include "tile.h" #include "workspace.h" /* == Declarations ========================================================= */ /** State of the title bar's title. */ struct _wlmtk_titlebar_title_t { /** Superclass: Buffer. */ wlmtk_buffer_t super_buffer; /** Pointer to the window the title element belongs to. */ wlmtk_window_t *window_ptr; /** The drawn title, when focussed. */ struct wlr_buffer *focussed_wlr_buffer_ptr; /** The drawn title, when blurred. */ struct wlr_buffer *blurred_wlr_buffer_ptr; }; static void _wlmtk_titlebar_title_element_destroy( wlmtk_element_t *element_ptr); static bool _wlmtk_titlebar_title_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static bool _wlmtk_titlebar_title_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); static void title_set_activated( wlmtk_titlebar_title_t *titlebar_title_ptr, bool activated); struct wlr_buffer *title_create_buffer( bs_gfxbuf_t *gfxbuf_ptr, unsigned position, unsigned width, uint32_t text_color, const char *title_ptr, const struct wlmtk_titlebar_style *style_ptr); /* == Data ================================================================= */ /** Extension to the superclass elment's virtual method table. */ static const wlmtk_element_vmt_t titlebar_title_element_vmt = { .destroy = _wlmtk_titlebar_title_element_destroy, .pointer_button = _wlmtk_titlebar_title_element_pointer_button, .pointer_axis = _wlmtk_titlebar_title_element_pointer_axis, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmtk_titlebar_title_t *wlmtk_titlebar_title_create(wlmtk_window_t *window_ptr) { wlmtk_titlebar_title_t *titlebar_title_ptr = logged_calloc( 1, sizeof(wlmtk_titlebar_title_t)); if (NULL == titlebar_title_ptr) return NULL; titlebar_title_ptr->window_ptr = window_ptr; if (!wlmtk_buffer_init(&titlebar_title_ptr->super_buffer)) { wlmtk_titlebar_title_destroy(titlebar_title_ptr); return NULL; } wlmtk_element_extend( &titlebar_title_ptr->super_buffer.super_element, &titlebar_title_element_vmt); return titlebar_title_ptr; } /* ------------------------------------------------------------------------- */ void wlmtk_titlebar_title_destroy(wlmtk_titlebar_title_t *titlebar_title_ptr) { wlr_buffer_drop_nullify(&titlebar_title_ptr->focussed_wlr_buffer_ptr); wlr_buffer_drop_nullify(&titlebar_title_ptr->blurred_wlr_buffer_ptr); wlmtk_buffer_fini(&titlebar_title_ptr->super_buffer); free(titlebar_title_ptr); } /* ------------------------------------------------------------------------- */ bool wlmtk_titlebar_title_redraw( wlmtk_titlebar_title_t *titlebar_title_ptr, bs_gfxbuf_t *focussed_gfxbuf_ptr, bs_gfxbuf_t *blurred_gfxbuf_ptr, int position, int width, bool activated, const char *title_ptr, const struct wlmtk_titlebar_style *style_ptr) { BS_ASSERT(focussed_gfxbuf_ptr->width == blurred_gfxbuf_ptr->width); BS_ASSERT(style_ptr->height == focussed_gfxbuf_ptr->height); BS_ASSERT(style_ptr->height == blurred_gfxbuf_ptr->height); BS_ASSERT(position <= (int)focussed_gfxbuf_ptr->width); BS_ASSERT(position + width <= (int)focussed_gfxbuf_ptr->width); if (NULL == title_ptr) title_ptr = ""; struct wlr_buffer *focussed_wlr_buffer_ptr = title_create_buffer( focussed_gfxbuf_ptr, position, width, style_ptr->focussed_text_color, title_ptr, style_ptr); struct wlr_buffer *blurred_wlr_buffer_ptr = title_create_buffer( blurred_gfxbuf_ptr, position, width, style_ptr->blurred_text_color, title_ptr, style_ptr); if (NULL == focussed_wlr_buffer_ptr || NULL == blurred_wlr_buffer_ptr) { wlr_buffer_drop_nullify(&focussed_wlr_buffer_ptr); wlr_buffer_drop_nullify(&blurred_wlr_buffer_ptr); return false; } wlr_buffer_drop_nullify(&titlebar_title_ptr->focussed_wlr_buffer_ptr); titlebar_title_ptr->focussed_wlr_buffer_ptr = focussed_wlr_buffer_ptr; wlr_buffer_drop_nullify(&titlebar_title_ptr->blurred_wlr_buffer_ptr); titlebar_title_ptr->blurred_wlr_buffer_ptr = blurred_wlr_buffer_ptr; title_set_activated(titlebar_title_ptr, activated); return true; } /* ------------------------------------------------------------------------- */ void wlmtk_titlebar_title_set_activated( wlmtk_titlebar_title_t *titlebar_title_ptr, bool activated) { title_set_activated(titlebar_title_ptr, activated); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmtk_titlebar_title_element( wlmtk_titlebar_title_t *titlebar_title_ptr) { return &titlebar_title_ptr->super_buffer.super_element; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Dtor. */ void _wlmtk_titlebar_title_element_destroy( wlmtk_element_t *element_ptr) { wlmtk_titlebar_title_t *titlebar_title_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_titlebar_title_t, super_buffer.super_element); wlmtk_titlebar_title_destroy(titlebar_title_ptr); } /* ------------------------------------------------------------------------- */ /** See @ref wlmtk_element_vmt_t::pointer_button. */ bool _wlmtk_titlebar_title_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmtk_titlebar_title_t *titlebar_title_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_titlebar_title_t, super_buffer.super_element); if (BTN_LEFT == button_event_ptr->button && WLMTK_BUTTON_DOWN == button_event_ptr->type) { wlmtk_workspace_begin_window_move( wlmtk_window_get_workspace(titlebar_title_ptr->window_ptr), titlebar_title_ptr->window_ptr); return true; } if (BTN_RIGHT == button_event_ptr->button && WLMTK_BUTTON_DOWN == button_event_ptr->type) { wlmtk_window_menu_set_enabled( titlebar_title_ptr->window_ptr, true); return true; } return true; } /* ------------------------------------------------------------------------- */ /** * Handles pointer axis events: Scroll wheel up will shade, down will unshade. * * @param element_ptr * @param wlr_pointer_axis_event_ptr * * @return true, if the axis event was consumed. That is the case if it's * source is a scroll wheel, and the orientation is vertical. */ bool _wlmtk_titlebar_title_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr) { wlmtk_titlebar_title_t *titlebar_title_ptr = BS_CONTAINER_OF( element_ptr, wlmtk_titlebar_title_t, super_buffer.super_element); // Only consider vertical wheel moves. if ( (WL_POINTER_AXIS_SOURCE_WHEEL != wlr_pointer_axis_event_ptr->source && WL_POINTER_AXIS_SOURCE_FINGER != wlr_pointer_axis_event_ptr->source) || WL_POINTER_AXIS_VERTICAL_SCROLL != wlr_pointer_axis_event_ptr->orientation ) { return false; } if (wlr_pointer_axis_event_ptr->delta > 0) { wlmtk_window_request_shaded(titlebar_title_ptr->window_ptr, false); } else if (wlr_pointer_axis_event_ptr->delta < 0) { wlmtk_window_request_shaded(titlebar_title_ptr->window_ptr, true); } return true; } /* ------------------------------------------------------------------------- */ /** * Sets whether the title is drawn focussed (activated) or blurred. * * @param titlebar_title_ptr * @param activated */ void title_set_activated( wlmtk_titlebar_title_t *titlebar_title_ptr, bool activated) { wlmtk_buffer_set( &titlebar_title_ptr->super_buffer, activated ? titlebar_title_ptr->focussed_wlr_buffer_ptr : titlebar_title_ptr->blurred_wlr_buffer_ptr); } /* ------------------------------------------------------------------------- */ /** * Creates a WLR buffer with the title's texture, as specified. * * @param gfxbuf_ptr * @param position * @param width * @param text_color * @param title_ptr * @param style_ptr * * @return A pointer to a `struct wlr_buffer` with the texture. */ struct wlr_buffer *title_create_buffer( bs_gfxbuf_t *gfxbuf_ptr, unsigned position, unsigned width, uint32_t text_color, const char *title_ptr, const struct wlmtk_titlebar_style *style_ptr) { BS_ASSERT(NULL != title_ptr); struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer( width, style_ptr->height); if (NULL == wlr_buffer_ptr) return NULL; bs_gfxbuf_copy_area( bs_gfxbuf_from_wlr_buffer(wlr_buffer_ptr), 0, 0, gfxbuf_ptr, position, 0, width, style_ptr->height); cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); return NULL; } wlmaker_primitives_draw_bezel_at( cairo_ptr, 0, 0, width, style_ptr->height, style_ptr->bezel_width, true); wlmaker_primitives_draw_window_title( cairo_ptr, &style_ptr->font, title_ptr, text_color); cairo_destroy(cairo_ptr); return wlr_buffer_ptr; } /* == Unit tests =========================================================== */ static void test_title(bs_test_t *test_ptr); static void test_shade(bs_test_t *test_ptr); /** Test cases */ static const bs_test_case_t _wlmtk_titlebar_title_test_cases[] = { // TODO(kaeser@gubbe.ch): Re-enable, once figuring out why this fails on // Trixie when running as a github action. { 0, "title", test_title }, { 1, "shade", test_shade }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmtk_titlebar_title_test_set = BS_TEST_SET( true, "titlebar_title", _wlmtk_titlebar_title_test_cases); /* ------------------------------------------------------------------------- */ /** Tests title drawing. */ void test_title(bs_test_t *test_ptr) { const struct wlmtk_titlebar_style style = { .focussed_text_color = 0xffc0c0c0, .blurred_text_color = 0xff808080, .height = 22, .font = { .face = "Helvetica", .weight = WLMTK_FONT_WEIGHT_BOLD, .size = 15, }, .bezel_width = 1 }; struct wl_display *display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create(display_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_output_layout_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( wlr_output_layout_ptr, "t", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_workspace_enable(ws_ptr, true); bs_gfxbuf_t *focussed_gfxbuf_ptr = bs_gfxbuf_create(120, 22); bs_gfxbuf_t *blurred_gfxbuf_ptr = bs_gfxbuf_create(120, 22); bs_gfxbuf_clear(focussed_gfxbuf_ptr, 0xff2020c0); bs_gfxbuf_clear(blurred_gfxbuf_ptr, 0xff404040); wlmtk_window_t *w = wlmtk_test_window_create(NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_workspace_map_window(ws_ptr, w); wlmtk_titlebar_title_t *title_ptr = wlmtk_titlebar_title_create(w); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, title_ptr); wlmtk_element_t *element_ptr = wlmtk_titlebar_title_element( title_ptr); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_titlebar_title_redraw( title_ptr, focussed_gfxbuf_ptr, blurred_gfxbuf_ptr, 10, 90, true, "Title", &style)); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(title_ptr->focussed_wlr_buffer_ptr), "toolkit/title_focussed.png"); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(title_ptr->blurred_wlr_buffer_ptr), "toolkit/title_blurred.png"); // We had started as "activated", verify that's correct. wlmtk_buffer_t *super_buffer_ptr = &title_ptr->super_buffer; BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), "toolkit/title_focussed.png"); // De-activated the title. Verify that was propagated. title_set_activated(title_ptr, false); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), "toolkit/title_blurred.png"); // Redraw with shorter width. Verify that's still correct. wlmtk_titlebar_title_redraw( title_ptr, focussed_gfxbuf_ptr, blurred_gfxbuf_ptr, 10, 70, false, "Title", &style); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG( test_ptr, bs_gfxbuf_from_wlr_buffer(super_buffer_ptr->wlr_buffer_ptr), "toolkit/title_blurred_short.png"); // Pressing the left button should trigger a move, not window menu. wlmtk_button_event_t button = { .button = BTN_LEFT, .type = WLMTK_BUTTON_DOWN }; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &button)); BS_TEST_VERIFY_FALSE( test_ptr, wlmtk_menu_is_open(wlmtk_window_menu(w))); // TODO(kaeser@gubbe.ch): We don't have a good way to test whether that // triggered the begin of a window move. // Pressing the right button should enable the window menu. wlmtk_window_set_activated(w, true); button.button = BTN_RIGHT; BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_element_pointer_button(element_ptr, &button)); BS_TEST_VERIFY_TRUE( test_ptr, wlmtk_menu_is_open(wlmtk_window_menu(w))); wlmtk_element_destroy(element_ptr); wlmtk_workspace_unmap_window(ws_ptr, w); wlmtk_window_destroy(w); bs_gfxbuf_destroy(focussed_gfxbuf_ptr); bs_gfxbuf_destroy(blurred_gfxbuf_ptr); wlmtk_workspace_destroy(ws_ptr); wl_display_destroy(display_ptr); } /* ------------------------------------------------------------------------- */ /** Tests that axis actions trigger 'shade'. */ void test_shade(bs_test_t *test_ptr) { wlmtk_fake_element_t *fe_ptr = wlmtk_fake_element_create(); wlmtk_window_t *w = wlmtk_test_window_create(&fe_ptr->element); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, w); wlmtk_titlebar_title_t *title_ptr = wlmtk_titlebar_title_create(w); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, title_ptr); wlmtk_element_t *element_ptr = wlmtk_titlebar_title_element(title_ptr); // Initial state: Not shaded. BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(w)); struct wlr_pointer_axis_event axis_event = { .source = WL_POINTER_AXIS_SOURCE_WHEEL, .orientation = WL_POINTER_AXIS_VERTICAL_SCROLL, .delta = -0.01 }; // Initial state: Not server-side-decorated, won't shade. wlmtk_element_pointer_axis(element_ptr, &axis_event); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(w)); // Decorate. Now it shall shade. wlmtk_window_set_server_side_decorated(w, true); wlmtk_element_pointer_axis(element_ptr, &axis_event); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_shaded(w)); // Scroll the other way: Unshade. axis_event.delta = 0.01; wlmtk_element_pointer_axis(element_ptr, &axis_event); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(w)); // Source 'finger from a touchpad' is accepted, too. axis_event.source = WL_POINTER_AXIS_SOURCE_FINGER; axis_event.delta = -0.01; wlmtk_element_pointer_axis(element_ptr, &axis_event); BS_TEST_VERIFY_TRUE(test_ptr, wlmtk_window_is_shaded(w)); axis_event.delta = 0.01; wlmtk_element_pointer_axis(element_ptr, &axis_event); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(w)); // Axis from another source: Ignored. axis_event.source = WL_POINTER_AXIS_SOURCE_WHEEL_TILT; axis_event.delta = -0.01; wlmtk_element_pointer_axis(element_ptr, &axis_event); BS_TEST_VERIFY_FALSE(test_ptr, wlmtk_window_is_shaded(w)); wlmtk_titlebar_title_destroy(title_ptr); wlmtk_window_destroy(w); wlmtk_element_destroy(&fe_ptr->element); } /* == End of titlebar_title.c ============================================== */ wlmaker-0.8.1/src/action_item.c0000644000175100017510000002634615222270710016027 0ustar runnerrunner/* ========================================================================= */ /** * @file action_item.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "action_item.h" #include #include #include #include #include #include "root_menu.h" /* == Declarations ========================================================= */ /** State of an action item that triggers a @ref wlmaker_action_t. */ struct _wlmaker_action_item_t { /** Composed from a menu item. */ wlmtk_menu_item_t *menu_item_ptr; /** Action to execute when triggered. */ wlmaker_action_t action; /** Argument for the action. May be NULL. */ char *action_arg_ptr; /** Back-link to @ref wlmaker_server_t, for executing the action. */ wlmaker_server_t *server_ptr; /** Listener for @ref wlmtk_menu_item_events_t::triggered. */ struct wl_listener triggered_listener; /** Listener for @ref wlmtk_menu_item_events_t::destroy. */ struct wl_listener destroy_listener; }; static void _wlmaker_action_item_destroy( wlmaker_action_item_t *action_item_ptr); static void _wlmaker_action_item_handle_triggered( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_action_item_handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_action_item_t *wlmaker_action_item_create( const char *text_ptr, wlmtk_menu_style_ref_t *style_ref_ptr, wlmaker_action_t action, const char *action_arg_ptr, wlmaker_server_t *server_ptr) { wlmaker_action_item_t *action_item_ptr = logged_calloc( 1, sizeof(wlmaker_action_item_t)); if (NULL == action_item_ptr) return NULL; action_item_ptr->action = action; if (NULL != action_arg_ptr) { action_item_ptr->action_arg_ptr = logged_strdup(action_arg_ptr); if (NULL == action_item_ptr->action_arg_ptr) { _wlmaker_action_item_destroy(action_item_ptr); return NULL; } } action_item_ptr->server_ptr = server_ptr; action_item_ptr->menu_item_ptr = wlmtk_menu_item_create(style_ref_ptr); if (NULL == action_item_ptr->menu_item_ptr) { _wlmaker_action_item_destroy(action_item_ptr); return NULL; } wlmtk_util_connect_listener_signal( &wlmtk_menu_item_events(action_item_ptr->menu_item_ptr)->triggered, &action_item_ptr->triggered_listener, _wlmaker_action_item_handle_triggered); wlmtk_util_connect_listener_signal( &wlmtk_menu_item_events(action_item_ptr->menu_item_ptr)->destroy, &action_item_ptr->destroy_listener, _wlmaker_action_item_handle_destroy); if (!wlmtk_menu_item_set_text(action_item_ptr->menu_item_ptr, text_ptr)) { _wlmaker_action_item_destroy(action_item_ptr); return NULL; } return action_item_ptr; } /* ------------------------------------------------------------------------- */ wlmaker_action_item_t *wlmaker_action_item_create_from_desc( const wlmaker_action_item_desc_t *desc_ptr, void *dest_ptr, wlmtk_menu_style_ref_t *style_ref_ptr, wlmaker_server_t *server_ptr) { wlmaker_action_item_t *action_item_ptr = wlmaker_action_item_create( desc_ptr->text_ptr, style_ref_ptr, desc_ptr->action, NULL, server_ptr); if (NULL == action_item_ptr) return NULL; *(wlmaker_action_item_t**)( (uint8_t*)dest_ptr + desc_ptr->destination_ofs) = action_item_ptr; return action_item_ptr; } /* ------------------------------------------------------------------------- */ wlmtk_menu_item_t *wlmaker_action_item_menu_item( wlmaker_action_item_t *action_item_ptr) { return action_item_ptr->menu_item_ptr; } /* ------------------------------------------------------------------------- */ bool wlmaker_menu_item_bind_action( wlmtk_menu_item_t* menu_item_ptr, wlmaker_action_t action, const char *action_arg_ptr, wlmaker_server_t *server_ptr) { wlmaker_action_item_t *action_item_ptr = logged_calloc( 1, sizeof(wlmaker_action_item_t)); if (NULL == action_item_ptr) return false; action_item_ptr->action = action; if (NULL != action_arg_ptr) { action_item_ptr->action_arg_ptr = logged_strdup(action_arg_ptr); if (NULL == action_item_ptr->action_arg_ptr) { _wlmaker_action_item_destroy(action_item_ptr); return false; } } action_item_ptr->server_ptr = server_ptr; action_item_ptr->menu_item_ptr = BS_ASSERT_NOTNULL(menu_item_ptr); wlmtk_util_connect_listener_signal( &wlmtk_menu_item_events(menu_item_ptr)->triggered, &action_item_ptr->triggered_listener, _wlmaker_action_item_handle_triggered); wlmtk_util_connect_listener_signal( &wlmtk_menu_item_events(menu_item_ptr)->destroy, &action_item_ptr->destroy_listener, _wlmaker_action_item_handle_destroy); return true; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** dtor for @ref wlmaker_action_item_t. */ void _wlmaker_action_item_destroy(wlmaker_action_item_t *action_item_ptr) { if (NULL != action_item_ptr->menu_item_ptr) { wlmtk_util_disconnect_listener(&action_item_ptr->destroy_listener); wlmtk_util_disconnect_listener(&action_item_ptr->triggered_listener); wlmtk_menu_item_destroy(action_item_ptr->menu_item_ptr); action_item_ptr->menu_item_ptr = NULL; } if (NULL != action_item_ptr->action_arg_ptr) { free(action_item_ptr->action_arg_ptr); action_item_ptr->action_arg_ptr = NULL; } free(action_item_ptr); } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_menu_item_events_t::triggered. Triggers the action */ void _wlmaker_action_item_handle_triggered( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_action_item_t *action_item_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_action_item_t, triggered_listener); wlmaker_action_execute( action_item_ptr->server_ptr, action_item_ptr->action, action_item_ptr->action_arg_ptr); if (NULL != action_item_ptr->server_ptr->root_menu_ptr) { wlmtk_menu_set_open( wlmaker_root_menu_menu(action_item_ptr->server_ptr->root_menu_ptr), false); } } /* ------------------------------------------------------------------------- */ /** Handles @ref wlmtk_menu_item_events_t::destroy. Destroy the action item. */ void _wlmaker_action_item_handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_action_item_t *action_item_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_action_item_t, destroy_listener); // Clear the reference to the menu item. It is already being destroyed. wlmtk_util_disconnect_listener(&action_item_ptr->destroy_listener); wlmtk_util_disconnect_listener(&action_item_ptr->triggered_listener); action_item_ptr->menu_item_ptr = NULL; _wlmaker_action_item_destroy(action_item_ptr); } /* == Unit tests =========================================================== */ static void _wlmaker_action_item_test_create(bs_test_t *test_ptr); static void _wlmaker_action_item_test_menu_dtor(bs_test_t *test_ptr); static void _wlmaker_action_item_test_bind(bs_test_t *test_ptr); /** Test cases for action items. */ static const bs_test_case_t wlmaker_action_item_test_cases[] = { { true, "create", _wlmaker_action_item_test_create }, { true, "menu_dtor", _wlmaker_action_item_test_menu_dtor }, { true, "bind", _wlmaker_action_item_test_bind }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_action_item_test_set = BS_TEST_SET( true, "action_item", wlmaker_action_item_test_cases); /** Test data: style for the menu item. */ static const struct wlmtk_menu_style _wlmaker_action_item_menu_style = {}; /** Test data: Descriptor for the action item used in tests. */ static const wlmaker_action_item_desc_t _wlmaker_action_item_desc = { "text", 42, NULL, 0 }; /* ------------------------------------------------------------------------- */ /** Tests creation the menu item. */ void _wlmaker_action_item_test_create(bs_test_t *test_ptr) { wlmaker_action_item_t *ai_ptr = NULL; wlmaker_server_t server = {}; struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _wlmaker_action_item_menu_style; BS_TEST_VERIFY_TRUE( test_ptr, wlmaker_action_item_create_from_desc( &_wlmaker_action_item_desc, &ai_ptr, wlmtk_menu_style_to_ref(s), &server)); BS_TEST_VERIFY_NEQ(test_ptr, NULL, ai_ptr); _wlmaker_action_item_destroy(ai_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* ------------------------------------------------------------------------- */ /** Tests that dtors are called as deisred from the menu. */ void _wlmaker_action_item_test_menu_dtor(bs_test_t *test_ptr) { wlmtk_menu_t *menu_ptr; wlmaker_action_item_t *ai_ptr; wlmaker_server_t server = {}; struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _wlmaker_action_item_menu_style; menu_ptr = wlmtk_menu_create(wlmtk_menu_style_to_ref(s)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_ptr); ai_ptr = wlmaker_action_item_create_from_desc( &_wlmaker_action_item_desc, &ai_ptr, wlmtk_menu_style_to_ref(s), &server); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ai_ptr); wlmtk_menu_add_item(menu_ptr, wlmaker_action_item_menu_item(ai_ptr)); wlmtk_menu_destroy(menu_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* ------------------------------------------------------------------------- */ /** Tests that binding works and cleanup leaves no leaks. */ void _wlmaker_action_item_test_bind(bs_test_t *test_ptr) { struct wlmtk_menu_style *s = wlmtk_menu_style_create(); *s = _wlmaker_action_item_menu_style; wlmtk_menu_item_t *menu_item_ptr = wlmtk_menu_item_create( wlmtk_menu_style_to_ref(s)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, menu_item_ptr); BS_TEST_VERIFY_TRUE( test_ptr, wlmaker_menu_item_bind_action(menu_item_ptr, 42, 0, NULL)); wlmtk_menu_item_destroy(menu_item_ptr); wlmtk_menu_style_ref_release(wlmtk_menu_style_to_ref(s)); } /* == End of action_item.c ================================================= */ wlmaker-0.8.1/src/lock_mgr.h0000644000175100017510000000320315222270710015321 0ustar runnerrunner/* ========================================================================= */ /** * @file lock_mgr.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LOCK_MGR_H__ #define __LOCK_MGR_H__ #include /** Forward declaration: State of the session lock manager. */ typedef struct _wlmaker_lock_mgr_t wlmaker_lock_mgr_t; #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates the session lock manager. * * @param server_ptr * * @return The session lock manager handler or NULL on error. */ wlmaker_lock_mgr_t *wlmaker_lock_mgr_create( wlmaker_server_t *server_ptr); /** * Destroys the session lock manager. * * @param lock_mgr_ptr */ void wlmaker_lock_mgr_destroy(wlmaker_lock_mgr_t *lock_mgr_ptr); /** Unit test set for @ref wlmaker_lock_mgr_t. */ extern const bs_test_set_t wlmaker_lock_mgr_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LOCK_MGR_H__ */ /* == End of lock_mgr.h ==================================================== */ wlmaker-0.8.1/src/action.c0000644000175100017510000010035115222270710014776 0ustar runnerrunner/* ========================================================================= */ /** * @file action.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "action.h" #include #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include "backend/backend.h" #include "background.h" #include "config.h" #include "default_configuration.h" #include "idle.h" #include "input/keyboard.h" #include "input/manager.h" #include "root_menu.h" #include "server.h" #include "subprocess_monitor.h" #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** State of the bound actions. */ struct _wlmaker_action_handle_t { /** Bindings, linked by @ref _wlmaker_action_binding_t::qnode. */ bs_dequeue_t bindings; /** Back-link to server state. */ wlmaker_server_t *server_ptr; /** Whether to add 'Logo' to the bindings. */ bool add_logo; }; /** Key binding for a standard action. */ typedef struct { /** Node of @ref wlmaker_action_handle_t::bindings. */ bs_dequeue_node_t qnode; /** The key binding. */ struct wlmim_keybinding_combo combo; /** The associated action. */ wlmaker_action_t action; /** Optional argument for the action (eg. command to execute, NULL when unset). */ char *action_arg_ptr; /** The key binding it to this node. */ wlmim_keybinding_t *keybinding_ptr; /** State of the bound actions. */ wlmaker_action_handle_t *handle_ptr; } _wlmaker_action_binding_t; static bool _wlmaker_keybindings_parse( const char *string_ptr, uint32_t *modifiers_ptr, xkb_keysym_t *keysym_ptr); static bool _wlmaker_keybindings_bind_item( const char *key_ptr, bspl_object_t *object_ptr, void *userdata_ptr); static bool _wlmaker_action_bound_callback( const struct wlmim_keybinding_combo *binding_ptr); static bool _wlmaker_action_theme_load_from_file( wlmaker_server_t *server_ptr, const char *fname_ptr); /* == Data ================================================================= */ /** Key to lookup the dict from the config dictionary. */ const char *wlmaker_action_config_dict_key = "KeyBindings"; /** The actions that can be bound. */ const bspl_enum_desc_t wlmaker_action_desc[] = { BSPL_ENUM("None", WLMAKER_ACTION_NONE), BSPL_ENUM("Quit", WLMAKER_ACTION_QUIT), BSPL_ENUM("LockScreen", WLMAKER_ACTION_LOCK_SCREEN), BSPL_ENUM("InhibitLockBegin", WLMAKER_ACTION_LOCK_INHIBIT_BEGIN), BSPL_ENUM("InhibitLockEnd", WLMAKER_ACTION_LOCK_INHIBIT_END), BSPL_ENUM("LaunchTerminal", WLMAKER_ACTION_LAUNCH_TERMINAL), BSPL_ENUM("ShellExecute", WLMAKER_ACTION_SHELL_EXECUTE), BSPL_ENUM("Execute", WLMAKER_ACTION_EXECUTE), BSPL_ENUM("WorkspacePrevious", WLMAKER_ACTION_WORKSPACE_TO_PREVIOUS), BSPL_ENUM("WorkspaceNext", WLMAKER_ACTION_WORKSPACE_TO_NEXT), BSPL_ENUM("WorkspaceAdd", WLMAKER_ACTION_WORKSPACE_ADD), BSPL_ENUM("WorkspaceDestroyLast", WLMAKER_ACTION_WORKSPACE_DESTROY_LAST), BSPL_ENUM("TaskPrevious", WLMAKER_ACTION_TASK_TO_PREVIOUS), BSPL_ENUM("TaskNext", WLMAKER_ACTION_TASK_TO_NEXT), BSPL_ENUM("WindowRaise", WLMAKER_ACTION_WINDOW_RAISE), BSPL_ENUM("WindowLower", WLMAKER_ACTION_WINDOW_LOWER), BSPL_ENUM("WindowToggleFullscreen", WLMAKER_ACTION_WINDOW_TOGGLE_FULLSCREEN), BSPL_ENUM("WindowToggleMaximized", WLMAKER_ACTION_WINDOW_TOGGLE_MAXIMIZED), BSPL_ENUM("WindowMaximize", WLMAKER_ACTION_WINDOW_MAXIMIZE), BSPL_ENUM("WindowUnmaximize", WLMAKER_ACTION_WINDOW_UNMAXIMIZE), BSPL_ENUM("WindowFullscreen", WLMAKER_ACTION_WINDOW_FULLSCREEN), BSPL_ENUM("WindowShade", WLMAKER_ACTION_WINDOW_SHADE), BSPL_ENUM("WindowUnshade", WLMAKER_ACTION_WINDOW_UNSHADE), BSPL_ENUM("WindowClose", WLMAKER_ACTION_WINDOW_CLOSE), BSPL_ENUM("WindowToNextWorkspace", WLMAKER_ACTION_WINDOW_TO_NEXT_WORKSPACE), BSPL_ENUM("WindowToPreviousWorkspace", WLMAKER_ACTION_WINDOW_TO_PREVIOUS_WORKSPACE), BSPL_ENUM("RootMenu", WLMAKER_ACTION_ROOT_MENU), BSPL_ENUM("ThemeLoadFromFile", WLMAKER_ACTION_THEME_LOAD_FROM_FILE), BSPL_ENUM("OutputMagnify", WLMAKER_ACTION_OUTPUT_MAGNIFY), BSPL_ENUM("OutputReduce", WLMAKER_ACTION_OUTPUT_REDUCE), BSPL_ENUM("OutputSaveState", WLMAKER_ACTION_OUTPUT_SAVE_STATE), BSPL_ENUM("SwitchToVT1", WLMAKER_ACTION_SWITCH_TO_VT1), BSPL_ENUM("SwitchToVT2", WLMAKER_ACTION_SWITCH_TO_VT2), BSPL_ENUM("SwitchToVT3", WLMAKER_ACTION_SWITCH_TO_VT3), BSPL_ENUM("SwitchToVT4", WLMAKER_ACTION_SWITCH_TO_VT4), BSPL_ENUM("SwitchToVT5", WLMAKER_ACTION_SWITCH_TO_VT5), BSPL_ENUM("SwitchToVT6", WLMAKER_ACTION_SWITCH_TO_VT6), BSPL_ENUM("SwitchToVT7", WLMAKER_ACTION_SWITCH_TO_VT7), BSPL_ENUM("SwitchToVT8", WLMAKER_ACTION_SWITCH_TO_VT8), BSPL_ENUM("SwitchToVT9", WLMAKER_ACTION_SWITCH_TO_VT9), BSPL_ENUM("SwitchToVT10", WLMAKER_ACTION_SWITCH_TO_VT10), BSPL_ENUM("SwitchToVT11", WLMAKER_ACTION_SWITCH_TO_VT11), BSPL_ENUM("SwitchToVT12", WLMAKER_ACTION_SWITCH_TO_VT12), // A duplicate to ShellExecute, permits `wmmenugen` compatibility. BSPL_ENUM("SHEXEC", WLMAKER_ACTION_SHELL_EXECUTE), // A duplicate to Execute, permits compatibility with Window Maker. BSPL_ENUM("EXEC", WLMAKER_ACTION_EXECUTE), BSPL_ENUM_SENTINEL(), }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_action_handle_t *wlmaker_action_bind_keys( wlmaker_server_t *server_ptr, bspl_dict_t *keybindings_dict_ptr, bool add_logo) { wlmaker_action_handle_t *handle_ptr = logged_calloc( 1, sizeof(wlmaker_action_handle_t)); if (NULL == handle_ptr) return NULL; handle_ptr->server_ptr = server_ptr; handle_ptr->add_logo = add_logo; if (bspl_dict_foreach( keybindings_dict_ptr, _wlmaker_keybindings_bind_item, handle_ptr)) { return handle_ptr; } wlmaker_action_unbind_keys(handle_ptr); return NULL; } /* ------------------------------------------------------------------------- */ void wlmaker_action_unbind_keys(wlmaker_action_handle_t *handle_ptr) { bs_dequeue_node_t *qnode_ptr = handle_ptr->bindings.head_ptr; while (NULL != qnode_ptr) { _wlmaker_action_binding_t *binding_ptr = BS_CONTAINER_OF( qnode_ptr, _wlmaker_action_binding_t, qnode); qnode_ptr = qnode_ptr->next_ptr; wlmim_unbind_key( handle_ptr->server_ptr->input_manager_ptr, binding_ptr->keybinding_ptr); free(binding_ptr->action_arg_ptr); free(binding_ptr); } free(handle_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_action_execute(wlmaker_server_t *server_ptr, wlmaker_action_t action, void *arg_ptr) { wlmtk_workspace_t *workspace_ptr, *next_workspace_ptr; wlmtk_window_t *window_ptr; const char **argv; switch (action) { case WLMAKER_ACTION_NONE: break; case WLMAKER_ACTION_QUIT: wl_display_terminate(server_ptr->wl_display_ptr); break; case WLMAKER_ACTION_LOCK_SCREEN: if (NULL != server_ptr->idle_monitor_ptr) { wlmaker_idle_monitor_lock(server_ptr->idle_monitor_ptr); } break; case WLMAKER_ACTION_LOCK_INHIBIT_BEGIN: wlmaker_idle_monitor_inhibit(server_ptr->idle_monitor_ptr); break; case WLMAKER_ACTION_LOCK_INHIBIT_END: wlmaker_idle_monitor_uninhibit(server_ptr->idle_monitor_ptr); break; case WLMAKER_ACTION_LAUNCH_TERMINAL: argv = (const char*[]){ "/bin/sh", "-c", "/usr/bin/foot", NULL }; wlmaker_subprocess_monitor_run( server_ptr->monitor_ptr, bs_subprocess_create(argv[0], argv, NULL)); break; case WLMAKER_ACTION_SHELL_EXECUTE: argv = (const char*[]){ "/bin/sh", "-c", arg_ptr, NULL }; wlmaker_subprocess_monitor_run( server_ptr->monitor_ptr, bs_subprocess_create(argv[0], argv, NULL)); break; case WLMAKER_ACTION_EXECUTE: wlmaker_subprocess_monitor_run( server_ptr->monitor_ptr, bs_subprocess_create_cmdline(arg_ptr)); break; case WLMAKER_ACTION_WORKSPACE_TO_PREVIOUS: wlmtk_root_switch_to_previous_workspace(server_ptr->root_ptr); break; case WLMAKER_ACTION_WORKSPACE_TO_NEXT: wlmtk_root_switch_to_next_workspace(server_ptr->root_ptr); break; case WLMAKER_ACTION_WORKSPACE_ADD: workspace_ptr = wlmtk_workspace_create( server_ptr->wlr_output_layout_ptr, "New", &server_ptr->style_ptr->tile); if (NULL != workspace_ptr) { bs_dllist_node_t *bg_ptr = wlmaker_background_create( workspace_ptr, server_ptr->wlr_output_layout_ptr, server_ptr->style_ptr->background_color); if (NULL != bg_ptr) { bs_dllist_push_back(&server_ptr->backgrounds, bg_ptr); wlmtk_root_add_workspace(server_ptr->root_ptr, workspace_ptr); } else { wlmtk_workspace_destroy(workspace_ptr); } } break; case WLMAKER_ACTION_WORKSPACE_DESTROY_LAST: // TODO(kaeser@gubbe.ch): This leaks the corresponding background. The // workspace dtor will remove the associated element via the // panel dtor, but it remains in @ref wlmaker_server_t::backgrounds. wlmtk_root_destroy_last_workspace(server_ptr->root_ptr); break; case WLMAKER_ACTION_TASK_TO_PREVIOUS: wlmtk_workspace_activate_previous_window( wlmtk_root_get_current_workspace(server_ptr->root_ptr)); wlmaker_server_activate_task_list(server_ptr); break; case WLMAKER_ACTION_TASK_TO_NEXT: wlmtk_workspace_activate_next_window( wlmtk_root_get_current_workspace(server_ptr->root_ptr)); wlmaker_server_activate_task_list(server_ptr); break; case WLMAKER_ACTION_WINDOW_RAISE: // TODO(kaeser@gubbe.ch): (re)implement using toolkit. bs_log(BS_WARNING, "Raise window: Unimplemented."); break; case WLMAKER_ACTION_WINDOW_LOWER: // TODO(kaeser@gubbe.ch): (re)implement using toolkit. bs_log(BS_WARNING, "Lower window: Unimplemented."); break; case WLMAKER_ACTION_WINDOW_TOGGLE_FULLSCREEN: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_window_request_fullscreen( window_ptr, !wlmtk_window_is_fullscreen(window_ptr)); } break; case WLMAKER_ACTION_WINDOW_TOGGLE_MAXIMIZED: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_window_request_maximized( window_ptr, !wlmtk_window_is_maximized(window_ptr)); } break; case WLMAKER_ACTION_WINDOW_MAXIMIZE: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_window_request_maximized(window_ptr, true); } break; case WLMAKER_ACTION_WINDOW_UNMAXIMIZE: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_window_request_maximized(window_ptr, false); } break; case WLMAKER_ACTION_WINDOW_FULLSCREEN: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_window_request_fullscreen(window_ptr, true); } break; case WLMAKER_ACTION_WINDOW_SHADE: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_window_request_shaded(window_ptr, true); } break; case WLMAKER_ACTION_WINDOW_UNSHADE: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_window_request_shaded(window_ptr, false); } break; case WLMAKER_ACTION_WINDOW_TO_NEXT_WORKSPACE: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); next_workspace_ptr = wlmtk_workspace_from_dlnode( wlmtk_dlnode_from_workspace(workspace_ptr)->next_ptr); if (NULL != window_ptr && NULL != next_workspace_ptr) { wlmtk_workspace_unmap_window(workspace_ptr, window_ptr); wlmtk_workspace_map_window(next_workspace_ptr, window_ptr); } break; case WLMAKER_ACTION_WINDOW_TO_PREVIOUS_WORKSPACE: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); next_workspace_ptr = wlmtk_workspace_from_dlnode( wlmtk_dlnode_from_workspace(workspace_ptr)->prev_ptr); if (NULL != window_ptr && NULL != next_workspace_ptr) { wlmtk_workspace_unmap_window(workspace_ptr, window_ptr); wlmtk_workspace_map_window(next_workspace_ptr, window_ptr); } break; case WLMAKER_ACTION_WINDOW_CLOSE: workspace_ptr = wlmtk_root_get_current_workspace( server_ptr->root_ptr); window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_window_request_close(window_ptr); } break; case WLMAKER_ACTION_ROOT_MENU: // TODO(kaeser@gubbe.ch): Clean up. if (NULL != server_ptr->root_menu_ptr && NULL == wlmtk_window_get_workspace( wlmaker_root_menu_window(server_ptr->root_menu_ptr))) { wlmtk_workspace_map_window( wlmtk_root_get_current_workspace(server_ptr->root_ptr), wlmaker_root_menu_window(server_ptr->root_menu_ptr)); wlmtk_workspace_set_window_position( wlmtk_root_get_current_workspace(server_ptr->root_ptr), wlmaker_root_menu_window(server_ptr->root_menu_ptr), wlmim_wlr_cursor(server_ptr->input_manager_ptr)->x, wlmim_wlr_cursor(server_ptr->input_manager_ptr)->y); wlmtk_workspace_confine_within( wlmtk_root_get_current_workspace(server_ptr->root_ptr), wlmaker_root_menu_window(server_ptr->root_menu_ptr)); wlmtk_menu_set_mode( wlmaker_root_menu_menu(server_ptr->root_menu_ptr), WLMTK_MENU_MODE_NORMAL); wlmtk_menu_set_open( wlmaker_root_menu_menu(server_ptr->root_menu_ptr), true); } break; case WLMAKER_ACTION_THEME_LOAD_FROM_FILE: _wlmaker_action_theme_load_from_file(server_ptr, arg_ptr); break; case WLMAKER_ACTION_OUTPUT_MAGNIFY: wlmbe_backend_magnify(server_ptr->backend_ptr); break; case WLMAKER_ACTION_OUTPUT_REDUCE: wlmbe_backend_reduce(server_ptr->backend_ptr); break; case WLMAKER_ACTION_OUTPUT_SAVE_STATE: wlmbe_backend_save_ephemeral_output_configs(server_ptr->backend_ptr); break; case WLMAKER_ACTION_SWITCH_TO_VT1: case WLMAKER_ACTION_SWITCH_TO_VT2: case WLMAKER_ACTION_SWITCH_TO_VT3: case WLMAKER_ACTION_SWITCH_TO_VT4: case WLMAKER_ACTION_SWITCH_TO_VT5: case WLMAKER_ACTION_SWITCH_TO_VT6: case WLMAKER_ACTION_SWITCH_TO_VT7: case WLMAKER_ACTION_SWITCH_TO_VT8: case WLMAKER_ACTION_SWITCH_TO_VT9: case WLMAKER_ACTION_SWITCH_TO_VT10: case WLMAKER_ACTION_SWITCH_TO_VT11: case WLMAKER_ACTION_SWITCH_TO_VT12: // Enums are required to be defined consecutively, so we can compute // the VT number from the action code. wlmbe_backend_switch_to_vt( server_ptr->backend_ptr, action - WLMAKER_ACTION_SWITCH_TO_VT1 + 1); break; default: bs_log(BS_WARNING, "Unhandled action %d.", action); break; } } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Binds an action for one item of the 'KeyBindings' dict. * * Supports two configuration formats: * - Array format: `"Key" = (Action, "command");` for actions with arguments. * - String format: `"Key" = Action;` for simple actions without arguments. * * @param key_ptr The key binding string (eg. "Logo+Return"). * @param object_ptr Configuration value: a string with the action * name, or an array with action and argument. * @param userdata_ptr Points to @ref wlmaker_action_handle_t. * * @return true on success. */ bool _wlmaker_keybindings_bind_item( const char *key_ptr, bspl_object_t *object_ptr, void *userdata_ptr) { wlmaker_action_handle_t *handle_ptr = userdata_ptr; const char *action_name_ptr = NULL; char *action_arg_ptr = NULL; // Extract action name and optional argument from the configuration value. bspl_array_t *array_ptr = bspl_array_from_object(object_ptr); if (NULL != array_ptr) { // Array format: (Action, "command"). The command argument is optional. action_name_ptr = bspl_array_string_value_at(array_ptr, 0); if (NULL == action_name_ptr) { bs_log(BS_WARNING, "Key binding \"%s\": first element must be a string.", key_ptr); return false; } // Second element is the command argument, may be absent. const char *arg_ptr = bspl_array_string_value_at(array_ptr, 1); if (NULL != arg_ptr) { action_arg_ptr = logged_strdup(arg_ptr); if (NULL == action_arg_ptr) return false; } } else { // String format: Action name only, no argument. bspl_string_t *string_ptr = bspl_string_from_object(object_ptr); if (NULL == string_ptr) { bs_log(BS_WARNING, "Key binding \"%s\": must be a string or array.", key_ptr); return false; } action_name_ptr = bspl_string_value(string_ptr); } // Parse the key binding string into modifiers and keysym. uint32_t modifiers; xkb_keysym_t keysym; if (!_wlmaker_keybindings_parse(key_ptr, &modifiers, &keysym)) { bs_log(BS_WARNING, "Failed to parse binding '%s' for keybinding action '%s'", key_ptr, action_name_ptr); free(action_arg_ptr); return false; } if (handle_ptr->add_logo) modifiers |= WLR_MODIFIER_LOGO; // Lookup the action enum value from the action name. int action; if (!bspl_enum_name_to_value( wlmaker_action_desc, action_name_ptr, &action)) { bs_log(BS_WARNING, "Not a valid keybinding action: '%s'", action_name_ptr); free(action_arg_ptr); return false; } // Create and populate the binding structure. _wlmaker_action_binding_t *action_binding_ptr = logged_calloc( 1, sizeof(_wlmaker_action_binding_t)); if (NULL == action_binding_ptr) { free(action_arg_ptr); return false; } action_binding_ptr->handle_ptr = handle_ptr; action_binding_ptr->action = action; action_binding_ptr->action_arg_ptr = action_arg_ptr; action_binding_ptr->combo.keysym = keysym; action_binding_ptr->combo.ignore_case = true; action_binding_ptr->combo.modifiers = modifiers; action_binding_ptr->combo.modifiers_mask = wlmim_modifiers_default_mask; // Register the key binding with the server. action_binding_ptr->keybinding_ptr = wlmim_bind_key( handle_ptr->server_ptr->input_manager_ptr, &action_binding_ptr->combo, _wlmaker_action_bound_callback); if (NULL == action_binding_ptr->keybinding_ptr) { free(action_binding_ptr->action_arg_ptr); free(action_binding_ptr); return false; } bs_dequeue_push_back(&handle_ptr->bindings, &action_binding_ptr->qnode); return true; } /* ------------------------------------------------------------------------- */ /** * Parses a keybinding string: Tokenizes into modifiers and keysym. * * @param string_ptr * @param modifiers_ptr * @param keysym_ptr * * @return true on success. */ bool _wlmaker_keybindings_parse( const char *string_ptr, uint32_t *modifiers_ptr, xkb_keysym_t *keysym_ptr) { *keysym_ptr = XKB_KEY_NoSymbol; *modifiers_ptr = 0; bool rv = true; // Tokenize along '+', then lookup each of the keys. for (const char *start_ptr = string_ptr; *start_ptr != '\0'; ++start_ptr) { const char *end_ptr = start_ptr; while (*end_ptr != '\0' && *end_ptr != '+') ++end_ptr; size_t len = end_ptr - start_ptr; char *token_ptr = malloc(len + 1); memcpy(token_ptr, start_ptr, len); token_ptr[len] = '\0'; start_ptr = end_ptr; int new_modifier; if (bspl_enum_name_to_value( wlmim_keyboard_modifiers, token_ptr, &new_modifier)) { *modifiers_ptr |= new_modifier; } else if (*keysym_ptr == XKB_KEY_NoSymbol) { *keysym_ptr = xkb_keysym_to_upper( xkb_keysym_from_name(token_ptr, XKB_KEYSYM_CASE_INSENSITIVE)); } else { rv = false; } free(token_ptr); if (!*start_ptr) break; } return rv && (XKB_KEY_NoSymbol != *keysym_ptr); } /* ------------------------------------------------------------------------- */ /** * Callback for when the bound key is triggered: Executes the corresponding * action. * * @param combo_ptr * * @return true always. */ bool _wlmaker_action_bound_callback( const struct wlmim_keybinding_combo *combo_ptr) { _wlmaker_action_binding_t *action_binding_ptr = BS_CONTAINER_OF( combo_ptr, _wlmaker_action_binding_t, combo); wlmaker_action_execute( action_binding_ptr->handle_ptr->server_ptr, action_binding_ptr->action, action_binding_ptr->action_arg_ptr); return true; } /* ------------------------------------------------------------------------- */ /** Loads a theme file and applies it. */ bool _wlmaker_action_theme_load_from_file( wlmaker_server_t *server_ptr, const char *fname_ptr) { if (!wlmaker_theme_load( server_ptr->files_ptr, fname_ptr, server_ptr->style_ptr)) return false; // TODO(kaeser@gubbe.ch): Als update style for ... // - Dock // - Clip // - Cursor // - BackgroundColor // - Tasklist bool rv = true; rv &= wlmtk_root_set_style( server_ptr->root_ptr, wlmtk_window_style_to_ref(server_ptr->style_ptr->window_style_ptr), wlmtk_menu_style_to_ref(server_ptr->style_ptr->menu_style_ptr)); if (NULL != server_ptr->root_menu_ptr) { wlmtk_menu_set_style( wlmaker_root_menu_menu(server_ptr->root_menu_ptr), wlmtk_menu_style_to_ref(server_ptr->style_ptr->menu_style_ptr)); wlmtk_window_set_style( wlmaker_root_menu_window(server_ptr->root_menu_ptr), wlmtk_window_style_to_ref(server_ptr->style_ptr->window_style_ptr), wlmtk_menu_style_to_ref(server_ptr->style_ptr->menu_style_ptr)); } bs_dllist_for_each( &server_ptr->backgrounds, wlmaker_background_dlnode_set_color, &server_ptr->style_ptr->background_color); rv &= wlmim_set_style( server_ptr->input_manager_ptr, &server_ptr->style_ptr->cursor); wl_signal_emit(&server_ptr->theme_changed_event, server_ptr->style_ptr); return rv; } /* == Unit tests =========================================================== */ static void test_keybindings_parse(bs_test_t *test_ptr); static void test_default_keybindings(bs_test_t *test_ptr); static void test_keybindings_formats(bs_test_t *test_ptr); /** Test cases for key bindings. */ static const bs_test_case_t wlmaker_action_test_cases[] = { { true, "parse", test_keybindings_parse }, { true, "default_keybindings", test_default_keybindings }, { true, "formats", test_keybindings_formats }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_action_test_set = BS_TEST_SET( true, "action", wlmaker_action_test_cases); /* ------------------------------------------------------------------------- */ /** Tests @ref _wlmaker_keybindings_parse. */ void test_keybindings_parse(bs_test_t *test_ptr) { uint32_t m; xkb_keysym_t ks; // Lower- and upper case. BS_TEST_VERIFY_TRUE(test_ptr, _wlmaker_keybindings_parse("A", &m, &ks)); BS_TEST_VERIFY_EQ(test_ptr, 0, m); BS_TEST_VERIFY_EQ(test_ptr, XKB_KEY_A, ks); BS_TEST_VERIFY_TRUE(test_ptr, _wlmaker_keybindings_parse("a", &m, &ks)); BS_TEST_VERIFY_EQ(test_ptr, 0, m); BS_TEST_VERIFY_EQ(test_ptr, XKB_KEY_A, ks); // Modifier. BS_TEST_VERIFY_TRUE( test_ptr, _wlmaker_keybindings_parse("Ctrl+Logo+Q", &m, &ks)); BS_TEST_VERIFY_EQ(test_ptr, WLR_MODIFIER_CTRL | WLR_MODIFIER_LOGO, m); BS_TEST_VERIFY_EQ(test_ptr, XKB_KEY_Q, ks); // Test some fancier keys. BS_TEST_VERIFY_TRUE( test_ptr, _wlmaker_keybindings_parse("Escape", &m, &ks)); BS_TEST_VERIFY_EQ(test_ptr, XKB_KEY_Escape, ks); BS_TEST_VERIFY_TRUE( test_ptr, _wlmaker_keybindings_parse("XF86AudioLowerVolume", &m, &ks)); BS_TEST_VERIFY_EQ(test_ptr, XKB_KEY_XF86AudioLowerVolume, ks); // Not peritted: Empty, just modifiers, more than one keysym. BS_TEST_VERIFY_FALSE( test_ptr, _wlmaker_keybindings_parse("", &m, &ks)); BS_TEST_VERIFY_FALSE( test_ptr, _wlmaker_keybindings_parse("A+B", &m, &ks)); BS_TEST_VERIFY_FALSE( test_ptr, _wlmaker_keybindings_parse("Shift+Ctrl", &m, &ks)); } /* ------------------------------------------------------------------------- */ /** Tests the default configuration's 'KeyBindings' section. */ void test_default_keybindings(bs_test_t *test_ptr) { bspl_dict_t *d = bspl_dict_create(); wlmaker_server_t server = { .input_manager_ptr = wlmim_input_manager_create( NULL, NULL, NULL, d, NULL, NULL) }; bspl_object_t *obj_ptr = bspl_create_object_from_plist_data( embedded_binary_default_configuration_data, embedded_binary_default_configuration_size); BS_TEST_VERIFY_NEQ(test_ptr, NULL, bspl_dict_from_object(obj_ptr)); bspl_dict_t *dict_ptr = bspl_dict_get_dict( bspl_dict_from_object(obj_ptr), wlmaker_action_config_dict_key); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); wlmaker_action_handle_t *handle_ptr = wlmaker_action_bind_keys( &server, dict_ptr, false); BS_TEST_VERIFY_NEQ(test_ptr, NULL, handle_ptr); bspl_object_unref(obj_ptr); wlmaker_action_unbind_keys(handle_ptr); bspl_dict_unref(d); wlmim_input_manager_destroy(server.input_manager_ptr); } /* ------------------------------------------------------------------------- */ /** Tests string and array format keybindings. */ void test_keybindings_formats(bs_test_t *test_ptr) { bspl_dict_t *d = bspl_dict_create(); wlmaker_server_t server = { .input_manager_ptr = wlmim_input_manager_create( NULL, NULL, NULL, d, NULL, NULL) }; bspl_object_t *obj_ptr; bspl_dict_t *dict_ptr; wlmaker_action_handle_t *handle_ptr; // Test: String format (original format). const char *string_format = "{ \"Logo+q\" = Quit; \"Logo+l\" = LockScreen; }"; obj_ptr = bspl_create_object_from_plist_data( (const uint8_t*)string_format, strlen(string_format)); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); dict_ptr = bspl_dict_from_object(obj_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); handle_ptr = wlmaker_action_bind_keys(&server, dict_ptr, false); BS_TEST_VERIFY_NEQ(test_ptr, NULL, handle_ptr); wlmaker_action_unbind_keys(handle_ptr); bspl_object_unref(obj_ptr); // Test: Array format with command argument. const char *array_format = "{ \"Logo+Return\" = (Execute, \"/usr/bin/foot\"); }"; obj_ptr = bspl_create_object_from_plist_data( (const uint8_t*)array_format, strlen(array_format)); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); dict_ptr = bspl_dict_from_object(obj_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); handle_ptr = wlmaker_action_bind_keys(&server, dict_ptr, false); BS_TEST_VERIFY_NEQ(test_ptr, NULL, handle_ptr); wlmaker_action_unbind_keys(handle_ptr); bspl_object_unref(obj_ptr); // Test: Array format without command argument. const char *array_no_arg = "{ \"Logo+q\" = (Quit); }"; obj_ptr = bspl_create_object_from_plist_data( (const uint8_t*)array_no_arg, strlen(array_no_arg)); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); dict_ptr = bspl_dict_from_object(obj_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); handle_ptr = wlmaker_action_bind_keys(&server, dict_ptr, false); BS_TEST_VERIFY_NEQ(test_ptr, NULL, handle_ptr); wlmaker_action_unbind_keys(handle_ptr); bspl_object_unref(obj_ptr); // Test: Mixed formats in one dict. const char *mixed_format = "{ \"Logo+q\" = Quit; \"Logo+Return\" = (Execute, \"/bin/sh\"); }"; obj_ptr = bspl_create_object_from_plist_data( (const uint8_t*)mixed_format, strlen(mixed_format)); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); dict_ptr = bspl_dict_from_object(obj_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); handle_ptr = wlmaker_action_bind_keys(&server, dict_ptr, false); BS_TEST_VERIFY_NEQ(test_ptr, NULL, handle_ptr); wlmaker_action_unbind_keys(handle_ptr); bspl_object_unref(obj_ptr); // Test: Empty array should fail. const char *empty_array = "{ \"Logo+q\" = (); }"; obj_ptr = bspl_create_object_from_plist_data( (const uint8_t*)empty_array, strlen(empty_array)); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); dict_ptr = bspl_dict_from_object(obj_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); handle_ptr = wlmaker_action_bind_keys(&server, dict_ptr, false); BS_TEST_VERIFY_EQ(test_ptr, NULL, handle_ptr); bspl_object_unref(obj_ptr); // Test: Invalid action name should fail. const char *invalid_action = "{ \"Logo+q\" = NotAnAction; }"; obj_ptr = bspl_create_object_from_plist_data( (const uint8_t*)invalid_action, strlen(invalid_action)); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); dict_ptr = bspl_dict_from_object(obj_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); handle_ptr = wlmaker_action_bind_keys(&server, dict_ptr, false); BS_TEST_VERIFY_EQ(test_ptr, NULL, handle_ptr); bspl_object_unref(obj_ptr); bspl_dict_unref(d); wlmim_input_manager_destroy(server.input_manager_ptr); } /* == End of action.c ====================================================== */ wlmaker-0.8.1/src/files.c0000644000175100017510000001503515222270710014627 0ustar runnerrunner/* ========================================================================= */ /** * @file files.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "files.h" #include #include #include #include #include /* == Declarations ========================================================= */ /** State of the files module. */ struct _wlmaker_files_t { /** Handle for libxdg-basedir. */ xdgHandle xdg_handle; /** Directory name to use beneath the XDG__HOME paths. */ char *dirname_ptr; }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_files_t *wlmaker_files_create(const char *dirname_ptr) { wlmaker_files_t *files_ptr = logged_calloc(1, sizeof(wlmaker_files_t)); if (NULL == files_ptr) return NULL; files_ptr->dirname_ptr = logged_strdup(dirname_ptr); if (NULL == files_ptr->dirname_ptr) goto error; if (NULL == xdgInitHandle(&files_ptr->xdg_handle)) goto error; return files_ptr; error: wlmaker_files_destroy(files_ptr); return NULL; } /* ------------------------------------------------------------------------- */ void wlmaker_files_destroy(wlmaker_files_t *files_ptr) { if (NULL != files_ptr->dirname_ptr) { free(files_ptr->dirname_ptr); files_ptr->dirname_ptr = NULL; } xdgWipeHandle(&files_ptr->xdg_handle); free(files_ptr); } /* ------------------------------------------------------------------------- */ char *wlmaker_files_xdg_config_fname( wlmaker_files_t *files_ptr, const char *fname_ptr) { const char *config_home_ptr = xdgConfigHome(&files_ptr->xdg_handle); if (NULL == config_home_ptr) return NULL; return bs_strdupf( "%s/%s/%s", config_home_ptr, files_ptr->dirname_ptr, fname_ptr); } /* ------------------------------------------------------------------------- */ char *wlmaker_files_xdg_config_find( wlmaker_files_t *files_ptr, const char *fname_ptr, int mode_type) { const char * const *dirs_ptr = xdgSearchableConfigDirectories( &files_ptr->xdg_handle); if (NULL == dirs_ptr) return NULL; while (NULL != *dirs_ptr) { char *candidate_path_ptr = bs_strdupf( "%s/%s/%s", *dirs_ptr, files_ptr->dirname_ptr, fname_ptr); if (bs_file_realpath_is(candidate_path_ptr, mode_type)) { return candidate_path_ptr; } free(candidate_path_ptr); ++dirs_ptr; } return NULL; } /* ------------------------------------------------------------------------- */ char *wlmaker_files_xdg_data_find( wlmaker_files_t *files_ptr, const char *fname_ptr, int mode_type) { const char * const *dirs_ptr = xdgSearchableDataDirectories( &files_ptr->xdg_handle); if (NULL == dirs_ptr) return NULL; while (NULL != *dirs_ptr) { char *candidate_path_ptr = bs_strdupf( "%s/%s/%s", *dirs_ptr, files_ptr->dirname_ptr, fname_ptr); if (bs_file_realpath_is(candidate_path_ptr, mode_type)) { return candidate_path_ptr; } free(candidate_path_ptr); ++dirs_ptr; } return NULL; } /* == Unit Tests =========================================================== */ static void _wlmaker_files_test_builders(bs_test_t *test_ptr); static void _wlmaker_files_test_config_find(bs_test_t *test_ptr); static void _wlmaker_files_test_data_find(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_files_test_cases[] = { { true, "builders", _wlmaker_files_test_builders }, { true, "config_find", _wlmaker_files_test_config_find }, { true, "data_find", _wlmaker_files_test_data_find }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_files_test_set = BS_TEST_SET( true, "files", wlmaker_files_test_cases); /* ------------------------------------------------------------------------- */ /** Tests building filenames relative to XDG base directories. */ void _wlmaker_files_test_builders(bs_test_t *test_ptr) { wlmaker_files_t *files_ptr = wlmaker_files_create("wlmaker"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, files_ptr); char *f = wlmaker_files_xdg_config_fname(files_ptr, "state.plist"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, f); BS_TEST_VERIFY_STRMATCH(test_ptr, f, "/wlmaker/state.plist$"); free(f); wlmaker_files_destroy(files_ptr); } /* ------------------------------------------------------------------------- */ /** Tests finding a config. */ void _wlmaker_files_test_config_find(bs_test_t *test_ptr) { const char *p = bs_test_data_path(test_ptr, "subdir"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, p); bs_test_setenv(test_ptr, "XDG_CONFIG_DIRS", "%s", p); wlmaker_files_t *files_ptr = wlmaker_files_create("wlmaker"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, files_ptr); char *f = wlmaker_files_xdg_config_find(files_ptr, "a.txt", S_IFREG); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, f); BS_TEST_VERIFY_STRMATCH(test_ptr, f, "/wlmaker/a.txt$"); free(f); wlmaker_files_destroy(files_ptr); } /* ------------------------------------------------------------------------- */ /** Tests finding a data file. */ void _wlmaker_files_test_data_find(bs_test_t *test_ptr) { const char *p = bs_test_data_path(test_ptr, "subdir"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, p); bs_test_setenv(test_ptr, "XDG_DATA_DIRS", "%s", p); wlmaker_files_t *files_ptr = wlmaker_files_create("wlmaker"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, files_ptr); char *f = wlmaker_files_xdg_data_find(files_ptr, "a.txt", S_IFREG); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, f); BS_TEST_VERIFY_STRMATCH(test_ptr, f, "/wlmaker/a.txt$"); free(f); wlmaker_files_destroy(files_ptr); } /* == End of files.c ======================================================= */ wlmaker-0.8.1/src/subprocess_monitor.h0000644000175100017510000001064015222270710017466 0ustar runnerrunner/* ========================================================================= */ /** * @file subprocess_monitor.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __SUBPROCESS_MONITOR_H__ #define __SUBPROCESS_MONITOR_H__ #include #include /** Forward definition for the subprocess monitor. */ typedef struct _wlmaker_subprocess_monitor_t wlmaker_subprocess_monitor_t; /** Forward definition for a subprocess handle. */ typedef struct _wlmaker_subprocess_handle_t wlmaker_subprocess_handle_t; #include "task_list.h" #include "toolkit/toolkit.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Callback for then the subprocess is terminated. * * @param userdata_ptr * @param subprocess_handle_ptr * @param state * @param code */ typedef void (*wlmaker_subprocess_terminated_callback_t)( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, int state, int code); /** * Callback for when window events happened for the subprocess. * * @param userdata_ptr * @param subprocess_handle_ptr * @param window_ptr */ typedef void (*wlmaker_subprocess_window_callback_t)( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr); /** * Creates the subprocess monitor * * @param server_ptr * * @return Pointer to the subprocess monitor or NULL on error. */ wlmaker_subprocess_monitor_t* wlmaker_subprocess_monitor_create( wlmaker_server_t *server_ptr); /** * Destroys the subprocess monitor. * * @param monitor_ptr */ void wlmaker_subprocess_monitor_destroy( wlmaker_subprocess_monitor_t *monitor_ptr); /** * Starts, entrust and cedes a subprocess to the monitor. * * @param monitor_ptr * @param subprocess_ptr Subprocess. Ignored, if NULL. Takes ownership. * * @return true on success. */ bool wlmaker_subprocess_monitor_run( wlmaker_subprocess_monitor_t *monitor_ptr, bs_subprocess_t *subprocess_ptr); /** * Passes ownership of the started `subprocess_ptr` to `monitor_ptr`. * * Also registers a set of callbacks that will be triggered. Permits to keep * a central register of all started sub-processes, to monitor for termination, * and to link up connecting clients with the sub-processes. * * @param monitor_ptr * @param subprocess_ptr * @param terminated_callback * @param userdata_ptr * @param window_created_callback * @param window_mapped_callback * @param window_unmapped_callback * @param window_destroyed_callback * @param stdout_dynbuf_ptr * * @return A pointer to the created subprocess handle or NULL on error. */ wlmaker_subprocess_handle_t *wlmaker_subprocess_monitor_entrust( wlmaker_subprocess_monitor_t *monitor_ptr, bs_subprocess_t *subprocess_ptr, wlmaker_subprocess_terminated_callback_t terminated_callback, void *userdata_ptr, wlmaker_subprocess_window_callback_t window_created_callback, wlmaker_subprocess_window_callback_t window_mapped_callback, wlmaker_subprocess_window_callback_t window_unmapped_callback, wlmaker_subprocess_window_callback_t window_destroyed_callback, bs_dynbuf_t *stdout_dynbuf_ptr); /** * Releases the reference held on `subprocess_handle_ptr`. Once the subprocess * terminates, all corresponding resources will be freed. * * @param monitor_ptr * @param subprocess_handle_ptr */ void wlmaker_subprocess_monitor_cede( wlmaker_subprocess_monitor_t *monitor_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr); /** Returns the `bs_subprocess_t` from the @ref wlmaker_subprocess_handle_t. */ bs_subprocess_t *wlmaker_subprocess_from_subprocess_handle( wlmaker_subprocess_handle_t *subprocess_handle_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __SUBPROCESS_MONITOR_H__ */ /* == End of subprocess_monitor.h ========================================== */ wlmaker-0.8.1/src/launcher.c0000644000175100017510000005426715222270710015340 0ustar runnerrunner/* ========================================================================= */ /** * @file launcher.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "launcher.h" #include #include #include #include #include #include #include #include #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** State of a launcher. */ struct _wlmaker_launcher_t { /** The launcher is derived from a @ref wlmtk_tile_t. */ wlmtk_tile_t super_tile; /** Original virtual method table fo the element. */ wlmtk_element_vmt_t orig_element_vmt; /** Image element. One element of @ref wlmaker_launcher_t::super_tile. */ wlmtk_image_t *image_ptr; /** Overlay element. Atop on the tile. */ wlmtk_buffer_t overlay_buffer; /** Subprocess monitor to register launched processes to. */ wlmaker_subprocess_monitor_t *monitor_ptr; /** Commandline to launch the associated application. */ char *cmdline_ptr; /** Path to the icon. */ char *icon_path_ptr; /** Resolved icon path. */ char *resolved_icon_path_ptr; /** Windows that are running from subprocesses of this App (launcher). */ bs_ptr_set_t *created_windows_ptr; /** Windows that are mapped from subprocesses of this App (launcher). */ bs_ptr_set_t *mapped_windows_ptr; /** Subprocesses that were created by this launcher. */ bs_ptr_set_t *subprocesses_ptr; }; /** Plist descroptor for a launcher. */ static const bspl_desc_t _wlmaker_launcher_plist_desc[] = { BSPL_DESC_STRING( "CommandLine", true, wlmaker_launcher_t, cmdline_ptr, cmdline_ptr, ""), BSPL_DESC_STRING( "Icon", true, wlmaker_launcher_t, icon_path_ptr, icon_path_ptr, ""), BSPL_DESC_SENTINEL(), }; static void _wlmaker_launcher_update_overlay(wlmaker_launcher_t *launcher_ptr); static struct wlr_buffer *_wlmaker_launcher_create_overlay_buffer( wlmaker_launcher_t *launcher_ptr); static void _wlmaker_launcher_element_destroy( wlmtk_element_t *element_ptr); static bool _wlmaker_launcher_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); static void _wlmaker_launcher_start(wlmaker_launcher_t *launcher_ptr); static void _wlmaker_launcher_handle_terminated( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, int state, int code); static void _wlmaker_launcher_handle_window_created( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr); static void _wlmaker_launcher_handle_window_mapped( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr); static void _wlmaker_launcher_handle_window_unmapped( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr); static void _wlmaker_launcher_handle_window_destroyed( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr); static bool _wlmaker_launcher_set_content_size( wlmtk_tile_t *tile_ptr, uint64_t content_size); /* == Data ================================================================= */ /** The launcher's extension to @ref wlmtk_element_t virtual method table. */ static const wlmtk_element_vmt_t _wlmaker_launcher_element_vmt = { .destroy = _wlmaker_launcher_element_destroy, .pointer_button = _wlmaker_launcher_pointer_button, }; /** Launcher's extension to @ref wlmtk_tile_t virtual method table. */ static const wlmtk_tile_vmt_t _wlmaker_launcher_tile_vmt = { .set_content_size = _wlmaker_launcher_set_content_size, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_launcher_t *wlmaker_launcher_create_from_plist( const struct wlmtk_tile_style *style_ptr, bspl_dict_t *dict_ptr, wlmaker_subprocess_monitor_t *monitor_ptr, wlmaker_files_t *files_ptr) { wlmaker_launcher_t *launcher_ptr = logged_calloc( 1, sizeof(wlmaker_launcher_t)); if (NULL == launcher_ptr) return NULL; launcher_ptr->monitor_ptr = monitor_ptr; if (!wlmtk_tile_init(&launcher_ptr->super_tile, style_ptr)) { return NULL; } launcher_ptr->orig_element_vmt = wlmtk_element_extend( wlmtk_tile_element(&launcher_ptr->super_tile), &_wlmaker_launcher_element_vmt); wlmtk_tile_extend(&launcher_ptr->super_tile, &_wlmaker_launcher_tile_vmt); wlmtk_element_set_visible( wlmtk_tile_element(&launcher_ptr->super_tile), true); launcher_ptr->created_windows_ptr = bs_ptr_set_create(); if (NULL == launcher_ptr->created_windows_ptr) { wlmaker_launcher_destroy(launcher_ptr); return NULL; } launcher_ptr->mapped_windows_ptr = bs_ptr_set_create(); if (NULL == launcher_ptr->mapped_windows_ptr) { wlmaker_launcher_destroy(launcher_ptr); return NULL; } launcher_ptr->subprocesses_ptr = bs_ptr_set_create(); if (NULL == launcher_ptr->subprocesses_ptr) { wlmaker_launcher_destroy(launcher_ptr); return NULL; } if (!wlmtk_buffer_init(&launcher_ptr->overlay_buffer)) { wlmaker_launcher_destroy(launcher_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_buffer_element(&launcher_ptr->overlay_buffer), true); _wlmaker_launcher_update_overlay(launcher_ptr); wlmtk_tile_set_overlay( &launcher_ptr->super_tile, wlmtk_buffer_element(&launcher_ptr->overlay_buffer)); if (!bspl_decode_dict( dict_ptr, _wlmaker_launcher_plist_desc, launcher_ptr)) { bs_log(BS_ERROR, "Failed to create launcher from plist dict."); wlmaker_launcher_destroy(launcher_ptr); return NULL; } // Resolves to a full path, and verifies the icon file exists. char *p = bs_strdupf("icons/%s", launcher_ptr->icon_path_ptr); if (NULL == p) { bs_log(BS_ERROR | BS_ERRNO, "Failed bs_strdupf(\"icons/%s\")", launcher_ptr->icon_path_ptr); wlmaker_launcher_destroy(launcher_ptr); return NULL; } launcher_ptr->resolved_icon_path_ptr = wlmaker_files_xdg_data_find( files_ptr, p, S_IFREG); free(p); if (NULL == launcher_ptr->resolved_icon_path_ptr) { bs_log(BS_ERROR, "Failed to locate \"icons/%s\" in ${XDG_DATA_DIRS}/wlmaker", launcher_ptr->icon_path_ptr); #ifndef WLMAKER_SOURCE_DIR wlmaker_launcher_destroy(launcher_ptr); return NULL; #else launcher_ptr->resolved_icon_path_ptr = bs_strdupf( WLMAKER_SOURCE_DIR "/share/wlmaker/icons/%s", launcher_ptr->icon_path_ptr); if (NULL == launcher_ptr->resolved_icon_path_ptr) { wlmaker_launcher_destroy(launcher_ptr); return NULL; } #endif } launcher_ptr->image_ptr = wlmtk_image_create_scaled( launcher_ptr->resolved_icon_path_ptr, launcher_ptr->super_tile.style.content_size, launcher_ptr->super_tile.style.content_size); if (NULL == launcher_ptr->image_ptr) { wlmaker_launcher_destroy(launcher_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_image_element(launcher_ptr->image_ptr), true); wlmtk_tile_set_content( &launcher_ptr->super_tile, wlmtk_image_element(launcher_ptr->image_ptr)); return launcher_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_launcher_destroy(wlmaker_launcher_t *launcher_ptr) { if (NULL != launcher_ptr->image_ptr) { wlmtk_tile_set_content(&launcher_ptr->super_tile, NULL); wlmtk_image_destroy(launcher_ptr->image_ptr); launcher_ptr->image_ptr = NULL; } wlmtk_tile_set_overlay(&launcher_ptr->super_tile, NULL); wlmtk_buffer_fini(&launcher_ptr->overlay_buffer); if (NULL != launcher_ptr->subprocesses_ptr) { wlmaker_subprocess_handle_t *subprocess_handle_ptr; while (NULL != (subprocess_handle_ptr = bs_ptr_set_any( launcher_ptr->subprocesses_ptr))) { wlmaker_subprocess_monitor_cede( launcher_ptr->monitor_ptr, subprocess_handle_ptr); bs_ptr_set_erase(launcher_ptr->subprocesses_ptr, subprocess_handle_ptr); } bs_ptr_set_destroy(launcher_ptr->subprocesses_ptr); launcher_ptr->subprocesses_ptr = NULL; } if (NULL != launcher_ptr->mapped_windows_ptr) { bs_ptr_set_destroy(launcher_ptr->mapped_windows_ptr); launcher_ptr->mapped_windows_ptr = NULL; } if (NULL != launcher_ptr->created_windows_ptr) { bs_ptr_set_destroy(launcher_ptr->created_windows_ptr); launcher_ptr->created_windows_ptr = NULL; } if (NULL != launcher_ptr->resolved_icon_path_ptr) { free(launcher_ptr->resolved_icon_path_ptr); launcher_ptr->resolved_icon_path_ptr = NULL; } if (NULL != launcher_ptr->cmdline_ptr) { free(launcher_ptr->cmdline_ptr); launcher_ptr->cmdline_ptr = NULL; } if (NULL != launcher_ptr->icon_path_ptr) { free(launcher_ptr->icon_path_ptr); launcher_ptr->icon_path_ptr = NULL; } wlmtk_tile_fini(&launcher_ptr->super_tile); free(launcher_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_tile_t *wlmaker_launcher_tile(wlmaker_launcher_t *launcher_ptr) { return &launcher_ptr->super_tile; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Redraws the overlay element. */ void _wlmaker_launcher_update_overlay(wlmaker_launcher_t *launcher_ptr) { struct wlr_buffer *wlr_buffer_ptr = _wlmaker_launcher_create_overlay_buffer( launcher_ptr); if (NULL == wlr_buffer_ptr) return; wlmtk_buffer_set(&launcher_ptr->overlay_buffer, wlr_buffer_ptr); wlr_buffer_drop(wlr_buffer_ptr); } /* ------------------------------------------------------------------------- */ /** Creates an overlay wlr_buffer. */ struct wlr_buffer *_wlmaker_launcher_create_overlay_buffer( wlmaker_launcher_t *launcher_ptr) { int s = launcher_ptr->super_tile.style.size; struct wlr_buffer *wlr_buffer_ptr = bs_gfxbuf_create_wlr_buffer(s, s); if (NULL == wlr_buffer_ptr) return NULL; const char *status_ptr = NULL; if (!bs_ptr_set_empty(launcher_ptr->mapped_windows_ptr)) { status_ptr = "Running"; } else if (!bs_ptr_set_empty(launcher_ptr->created_windows_ptr)) { status_ptr = "Started"; } if (NULL == status_ptr) return wlr_buffer_ptr; cairo_t *cairo_ptr = cairo_create_from_wlr_buffer(wlr_buffer_ptr); if (NULL == cairo_ptr) { wlr_buffer_drop(wlr_buffer_ptr); return NULL; } float r, g, b, alpha; bs_gfxbuf_argb8888_to_floats(0xff12905a, &r, &g, &b, &alpha); cairo_pattern_t *cairo_pattern_ptr = cairo_pattern_create_rgba( r, g, b, alpha); cairo_set_source(cairo_ptr, cairo_pattern_ptr); cairo_pattern_destroy(cairo_pattern_ptr); cairo_rectangle(cairo_ptr, 0, s - 12 * s / 64, s, 12 * s / 64); cairo_fill(cairo_ptr); cairo_stroke(cairo_ptr); cairo_select_font_face(cairo_ptr, "Helvetica", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cairo_ptr, 10.0 * s / 64.0); cairo_set_source_argb8888(cairo_ptr, 0xffffffff); cairo_move_to(cairo_ptr, 4 * s / 64, s - 2 * s / 64); cairo_show_text(cairo_ptr, status_ptr); cairo_destroy(cairo_ptr); return wlr_buffer_ptr; } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::destroy. Calls * @ref wlmaker_launcher_destroy. * * @param element_ptr */ void _wlmaker_launcher_element_destroy( wlmtk_element_t *element_ptr) { wlmaker_launcher_t *launcher_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_launcher_t, super_tile.super_container.super_element); wlmaker_launcher_destroy(launcher_ptr); } /* ------------------------------------------------------------------------- */ /** * Implements @ref wlmtk_element_vmt_t::pointer_button. * * @param element_ptr * @param button_event_ptr * * @return true always. */ bool _wlmaker_launcher_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr) { wlmaker_launcher_t *launcher_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_launcher_t, super_tile.super_container.super_element); if (BTN_LEFT != button_event_ptr->button) return true; if (WLMTK_BUTTON_CLICK != button_event_ptr->type) return true; _wlmaker_launcher_start(launcher_ptr); return true; } /* ------------------------------------------------------------------------- */ /** * Starts the application, called when the launcher is clicked. * * @param launcher_ptr */ void _wlmaker_launcher_start(wlmaker_launcher_t *launcher_ptr) { bs_subprocess_t *subprocess_ptr = bs_subprocess_create_cmdline( launcher_ptr->cmdline_ptr); if (NULL == subprocess_ptr) { bs_log(BS_ERROR, "Failed bs_subprocess_create_cmdline(%s)", launcher_ptr->cmdline_ptr); return; } if (!bs_subprocess_start(subprocess_ptr)) { bs_log(BS_ERROR, "Failed bs_subprocess_start for %s", launcher_ptr->cmdline_ptr); bs_subprocess_destroy(subprocess_ptr); return; } wlmaker_subprocess_handle_t *subprocess_handle_ptr; subprocess_handle_ptr = wlmaker_subprocess_monitor_entrust( launcher_ptr->monitor_ptr, subprocess_ptr, _wlmaker_launcher_handle_terminated, launcher_ptr, _wlmaker_launcher_handle_window_created, _wlmaker_launcher_handle_window_mapped, _wlmaker_launcher_handle_window_unmapped, _wlmaker_launcher_handle_window_destroyed, NULL); if (!bs_ptr_set_insert(launcher_ptr->subprocesses_ptr, subprocess_handle_ptr)) { bs_log(BS_WARNING, "Launcher %p: Failed bs_ptr_set_insert(%p, %p). " "Will not show status of subprocess in App.", launcher_ptr, launcher_ptr->subprocesses_ptr, subprocess_handle_ptr); wlmaker_subprocess_monitor_cede( launcher_ptr->monitor_ptr, subprocess_handle_ptr); } } /* ------------------------------------------------------------------------- */ /** * Callback handler for when the registered subprocess terminates. * * @param userdata_ptr Points to @ref wlmaker_launcher_t. * @param subprocess_handle_ptr * @param exit_status * @param signal_number */ void _wlmaker_launcher_handle_terminated( void *userdata_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr, int exit_status, int signal_number) { wlmaker_launcher_t *launcher_ptr = userdata_ptr; const char *format_ptr; int code; if (0 == signal_number) { format_ptr = "App '%s' (%p) terminated, status code %d."; code = exit_status; } else { format_ptr = "App '%s' (%p) killed by signal %d."; code = signal_number; } bs_log(BS_INFO, format_ptr, launcher_ptr->cmdline_ptr, launcher_ptr, code); // TODO(kaeser@gubbe.ch): Keep exit status and latest output available // for visualization. wlmaker_subprocess_monitor_cede( launcher_ptr->monitor_ptr, subprocess_handle_ptr); bs_ptr_set_erase(launcher_ptr->subprocesses_ptr, subprocess_handle_ptr); } /* ------------------------------------------------------------------------- */ /** * Callback for then a window from the launched subprocess is created. * * Registers the windows as "created", and will then redraw the launcher tile * to reflect potential status changes. * * @param userdata_ptr Points to the @ref wlmaker_launcher_t. * @param subprocess_handle_ptr * @param window_ptr */ void _wlmaker_launcher_handle_window_created( void *userdata_ptr, __UNUSED__ wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr) { wlmaker_launcher_t *launcher_ptr = userdata_ptr; bool rv = bs_ptr_set_insert(launcher_ptr->created_windows_ptr, window_ptr); if (!rv) bs_log(BS_ERROR, "Failed bs_ptr_set_insert(%p)", window_ptr); _wlmaker_launcher_update_overlay(launcher_ptr); } /* ------------------------------------------------------------------------- */ /** * Callback for then a window from the launched subprocess is mapped. * * Registers the window as "mapped", and will then redraw the launcher tile * to reflect potential status changes. * * @param userdata_ptr Points to the @ref wlmaker_launcher_t. * @param subprocess_handle_ptr * @param window_ptr */ void _wlmaker_launcher_handle_window_mapped( void *userdata_ptr, __UNUSED__ wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr) { wlmaker_launcher_t *launcher_ptr = userdata_ptr; // TODO(kaeser@gubbe.ch): Appears we do encounter this scenario. File this // as a bug and fix it. // BS_ASSERT(bs_ptr_set_contains(launcher_ptr->created_windows_ptr, window_ptr)); bool rv = bs_ptr_set_insert(launcher_ptr->mapped_windows_ptr, window_ptr); if (!rv) bs_log(BS_ERROR, "Failed bs_ptr_set_insert(%p)", window_ptr); _wlmaker_launcher_update_overlay(launcher_ptr); } /* ------------------------------------------------------------------------- */ /** * Callback for then a window from the launched subprocess is unmapped. * * Removes the window from the set of "mapped" windows, and will then redraw * the launcher tile to reflect potential status changes. * * @param userdata_ptr Points to the @ref wlmaker_launcher_t. * @param subprocess_handle_ptr * @param window_ptr */ void _wlmaker_launcher_handle_window_unmapped( void *userdata_ptr, __UNUSED__ wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr) { wlmaker_launcher_t *launcher_ptr = userdata_ptr; bs_ptr_set_erase(launcher_ptr->mapped_windows_ptr, window_ptr); _wlmaker_launcher_update_overlay(launcher_ptr); } /* ------------------------------------------------------------------------- */ /** * Callback for then a window from the launched subprocess is destroyed. * * Removes the window from the set of "created" windows, and will then redraw * the launcher tile to reflect potential status changes. * * @param userdata_ptr Points to the @ref wlmaker_launcher_t. * @param subprocess_handle_ptr * @param window_ptr */ void _wlmaker_launcher_handle_window_destroyed( void *userdata_ptr, __UNUSED__ wlmaker_subprocess_handle_t *subprocess_handle_ptr, wlmtk_window_t *window_ptr) { wlmaker_launcher_t *launcher_ptr = userdata_ptr; bs_ptr_set_erase(launcher_ptr->created_windows_ptr, window_ptr); _wlmaker_launcher_update_overlay(launcher_ptr); } /* ------------------------------------------------------------------------- */ /** Implements @ref wlmtk_tile_vmt_t::set_content_size. Set the image size. */ bool _wlmaker_launcher_set_content_size( wlmtk_tile_t *tile_ptr, uint64_t content_size) { wlmaker_launcher_t *launcher_ptr = BS_CONTAINER_OF( tile_ptr, wlmaker_launcher_t, super_tile); wlmtk_image_t *image_ptr = wlmtk_image_create_scaled( launcher_ptr->resolved_icon_path_ptr, content_size, content_size); if (NULL == image_ptr) return false; wlmtk_element_set_visible(wlmtk_image_element(image_ptr), true); wlmtk_tile_set_content(tile_ptr, wlmtk_image_element(image_ptr)); wlmtk_image_destroy(launcher_ptr->image_ptr); launcher_ptr->image_ptr = image_ptr; return true; } /* == Unit tests =========================================================== */ static void test_create_from_plist(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_launcher_test_cases[] = { { true, "create_from_plist", test_create_from_plist }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_launcher_test_set = BS_TEST_SET( true, "launcher", wlmaker_launcher_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises plist parser. */ void test_create_from_plist(bs_test_t *test_ptr) { static const struct wlmtk_tile_style style = { .size = 96 }; static const char *plist_ptr = "{CommandLine = \"a\"; Icon = \"chrome-56x56.png\";}"; bs_test_setenv(test_ptr, "XDG_DATA_DIRS", WLMAKER_SOURCE_DIR "/share"); bspl_dict_t *dict_ptr = bspl_dict_from_object( bspl_create_object_from_plist_string(plist_ptr)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dict_ptr); wlmaker_files_t *files_ptr = wlmaker_files_create("wlmaker"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, files_ptr); wlmaker_launcher_t *launcher_ptr = wlmaker_launcher_create_from_plist( &style, dict_ptr, NULL, files_ptr); bspl_dict_unref(dict_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, launcher_ptr); BS_TEST_VERIFY_STREQ(test_ptr, "a", launcher_ptr->cmdline_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "chrome-56x56.png", launcher_ptr->icon_path_ptr); wlmaker_launcher_destroy(launcher_ptr); wlmaker_files_destroy(files_ptr); } /* == End of launcher.c ==================================================== */ wlmaker-0.8.1/src/subprocess_monitor.c0000644000175100017510000007706115222270710017473 0ustar runnerrunner/* ========================================================================= */ /** * @file subprocess_monitor.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "subprocess_monitor.h" #include #include #include #include #include #include #include "toolkit/toolkit.h" #include "server.h" struct wl_event_loop; struct wl_event_source; /* == Declarations ========================================================= */ /** State of the subprocess monitor. */ struct _wlmaker_subprocess_monitor_t { /** Reference to the event loop. */ struct wl_event_loop *wl_event_loop_ptr; /** Event source used for monitoring SIGCHLD. */ struct wl_event_source *sigchld_event_source_ptr; /** Listener: Receives a signal whenever a window is created. */ struct wl_listener window_created_listener; /** Listener: Receives a signal whenever a window is mapped. */ struct wl_listener window_mapped_listener; /** Listener: Receives a signal whenever a window is unmapped. */ struct wl_listener window_unmapped_listener; /** Listener: Receives a signal whenever a window is destroyed. */ struct wl_listener window_destroyed_listener; /** Monitored subprocesses. */ bs_dllist_t subprocesses; /** Windows for monitored subprocesses. */ bs_avltree_t *window_tree_ptr; }; /** A subprocess. */ struct _wlmaker_subprocess_handle_t { /** Element of @ref wlmaker_subprocess_monitor_t `subprocesses`. */ bs_dllist_node_t dlnode; /** Points to the subprocess. */ bs_subprocess_t *subprocess_ptr; /** File descriptor of the subprocess' stdout. */ int stdout_read_fd; /** Event source corresponding to events related to reading stdout. */ struct wl_event_source *stdout_wl_event_source_ptr; /** File descriptor of the subprocess' strderr. */ int stderr_read_fd; /** Event source corresponding to events related to reading stderr. */ struct wl_event_source *stderr_wl_event_source_ptr; /** Callback: The subprocess was terminated. */ wlmaker_subprocess_terminated_callback_t terminated_callback; /** Argument to all the callbacks. */ void *userdata_ptr; /** Subprocess's windows. @ref wlmaker_subprocess_window_t::dlnode. */ bs_dllist_t windows; /** Dynamic buffer holding the process' stdout, or NULL if not set. */ bs_dynbuf_t *stdout_dynbuf_ptr; /** Callback: A window was created from this subprocess. */ wlmaker_subprocess_window_callback_t window_created_callback; /** Callback: Window was mapped from this subprocess. */ wlmaker_subprocess_window_callback_t window_mapped_callback; /** Callback: Window was unmapped from this subprocess. */ wlmaker_subprocess_window_callback_t window_unmapped_callback; /** Callback: Window was destroyed from this subprocess. */ wlmaker_subprocess_window_callback_t window_destroyed_callback; }; /** Registry entry for @ref wlmtk_window_t and subprocesses. */ typedef struct { /** See @ref wlmaker_subprocess_monitor_t::window_tree_ptr. */ bs_avltree_node_t avlnode; /** The window registered here. Also the tree lookup key. */ wlmtk_window_t *window_ptr; /** See @ref wlmaker_subprocess_handle_t::windows. */ bs_dllist_node_t dlnode; /** The subprocess that the window is mapped to, or NULL. */ wlmaker_subprocess_handle_t *subprocess_handle_ptr; /** Whether the window was reported as mapped. */ bool mapped; } wlmaker_subprocess_window_t; static wlmaker_subprocess_handle_t *wlmaker_subprocess_handle_create( bs_subprocess_t *subprocess_ptr, struct wl_event_loop *wl_event_loop_ptr); static void wlmaker_subprocess_handle_destroy( wlmaker_subprocess_handle_t *sp_handle_ptr); static int _wlmaker_subprocess_monitor_handle_read_stdout( int fd, uint32_t mask, void *data_ptr); static int _wlmaker_subprocess_monitor_handle_read_stderr( int fd, uint32_t mask, void *data_ptr); static int _wlmaker_subprocess_monitor_process_fd( wlmaker_subprocess_handle_t *subprocess_handle_ptr, struct wl_event_source **wl_event_source_ptr_ptr, int fd, uint32_t mask, const char *fd_name_ptr, bs_dynbuf_t *dynbuf_ptr); static int _wlmaker_subprocess_monitor_handle_sigchld(int signum, void *data_ptr); static void _wlmaker_subprocess_monitor_handle_window_created( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_subprocess_monitor_handle_window_mapped( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_subprocess_monitor_handle_window_unmapped( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_subprocess_monitor_handle_window_destroyed( struct wl_listener *listener_ptr, void *data_ptr); static wlmaker_subprocess_handle_t *subprocess_handle_from_window( wlmaker_subprocess_monitor_t *monitor_ptr, wlmtk_window_t *window_ptr); static wlmaker_subprocess_window_t *wlmaker_subprocess_window_create( wlmtk_window_t *window_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr); static void wlmaker_subprocess_window_destroy( wlmaker_subprocess_window_t *ws_window_ptr); static int wlmaker_subprocess_window_node_cmp( const bs_avltree_node_t *node_ptr, const void *key_ptr); static void wlmaker_subprocess_window_node_destroy( bs_avltree_node_t *node_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_subprocess_monitor_t* wlmaker_subprocess_monitor_create( wlmaker_server_t *server_ptr) { wlmaker_subprocess_monitor_t *monitor_ptr = logged_calloc( 1, sizeof(wlmaker_subprocess_monitor_t)); if (NULL == monitor_ptr) return NULL; monitor_ptr->window_tree_ptr = bs_avltree_create( wlmaker_subprocess_window_node_cmp, wlmaker_subprocess_window_node_destroy); if (NULL == monitor_ptr->window_tree_ptr) { bs_log(BS_ERROR, "Failed bs_avltree_create(%p, %p)", wlmaker_subprocess_window_node_cmp, wlmaker_subprocess_window_node_destroy); wlmaker_subprocess_monitor_destroy(monitor_ptr); return NULL; } monitor_ptr->wl_event_loop_ptr = wl_display_get_event_loop( server_ptr->wl_display_ptr); if (NULL == monitor_ptr->wl_event_loop_ptr) { bs_log(BS_ERROR, "Failed wl_display_get_event_loop()."); wlmaker_subprocess_monitor_destroy(monitor_ptr); return NULL; } monitor_ptr->sigchld_event_source_ptr = wl_event_loop_add_signal( monitor_ptr->wl_event_loop_ptr, SIGCHLD, _wlmaker_subprocess_monitor_handle_sigchld, monitor_ptr); wlmtk_util_connect_listener_signal( &server_ptr->window_created_event, &monitor_ptr->window_created_listener, _wlmaker_subprocess_monitor_handle_window_created); wlmtk_util_connect_listener_signal( &server_ptr->window_destroyed_event, &monitor_ptr->window_destroyed_listener, _wlmaker_subprocess_monitor_handle_window_destroyed); if (NULL != server_ptr->root_ptr) { wlmtk_util_connect_listener_signal( &wlmtk_root_events(server_ptr->root_ptr)->window_mapped, &monitor_ptr->window_mapped_listener, _wlmaker_subprocess_monitor_handle_window_mapped); wlmtk_util_connect_listener_signal( &wlmtk_root_events(server_ptr->root_ptr)->window_unmapped, &monitor_ptr->window_unmapped_listener, _wlmaker_subprocess_monitor_handle_window_unmapped); } return monitor_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_subprocess_monitor_destroy( wlmaker_subprocess_monitor_t *monitor_ptr) { wlmtk_util_disconnect_listener(&monitor_ptr->window_destroyed_listener); wlmtk_util_disconnect_listener(&monitor_ptr->window_created_listener); wlmtk_util_disconnect_listener(&monitor_ptr->window_unmapped_listener); wlmtk_util_disconnect_listener(&monitor_ptr->window_mapped_listener); if (NULL != monitor_ptr->sigchld_event_source_ptr) { wl_event_source_remove(monitor_ptr->sigchld_event_source_ptr); monitor_ptr->sigchld_event_source_ptr = NULL; } if (NULL != monitor_ptr->window_tree_ptr) { bs_avltree_destroy(monitor_ptr->window_tree_ptr); monitor_ptr->window_tree_ptr = NULL; } monitor_ptr->wl_event_loop_ptr = NULL; free(monitor_ptr); } /* ------------------------------------------------------------------------- */ bool wlmaker_subprocess_monitor_run( wlmaker_subprocess_monitor_t *monitor_ptr, bs_subprocess_t *subprocess_ptr) { if (NULL == subprocess_ptr) return false; if (!bs_subprocess_start(subprocess_ptr)) goto error; wlmaker_subprocess_handle_t *subprocess_handle_ptr = wlmaker_subprocess_monitor_entrust( monitor_ptr, subprocess_ptr, NULL, NULL, NULL, NULL, NULL, NULL, NULL); if (NULL == subprocess_handle_ptr) goto error; wlmaker_subprocess_monitor_cede(monitor_ptr, subprocess_handle_ptr); return true; error: if (NULL != subprocess_ptr) bs_subprocess_destroy(subprocess_ptr); return NULL; } /* ------------------------------------------------------------------------- */ wlmaker_subprocess_handle_t *wlmaker_subprocess_monitor_entrust( wlmaker_subprocess_monitor_t *monitor_ptr, bs_subprocess_t *subprocess_ptr, wlmaker_subprocess_terminated_callback_t terminated_callback, void *userdata_ptr, wlmaker_subprocess_window_callback_t window_created_callback, wlmaker_subprocess_window_callback_t window_mapped_callback, wlmaker_subprocess_window_callback_t window_unmapped_callback, wlmaker_subprocess_window_callback_t window_destroyed_callback, bs_dynbuf_t *stdout_dynbuf_ptr) { wlmaker_subprocess_handle_t *subprocess_handle_ptr = wlmaker_subprocess_handle_create( subprocess_ptr, monitor_ptr->wl_event_loop_ptr); if (NULL == subprocess_handle_ptr) return NULL; bs_dllist_push_back(&monitor_ptr->subprocesses, &subprocess_handle_ptr->dlnode); subprocess_handle_ptr->terminated_callback = terminated_callback; subprocess_handle_ptr->userdata_ptr = userdata_ptr; subprocess_handle_ptr->window_created_callback = window_created_callback; subprocess_handle_ptr->window_mapped_callback = window_mapped_callback; subprocess_handle_ptr->window_unmapped_callback = window_unmapped_callback; subprocess_handle_ptr->window_destroyed_callback = window_destroyed_callback; subprocess_handle_ptr->stdout_dynbuf_ptr = stdout_dynbuf_ptr; return subprocess_handle_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_subprocess_monitor_cede( __UNUSED__ wlmaker_subprocess_monitor_t *monitor_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr) { bs_dllist_node_t *dlnode_ptr; while (NULL != (dlnode_ptr = bs_dllist_pop_front( &subprocess_handle_ptr->windows))) { wlmaker_subprocess_window_t *ws_window_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmaker_subprocess_window_t, dlnode); BS_ASSERT(ws_window_ptr->subprocess_handle_ptr == subprocess_handle_ptr) ; if (NULL != subprocess_handle_ptr->window_unmapped_callback) { subprocess_handle_ptr->window_unmapped_callback( subprocess_handle_ptr->userdata_ptr, subprocess_handle_ptr, ws_window_ptr->window_ptr); ws_window_ptr->mapped = false; } if (NULL != subprocess_handle_ptr->window_destroyed_callback) { subprocess_handle_ptr->window_destroyed_callback( subprocess_handle_ptr->userdata_ptr, subprocess_handle_ptr, ws_window_ptr->window_ptr); } ws_window_ptr->subprocess_handle_ptr = NULL; } subprocess_handle_ptr->terminated_callback = NULL; } /* ------------------------------------------------------------------------- */ bs_subprocess_t *wlmaker_subprocess_from_subprocess_handle( wlmaker_subprocess_handle_t *subprocess_handle_ptr) { return subprocess_handle_ptr->subprocess_ptr; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Creates a @ref wlmaker_subprocess_handle_t and connects to subprocess_ptr. * * @param subprocess_ptr * @param wl_event_loop_ptr * * @return The subprocess handle or NULL on error. */ wlmaker_subprocess_handle_t *wlmaker_subprocess_handle_create( bs_subprocess_t *subprocess_ptr, struct wl_event_loop *wl_event_loop_ptr) { wlmaker_subprocess_handle_t *subprocess_handle_ptr = logged_calloc( 1, sizeof(wlmaker_subprocess_handle_t)); if (NULL == subprocess_handle_ptr) return NULL; subprocess_handle_ptr->subprocess_ptr = subprocess_ptr; bs_subprocess_get_fds( subprocess_ptr, NULL, // no interest in stdin. &subprocess_handle_ptr->stdout_read_fd, &subprocess_handle_ptr->stderr_read_fd); subprocess_handle_ptr->stdout_wl_event_source_ptr = wl_event_loop_add_fd( wl_event_loop_ptr, subprocess_handle_ptr->stdout_read_fd, WL_EVENT_READABLE, _wlmaker_subprocess_monitor_handle_read_stdout, subprocess_handle_ptr); subprocess_handle_ptr->stderr_wl_event_source_ptr = wl_event_loop_add_fd( wl_event_loop_ptr, subprocess_handle_ptr->stderr_read_fd, WL_EVENT_READABLE, _wlmaker_subprocess_monitor_handle_read_stderr, subprocess_handle_ptr); return subprocess_handle_ptr; } /* ------------------------------------------------------------------------- */ /** * Destroys the subprocess handle and frees up associated resources. * * @param sp_handle_ptr */ void wlmaker_subprocess_handle_destroy( wlmaker_subprocess_handle_t *sp_handle_ptr) { BS_ASSERT(NULL == sp_handle_ptr->dlnode.prev_ptr); int exit_status, signal_number; if (!bs_subprocess_terminated( sp_handle_ptr->subprocess_ptr, &exit_status, &signal_number)) { bs_log(BS_FATAL, "Destroying subprocess handle, but still running: " "subprocess %p (pid: %"PRIdMAX")", sp_handle_ptr->subprocess_ptr, (intmax_t)bs_subprocess_pid(sp_handle_ptr->subprocess_ptr)); } bs_log(BS_DEBUG, "Terminated subprocess %p. Status %d, signal %d.", sp_handle_ptr->subprocess_ptr, exit_status, signal_number); if (NULL != sp_handle_ptr->terminated_callback) { // Attempt to drain stdout & stderr before closing the pipes. _wlmaker_subprocess_monitor_handle_read_stdout( sp_handle_ptr->stdout_read_fd, WL_EVENT_READABLE, sp_handle_ptr); _wlmaker_subprocess_monitor_handle_read_stderr( sp_handle_ptr->stderr_read_fd, WL_EVENT_READABLE, sp_handle_ptr); sp_handle_ptr->terminated_callback( sp_handle_ptr->userdata_ptr, sp_handle_ptr, exit_status, signal_number); sp_handle_ptr->terminated_callback = NULL; } if (NULL != sp_handle_ptr->subprocess_ptr) { bs_subprocess_destroy(sp_handle_ptr->subprocess_ptr); sp_handle_ptr->subprocess_ptr = NULL; } if (NULL != sp_handle_ptr->stdout_wl_event_source_ptr) { wl_event_source_remove(sp_handle_ptr->stdout_wl_event_source_ptr); sp_handle_ptr->stdout_wl_event_source_ptr = NULL; } if (NULL != sp_handle_ptr->stderr_wl_event_source_ptr) { wl_event_source_remove(sp_handle_ptr->stderr_wl_event_source_ptr); sp_handle_ptr->stderr_wl_event_source_ptr = NULL; } free(sp_handle_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for activity on stdout file descriptor, as prescribed by * wl_event_loop_fd_func_t. * * @param fd * @param mask A bitmask of WL_EVENT_READABLE, WL_EVENT_HANGUP * or WL_EVENT_ERROR. * @param data_ptr Points ot a @ref wlmaker_subprocess_handle_t. * * @return 0. */ int _wlmaker_subprocess_monitor_handle_read_stdout( int fd, uint32_t mask, void *data_ptr) { char buf[1024]; bs_dynbuf_t dynbuf, *dynbuf_ptr; wlmaker_subprocess_handle_t *subprocess_handle_ptr = data_ptr; BS_ASSERT(fd == subprocess_handle_ptr->stdout_read_fd); dynbuf_ptr = subprocess_handle_ptr->stdout_dynbuf_ptr; if (NULL == dynbuf_ptr) { bs_dynbuf_init_unmanaged(&dynbuf, buf, sizeof(buf) - 1); dynbuf_ptr = &dynbuf; } int rv = _wlmaker_subprocess_monitor_process_fd( subprocess_handle_ptr, &subprocess_handle_ptr->stdout_wl_event_source_ptr, subprocess_handle_ptr->stdout_read_fd, mask, "stdout", dynbuf_ptr); // Log subprocess stdout, but only if not using an explicit stdout dynbuf. if (dynbuf_ptr != subprocess_handle_ptr->stdout_dynbuf_ptr && 0 < dynbuf_ptr->length) { size_t len = BS_MIN(sizeof(buf) - 1, dynbuf.length); if (0 < len && buf[len - 1] == '\n') { buf[--len] = '\0'; } else { buf[len] = '\0'; } if (0 < len) { bs_log_write( BS_DEBUG, "subprocess.stdout", (intmax_t)bs_subprocess_pid(subprocess_handle_ptr->subprocess_ptr), "%s", buf); } } return rv; } /* ------------------------------------------------------------------------- */ /** * Handler for activity on stderr file descriptor, as prescribed by * wl_event_loop_fd_func_t. * * @param fd * @param mask A bitmask of WL_EVENT_READABLE, WL_EVENT_HANGUP * or WL_EVENT_ERROR. * @param data_ptr Points to a @ref wlmaker_subprocess_handle_t. * * @return 0. */ int _wlmaker_subprocess_monitor_handle_read_stderr( int fd, uint32_t mask, void *data_ptr) { char buf[1024]; bs_dynbuf_t dynbuf; wlmaker_subprocess_handle_t *subprocess_handle_ptr = data_ptr; BS_ASSERT(fd == subprocess_handle_ptr->stderr_read_fd); bs_dynbuf_init_unmanaged(&dynbuf, buf, sizeof(buf)); int rv = _wlmaker_subprocess_monitor_process_fd( subprocess_handle_ptr, &subprocess_handle_ptr->stderr_wl_event_source_ptr, subprocess_handle_ptr->stderr_read_fd, mask, "stdout", &dynbuf); if (0 < dynbuf.length) { size_t len = BS_MIN(sizeof(buf) - 1, dynbuf.length); if (0 < len && buf[len - 1] == '\n') { buf[--len] = '\0'; } else { buf[len] = '\0'; } if (0 < len) { bs_log_write( BS_INFO, "subprocess.stderr", (intmax_t)bs_subprocess_pid(subprocess_handle_ptr->subprocess_ptr), "%s", buf); } } return rv; } /* ------------------------------------------------------------------------- */ /** * Processes activity on a file descriptor, matches wl_event_loop_fd_func_t. * * @param subprocess_handle_ptr * @param wl_event_source_ptr_ptr * @param fd * @param mask * @param fd_name_ptr * @param dynbuf_ptr * * @return 0. */ int _wlmaker_subprocess_monitor_process_fd( wlmaker_subprocess_handle_t *subprocess_handle_ptr, struct wl_event_source **wl_event_source_ptr_ptr, int fd, uint32_t mask, const char *fd_name_ptr, bs_dynbuf_t *dynbuf_ptr) { // Convenience copy. intmax_t pid = bs_subprocess_pid(subprocess_handle_ptr->subprocess_ptr); if (mask & WL_EVENT_READABLE) { bs_dynbuf_read(dynbuf_ptr, fd); return 0; } if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR) && NULL != *wl_event_source_ptr_ptr) { bs_log(BS_DEBUG, "subprocess %"PRIdMAX" %s: Mask 0x%x, removing.", pid, fd_name_ptr, mask); wl_event_source_remove(*wl_event_source_ptr_ptr); *wl_event_source_ptr_ptr = NULL; return 0; } bs_log(BS_WARNING, "subprocess %"PRIdMAX" %s: Unexpected event, mask 0x%x", pid, fd_name_ptr, mask); return 0; } /* ------------------------------------------------------------------------- */ /** * Handles SIGCHLD. Callback for Wayland event loop. * * @param signum * * @param data_ptr Points to @ref wlmaker_subprocess_monitor_t. */ int _wlmaker_subprocess_monitor_handle_sigchld( __UNUSED__ int signum, void *data_ptr) { wlmaker_subprocess_monitor_t *monitor_ptr = data_ptr; bs_dllist_node_t *dlnode_ptr = monitor_ptr->subprocesses.head_ptr; while (NULL != dlnode_ptr) { wlmaker_subprocess_handle_t *subprocess_handle_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmaker_subprocess_handle_t, dlnode); dlnode_ptr = dlnode_ptr->next_ptr; int exit_status, signal_number; if (bs_subprocess_terminated(subprocess_handle_ptr->subprocess_ptr, &exit_status, &signal_number)) { bs_dllist_remove( &monitor_ptr->subprocesses, &subprocess_handle_ptr->dlnode); wlmaker_subprocess_handle_destroy(subprocess_handle_ptr); } } return 0; } /* ------------------------------------------------------------------------- */ /** * Handles window creation: Will see if there's a subprocess mapping to the * corresponding client's PID, and call the "created" callback, if registered. * * Note: A client may have an arbitrary number of windows created. * * @param listener_ptr * @param data_ptr Points to a @ref wlmaker_subprocess_monitor_t. */ void _wlmaker_subprocess_monitor_handle_window_created( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_subprocess_monitor_t *monitor_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_subprocess_monitor_t, window_created_listener); wlmtk_window_t *window_ptr = data_ptr; wlmaker_subprocess_handle_t *subprocess_handle_ptr = subprocess_handle_from_window(monitor_ptr, window_ptr); if (NULL == subprocess_handle_ptr) return; wlmaker_subprocess_window_t *ws_window_ptr = wlmaker_subprocess_window_create(window_ptr, subprocess_handle_ptr); if (NULL == ws_window_ptr) return; bs_avltree_insert( monitor_ptr->window_tree_ptr, ws_window_ptr->window_ptr, &ws_window_ptr->avlnode, true); } /* ------------------------------------------------------------------------- */ /** * Handles window mapping: Will see if there's a window and corresponding * subprocess, and calls the "mapped" callback, if registered. * * @param listener_ptr * @param data_ptr Points to a @ref wlmaker_subprocess_monitor_t. */ void _wlmaker_subprocess_monitor_handle_window_mapped( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_subprocess_monitor_t *monitor_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_subprocess_monitor_t, window_mapped_listener); wlmtk_window_t *window_ptr = data_ptr; bs_avltree_node_t *avlnode_ptr = bs_avltree_lookup( monitor_ptr->window_tree_ptr, window_ptr); if (NULL == avlnode_ptr) return; wlmaker_subprocess_window_t *ws_window_ptr = BS_CONTAINER_OF( avlnode_ptr, wlmaker_subprocess_window_t, avlnode); wlmaker_subprocess_handle_t *subprocess_handle_ptr = ws_window_ptr->subprocess_handle_ptr; if (NULL == subprocess_handle_ptr) return; if (NULL != subprocess_handle_ptr->window_mapped_callback) { subprocess_handle_ptr->window_mapped_callback( subprocess_handle_ptr->userdata_ptr, subprocess_handle_ptr, ws_window_ptr->window_ptr); } ws_window_ptr->mapped = true; } /* ------------------------------------------------------------------------- */ /** * Handles window unmapping: Will see if there's a window and corresponding * subprocess, and calls the "unmapped" callback, if registered. * * @param listener_ptr * @param data_ptr Points to a @ref wlmaker_subprocess_monitor_t. */ void _wlmaker_subprocess_monitor_handle_window_unmapped( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_subprocess_monitor_t *monitor_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_subprocess_monitor_t, window_unmapped_listener); wlmtk_window_t *window_ptr = data_ptr; bs_avltree_node_t *avlnode_ptr = bs_avltree_lookup( monitor_ptr->window_tree_ptr, window_ptr); if (NULL == avlnode_ptr) return; wlmaker_subprocess_window_t *ws_window_ptr = BS_CONTAINER_OF( avlnode_ptr, wlmaker_subprocess_window_t, avlnode); wlmaker_subprocess_handle_t *subprocess_handle_ptr = ws_window_ptr->subprocess_handle_ptr; if (NULL == subprocess_handle_ptr) return; if (NULL != subprocess_handle_ptr->window_unmapped_callback) { subprocess_handle_ptr->window_unmapped_callback( subprocess_handle_ptr->userdata_ptr, subprocess_handle_ptr, ws_window_ptr->window_ptr); } ws_window_ptr->mapped = false; } /* ------------------------------------------------------------------------- */ /** * Handles window destruction: Will retrieve the wlmaker_subprocess_window_t * structure for tracking windows for subprocesses, call the respective * callbacks and destroy the associated window tracking structure. * * @param listener_ptr * @param data_ptr Points to a @ref wlmaker_subprocess_monitor_t. */ void _wlmaker_subprocess_monitor_handle_window_destroyed( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_subprocess_monitor_t *monitor_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_subprocess_monitor_t, window_destroyed_listener); wlmtk_window_t *window_ptr = data_ptr; bs_avltree_node_t *avlnode_ptr = bs_avltree_delete( monitor_ptr->window_tree_ptr, window_ptr); if (NULL == avlnode_ptr) return; wlmaker_subprocess_window_t *ws_window_ptr = BS_CONTAINER_OF( avlnode_ptr, wlmaker_subprocess_window_t, avlnode); wlmaker_subprocess_window_destroy(ws_window_ptr); } /* ------------------------------------------------------------------------- */ /** * Returns the subprocess matching the window's client, if any. * * Practically, there should only ever be one subprocess matching, since the * PID of a subprocess is supposed to be unique. * * @param monitor_ptr * @param window_ptr * * @return A pointer to the subprocess handle corresponding to the window's * client, or NULL if not found. */ wlmaker_subprocess_handle_t *subprocess_handle_from_window( wlmaker_subprocess_monitor_t *monitor_ptr, wlmtk_window_t *window_ptr) { const wlmtk_util_client_t *client_ptr = wlmtk_window_get_client_ptr( window_ptr); // TODO(kaeser@gubbe.ch): Should be a O(1) or O(log(n)) structure. for (bs_dllist_node_t *dlnode_ptr = monitor_ptr->subprocesses.head_ptr; NULL != dlnode_ptr; dlnode_ptr = dlnode_ptr->next_ptr) { wlmaker_subprocess_handle_t *subprocess_handle_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmaker_subprocess_handle_t, dlnode); if (client_ptr->pid == bs_subprocess_pid(subprocess_handle_ptr->subprocess_ptr)) { return subprocess_handle_ptr; } } return NULL; } /* ------------------------------------------------------------------------- */ /** * Creates a structure to track windows for subprocesses. * * Also calls the `window_created_callback`, if given. * * @param window_ptr * @param subprocess_handle_ptr * * @return A pointer to @ref wlmaker_subprocess_window_t or NULL on error. */ wlmaker_subprocess_window_t *wlmaker_subprocess_window_create( wlmtk_window_t *window_ptr, wlmaker_subprocess_handle_t *subprocess_handle_ptr) { // Guard clause: No need for window handle, if no window nor process. if (NULL == window_ptr || NULL == subprocess_handle_ptr) return NULL; wlmaker_subprocess_window_t *ws_window_ptr = logged_calloc( 1, sizeof(wlmaker_subprocess_window_t)); if (NULL == ws_window_ptr) return NULL; ws_window_ptr->window_ptr = window_ptr; ws_window_ptr->subprocess_handle_ptr = subprocess_handle_ptr; if (NULL != subprocess_handle_ptr->window_created_callback) { subprocess_handle_ptr->window_created_callback( subprocess_handle_ptr->userdata_ptr, subprocess_handle_ptr, ws_window_ptr->window_ptr); } bs_dllist_push_back(&subprocess_handle_ptr->windows, &ws_window_ptr->dlnode); return ws_window_ptr; } /* ------------------------------------------------------------------------- */ /** * Destroys the structure for tracking windows for subprocesses. * * Calls the `window_destroyed_callback`, if given. * * @param ws_window_ptr */ void wlmaker_subprocess_window_destroy( wlmaker_subprocess_window_t *ws_window_ptr) { wlmaker_subprocess_handle_t *subprocess_handle_ptr = ws_window_ptr->subprocess_handle_ptr; if (ws_window_ptr->mapped && NULL != subprocess_handle_ptr && NULL != subprocess_handle_ptr->window_unmapped_callback) { subprocess_handle_ptr->window_unmapped_callback( subprocess_handle_ptr->userdata_ptr, subprocess_handle_ptr, ws_window_ptr->window_ptr); ws_window_ptr->mapped = false; } if (NULL != subprocess_handle_ptr && NULL != subprocess_handle_ptr->window_destroyed_callback) { subprocess_handle_ptr->window_destroyed_callback( subprocess_handle_ptr->userdata_ptr, subprocess_handle_ptr, ws_window_ptr->window_ptr); } if (NULL != ws_window_ptr->subprocess_handle_ptr) { bs_dllist_remove( &ws_window_ptr->subprocess_handle_ptr->windows, &ws_window_ptr->dlnode); ws_window_ptr->subprocess_handle_ptr = NULL; } free(ws_window_ptr); } /* ------------------------------------------------------------------------- */ /** Comparator for window registry tree nodes. */ int wlmaker_subprocess_window_node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr) { wlmaker_subprocess_window_t *ws_window_ptr = BS_CONTAINER_OF( node_ptr, wlmaker_subprocess_window_t, avlnode); return bs_avltree_cmp_ptr(ws_window_ptr->window_ptr, key_ptr); } /* ------------------------------------------------------------------------- */ /** Destructor for window registry tree nodes. */ void wlmaker_subprocess_window_node_destroy(bs_avltree_node_t *node_ptr) { wlmaker_subprocess_window_t *ws_window_ptr = BS_CONTAINER_OF( node_ptr, wlmaker_subprocess_window_t, avlnode); wlmaker_subprocess_window_destroy(ws_window_ptr); } /* == End of subprocess_monitor.c ========================================== */ wlmaker-0.8.1/src/task_list.h0000644000175100017510000000645615222270710015536 0ustar runnerrunner/* ========================================================================= */ /** * @file task_list.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __TASK_LIST_H__ #define __TASK_LIST_H__ #include #include /** Forward definition: Task list handle. */ typedef struct _wlmaker_task_list_t wlmaker_task_list_t; /** A handle for a wlmaker server. */ typedef struct _wlmaker_server_t wlmaker_server_t; #include "toolkit/toolkit.h" /** Style of the task list overlay. */ struct wlmaker_task_list_style { /** Fill style. */ wlmtk_style_fill_t fill; /** Font to use. */ wlmtk_style_font_t font; /** Text color for tasks listed. */ uint32_t text_color; }; #ifdef __cplusplus extern "C" { #endif // __cplusplus // TODO(kaeser@gubbe.ch): Move this to wlmaker_keyboard. // Taskswitch: // modifer = ALT, and PRESSED is TAB enables it, and will switch focus // to the next-open View. // // - TAB will switch focus one further // - Shift-TAB will switch focus one back // (- Cursor left/right will also switch focus further/one back) // - Esc will restore focus of the view that has it before switcher. // // will remain active until: // - ALT is released // - any key outside the handled keys are pressed // - mouse is presset outside the task switch window // - workspace is switched. // // Means: It needs a means of... // - grabbing keyboard focus and holding it until release. // - grabbing mouse focus and holding it until release. // - not losing focus and top-of-stack until release. // => Should be atop each layer -> have it's own layer? or OVERLAY ? // (likely go with overlay) // // => means, this is like a "layer view" except the extra focus constraints. /** * Creates a task list for the server. * * Will allocate the task list handle, and register signal handlers so the task * list reacts to `task_list_enabled_event` and `task_list_disabled_event` of * the `wlmaker_server_t`. * * @param server_ptr * @param style_ptr * * @return The task list handle or NULL on error. Must be released by calling * @ref wlmaker_task_list_destroy. */ wlmaker_task_list_t *wlmaker_task_list_create( wlmaker_server_t *server_ptr, const struct wlmaker_task_list_style *style_ptr); /** * Destroys the task list, as created by @ref wlmaker_task_list_create. * * @param task_list_ptr */ void wlmaker_task_list_destroy( wlmaker_task_list_t *task_list_ptr); /** Descriptor for decoding the "TaskList" dictionary. */ extern const bspl_desc_t wlmaker_task_list_style_desc[]; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __TASK_LIST_H__ */ /* == End of task_list.h =================================================== */ wlmaker-0.8.1/src/xwl.h0000644000175100017510000000554515222270710014351 0ustar runnerrunner/* ========================================================================= */ /** * @file xwl.h * * Interface layer to Wlroots XWayland. * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __XWL_H__ #define __XWL_H__ #if defined(WLMAKER_HAVE_XWAYLAND) #include #include struct wlr_xwayland_surface; #endif // defined(WLMAKER_HAVE_XWAYLAND) /** Forward declaration: XWayland interface. */ typedef struct _wlmaker_xwl_t wlmaker_xwl_t; #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus #if defined(WLMAKER_HAVE_XWAYLAND) /** XCB Atom identifiers. */ typedef enum { NET_WM_WINDOW_TYPE_NORMAL, NET_WM_WINDOW_TYPE_DIALOG, NET_WM_WINDOW_TYPE_UTILITY, NET_WM_WINDOW_TYPE_TOOLBAR, NET_WM_WINDOW_TYPE_SPLASH, NET_WM_WINDOW_TYPE_MENU, NET_WM_WINDOW_TYPE_DROPDOWN_MENU, NET_WM_WINDOW_TYPE_POPUP_MENU, NET_WM_WINDOW_TYPE_TOOLTIP, NET_WM_WINDOW_TYPE_NOTIFICATION, // Sentinel element. XWL_MAX_ATOM_ID } xwl_atom_identifier_t; #endif // defined(WLMAKER_HAVE_XWAYLAND) /** * Creates the XWayland interface. * * @param server_ptr * * @return NULL on error, or a pointer to a @ref wlmaker_xwl_t. Must be free-d * by calling @ref wlmaker_xwl_destroy. */ wlmaker_xwl_t *wlmaker_xwl_create(wlmaker_server_t *server_ptr); /** * Destroys the XWayland interface. * * @param xwl_ptr */ void wlmaker_xwl_destroy(wlmaker_xwl_t *xwl_ptr); #if defined(WLMAKER_HAVE_XWAYLAND) /** * Returns whether the XWayland surface has any of the window types. * * @param xwl_ptr * @param wlr_xwayland_surface_ptr * @param atom_identifiers NULL-terminated set of window type we're looking * for. * * @return Whether `atom_identifiers` is in any of the window types. */ bool xwl_is_window_type( wlmaker_xwl_t *xwl_ptr, struct wlr_xwayland_surface *wlr_xwayland_surface_ptr, const xwl_atom_identifier_t *atom_identifiers); /** Returns a human-readable name for the atom. */ const char *xwl_atom_name( wlmaker_xwl_t *xwl_ptr, xcb_atom_t atom); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // defined(WLMAKER_HAVE_XWAYLAND) #endif /* __XWL_H__ */ /* == End of xwl.h ========================================================= */ wlmaker-0.8.1/src/config.c0000644000175100017510000002672615222270710015003 0ustar runnerrunner/* ========================================================================= */ /** * @file config.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Configurables for wlmaker. Currently, this file lists hardcoded entities, * and mainly serves as a catalog about which entities should be dynamically * configurable. */ #include "config.h" #include #include #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "../share/theme.h" // IWYU pragma: keep #include "default_configuration.h" #include "default_state.h" #include "input/cursor.h" /* == Declarations ========================================================= */ static bspl_object_t *_wlmaker_plist_load( const char *name_ptr, const char *fname_ptr, const uint8_t *default_data_ptr, size_t default_data_size); static bspl_dict_t *_wlmaker_config_from_plist(const char *fname_ptr); /* == Data ================================================================= */ /** Descriptor for decoding the "Clip" dictionary. */ static const bspl_desc_t _wlmaker_clip_style_desc[] = { BSPL_DESC_DICT( "Font", true, wlmaker_config_clip_style_t, font, font, wlmtk_style_font_desc), BSPL_DESC_ARGB32( "TextColor", true, wlmaker_config_clip_style_t, text_color, text_color, 0), BSPL_DESC_SENTINEL() }; /** Desciptor for decoding the style information from a plist. */ const bspl_desc_t wlmaker_config_style_desc[] = { BSPL_DESC_ARGB32( "BackgroundColor", true, wlmaker_config_style_t, background_color, background_color, 0), BSPL_DESC_DICT( "Tile", true, wlmaker_config_style_t, tile, tile, wlmtk_tile_style_desc), BSPL_DESC_DICT( "Dock", true, wlmaker_config_style_t, dock, dock, wlmtk_dock_style_desc), BSPL_DESC_CUSTOM( "Window", true, wlmaker_config_style_t, window_style_ptr, has_window_style, wlmtk_window_style_decode, NULL, wlmtk_window_style_decode_init, wlmtk_window_style_decode_fini), BSPL_DESC_CUSTOM( "Menu", true, wlmaker_config_style_t, menu_style_ptr, has_menu_style, wlmtk_menu_style_decode, NULL, wlmtk_menu_style_decode_init, wlmtk_menu_style_decode_fini), BSPL_DESC_DICT( "TaskList", true, wlmaker_config_style_t, task_list, task_list, wlmaker_task_list_style_desc), BSPL_DESC_DICT( "Clip", true, wlmaker_config_style_t, clip, clip, _wlmaker_clip_style_desc), BSPL_DESC_DICT( "Cursor", true, wlmaker_config_style_t, cursor, cursor, wlmim_cursor_style_desc), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bspl_object_t *wlmaker_config_object_load( wlmaker_files_t *files_ptr, const char *name_ptr, const char *arg_fname_ptr, const char *xdg_config_fname_ptr, const uint8_t *default_data_ptr, size_t default_data_size) { char *fname_ptr = NULL; if (NULL == arg_fname_ptr && NULL != files_ptr) { fname_ptr = wlmaker_files_xdg_config_find( files_ptr, xdg_config_fname_ptr, S_IFREG); } bspl_object_t *object_ptr = _wlmaker_plist_load( name_ptr, NULL != arg_fname_ptr ? arg_fname_ptr : fname_ptr, default_data_ptr, default_data_size); if (NULL != fname_ptr) free(fname_ptr); return object_ptr; } /* ------------------------------------------------------------------------- */ bspl_dict_t *wlmaker_config_load( wlmaker_files_t *files_ptr, const char *fname_ptr) { return BS_ASSERT_NOTNULL( bspl_dict_from_object( wlmaker_config_object_load( files_ptr, "wlmaker config", fname_ptr, "Config.plist", embedded_binary_default_configuration_data, embedded_binary_default_configuration_size))); } /* ------------------------------------------------------------------------- */ bspl_dict_t *wlmaker_state_load( wlmaker_files_t *files_ptr, const char *fname_ptr) { return BS_ASSERT_NOTNULL( bspl_dict_from_object( wlmaker_config_object_load( files_ptr, "wlmaker state", fname_ptr, "State.plist", embedded_binary_default_state_data, embedded_binary_default_state_size))); } /* ------------------------------------------------------------------------- */ bool wlmaker_theme_load( wlmaker_files_t *files_ptr, const char *arg_fname_ptr, wlmaker_config_style_t *style_ptr) { char *fname_ptr = NULL; if (NULL != files_ptr) { fname_ptr = wlmaker_files_xdg_data_find( files_ptr, "Themes/Default.plist", S_IFREG); } bspl_object_t *o = _wlmaker_plist_load( "Theme", NULL != arg_fname_ptr ? arg_fname_ptr : fname_ptr, embedded_binary_theme_data, embedded_binary_theme_size); if (NULL != fname_ptr) free(fname_ptr); bspl_dict_t *style_dict_ptr = bspl_dict_from_object(o); if (NULL == style_dict_ptr) { bspl_object_unref(o); return NULL; } wlmaker_config_style_t tmp_style = {}; bool rv = bspl_decode_dict( style_dict_ptr, wlmaker_config_style_desc, &tmp_style); bspl_dict_unref(style_dict_ptr); if (!rv || NULL == tmp_style.window_style_ptr) return false; bspl_decoded_destroy(wlmaker_config_style_desc, style_ptr); *style_ptr = tmp_style; return true; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Loads a plist object from the file or the default data. */ bspl_object_t *_wlmaker_plist_load( const char *name_ptr, const char *fname_ptr, const uint8_t *default_data_ptr, size_t default_data_size) { if (NULL != fname_ptr) { bs_log(BS_INFO, "Loading %s plist from file \"%s\"", name_ptr, fname_ptr); bspl_object_t *object_ptr = bspl_create_object_from_plist_file( fname_ptr); if (NULL == object_ptr) { bs_log(BS_ERROR, "Failed bspl_create_object_from_plist(\"%s\") for %s", fname_ptr, name_ptr); } return object_ptr; } if (NULL == default_data_ptr) return NULL; bs_log(BS_INFO, "Using compiled-in data for %s plist.", name_ptr); return bspl_create_object_from_plist_data( default_data_ptr, default_data_size); } /* ------------------------------------------------------------------------- */ /** Loads a plist dict from fname_ptr. Returns NULL on error. */ bspl_dict_t *_wlmaker_config_from_plist(const char *fname_ptr) { bspl_object_t *obj_ptr = bspl_create_object_from_plist_file(fname_ptr); if (NULL == obj_ptr) { bs_log(BS_ERROR, "Failed bspl_create_object_from_plist(%s)", fname_ptr); return NULL; } bspl_dict_t *dict_ptr = bspl_dict_from_object(obj_ptr); if (NULL == dict_ptr) { bs_log(BS_ERROR, "Not a plist dict in %s", fname_ptr); bspl_object_unref(obj_ptr); return NULL; } return dict_ptr; } /* == Unit tests =========================================================== */ static void test_embedded(bs_test_t *test_ptr); static void test_file(bs_test_t *test_ptr); static void test_style_file(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_config_test_cases[] = { { true, "embedded", test_embedded }, { true, "file", test_file }, { true, "style_file", test_style_file }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_config_test_set = BS_TEST_SET( true, "config", wlmaker_config_test_cases); /* ------------------------------------------------------------------------- */ /** Verifies that the embedded config loads. */ // TODO(kaeser@gubbe.ch): For now, this just verifies that the configuration // **parses**. There is no further verification of the dict contents. Would // be great to extend this. void test_embedded(bs_test_t *test_ptr) { bspl_object_t *obj_ptr; obj_ptr = bspl_create_object_from_plist_data( embedded_binary_default_configuration_data, embedded_binary_default_configuration_size); BS_TEST_VERIFY_NEQ(test_ptr, NULL, bspl_dict_from_object(obj_ptr)); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_data( embedded_binary_default_state_data, embedded_binary_default_state_size); BS_TEST_VERIFY_NEQ(test_ptr, NULL, bspl_dict_from_object(obj_ptr)); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_data( embedded_binary_theme_data, embedded_binary_theme_size); BS_TEST_VERIFY_NEQ(test_ptr, NULL, bspl_dict_from_object(obj_ptr)); bspl_object_unref(obj_ptr); } /* ------------------------------------------------------------------------- */ /** Verifies that the (example) config files are loading. */ // TODO(kaeser@gubbe.ch): For now, this just verifies that the configuration // file **parses**. There is no further verification of the dict contents. // Would be great to extend this. void test_file(bs_test_t *test_ptr) { bspl_dict_t *dict_ptr; #ifndef WLMAKER_SOURCE_DIR #error "Missing definition of WLMAKER_SOURCE_DIR!" #endif dict_ptr = _wlmaker_config_from_plist( WLMAKER_SOURCE_DIR "/etc/Config.plist"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); bspl_dict_unref(dict_ptr); dict_ptr = _wlmaker_config_from_plist( WLMAKER_SOURCE_DIR "/etc/ExampleConfig.plist"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); bspl_dict_unref(dict_ptr); dict_ptr = _wlmaker_config_from_plist( WLMAKER_SOURCE_DIR "/etc/HomeConfig.plist"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); bspl_dict_unref(dict_ptr); dict_ptr = _wlmaker_config_from_plist( WLMAKER_SOURCE_DIR "/etc/State.plist"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); bspl_dict_unref(dict_ptr); } /* ------------------------------------------------------------------------- */ /** Loads and decodes the style file. */ void test_style_file(bs_test_t *test_ptr) { wlmaker_config_style_t cs = {}; #ifndef WLMAKER_SOURCE_DIR #error "Missing definition of WLMAKER_SOURCE_DIR!" #endif const char *f = WLMAKER_SOURCE_DIR "/share/Themes/Default.plist"; BS_TEST_VERIFY_TRUE(test_ptr, wlmaker_theme_load(NULL, f, &cs)); bspl_decoded_destroy(wlmaker_config_style_desc, &cs); f = WLMAKER_SOURCE_DIR "/share/Themes/Debian.plist"; BS_TEST_VERIFY_TRUE(test_ptr, wlmaker_theme_load(NULL, f, &cs)); bspl_decoded_destroy(wlmaker_config_style_desc, &cs); } /* == End of config.c ====================================================== */ wlmaker-0.8.1/src/xdg_popup.c0000644000175100017510000002243415222270710015533 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_popup.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "xdg_popup.h" #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #undef WLR_USE_UNSTABLE /* == Declarations ========================================================= */ /** State of toolkit popup. */ struct _wlmaker_xdg_popup_t { /** Base element for the popup. Surface is the content. */ wlmtk_base_t base; /** Listener for the `reposition` signal of `wlr_xdg_popup::events` */ struct wl_listener reposition_listener; /** Listener for the `destroy` signal of `wlr_xdg_surface::events`. */ struct wl_listener destroy_listener; /** Listener for the `new_popup` signal of `wlr_xdg_surface::events`. */ struct wl_listener new_popup_listener; /** Listener for the `commit` signal of the `wlr_surface`. */ struct wl_listener surface_commit_listener; /** Seat. */ struct wlr_seat *wlr_seat_ptr; /** The WLR popup. */ struct wlr_xdg_popup *wlr_xdg_popup_ptr; }; static void handle_reposition( struct wl_listener *listener_ptr, void *data_ptr); static void handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void handle_new_popup( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_xdg_popup_element_destroy( wlmtk_element_t *element_ptr); /* == Data ================================================================= */ /** Virtual method table of the parent's @ref wlmtk_element_t. */ static const wlmtk_element_vmt_t _wlmaker_xdg_popup_element_vmt = { .destroy = _wlmaker_xdg_popup_element_destroy }; static void handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_xdg_popup_t *wlmaker_xdg_popup_create( struct wlr_xdg_popup *wlr_xdg_popup_ptr, struct wlr_seat *wlr_seat_ptr) { wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr = logged_calloc( 1, sizeof(wlmaker_xdg_popup_t)); if (NULL == wlmaker_xdg_popup_ptr) return NULL; wlmaker_xdg_popup_ptr->wlr_seat_ptr = wlr_seat_ptr; wlmaker_xdg_popup_ptr->wlr_xdg_popup_ptr = wlr_xdg_popup_ptr; if (!wlmtk_base_init(&wlmaker_xdg_popup_ptr->base, NULL)) { wlmaker_xdg_popup_destroy(wlmaker_xdg_popup_ptr); return NULL; } wlmtk_element_set_visible( wlmaker_xdg_popup_element(wlmaker_xdg_popup_ptr), true); wlmtk_surface_t *surface_ptr = wlmtk_surface_create( wlr_xdg_popup_ptr->base->surface, wlr_seat_ptr); if (NULL == surface_ptr) { wlmaker_xdg_popup_destroy(wlmaker_xdg_popup_ptr); return NULL; } wlmtk_base_set_content_element( &wlmaker_xdg_popup_ptr->base, wlmtk_surface_element(surface_ptr)); wlmtk_util_connect_listener_signal( &wlr_xdg_popup_ptr->base->surface->events.commit, &wlmaker_xdg_popup_ptr->surface_commit_listener, handle_surface_commit); wlmtk_element_extend( wlmtk_base_element(&wlmaker_xdg_popup_ptr->base), &_wlmaker_xdg_popup_element_vmt); wlmtk_element_set_position( wlmtk_base_element(&wlmaker_xdg_popup_ptr->base), wlr_xdg_popup_ptr->scheduled.geometry.x, wlr_xdg_popup_ptr->scheduled.geometry.y); wlmtk_util_connect_listener_signal( &wlr_xdg_popup_ptr->events.reposition, &wlmaker_xdg_popup_ptr->reposition_listener, handle_reposition); wlmtk_util_connect_listener_signal( &wlr_xdg_popup_ptr->events.destroy, &wlmaker_xdg_popup_ptr->destroy_listener, handle_destroy); wlmtk_util_connect_listener_signal( &wlr_xdg_popup_ptr->base->events.new_popup, &wlmaker_xdg_popup_ptr->new_popup_listener, handle_new_popup); bs_log(BS_INFO, "Created XDG popup %p", wlmaker_xdg_popup_ptr); return wlmaker_xdg_popup_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_xdg_popup_destroy(wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr) { bs_log(BS_INFO, "Destroying XDG popup %p", wlmaker_xdg_popup_ptr); // For XDG popups, we don't keep reference when adding to a container. So // we need to remove from a potential parent container. wlmtk_element_t *e = wlmtk_base_element(&wlmaker_xdg_popup_ptr->base); if (NULL != e->parent_container_ptr) { wlmtk_container_remove_element(e->parent_container_ptr, e); } wlmtk_util_disconnect_listener( &wlmaker_xdg_popup_ptr->new_popup_listener); wlmtk_util_disconnect_listener( &wlmaker_xdg_popup_ptr->destroy_listener); wlmtk_util_disconnect_listener( &wlmaker_xdg_popup_ptr->reposition_listener); wlmtk_util_disconnect_listener( &wlmaker_xdg_popup_ptr->surface_commit_listener); wlmtk_base_fini(&wlmaker_xdg_popup_ptr->base); free(wlmaker_xdg_popup_ptr); } /* ------------------------------------------------------------------------- */ wlmtk_element_t *wlmaker_xdg_popup_element( wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr) { return wlmtk_base_element(&wlmaker_xdg_popup_ptr->base); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Handles repositioning. */ void handle_reposition( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_popup_t, reposition_listener); wlmtk_element_set_position( wlmtk_base_element(&wlmaker_xdg_popup_ptr->base), wlmaker_xdg_popup_ptr->wlr_xdg_popup_ptr->scheduled.geometry.x, wlmaker_xdg_popup_ptr->wlr_xdg_popup_ptr->scheduled.geometry.y); } /* ------------------------------------------------------------------------- */ /** Handles popup destruction: Removes from parent, and destroy. */ void handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_popup_t, destroy_listener); wlmaker_xdg_popup_destroy(wlmaker_xdg_popup_ptr); } /* ------------------------------------------------------------------------- */ /** Handles further popups. Creates them and adds them to parent's content. */ void handle_new_popup( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_popup_t, new_popup_listener); struct wlr_xdg_popup *wlr_xdg_popup_ptr = data_ptr; wlmaker_xdg_popup_t *new_popup_ptr = wlmaker_xdg_popup_create( wlr_xdg_popup_ptr, wlmaker_xdg_popup_ptr->wlr_seat_ptr); if (NULL == new_popup_ptr) { wl_resource_post_error( wlr_xdg_popup_ptr->resource, WL_DISPLAY_ERROR_NO_MEMORY, "Failed wlmtk_xdg_popup_create."); return; } wlmtk_base_push_element( &wlmaker_xdg_popup_ptr->base, wlmaker_xdg_popup_element(new_popup_ptr)); bs_log(BS_INFO, "XDG popup %p: New popup %p", wlmaker_xdg_popup_ptr, wlr_xdg_popup_ptr); } /* ------------------------------------------------------------------------- */ /** Handles `commit` for the popup's surface. */ void handle_surface_commit( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_popup_t, surface_commit_listener); if (wlmaker_xdg_popup_ptr->wlr_xdg_popup_ptr->base->initial_commit) { // Initial commit: Ensure a configure is responded with. wlr_xdg_surface_schedule_configure( wlmaker_xdg_popup_ptr->wlr_xdg_popup_ptr->base); } } /* ------------------------------------------------------------------------- */ /** * Implementation of @ref wlmtk_element_vmt_t::destroy. Virtual dtor. * * @param element_ptr */ void _wlmaker_xdg_popup_element_destroy( wlmtk_element_t *element_ptr) { wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_xdg_popup_t, base.super_container.super_element); wlmaker_xdg_popup_destroy(wlmaker_xdg_popup_ptr); } /* == End of xdg_popup.c ==================================================== */ wlmaker-0.8.1/src/idle.h0000644000175100017510000000462715222270710014454 0ustar runnerrunner/* ========================================================================= */ /** * @file idle.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __IDLE_H__ #define __IDLE_H__ #include /** Forward declaration: Idle monitor handle. */ typedef struct _wlmaker_idle_monitor_t wlmaker_idle_monitor_t; #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates the idle monitor. * * @param server_ptr * * @return Handle of the idle monitor or NULL on error. */ wlmaker_idle_monitor_t *wlmaker_idle_monitor_create( wlmaker_server_t *server_ptr); /** * Destroys the idle monitor. * * @param idle_monitor_ptr */ void wlmaker_idle_monitor_destroy(wlmaker_idle_monitor_t *idle_monitor_ptr); /** * Resets the idle monitor: For example, when a key is pressed. * * @param idle_monitor_ptr */ void wlmaker_idle_monitor_reset(wlmaker_idle_monitor_t *idle_monitor_ptr); /** * Executes the configured 'Command' for locking. Overrides inhibits. * * @param idle_monitor_ptr * * @return true on success. */ bool wlmaker_idle_monitor_lock(wlmaker_idle_monitor_t *idle_monitor_ptr); /** * Inhibits locking: Increases inhibitor counter, which will prevent locking * when the idle timer expires. @see wlmaker_idle_monitor_uninhibit for * releasing the inhibitor. * * @param idle_monitor_ptr */ void wlmaker_idle_monitor_inhibit(wlmaker_idle_monitor_t *idle_monitor_ptr); /** * Uninhibits locking: Decreases the counter. If 0, and no other inhibitors * found, an expired idle timer will lock. * * @param idle_monitor_ptr */ void wlmaker_idle_monitor_uninhibit(wlmaker_idle_monitor_t *idle_monitor_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __IDLE_H__ */ /* == End of idle.h ======================================================== */ wlmaker-0.8.1/src/icon_manager.c0000644000175100017510000005054115222270710016150 0ustar runnerrunner/* ========================================================================= */ /** * @file icon_manager.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "icon_manager.h" #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include "toolkit/toolkit.h" #include "wlmaker-icon-unstable-v1-server-protocol.h" #include "server.h" #include "xdg_shell.h" struct wl_client; struct wl_resource; /* == Declarations ========================================================= */ /** State of the toplevel icon manager. */ struct _wlmaker_icon_manager_t { /** Back-link to the server. */ wlmaker_server_t *server_ptr; /** Back-link to the wayland Display. */ struct wl_display *wl_display_ptr; /** The global holding the icon manager's interface. */ struct wl_global *wl_global_ptr; }; /** State of a toplevel icon. */ struct _wlmaker_toplevel_icon_t { /** The icon is also a toolkit tile. */ wlmtk_tile_t super_tile; /** The surface element, being the content of the tile. */ wlmtk_surface_t *content_surface_ptr; /** Back-link to the client requesting the toplevel. */ struct wl_client *wl_client_ptr; /** Back-link to the icon manager. */ wlmaker_icon_manager_t *icon_manager_ptr; /** The provided ID. */ uint32_t id; /** The XDG toplevel for which the icon is specified. */ struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr; /** The surface to use for the icon of this toplevel. */ struct wlr_surface *wlr_surface_ptr; /** The resource associated with this icon. */ struct wl_resource *wl_resource_ptr; /** Whether the configuration sequence was acknowledged. */ bool acknowledged; /** Serial that needs to be acknowledged. */ uint32_t pending_serial; /** Listener for the `commit` event of `wlr_surface_ptr`. */ struct wl_listener surface_commit_listener; /** Listener for the `destroy` event of `wlr_surface_ptr`. */ struct wl_listener surface_destroy_listener; }; static wlmaker_icon_manager_t *icon_manager_from_resource( struct wl_resource *wl_resource_ptr); static wlmaker_toplevel_icon_t *wlmaker_toplevel_icon_from_resource( struct wl_resource *wl_resource_ptr); static void bind_icon_manager( struct wl_client *wl_client_ptr, void *data_ptr, uint32_t version, uint32_t id); static void handle_resource_destroy( struct wl_client *wl_client_ptr, struct wl_resource *wl_resource_ptr); static void handle_get_toplevel_icon( struct wl_client *wl_client_ptr, struct wl_resource *wl_icon_manager_resource_ptr, uint32_t id, struct wl_resource *wl_toplevel_resource_ptr, struct wl_resource *wl_surface_resource_ptr); static wlmaker_toplevel_icon_t *wlmaker_toplevel_icon_create( struct wl_client *wl_client_ptr, wlmaker_icon_manager_t *icon_manager_ptr, uint32_t id, int version, struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, struct wlr_surface *wlr_surface_ptr); static void wlmaker_toplevel_icon_destroy( wlmaker_toplevel_icon_t *toplevel_icon_ptr); static void toplevel_icon_resource_destroy( struct wl_resource *wl_resource_ptr); static void handle_icon_ack_configure( struct wl_client *wl_client_ptr, struct wl_resource *wl_resource_ptr, uint32_t serial); static void handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr); static void handle_surface_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_toplevel_icon_element_destroy( wlmtk_element_t *element_ptr); static bool _wlmaker_toplevel_icon_tile_set_content_size( wlmtk_tile_t *tile_ptr, uint64_t content_size); /* == Data ================================================================= */ /** Implementation of the toplevel icon manager interface. */ static const struct zwlmaker_icon_manager_v1_interface icon_manager_v1_implementation = { .destroy = handle_resource_destroy, .get_toplevel_icon = handle_get_toplevel_icon }; /** Implementation of the toplevel icon interface. */ static const struct zwlmaker_toplevel_icon_v1_interface toplevel_icon_v1_implementation = { .destroy = handle_resource_destroy, .ack_configure = handle_icon_ack_configure, }; /** The icon's extension to @ref wlmtk_element_t virtual method table. */ static const wlmtk_element_vmt_t _wlmaker_toplevel_icon_element_vmt = { .destroy = _wlmaker_toplevel_icon_element_destroy, }; /** Icon's extension to @ref wlmtk_tile_t virtual method table. */ static const wlmtk_tile_vmt_t _wlmaker_toplevel_icon_tile_vmt = { .set_content_size = _wlmaker_toplevel_icon_tile_set_content_size, }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_icon_manager_t *wlmaker_icon_manager_create( struct wl_display *wl_display_ptr, wlmaker_server_t *server_ptr) { wlmaker_icon_manager_t *icon_manager_ptr = logged_calloc(1, sizeof(wlmaker_icon_manager_t)); if (NULL == icon_manager_ptr) return NULL; icon_manager_ptr->server_ptr = server_ptr; icon_manager_ptr->wl_display_ptr = wl_display_ptr; icon_manager_ptr->wl_global_ptr = wl_global_create( wl_display_ptr, &zwlmaker_icon_manager_v1_interface, 1, icon_manager_ptr, bind_icon_manager); if (NULL == icon_manager_ptr->wl_global_ptr) { wlmaker_icon_manager_destroy(icon_manager_ptr); return NULL; } return icon_manager_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_icon_manager_destroy( wlmaker_icon_manager_t *icon_manager_ptr) { if (NULL != icon_manager_ptr->wl_global_ptr) { wl_global_destroy(icon_manager_ptr->wl_global_ptr); icon_manager_ptr->wl_global_ptr = NULL; } free(icon_manager_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Returns the toplevel icon manager from the resource, with type check. * * @param wl_resource_ptr * * @return Pointer to the @ref wlmaker_icon_manager_t. */ wlmaker_icon_manager_t *icon_manager_from_resource( struct wl_resource *wl_resource_ptr) { BS_ASSERT(wl_resource_instance_of( wl_resource_ptr, &zwlmaker_icon_manager_v1_interface, &icon_manager_v1_implementation)); return wl_resource_get_user_data(wl_resource_ptr); } /* ------------------------------------------------------------------------- */ /** * Returns the toplevel icon from the resource, with type check. * * @param wl_resource_ptr * * @return Pointer to the @ref wlmaker_toplevel_icon_t. */ wlmaker_toplevel_icon_t *wlmaker_toplevel_icon_from_resource( struct wl_resource *wl_resource_ptr) { BS_ASSERT(wl_resource_instance_of( wl_resource_ptr, &zwlmaker_toplevel_icon_v1_interface, &toplevel_icon_v1_implementation)); return wl_resource_get_user_data(wl_resource_ptr); } /* ------------------------------------------------------------------------- */ /** * Binds an icon manager for the client. * * @param wl_client_ptr * @param data_ptr * @param version * @param id */ void bind_icon_manager( struct wl_client *wl_client_ptr, void *data_ptr, uint32_t version, uint32_t id) { struct wl_resource *wl_resource_ptr = wl_resource_create( wl_client_ptr, &zwlmaker_icon_manager_v1_interface, version, id); if (NULL == wl_resource_ptr) { wl_client_post_no_memory(wl_client_ptr); return; } wlmaker_icon_manager_t *icon_manager_ptr = data_ptr; wl_resource_set_implementation( wl_resource_ptr, &icon_manager_v1_implementation, // implementation. icon_manager_ptr, // data NULL); // dtor. We don't have an explicit one. } /* ------------------------------------------------------------------------- */ /** * Handler for the 'destroy' method: Destroys the resource. * * @param wl_client_ptr * @param wl_resource_ptr */ void handle_resource_destroy( __UNUSED__ struct wl_client *wl_client_ptr, struct wl_resource *wl_resource_ptr) { wl_resource_destroy(wl_resource_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the 'get_toplevel_icon' method. * * @param wl_client_ptr * @param wl_icon_manager_resource_ptr * @param id * @param wl_toplevel_resource_ptr * @param wl_surface_resource_ptr */ void handle_get_toplevel_icon( struct wl_client *wl_client_ptr, struct wl_resource *wl_icon_manager_resource_ptr, uint32_t id, struct wl_resource *wl_toplevel_resource_ptr, struct wl_resource *wl_surface_resource_ptr) { wlmaker_icon_manager_t *icon_manager_ptr = icon_manager_from_resource( wl_icon_manager_resource_ptr); struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr = NULL; if (NULL != wl_toplevel_resource_ptr) { wlr_xdg_toplevel_ptr = wlr_xdg_toplevel_from_resource( wl_toplevel_resource_ptr); } struct wlr_surface *wlr_surface_ptr = wlr_surface_from_resource(wl_surface_resource_ptr); wlmaker_toplevel_icon_t *toplevel_icon_ptr = wlmaker_toplevel_icon_create( wl_client_ptr, icon_manager_ptr, id, wl_resource_get_version(wl_icon_manager_resource_ptr), wlr_xdg_toplevel_ptr, wlr_surface_ptr); if (NULL == toplevel_icon_ptr) { wl_client_post_no_memory(wl_client_ptr); return; } } /* ------------------------------------------------------------------------- */ /** * Creates a new toplevel icon. * * @param wl_client_ptr * @param icon_manager_ptr * @param id * @param version * @param wlr_xdg_toplevel_ptr * @param wlr_surface_ptr * * @return A pointer to the new toplevel icon or NULL on error. The toplevel * icon's resources must be free'd via @ref wlmaker_toplevel_icon_destroy. */ wlmaker_toplevel_icon_t *wlmaker_toplevel_icon_create( struct wl_client *wl_client_ptr, wlmaker_icon_manager_t *icon_manager_ptr, uint32_t id, int version, struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, struct wlr_surface *wlr_surface_ptr) { wlmaker_toplevel_icon_t *toplevel_icon_ptr = logged_calloc( 1, sizeof(wlmaker_toplevel_icon_t)); if (NULL == toplevel_icon_ptr) return NULL; toplevel_icon_ptr->wl_client_ptr = wl_client_ptr; toplevel_icon_ptr->icon_manager_ptr = icon_manager_ptr; toplevel_icon_ptr->id = id; toplevel_icon_ptr->wlr_xdg_toplevel_ptr = wlr_xdg_toplevel_ptr; toplevel_icon_ptr->wlr_surface_ptr = wlr_surface_ptr; toplevel_icon_ptr->wl_resource_ptr = wl_resource_create( wl_client_ptr, &zwlmaker_toplevel_icon_v1_interface, version, id); if (NULL == toplevel_icon_ptr->wl_resource_ptr) { bs_log(BS_ERROR, "Failed wl_resource_create(%p, %p, %d, %"PRIu32")", wl_client_ptr, &zwlmaker_toplevel_icon_v1_interface, version, id); wlmaker_toplevel_icon_destroy(toplevel_icon_ptr); return NULL; } wl_resource_set_implementation( toplevel_icon_ptr->wl_resource_ptr, &toplevel_icon_v1_implementation, toplevel_icon_ptr, toplevel_icon_resource_destroy); if (!wlmtk_tile_init( &toplevel_icon_ptr->super_tile, &icon_manager_ptr->server_ptr->style_ptr->tile)) { wlmaker_toplevel_icon_destroy(toplevel_icon_ptr); return NULL; } wlmtk_element_extend( wlmtk_tile_element(&toplevel_icon_ptr->super_tile), &_wlmaker_toplevel_icon_element_vmt); wlmtk_tile_extend( &toplevel_icon_ptr->super_tile, &_wlmaker_toplevel_icon_tile_vmt); wlmtk_element_set_visible( wlmtk_tile_element(&toplevel_icon_ptr->super_tile), true); wlmtk_dock_add_tile( icon_manager_ptr->server_ptr->clip_dock_ptr, &toplevel_icon_ptr->super_tile); toplevel_icon_ptr->content_surface_ptr = wlmtk_surface_create( wlr_surface_ptr, icon_manager_ptr->server_ptr->wlr_seat_ptr); if (NULL == toplevel_icon_ptr->content_surface_ptr) { wlmaker_toplevel_icon_destroy(toplevel_icon_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_surface_element(toplevel_icon_ptr->content_surface_ptr), true); // Hack: Connect this listener after wlmtk_surface creation, so that the // surface knows it's size before added... wlmtk_util_connect_listener_signal( &toplevel_icon_ptr->wlr_surface_ptr->events.commit, &toplevel_icon_ptr->surface_commit_listener, handle_surface_commit); wlmtk_util_connect_listener_signal( &toplevel_icon_ptr->wlr_surface_ptr->events.destroy, &toplevel_icon_ptr->surface_destroy_listener, handle_surface_destroy); bs_log(BS_INFO, "created toplevel icon %p for toplevel %p, surface %p, " "wlmtk surface %p", toplevel_icon_ptr, wlr_xdg_toplevel_ptr, wlr_surface_ptr, toplevel_icon_ptr->content_surface_ptr); return toplevel_icon_ptr; } /* ------------------------------------------------------------------------- */ /** * Destroys the toplevel icon. * * @param toplevel_icon_ptr */ void wlmaker_toplevel_icon_destroy( wlmaker_toplevel_icon_t *toplevel_icon_ptr) { bs_log(BS_INFO, "Destroying toplevel icon %p", toplevel_icon_ptr); _wlmaker_toplevel_icon_element_destroy( wlmtk_tile_element(&toplevel_icon_ptr->super_tile)); wlmtk_tile_fini(&toplevel_icon_ptr->super_tile); // Note: Not destroying toplevel_icon_ptr->resource, since that causes // cycles... free(toplevel_icon_ptr); } /* ------------------------------------------------------------------------- */ /** * Destructor for the toplevel icon's resource. * * @param wl_resource_ptr */ void toplevel_icon_resource_destroy(struct wl_resource *wl_resource_ptr) { wlmaker_toplevel_icon_t *toplevel_icon_ptr = wlmaker_toplevel_icon_from_resource(wl_resource_ptr); wlmaker_toplevel_icon_destroy(toplevel_icon_ptr); } /* ------------------------------------------------------------------------- */ /** * Handles the `ack_configure` request by the icon. * * @param wl_client_ptr * @param wl_resource_ptr * @param serial */ void handle_icon_ack_configure( __UNUSED__ struct wl_client *wl_client_ptr, struct wl_resource *wl_resource_ptr, uint32_t serial) { wlmaker_toplevel_icon_t *toplevel_icon_ptr = wlmaker_toplevel_icon_from_resource(wl_resource_ptr); if (serial == toplevel_icon_ptr->pending_serial) { toplevel_icon_ptr->acknowledged = true; toplevel_icon_ptr->pending_serial = 0; } } /* ------------------------------------------------------------------------- */ /** * Event handler for the `commit` signal of the icon's surface. * * The protocol expects a first `commit` with a NULL-buffer attached to the * surface. This will trigger a `configure` event, informing the client of the * suggested icon size. * * Only when the configuration was suggested and acknowledged a first time, * will we accept `commit` with attached buffers. * * @param listener_ptr * @param data_ptr Points to the `struct wlr_surface` of the icon. */ void handle_surface_commit( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_toplevel_icon_t *toplevel_icon_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_toplevel_icon_t, surface_commit_listener); struct wlr_surface *wlr_surface_ptr = data_ptr; BS_ASSERT(toplevel_icon_ptr->wlr_surface_ptr == wlr_surface_ptr); if (NULL == wlr_surface_ptr->buffer) { // An initial commit is expected with a NULL buffer, so we can // respond with a `configure` event. toplevel_icon_ptr->pending_serial = wl_display_next_serial( toplevel_icon_ptr->icon_manager_ptr->wl_display_ptr); zwlmaker_toplevel_icon_v1_send_configure( toplevel_icon_ptr->wl_resource_ptr, toplevel_icon_ptr->super_tile.style.content_size, toplevel_icon_ptr->super_tile.style.content_size, toplevel_icon_ptr->pending_serial); return; } BS_ASSERT(NULL != wlr_surface_ptr->buffer); if (!toplevel_icon_ptr->acknowledged) { wl_resource_post_error( toplevel_icon_ptr->wl_resource_ptr, 1, "Commit non-NULL buffer without configure sequence."); return; } // TODO(kaeser@gubbe.ch): Constrain image size to the content size. wlmtk_tile_set_content( &toplevel_icon_ptr->super_tile, wlmtk_surface_element(toplevel_icon_ptr->content_surface_ptr)); } /* ------------------------------------------------------------------------- */ /** Handles when the surface is destroyed. */ static void handle_surface_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_toplevel_icon_t *toplevel_icon_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_toplevel_icon_t, surface_destroy_listener); _wlmaker_toplevel_icon_element_destroy( wlmtk_tile_element(&toplevel_icon_ptr->super_tile)); } /* ------------------------------------------------------------------------- */ /** * Destructor of the icon's corresponding tile element. * * This is a hack: The `wlmaker_toplevel_icon_t` is owned by the wl_resource * and must only be freed when that dtor is caller. But, the element dtor may * be called on wlmaker shutdown, when eg. an icon app is still running. * So, for that case, we just detach the icon here. * * Leaving as is, since the icon model will need to be revamped anyway, to * line up with the new XDG icon protocol. * * @param element_ptr */ void _wlmaker_toplevel_icon_element_destroy(wlmtk_element_t *element_ptr) { wlmaker_toplevel_icon_t *toplevel_icon_ptr = BS_CONTAINER_OF( element_ptr, wlmaker_toplevel_icon_t, super_tile.super_container.super_element); if (NULL != toplevel_icon_ptr->content_surface_ptr) { wlmtk_util_disconnect_listener( &toplevel_icon_ptr->surface_destroy_listener); wlmtk_util_disconnect_listener( &toplevel_icon_ptr->surface_commit_listener); wlmtk_tile_set_content(&toplevel_icon_ptr->super_tile, NULL); wlmtk_surface_destroy(toplevel_icon_ptr->content_surface_ptr); toplevel_icon_ptr->content_surface_ptr = NULL; } if (wlmtk_tile_element(&toplevel_icon_ptr->super_tile )->parent_container_ptr) { wlmtk_dock_remove_tile( toplevel_icon_ptr->icon_manager_ptr->server_ptr->clip_dock_ptr, &toplevel_icon_ptr->super_tile); } } /* ------------------------------------------------------------------------- */ /** Implements wlmtk_tile_vmt_t::set_content_size. Updates the content size. */ bool _wlmaker_toplevel_icon_tile_set_content_size( wlmtk_tile_t *tile_ptr, uint64_t content_size) { wlmaker_toplevel_icon_t *toplevel_icon_ptr = BS_CONTAINER_OF( tile_ptr, wlmaker_toplevel_icon_t, super_tile); toplevel_icon_ptr->pending_serial = wl_display_next_serial( toplevel_icon_ptr->icon_manager_ptr->wl_display_ptr); zwlmaker_toplevel_icon_v1_send_configure( toplevel_icon_ptr->wl_resource_ptr, content_size, content_size, toplevel_icon_ptr->pending_serial); // TODO(kaeser@gubbe.ch): Constrain image size to the content size. return true; } /* == End of icon_manager.c ================================================ */ wlmaker-0.8.1/src/input_observation.h0000644000175100017510000000401415222270710017277 0ustar runnerrunner/* ========================================================================= */ /** * @file input_observation.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __INPUT_OBSERVATION_H__ #define __INPUT_OBSERVATION_H__ /** Forward declaration: Input observation manager handle. */ typedef struct _wlmaker_input_observation_manager_t wlmaker_input_observation_manager_t; /** Forward declaration: Observer handle. */ typedef struct _wlmaker_input_position_observer_t wlmaker_input_position_observer_t; struct wl_display; struct wlr_cursor; struct wlr_seat; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates an input observation manager. * * @param wl_display_ptr * @param wlr_seat_ptr * @param wlr_cursor_ptr * * @return The handle of the input observation or NULL on error. Must be * destroyed by calling @ref wlmaker_input_observation_manager_destroy. */ wlmaker_input_observation_manager_t *wlmaker_input_observation_manager_create( struct wl_display *wl_display_ptr, struct wlr_seat *wlr_seat_ptr, struct wlr_cursor *wlr_cursor_ptr); /** * Destroys the input observation manager. * * @param manager_ptr */ void wlmaker_input_observation_manager_destroy( wlmaker_input_observation_manager_t *manager_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __INPUT_OBSERVATION_H__ */ /* == End of input_observation.h =========================================== */ wlmaker-0.8.1/src/xdg_shell.c0000644000175100017510000001225315222270710015475 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_shell.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "xdg_shell.h" #include #include #include #include "server.h" #include "toolkit/toolkit.h" #include "xdg_popup.h" #include "xdg_toplevel.h" /* == Declarations ========================================================= */ static void handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); static void handle_new_toplevel( struct wl_listener *listener_ptr, void *data_ptr); static void handle_new_popup( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_xdg_shell_t *wlmaker_xdg_shell_create(wlmaker_server_t *server_ptr) { wlmaker_xdg_shell_t *xdg_shell_ptr = logged_calloc( 1, sizeof(wlmaker_xdg_shell_t)); if (NULL == xdg_shell_ptr) return NULL; xdg_shell_ptr->server_ptr = server_ptr; xdg_shell_ptr->wlr_xdg_shell_ptr = wlr_xdg_shell_create( server_ptr->wl_display_ptr, 2); if (NULL == xdg_shell_ptr->wlr_xdg_shell_ptr) { wlmaker_xdg_shell_destroy(xdg_shell_ptr); return NULL; } wlmtk_util_connect_listener_signal( &xdg_shell_ptr->wlr_xdg_shell_ptr->events.new_toplevel, &xdg_shell_ptr->new_toplevel_listener, handle_new_toplevel); wlmtk_util_connect_listener_signal( &xdg_shell_ptr->wlr_xdg_shell_ptr->events.new_popup, &xdg_shell_ptr->new_popup_listener, handle_new_popup); wlmtk_util_connect_listener_signal( &xdg_shell_ptr->wlr_xdg_shell_ptr->events.destroy, &xdg_shell_ptr->destroy_listener, handle_destroy); return xdg_shell_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_xdg_shell_destroy(wlmaker_xdg_shell_t *xdg_shell_ptr) { wlmtk_util_disconnect_listener(&xdg_shell_ptr->destroy_listener); wlmtk_util_disconnect_listener(&xdg_shell_ptr->new_popup_listener); wlmtk_util_disconnect_listener(&xdg_shell_ptr->new_toplevel_listener); // Note: xdg_shell_ptr->wlr_xdg_shell_ptr is destroyed when the display // is destroyed. free(xdg_shell_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Event handler for the `destroy` signal raised by `wlr_xdg_shell`. * * @param listener_ptr * @param data_ptr */ void handle_destroy(struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_xdg_shell_t *xdg_shell_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_shell_t, destroy_listener); wlmaker_xdg_shell_destroy(xdg_shell_ptr); } /* ------------------------------------------------------------------------- */ /** * Event handler for the `new_toplevel` signal raised by `wlr_xdg_shell`. * * @param listener_ptr * @param data_ptr Points to the new struct wlr_xdg_toplevel. */ void handle_new_toplevel(struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xdg_shell_t *xdg_shell_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_shell_t, new_toplevel_listener); struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr = data_ptr; struct wlmaker_xdg_toplevel *wxt_ptr = wlmaker_xdg_toplevel_create( wlr_xdg_toplevel_ptr, xdg_shell_ptr->server_ptr); if (NULL == wxt_ptr) { wl_resource_post_error( wlr_xdg_toplevel_ptr->resource, WL_DISPLAY_ERROR_NO_MEMORY, "Failed wlmaker_xdg_toplevel_create"); } } /* ------------------------------------------------------------------------- */ /** * Event handler for the `new_popup` signal raised by `wlr_xdg_shell`. * * @param listener_ptr * @param data_ptr Points to the new struct wlr_xdg_popup. */ void handle_new_popup(struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_xdg_shell_t *xdg_shell_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_xdg_shell_t, new_popup_listener); struct wlr_xdg_popup *wlr_xdg_popup_ptr = data_ptr; if (NULL == wlr_xdg_popup_ptr->parent) { bs_log(BS_WARNING, "Unimplemented: XDG shell %p: Creating popup %p without parent", xdg_shell_ptr, wlr_xdg_popup_ptr); } } /* == End of xdg_shell.c =================================================== */ wlmaker-0.8.1/src/server.c0000644000175100017510000005214315222270710015034 0ustar runnerrunner/* ========================================================================= */ /** * @file server.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "server.h" #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "background.h" #include "input/manager.h" #include "task_list.h" #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** Name of the "seat". */ static const char *seat_name_ptr = "seat0"; static void _wlmaker_server_unclaimed_button_event_handler( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_server_handle_input_activity( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_server_handle_deactivate_task_list( struct wl_listener *listener_ptr, void *data_ptr); static void handle_request_set_selection( struct wl_listener *listener_ptr, void *data_ptr); static void handle_request_set_primary_selection( struct wl_listener *listener_ptr, void *data_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_server_t *wlmaker_server_create( bspl_dict_t *config_dict_ptr, wlmaker_files_t *files_ptr, wlmaker_config_style_t *style_ptr, const wlmaker_server_options_t *options_ptr) { wlmaker_server_t *server_ptr = logged_calloc(1, sizeof(wlmaker_server_t)); if (NULL == server_ptr) return NULL; server_ptr->options_ptr = options_ptr; server_ptr->files_ptr = files_ptr; server_ptr->style_ptr = style_ptr; server_ptr->config_dict_ptr = bspl_dict_ref(config_dict_ptr); if (NULL == server_ptr->config_dict_ptr) { wlmaker_server_destroy(server_ptr); return NULL; } wl_signal_init(&server_ptr->task_list_enabled_event); wl_signal_init(&server_ptr->task_list_disabled_event); wl_signal_init(&server_ptr->window_created_event); wl_signal_init(&server_ptr->window_destroyed_event); wl_signal_init(&server_ptr->theme_changed_event); // Prepare display and socket. server_ptr->wl_display_ptr = wl_display_create(); if (NULL == server_ptr->wl_display_ptr) { bs_log(BS_ERROR, "Failed wl_display_create()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->wl_socket_name_ptr = wl_display_add_socket_auto( server_ptr->wl_display_ptr); if (NULL == server_ptr->wl_socket_name_ptr) { bs_log(BS_ERROR, "Failed wl_display_add_socket_auto()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->wlr_viewporter_ptr = wlr_viewporter_create( server_ptr->wl_display_ptr); if (NULL == server_ptr->wlr_viewporter_ptr) { bs_log(BS_ERROR, "Failed wlr_viewporter_create()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->wlr_fractional_scale_manager_ptr = wlr_fractional_scale_manager_v1_create(server_ptr->wl_display_ptr, 1); if (NULL == server_ptr->wlr_fractional_scale_manager_ptr) { bs_log(BS_ERROR, "Failed wlr_fractional_scale_manager_v1_create()"); wlmaker_server_destroy(server_ptr); return NULL; } // Configure the seat, which is the potential set of input devices operated // by one user at a computer's "seat". server_ptr->wlr_seat_ptr = wlr_seat_create( server_ptr->wl_display_ptr, seat_name_ptr); if (NULL == server_ptr->wlr_seat_ptr) { bs_log(BS_ERROR, "Failed wlr_seat_create()"); wlmaker_server_destroy(server_ptr); } // The scene graph. server_ptr->wlr_scene_ptr = wlr_scene_create(); if (NULL == server_ptr->wlr_scene_ptr) { bs_log(BS_ERROR, "Failed wlr_scene_create()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->wlr_output_layout_ptr = wlr_output_layout_create( server_ptr->wl_display_ptr); if (NULL == server_ptr->wlr_output_layout_ptr) { bs_log(BS_ERROR, "Failed wlr_output_layout_create(%p)", server_ptr->wl_display_ptr); wlmaker_server_destroy(server_ptr); return NULL; } char *f = wlmaker_files_xdg_config_fname( server_ptr->files_ptr, "OutputState.plist"); if (NULL == f) { wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->backend_ptr = wlmbe_backend_create( server_ptr->wl_display_ptr, server_ptr->wlr_scene_ptr, server_ptr->wlr_output_layout_ptr, server_ptr->options_ptr->width, server_ptr->options_ptr->height, server_ptr->config_dict_ptr, f); free(f); if (NULL == server_ptr->backend_ptr) { bs_log(BS_ERROR, "Failed wlmbe_backend_create()"); wlmaker_server_destroy(server_ptr); return NULL; } // Root element. server_ptr->root_ptr = wlmtk_root_create( server_ptr->wlr_scene_ptr, server_ptr->wlr_output_layout_ptr); if (NULL == server_ptr->root_ptr) { wlmaker_server_destroy(server_ptr); return NULL; } wlmtk_util_connect_listener_signal( &wlmtk_root_events(server_ptr->root_ptr)->unclaimed_button_event, &server_ptr->unclaimed_button_event_listener, _wlmaker_server_unclaimed_button_event_handler); server_ptr->input_manager_ptr = wlmim_input_manager_create( wlmbe_backend_wlr(server_ptr->backend_ptr), server_ptr->wlr_output_layout_ptr, server_ptr->wlr_seat_ptr, server_ptr->config_dict_ptr, &style_ptr->cursor, server_ptr->root_ptr); if (NULL == server_ptr->input_manager_ptr) { wlmaker_server_destroy(server_ptr); return NULL; } wlmtk_util_connect_listener_signal( &wlmim_events(server_ptr->input_manager_ptr)->deactivate_task_list, &server_ptr->deactivate_task_list_listener, _wlmaker_server_handle_deactivate_task_list); wlmtk_util_connect_listener_signal( &wlmim_events(server_ptr->input_manager_ptr)->activity, &server_ptr->input_activity_listener, _wlmaker_server_handle_input_activity); // Session lock manager. server_ptr->lock_mgr_ptr = wlmaker_lock_mgr_create(server_ptr); if (NULL == server_ptr->lock_mgr_ptr) { bs_log(BS_ERROR, "Failed wlmaker_lock_mgr_create(%p)", server_ptr); wlmaker_server_destroy(server_ptr); return NULL; } // Idle monitor. server_ptr->idle_monitor_ptr = wlmaker_idle_monitor_create(server_ptr); if (NULL == server_ptr->idle_monitor_ptr) { bs_log(BS_ERROR, "Failed wlmaker_idle_monitor_create(%p)", server_ptr); return NULL; } // The below clipboard-related helpers all setup a listener |display_destroy| // for freeing the assets held via the respective create() calls. Hence no // need to call a clean-up method from our end. server_ptr->wlr_data_device_manager_ptr = wlr_data_device_manager_create( server_ptr->wl_display_ptr); if (NULL == server_ptr->wlr_data_device_manager_ptr) { bs_log(BS_ERROR, "Failed wlr_data_device_manager_create()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->wlr_primary_selection_v1_device_manager_ptr = wlr_primary_selection_v1_device_manager_create( server_ptr->wl_display_ptr); if (NULL == server_ptr->wlr_primary_selection_v1_device_manager_ptr) { bs_log(BS_ERROR, "Failed wlr_primary_selection_v1_device_manager_create()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->wlr_data_control_manager_v1_ptr = wlr_data_control_manager_v1_create(server_ptr->wl_display_ptr); if (NULL == server_ptr->wlr_data_control_manager_v1_ptr) { bs_log(BS_ERROR, "Failed wlr_data_control_manager_v1_create()"); wlmaker_server_destroy(server_ptr); return NULL; } wlmtk_util_connect_listener_signal( &server_ptr->wlr_seat_ptr->events.request_set_selection, &server_ptr->request_set_selection_listener, handle_request_set_selection); wlmtk_util_connect_listener_signal( &server_ptr->wlr_seat_ptr->events.request_set_primary_selection, &server_ptr->request_set_primary_selection_listener, handle_request_set_primary_selection); server_ptr->xdg_shell_ptr = wlmaker_xdg_shell_create(server_ptr); if (NULL == server_ptr->xdg_shell_ptr) { bs_log(BS_ERROR, "Failed wlmaker_xdg_shell_create()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->xdg_decoration_manager_ptr = wlmaker_xdg_decoration_manager_create( server_ptr->wl_display_ptr, server_ptr->config_dict_ptr); if (NULL == server_ptr->xdg_decoration_manager_ptr) { bs_log(BS_ERROR, "Failed wlmaker_xdg_decoration_manager_create(%p, %p)", server_ptr->wl_display_ptr, server_ptr->config_dict_ptr); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->layer_shell_ptr = wlmaker_layer_shell_create(server_ptr); if (NULL == server_ptr->layer_shell_ptr) { bs_log(BS_ERROR, "Failed wlmaker_layer_shell_create()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->icon_manager_ptr = wlmaker_icon_manager_create( server_ptr->wl_display_ptr, server_ptr); if (NULL == server_ptr->icon_manager_ptr) { wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->input_observation_manager_ptr = wlmaker_input_observation_manager_create( server_ptr->wl_display_ptr, server_ptr->wlr_seat_ptr, wlmim_wlr_cursor(server_ptr->input_manager_ptr)); if (NULL == server_ptr->input_observation_manager_ptr) { wlmaker_server_destroy(server_ptr); return NULL; } if (server_ptr->options_ptr->start_xwayland) { server_ptr->xwl_ptr = wlmaker_xwl_create(server_ptr); if (NULL == server_ptr->xwl_ptr) { wlmaker_server_destroy(server_ptr); return NULL; } } server_ptr->monitor_ptr = wlmaker_subprocess_monitor_create(server_ptr); if (NULL == server_ptr->monitor_ptr) { bs_log(BS_ERROR, "Failed wlmaker_subprocess_monitor_create()"); wlmaker_server_destroy(server_ptr); return NULL; } server_ptr->corner_ptr = wlmaker_corner_create( bspl_dict_get_dict(server_ptr->config_dict_ptr, "HotCorner"), wl_display_get_event_loop(server_ptr->wl_display_ptr), server_ptr->wlr_output_layout_ptr, wlmim_wlr_cursor(server_ptr->input_manager_ptr), &wlmim_events(server_ptr->input_manager_ptr)->cursor_position_updated, server_ptr); if (NULL == server_ptr->corner_ptr) { bs_log(BS_ERROR, "Failed wlmaker_corner_create(%p, %p, %p, %p, %p, %p)", bspl_dict_get_dict(server_ptr->config_dict_ptr, "HotCorner"), wl_display_get_event_loop(server_ptr->wl_display_ptr), server_ptr->wlr_output_layout_ptr, wlmim_wlr_cursor(server_ptr->input_manager_ptr), &wlmim_events(server_ptr->input_manager_ptr)->cursor_position_updated, server_ptr); wlmaker_server_destroy(server_ptr); return NULL; } return server_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_server_destroy(wlmaker_server_t *server_ptr) { bs_dllist_for_each( &server_ptr->backgrounds, wlmaker_background_dlnode_destroy, NULL); if (NULL != server_ptr->root_menu_ptr) { wlmaker_root_menu_destroy(server_ptr->root_menu_ptr); server_ptr->root_menu_ptr = NULL; } // We don't destroy a few of the handlers, since wlroots will crash if // they are destroyed -- and apparently, wlroots cleans them up anyway. // These are: // * server_ptr->wlr_seat_ptr // * server_ptr->wlr_backend_ptr // * server_ptr->wlr_scene_ptr (there is no "destroy" function) if (NULL != server_ptr->corner_ptr) { wlmaker_corner_destroy(server_ptr->corner_ptr); server_ptr->corner_ptr = NULL; } if (NULL != server_ptr->monitor_ptr) { wlmaker_subprocess_monitor_destroy(server_ptr->monitor_ptr); server_ptr->monitor_ptr =NULL; } if (NULL != server_ptr->xwl_ptr) { wlmaker_xwl_destroy(server_ptr->xwl_ptr); server_ptr->xwl_ptr = NULL; } if (NULL != server_ptr->input_observation_manager_ptr) { wlmaker_input_observation_manager_destroy( server_ptr->input_observation_manager_ptr); server_ptr->input_observation_manager_ptr = NULL; } if (NULL != server_ptr->icon_manager_ptr) { wlmaker_icon_manager_destroy(server_ptr->icon_manager_ptr); server_ptr->icon_manager_ptr = NULL; } if (NULL != server_ptr->layer_shell_ptr) { wlmaker_layer_shell_destroy(server_ptr->layer_shell_ptr); server_ptr->layer_shell_ptr = NULL; } if (NULL != server_ptr->xdg_decoration_manager_ptr) { wlmaker_xdg_decoration_manager_destroy( server_ptr->xdg_decoration_manager_ptr); server_ptr->xdg_decoration_manager_ptr = NULL; } if (NULL != server_ptr->xdg_shell_ptr) { wlmaker_xdg_shell_destroy(server_ptr->xdg_shell_ptr); server_ptr->xdg_shell_ptr = NULL; } if (NULL != server_ptr->wl_display_ptr) { wl_display_destroy_clients(server_ptr->wl_display_ptr); server_ptr->wl_display_ptr = NULL; } if (NULL != server_ptr->idle_monitor_ptr) { wlmaker_idle_monitor_destroy(server_ptr->idle_monitor_ptr); server_ptr->idle_monitor_ptr = NULL; } if (NULL != server_ptr->lock_mgr_ptr) { wlmaker_lock_mgr_destroy(server_ptr->lock_mgr_ptr); server_ptr->lock_mgr_ptr = NULL; } if (NULL != server_ptr->input_manager_ptr) { wlmtk_util_disconnect_listener( &server_ptr->deactivate_task_list_listener); wlmtk_util_disconnect_listener( &server_ptr->input_activity_listener); wlmim_input_manager_destroy(server_ptr->input_manager_ptr); server_ptr->input_manager_ptr = NULL; } if (NULL != server_ptr->root_ptr) { wlmtk_util_disconnect_listener( &server_ptr->unclaimed_button_event_listener); wlmtk_root_destroy(server_ptr->root_ptr); server_ptr->root_ptr = NULL; } if (NULL != server_ptr->backend_ptr) { wlmbe_backend_destroy(server_ptr->backend_ptr); server_ptr->backend_ptr = NULL; } if (NULL != server_ptr->wl_display_ptr) { wl_display_destroy(server_ptr->wl_display_ptr); server_ptr->wl_display_ptr = NULL; } if (NULL != server_ptr->config_dict_ptr) { bspl_dict_unref(server_ptr->config_dict_ptr); server_ptr->config_dict_ptr = NULL; } if (NULL != server_ptr->files_ptr) { wlmaker_files_destroy(server_ptr->files_ptr); server_ptr->files_ptr = NULL; } free(server_ptr); } /* ------------------------------------------------------------------------- */ void wlmaker_server_activate_task_list(wlmaker_server_t *server_ptr) { server_ptr->task_list_enabled = true; wl_signal_emit(&server_ptr->task_list_enabled_event, NULL); } /* ------------------------------------------------------------------------- */ struct wlr_output *wlmaker_server_get_output_at_cursor( wlmaker_server_t *server_ptr) { return wlr_output_layout_output_at( server_ptr->wlr_output_layout_ptr, wlmim_wlr_cursor(server_ptr->input_manager_ptr)->x, wlmim_wlr_cursor(server_ptr->input_manager_ptr)->y); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Handles unclaimed button events: Right 'down' opens root menu. */ void _wlmaker_server_unclaimed_button_event_handler( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_server_t *server_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_server_t, unclaimed_button_event_listener); wlmtk_button_event_t *button_event_ptr = data_ptr; if (BTN_RIGHT == button_event_ptr->button && WLMTK_BUTTON_DOWN == button_event_ptr->type && NULL != server_ptr->root_menu_ptr && // TODO(kaeser@gubbe.ch): Clean up. !wlmtk_menu_is_open( wlmaker_root_menu_menu(server_ptr->root_menu_ptr))) { wlmtk_workspace_map_window( wlmtk_root_get_current_workspace(server_ptr->root_ptr), wlmaker_root_menu_window(server_ptr->root_menu_ptr)); wlmtk_workspace_set_window_position( wlmtk_root_get_current_workspace(server_ptr->root_ptr), wlmaker_root_menu_window(server_ptr->root_menu_ptr), wlmim_wlr_cursor(server_ptr->input_manager_ptr)->x, wlmim_wlr_cursor(server_ptr->input_manager_ptr)->y); wlmtk_workspace_confine_within( wlmtk_root_get_current_workspace(server_ptr->root_ptr), wlmaker_root_menu_window(server_ptr->root_menu_ptr)); wlmtk_menu_set_mode( wlmaker_root_menu_menu(server_ptr->root_menu_ptr), WLMTK_MENU_MODE_RIGHTCLICK); wlmtk_menu_set_open( wlmaker_root_menu_menu(server_ptr->root_menu_ptr), true); } } /* ------------------------------------------------------------------------- */ /** Handles input activity: Resets the idle timer. */ void _wlmaker_server_handle_input_activity( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_server_t *server_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_server_t, input_activity_listener); wlmaker_idle_monitor_reset(server_ptr->idle_monitor_ptr); } /* ------------------------------------------------------------------------- */ /** Handles suggestions to deactivate the task list. */ static void _wlmaker_server_handle_deactivate_task_list( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_server_t *server_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_server_t, deactivate_task_list_listener); if (!server_ptr->task_list_enabled) return; server_ptr->task_list_enabled = false; wl_signal_emit(&server_ptr->task_list_disabled_event, NULL); wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(server_ptr->root_ptr); wlmtk_window_t *window_ptr = wlmtk_workspace_get_activated_window(workspace_ptr); if (NULL != window_ptr) { wlmtk_workspace_raise_window(workspace_ptr, window_ptr); } } /* ------------------------------------------------------------------------- */ /** * Approves a client's request to set the clipboard selection. * * @param listener_ptr * @param data_ptr Points to a struct wlr_seat_request_set_selection_event. */ void handle_request_set_selection( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_server_t *server_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_server_t, request_set_selection_listener); struct wlr_seat_request_set_selection_event *event_ptr = data_ptr; wlr_seat_set_selection( server_ptr->wlr_seat_ptr, event_ptr->source, event_ptr->serial); } /* ------------------------------------------------------------------------- */ /** * Approves a client's request to set the primary selection. * * @param listener_ptr * @param data_ptr Points to a struct * wlr_seat_request_set_primary_selection_event. */ void handle_request_set_primary_selection( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_server_t *server_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_server_t, request_set_primary_selection_listener); struct wlr_seat_request_set_primary_selection_event *event_ptr = data_ptr; wlr_seat_set_primary_selection( server_ptr->wlr_seat_ptr, event_ptr->source, event_ptr->serial); } /* == End of server.c ====================================================== */ wlmaker-0.8.1/src/xdg_toplevel.h0000644000175100017510000000355115222270710016226 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_toplevel.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __XDG_TOPLEVEL_H__ #define __XDG_TOPLEVEL_H__ #include #include #include "task_list.h" struct wlr_xdg_toplevel; struct wlmaker_xdg_toplevel; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates an XDG toplevel. * * @param wlr_xdg_toplevel_ptr * @param server_ptr * * @return wlmaker's toplevel handle or NULL on error. */ struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_create( struct wlr_xdg_toplevel *wlr_xdg_toplevel_ptr, wlmaker_server_t *server_ptr); /** Dtor for the XDG toplevel. */ void wlmaker_xdg_toplevel_destroy( struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr); /** En-/Disables server-side decoration for the XDG toplevel. */ void wlmaker_xdg_toplevel_set_server_side_decorated( struct wlmaker_xdg_toplevel *wlmaker_xdg_toplevel_ptr, bool server_side_decorated); /** Unit test set. */ extern const bs_test_set_t wlmaker_xdg_toplevel_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __XDG_TOPLEVEL_H__ */ /* == End of xdg_toplevel.h ================================================ */ wlmaker-0.8.1/src/dock.h0000644000175100017510000000365615222270710014460 0ustar runnerrunner/* ========================================================================= */ /** * @file dock.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Creates the wlmaker dock: A view, with server-bound surfaces, that acts as * launcher for Apps. * * Corresponding Window Maker documentation: * http://www.windowmaker.org/docs/guidedtour/dock.html */ #ifndef __DOCK_H__ #define __DOCK_H__ #include #include /** Forward definition: Dock handle. */ typedef struct _wlmaker_dock_t wlmaker_dock_t; #include "config.h" #include "server.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates the Dock handle. Needs the server to be up with workspaces running. * * @param server_ptr * @param state_dict_ptr * @param style_ptr * * @return Pointer to the Dock handle, or NULL on error. */ wlmaker_dock_t *wlmaker_dock_create( wlmaker_server_t *server_ptr, bspl_dict_t *state_dict_ptr, const wlmaker_config_style_t *style_ptr); /** * Destroys the Dock handle. * * @param dock_ptr */ void wlmaker_dock_destroy(wlmaker_dock_t *dock_ptr); /** Unit test test. */ extern const bs_test_set_t wlmaker_dock_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __DOCK_H__ */ /* == End of dock.h ======================================================== */ wlmaker-0.8.1/src/layer_panel.h0000644000175100017510000000354015222270710016023 0ustar runnerrunner/* ========================================================================= */ /** * @file layer_panel.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LAYER_PANEL_H__ #define __LAYER_PANEL_H__ #include /** Handler for a layer panel. */ typedef struct _wlmaker_layer_panel_t wlmaker_layer_panel_t; #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration for wlroots layer surface. */ struct wlr_layer_surface_v1; /** * Creates a layer panel for the given layer surface. * * A layer panel is the implementation of a WLroots layer shell surface. * * @param wlr_layer_surface_v1_ptr * @param server_ptr * * @return The handler for the layer surface or NULL on error. The associated * resources will be destroyed once `wlr_layer_surface_v1_ptr` is * destroyed. */ wlmaker_layer_panel_t *wlmaker_layer_panel_create( struct wlr_layer_surface_v1 *wlr_layer_surface_v1_ptr, wlmaker_server_t *server_ptr); /** Unit test set of layer panel. */ extern const bs_test_set_t wlmaker_layer_panel_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LAYER_PANEL_H__ */ /* == End of layer_panel.h ================================================= */ wlmaker-0.8.1/src/dock.c0000644000175100017510000003504015222270710014443 0ustar runnerrunner/* ========================================================================= */ /** * @file dock.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "dock.h" #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #undef WLR_USE_UNSTABLE #include "backend/backend.h" #include "backend/output_config.h" #include "config.h" #include "default_state.h" #include "files.h" #include "launcher.h" #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** Dock handle. */ struct _wlmaker_dock_t { /** Toolkit dock. */ wlmtk_dock_t *wlmtk_dock_ptr; /** Back-link to server. */ wlmaker_server_t *server_ptr; /** Description of the desired output, if any. */ wlmbe_output_description_t output_description; /** Listener for @ref wlmtk_root_events_t::workspace_changed. */ struct wl_listener workspace_changed_listener; /** Listener for wlr_output_layout::events.change. */ struct wl_listener output_layout_change_listener; /** Listener for @ref wlmaker_server_t::theme_changed_event. */ struct wl_listener theme_changed_listener; }; static bool _wlmaker_dock_decode_launchers( bspl_object_t *object_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); static void _wlmaker_dock_handle_workspace_changed( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_dock_handle_output_layout_change( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_dock_handle_theme_changed( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** TODO: Replace this. */ typedef struct { /** Positioning data. */ wlmtk_dock_positioning_t positioning; /** Launchers. */ bspl_array_t *launchers_array_ptr; } parse_args; /** Enum descriptor for `enum wlr_edges`. */ static const bspl_enum_desc_t _wlmaker_dock_edges[] = { BSPL_ENUM("TOP", WLR_EDGE_TOP), BSPL_ENUM("BOTTOM", WLR_EDGE_BOTTOM), BSPL_ENUM("LEFT", WLR_EDGE_LEFT), BSPL_ENUM("RIGHT", WLR_EDGE_RIGHT), BSPL_ENUM_SENTINEL(), }; /** Descriptor for the dock's plist. */ const bspl_desc_t _wlmaker_dock_desc[] = { BSPL_DESC_ENUM("Edge", true, parse_args, positioning.edge, positioning.edge, WLR_EDGE_NONE, _wlmaker_dock_edges), BSPL_DESC_ENUM("Anchor", true, parse_args, positioning.anchor, positioning.anchor, WLR_EDGE_NONE, _wlmaker_dock_edges), BSPL_DESC_CUSTOM("Launchers", true, parse_args, launchers_array_ptr, launchers_array_ptr, _wlmaker_dock_decode_launchers, NULL, NULL, NULL), BSPL_DESC_SENTINEL(), }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_dock_t *wlmaker_dock_create( wlmaker_server_t *server_ptr, bspl_dict_t *state_dict_ptr, const wlmaker_config_style_t *style_ptr) { wlmaker_dock_t *dock_ptr = logged_calloc(1, sizeof(wlmaker_dock_t)); if (NULL == dock_ptr) return NULL; dock_ptr->server_ptr = server_ptr; parse_args args = {}; bspl_dict_t *dict_ptr = bspl_dict_get_dict(state_dict_ptr, "Dock"); if (NULL == dict_ptr) { bs_log(BS_ERROR, "No 'Dock' dict found in configuration."); wlmaker_dock_destroy(dock_ptr); return NULL; } bspl_decode_dict(dict_ptr, _wlmaker_dock_desc, &args); bspl_dict_t *output_dict_ptr = bspl_dict_get_dict(dict_ptr, "Output"); if (NULL != output_dict_ptr) { if (!wlmbe_output_description_init_from_plist( &dock_ptr->output_description, output_dict_ptr)) { wlmaker_dock_destroy(dock_ptr); return NULL; } } dock_ptr->wlmtk_dock_ptr = wlmtk_dock_create( &args.positioning, &style_ptr->dock); if (NULL == dock_ptr->wlmtk_dock_ptr) { wlmaker_dock_destroy(dock_ptr); return NULL; } wlmtk_element_set_visible( wlmtk_dock_element(dock_ptr->wlmtk_dock_ptr), true); struct wlr_output *wlr_output_ptr = wlmbe_output_description_first_fnmatch( &dock_ptr->output_description, server_ptr->wlr_output_layout_ptr); if (NULL == wlr_output_ptr) { wlr_output_ptr = wlmbe_primary_output( server_ptr->wlr_output_layout_ptr); } wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(server_ptr->root_ptr); wlmtk_layer_t *layer_ptr = wlmtk_workspace_get_layer( workspace_ptr, WLMTK_WORKSPACE_LAYER_TOP); if (!wlmtk_layer_add_panel( layer_ptr, wlmtk_dock_panel(dock_ptr->wlmtk_dock_ptr), wlr_output_ptr)) { wlmaker_dock_destroy(dock_ptr); return NULL; } for (size_t i = 0; i < bspl_array_size(args.launchers_array_ptr); ++i) { bspl_dict_t *dict_ptr = bspl_dict_from_object( bspl_array_at(args.launchers_array_ptr, i)); if (NULL == dict_ptr) { bs_log(BS_ERROR, "Elements of 'Launchers' must be dicts."); wlmaker_dock_destroy(dock_ptr); return NULL; } wlmaker_launcher_t *launcher_ptr = wlmaker_launcher_create_from_plist( &style_ptr->tile, dict_ptr, server_ptr->monitor_ptr, server_ptr->files_ptr); if (NULL == launcher_ptr) { wlmaker_dock_destroy(dock_ptr); return NULL; } wlmtk_dock_add_tile( dock_ptr->wlmtk_dock_ptr, wlmaker_launcher_tile(launcher_ptr)); } // FIXME: This is leaky. if (NULL != args.launchers_array_ptr) { bspl_array_unref(args.launchers_array_ptr); args.launchers_array_ptr = NULL; } wlmtk_util_connect_listener_signal( &wlmtk_root_events(server_ptr->root_ptr)->workspace_changed, &dock_ptr->workspace_changed_listener, _wlmaker_dock_handle_workspace_changed); wlmtk_util_connect_listener_signal( &server_ptr->theme_changed_event, &dock_ptr->theme_changed_listener, _wlmaker_dock_handle_theme_changed); // TODO(kaeser@gubbe.ch): This is a very hacky way of updating the output // before the layer's handler removes all associated panels. Should be // a native method of wlmtk_dock_t or wlmtk_panel_t. dock_ptr->output_layout_change_listener.notify = _wlmaker_dock_handle_output_layout_change; wl_list_insert( server_ptr->wlr_output_layout_ptr->events.change.listener_list.next, &dock_ptr->output_layout_change_listener.link); bs_log(BS_INFO, "Created dock %p", dock_ptr); return dock_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_dock_destroy(wlmaker_dock_t *dock_ptr) { wlmtk_util_disconnect_listener(&dock_ptr->output_layout_change_listener); wlmtk_util_disconnect_listener(&dock_ptr->workspace_changed_listener); wlmtk_util_disconnect_listener(&dock_ptr->theme_changed_listener); if (NULL != dock_ptr->wlmtk_dock_ptr) { if (NULL != wlmtk_panel_get_layer( wlmtk_dock_panel(dock_ptr->wlmtk_dock_ptr))) { wlmtk_layer_remove_panel( wlmtk_panel_get_layer(wlmtk_dock_panel(dock_ptr->wlmtk_dock_ptr)), wlmtk_dock_panel(dock_ptr->wlmtk_dock_ptr)); } wlmtk_dock_destroy(dock_ptr->wlmtk_dock_ptr); dock_ptr->wlmtk_dock_ptr = NULL; } wlmbe_output_description_fini(&dock_ptr->output_description); free(dock_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Decoder for the "Launchers" array. Currently just stores a reference. */ bool _wlmaker_dock_decode_launchers( bspl_object_t *object_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { bspl_array_t **array_ptr_ptr = value_ptr; *array_ptr_ptr = bspl_array_from_object(object_ptr); if (NULL == *array_ptr_ptr) return false; bspl_object_ref(bspl_object_from_array(*array_ptr_ptr)); return true; } /* ------------------------------------------------------------------------- */ /** Re-attaches the dock to the new "current" workspace. */ void _wlmaker_dock_handle_workspace_changed( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_dock_t *dock_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_dock_t, workspace_changed_listener); wlmtk_panel_t *panel_ptr = wlmtk_dock_panel(dock_ptr->wlmtk_dock_ptr); wlmtk_layer_t *current_layer_ptr = wlmtk_panel_get_layer(panel_ptr); wlmtk_workspace_t *workspace_ptr = wlmtk_root_get_current_workspace(dock_ptr->server_ptr->root_ptr); wlmtk_layer_t *new_layer_ptr = wlmtk_workspace_get_layer( workspace_ptr, WLMTK_WORKSPACE_LAYER_TOP); if (current_layer_ptr == new_layer_ptr) return; if (NULL != current_layer_ptr) { wlmtk_layer_remove_panel(current_layer_ptr, panel_ptr); } BS_ASSERT(wlmtk_layer_add_panel( new_layer_ptr, panel_ptr, wlmbe_primary_output( dock_ptr->server_ptr->wlr_output_layout_ptr))); } /* ------------------------------------------------------------------------- */ /** Handles when output layout changes; Re-computes the output to attach. */ void _wlmaker_dock_handle_output_layout_change( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_dock_t *dock_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_dock_t, output_layout_change_listener); struct wlr_output *wlr_output_ptr = wlmbe_output_description_first_fnmatch( &dock_ptr->output_description, dock_ptr->server_ptr->wlr_output_layout_ptr); if (NULL == wlr_output_ptr) { wlr_output_ptr = wlmbe_primary_output( dock_ptr->server_ptr->wlr_output_layout_ptr); } wlmtk_layer_t *layer_ptr = wlmtk_panel_get_layer( wlmtk_dock_panel(dock_ptr->wlmtk_dock_ptr)); wlmtk_layer_remove_panel(layer_ptr, wlmtk_dock_panel(dock_ptr->wlmtk_dock_ptr)); if (NULL != wlr_output_ptr) { BS_ASSERT(wlmtk_layer_add_panel( layer_ptr, wlmtk_dock_panel(dock_ptr->wlmtk_dock_ptr), wlr_output_ptr)); } } /* ------------------------------------------------------------------------- */ /** * Handler for the `theme_changed_listener`: Updates the style * * @param listener_ptr * @param data_ptr */ void _wlmaker_dock_handle_theme_changed( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_dock_t *dock_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_dock_t, theme_changed_listener); wlmaker_config_style_t *style_ptr = data_ptr; wlmtk_dock_set_style( dock_ptr->wlmtk_dock_ptr, &style_ptr->dock, &style_ptr->tile); } /* == Unit tests =========================================================== */ static void test_create_destroy(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_dock_test_cases[] = { { true, "create_destroy", test_create_destroy }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_dock_test_set = BS_TEST_SET( true, "dock", wlmaker_dock_test_cases); /* ------------------------------------------------------------------------- */ /** Tests ctor and dtor; to help fix leaks. */ void test_create_destroy(bs_test_t *test_ptr) { bs_test_setenv(test_ptr, "XDG_DATA_DIRS", WLMAKER_SOURCE_DIR "/share"); struct wlr_scene *wlr_scene_ptr = wlr_scene_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wlr_scene_ptr); wlmaker_server_t server = { .wlr_scene_ptr = wlr_scene_ptr, .wl_display_ptr = wl_display_create(), .files_ptr = wlmaker_files_create("wlmaker"), }; BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.files_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.wl_display_ptr); wl_signal_init(&server.theme_changed_event); server.wlr_output_layout_ptr = wlr_output_layout_create( server.wl_display_ptr); struct wlr_output output = { .width = 1024, .height = 768, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add_auto(server.wlr_output_layout_ptr, &output); bspl_dict_t *dict_ptr = bspl_dict_from_object( bspl_create_object_from_plist_data( embedded_binary_default_state_data, embedded_binary_default_state_size)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dict_ptr); server.root_ptr = wlmtk_root_create( server.wlr_scene_ptr, server.wlr_output_layout_ptr); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, server.root_ptr); struct wlmtk_tile_style ts = {}; wlmtk_workspace_t *ws_ptr = wlmtk_workspace_create( server.wlr_output_layout_ptr, "1", &ts); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, ws_ptr); wlmtk_root_add_workspace(server.root_ptr, ws_ptr); wlmaker_config_style_t style = {}; wlmaker_dock_t *dock_ptr = wlmaker_dock_create(&server, dict_ptr, &style); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dock_ptr); wlmaker_dock_destroy(dock_ptr); bspl_dict_unref(dict_ptr); wlmtk_root_remove_workspace(server.root_ptr, ws_ptr); wlmtk_workspace_destroy(ws_ptr); wlmtk_root_destroy(server.root_ptr); wl_display_destroy(server.wl_display_ptr); wlr_scene_node_destroy(&wlr_scene_ptr->tree.node); wlmaker_files_destroy(server.files_ptr); } /* == End of dock.c ======================================================== */ wlmaker-0.8.1/src/xdg_popup.h0000644000175100017510000000347515222270710015544 0ustar runnerrunner/* ========================================================================= */ /** * @file xdg_popup.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __XDG_POPUP_H__ #define __XDG_POPUP_H__ #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "toolkit/toolkit.h" struct wlr_seat; struct wlr_xdg_popup; /** Forward declaration: State of the toolkit's XDG popup. */ typedef struct _wlmaker_xdg_popup_t wlmaker_xdg_popup_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a popup. * * @param wlr_xdg_popup_ptr * @param wlr_seat_ptr * * @return Popup handle or NULL on error. */ wlmaker_xdg_popup_t *wlmaker_xdg_popup_create( struct wlr_xdg_popup *wlr_xdg_popup_ptr, struct wlr_seat *wlr_seat_ptr); /** * Destroys the popup. * * @param wlmaker_xdg_popup_ptr */ void wlmaker_xdg_popup_destroy( wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr); /** Returns the superclass element. */ wlmtk_element_t *wlmaker_xdg_popup_element( wlmaker_xdg_popup_t *wlmaker_xdg_popup_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __XDG_POPUP_H__ */ /* == End of xdg_popup.h =================================================== */ wlmaker-0.8.1/src/input/0000755000175100017510000000000015222270710014514 5ustar runnerrunnerwlmaker-0.8.1/src/input/cursor.h0000644000175100017510000000747715222270710016221 0ustar runnerrunner/* ========================================================================= */ /** * @file cursor.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_INPUT_CURSOR_H__ #define __WLMAKER_INPUT_CURSOR_H__ #include #include #include #include #include "manager.h" #include "toolkit/toolkit.h" /** Forward declaration of wlmaker cursor state. */ typedef struct _wlmim_cursor_t wlmim_cursor_t; struct wlr_input_device; struct wlr_output_layout; struct wlr_seat; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Style of the cursor. */ struct wlmim_cursor_style { /** Whether to override the system-wide configuration. */ bool override_system_configuration; /** Name of the XCursor theme to use. */ char *name_ptr; /** Size, when non-scaled. */ uint64_t size; }; /** Options that configure cursor behaviour. */ struct wlmim_cursor_options { /** Keyboard modifier to change a left button into right button action. */ uint32_t emulate_right_button_modifier; /** Keyboard modifier for moving a window when left-dragging. */ uint32_t move_window_modifier; }; /** * Creates the cursor handlers. * * @param input_manager_ptr * @param style_ptr * @param options_ptr * @param wlr_output_layout_ptr * @param wlr_seat_ptr * @param root_ptr * * @return The cursor handler or NULL on error. */ wlmim_cursor_t *wlmim_cursor_create( wlmim_t *input_manager_ptr, const struct wlmim_cursor_style *style_ptr, const struct wlmim_cursor_options *options_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_seat *wlr_seat_ptr, wlmtk_root_t *root_ptr); /** * Destroys the cursor handlers. * * @param cursor_ptr */ void wlmim_cursor_destroy(wlmim_cursor_t *cursor_ptr); /** * Updates the cursor's style. * * @param cursor_ptr * @param style_ptr * * @return true on success. */ bool wlmim_cursor_set_style( wlmim_cursor_t *cursor_ptr, const struct wlmim_cursor_style *style_ptr); /** @return @ref wlmim_cursor_t::wlr_cursor_ptr. */ struct wlr_cursor *wlmim_cursor_wlr_cursor(wlmim_cursor_t *cursor_ptr); /** * Attaches the input device. May be a pointer, touch or tablet_tool device. * * @param cursor_ptr * @param wlr_input_device_ptr */ void wlmim_cursor_attach_input_device( wlmim_cursor_t *cursor_ptr, struct wlr_input_device *wlr_input_device_ptr); /** * Detaches the input device. @see wlmim_cursor_attach_input_device. * * @param cursor_ptr * @param wlr_input_device_ptr */ void wlmim_cursor_detach_input_device( wlmim_cursor_t *cursor_ptr, struct wlr_input_device *wlr_input_device_ptr); /** Descriptor for decoding the "Cursor" style dictionary. */ extern const bspl_desc_t wlmim_cursor_style_desc[]; /** Descriptor for decoding Cursor-relevant config dictionary. */ extern const bspl_desc_t wlmim_cursor_options_desc[]; /** Unit tests for cursor. */ extern const bs_test_set_t wlmim_cursor_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMAKER_INPUT_CURSOR_H__ */ /* == End of cursor.h ====================================================== */ wlmaker-0.8.1/src/input/keyboard.h0000644000175100017510000000420115222270710016462 0ustar runnerrunner/* ========================================================================= */ /** * @file keyboard.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_INPUT_KEYBOARD_H__ #define __WLMAKER_INPUT_KEYBOARD_H__ #include #include #include "manager.h" #include "toolkit/toolkit.h" struct wlr_keyboard; struct wlr_seat; /** Type of the keyboard handle. */ typedef struct _wlmim_keyboard_t wlmim_keyboard_t; /** Forward declaration. */ typedef struct _wlmtk_element_t wlmtk_element_t; /** * Creates a handle for a registered keyboard. * * @param input_manager_ptr * @param wlmaker_config_dict_ptr * @param wlr_keyboard_ptr * @param wlr_seat_ptr * @param root_element_ptr * * @return The handle or NULL on error. Free via wlmim_keyboard_destroy(). */ wlmim_keyboard_t *wlmim_keyboard_create( wlmim_t *input_manager_ptr, bspl_dict_t *wlmaker_config_dict_ptr, struct wlr_keyboard *wlr_keyboard_ptr, struct wlr_seat *wlr_seat_ptr, wlmtk_element_t *root_element_ptr); /** * Destroys the keyboard handle. * * @param keyboard_ptr */ void wlmim_keyboard_destroy(wlmim_keyboard_t *keyboard_ptr); /** Keyboard modifiers, as enum to lookup. */ extern const bspl_enum_desc_t wlmim_keyboard_modifiers[]; /** Unit test set for @ref wlmim_keyboard_t. */ extern const bs_test_set_t wlmim_keyboard_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMAKER_INPUT_KEYBOARD_H__ */ /* == End of keyboard.h ==================================================== */ wlmaker-0.8.1/src/input/pointer.c0000644000175100017510000002574515222270710016355 0ustar runnerrunner/* ========================================================================= */ /** * @file pointer.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pointer.h" #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE /* == Declarations ========================================================= */ /** Pointer handle */ struct _wlmim_pointer_t { /** The WLR input device this pointer corresponds to. */ struct wlr_input_device *wlr_input_device_ptr; /** Whether this pointer device is enabled. */ bool enabled; }; /* == Data ================================================================= */ /** Enum values for "ClickMethod" */ static const bspl_enum_desc_t _wlmim_pointer_click_methods[] = { BSPL_ENUM("None", LIBINPUT_CONFIG_CLICK_METHOD_NONE), BSPL_ENUM("ButtonAreas", LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS), BSPL_ENUM("ClickFinger", LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER), BSPL_ENUM_SENTINEL() }; /** Enum values for "ScrollMethod" */ static const bspl_enum_desc_t _wlmim_pointer_scroll_methods[] = { BSPL_ENUM("NoScroll", LIBINPUT_CONFIG_SCROLL_NO_SCROLL), BSPL_ENUM("TwoFingers", LIBINPUT_CONFIG_SCROLL_2FG), BSPL_ENUM("Edge", LIBINPUT_CONFIG_SCROLL_EDGE), BSPL_ENUM("OnButtonDown", LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN), BSPL_ENUM_SENTINEL() }; const bspl_desc_t wlmim_pointer_config_touchpad[] = { BSPL_DESC_BOOL( "Enabled", false, struct wlmim_pointer_options, touchpad.enabled, touchpad.enabled, true), BSPL_DESC_BOOL( "DisableWhileTyping", false, struct wlmim_pointer_options, touchpad.disable_while_typing, touchpad.disable_while_typing, true), BSPL_DESC_ENUM( "ClickMethod", false, struct wlmim_pointer_options, touchpad.click_method, touchpad.click_method, LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, _wlmim_pointer_click_methods), BSPL_DESC_BOOL( "TapToClick", false, struct wlmim_pointer_options, touchpad.tap_to_click, touchpad.tap_to_click, true), BSPL_DESC_ENUM( "ScrollMethod", false, struct wlmim_pointer_options, touchpad.scroll_method, touchpad.scroll_method, LIBINPUT_CONFIG_SCROLL_2FG, _wlmim_pointer_scroll_methods), BSPL_DESC_BOOL( "NaturalScrolling", true, struct wlmim_pointer_options, touchpad.natural_scrolling, touchpad.natural_scrolling, true), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ void _wlmim_pointer_apply( wlmim_pointer_t *pointer_ptr, const struct wlmim_pointer_options *options_ptr); /* ------------------------------------------------------------------------- */ wlmim_pointer_t *wlmim_pointer_create( struct wlr_input_device *wlr_input_device_ptr, const struct wlmim_pointer_options *options_ptr) { wlmim_pointer_t *pointer_ptr = logged_calloc(1, sizeof(*pointer_ptr)); if (NULL == pointer_ptr) return NULL; pointer_ptr->enabled = true; pointer_ptr->wlr_input_device_ptr = wlr_input_device_ptr; _wlmim_pointer_apply(pointer_ptr, options_ptr); return pointer_ptr; } /* ------------------------------------------------------------------------- */ void wlmim_pointer_destroy(wlmim_pointer_t *pointer_ptr) { free(pointer_ptr); } /* ------------------------------------------------------------------------- */ bool wlmim_pointer_enabled(wlmim_pointer_t *pointer_ptr) { return pointer_ptr->enabled; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Applies the touchpad configuration to the pointer input device. * * A no-op if the device is not a touchpad, ie. has zero 'tap fingers'. * * @param p * @param options_ptr */ void _wlmim_pointer_apply( wlmim_pointer_t *p, const struct wlmim_pointer_options *options_ptr) { enum libinput_config_status s; struct libinput_device *lid_ptr; if (!wlr_input_device_is_libinput(p->wlr_input_device_ptr)) return; lid_ptr = wlr_libinput_get_device_handle(p->wlr_input_device_ptr); // Guard clause: Ignore non-touchpad pointers. if (0 >= libinput_device_config_tap_get_finger_count(lid_ptr)) return; // (only) touchpads can be disabled. p->enabled = options_ptr->touchpad.enabled; // Configure disable-while-typing. if (libinput_device_config_dwt_is_available(lid_ptr)) { s = libinput_device_config_dwt_set_enabled( lid_ptr, options_ptr->touchpad.disable_while_typing ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED); if (LIBINPUT_CONFIG_STATUS_SUCCESS != s) { bs_log(BS_WARNING, "Failed libinput_device_config_dwt_set_enabled(%p, %d)", lid_ptr, options_ptr->touchpad.disable_while_typing ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED); } } else if (options_ptr->touchpad.disable_while_typing) { bs_log(BS_WARNING, "disable-while-typing not supported on device %p", lid_ptr); } // Configure click method. uint32_t methods = libinput_device_config_click_get_methods(lid_ptr); if ((methods & options_ptr->touchpad.click_method) == options_ptr->touchpad.click_method) { s = libinput_device_config_click_set_method( lid_ptr, options_ptr->touchpad.click_method); if (LIBINPUT_CONFIG_STATUS_SUCCESS != s) { bs_log(BS_WARNING, "Failed libinput_device_config_click_set_method(%p, %u)", lid_ptr, options_ptr->touchpad.click_method); } } else if (LIBINPUT_CONFIG_CLICK_METHOD_NONE != options_ptr->touchpad.click_method) { bs_log(BS_WARNING, "Click method %d not in supported set %"PRIx32" on device %p", options_ptr->touchpad.click_method, methods, lid_ptr); } // Configure tap to click: Note - capability check isn't needed, since we // expect to only get here if this is a touchpad, ie. having a non-zero // finger count. s = libinput_device_config_tap_set_enabled( lid_ptr, options_ptr->touchpad.tap_to_click ? LIBINPUT_CONFIG_TAP_ENABLED : LIBINPUT_CONFIG_TAP_DISABLED); if (LIBINPUT_CONFIG_STATUS_SUCCESS != s) { bs_log(BS_WARNING, "Failed libinput_device_config_tap_set_enabled(%p, %d)", lid_ptr, options_ptr->touchpad.tap_to_click ? LIBINPUT_CONFIG_TAP_ENABLED : LIBINPUT_CONFIG_TAP_DISABLED); } // Configure scroll_method. methods = libinput_device_config_scroll_get_methods(lid_ptr); if ((methods & options_ptr->touchpad.scroll_method) == options_ptr->touchpad.scroll_method) { s = libinput_device_config_scroll_set_method( lid_ptr, options_ptr->touchpad.scroll_method); if (LIBINPUT_CONFIG_STATUS_SUCCESS != s) { bs_log(BS_WARNING, "Failed libinput_device_config_scroll_set_method(%p, %d)", lid_ptr, options_ptr->touchpad.scroll_method); } } else if (LIBINPUT_CONFIG_SCROLL_NO_SCROLL != options_ptr->touchpad.scroll_method) { bs_log(BS_WARNING, "Scroll method %d not in supported set %"PRIx32" on device %p", options_ptr->touchpad.scroll_method, methods, lid_ptr); } // Configure natural_scrolling -- if set, the scrolled content moves in the // same direction as the finger movements ("natural"). If not, it's the view // (the scroll bar) that moves aligned with the finger movements. if (libinput_device_config_scroll_has_natural_scroll(lid_ptr)) { s = libinput_device_config_scroll_set_natural_scroll_enabled( lid_ptr, options_ptr->touchpad.natural_scrolling); if (LIBINPUT_CONFIG_STATUS_SUCCESS != s) { bs_log(BS_WARNING, "Failed libinput_device_config_scroll_set" "_natural_scroll_enabled(%p, %d)", lid_ptr, options_ptr->touchpad.natural_scrolling); } } else if (options_ptr->touchpad.natural_scrolling) { bs_log(BS_WARNING, "NaturalScrolling not supported on device %p", lid_ptr); } } /* == Unit Tests =========================================================== */ static void _wlmim_pointer_test_parse(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmim_pointer_test_cases[] = { { true, "parse", _wlmim_pointer_test_parse }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmim_pointer_test_set = BS_TEST_SET( true, "pointer", wlmim_pointer_test_cases); /* ------------------------------------------------------------------------- */ /** Tests parsing the dict. */ void _wlmim_pointer_test_parse(bs_test_t *test_ptr) { bspl_dict_t *d = bspl_dict_from_object( bspl_create_object_from_plist_string( "{" "Enabled=Yes;" "DisableWhileTyping=False;" "ClickMethod=ButtonAreas;" "TapToClick=Disabled;" "ScrollMethod=OnButtonDown;" "NaturalScrolling=False;" "}")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, d); struct wlmim_pointer_options o = {}; BS_TEST_VERIFY_TRUE( test_ptr, bspl_decode_dict(d, wlmim_pointer_config_touchpad, &o)); BS_TEST_VERIFY_TRUE(test_ptr, o.touchpad.enabled); BS_TEST_VERIFY_FALSE(test_ptr, o.touchpad.disable_while_typing); BS_TEST_VERIFY_EQ( test_ptr, LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS, o.touchpad.click_method); BS_TEST_VERIFY_FALSE(test_ptr, o.touchpad.tap_to_click); BS_TEST_VERIFY_EQ( test_ptr, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN, o.touchpad.scroll_method); BS_TEST_VERIFY_FALSE(test_ptr, o.touchpad.natural_scrolling); bspl_dict_unref(d); } /* == End of pointer.c ===================================================== */ wlmaker-0.8.1/src/input/manager.c0000644000175100017510000005113615222270710016300 0ustar runnerrunner/* ========================================================================= */ /** * @file manager.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "manager.h" #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #undef WLR_USE_UNSTABLE #include #include "cursor.h" #include "keyboard.h" #include "pointer.h" /* == Declarations ========================================================= */ /** Handle for the input manager. */ struct _wlmim_t { /** Events provided by the input manager. */ struct wlmim_events events; /** Holds multiple @ref wlmim_device through @ref wlmim_device::dlnode. */ bs_dllist_t devices; /** List of all bound keys, see @ref wlmim_keybinding_t::dlnode */ bs_dllist_t keybindings; /** Listener for `new_input` signals raised by `wlr_backend`. */ struct wl_listener backend_new_input_device_listener; /** The last-used group of the keyboard layout. Used when using groups. */ uint32_t last_keyboard_group_index; /** Pointer configuration. */ struct wlmim_pointer_options pointer_options; /** Cursor configuration. */ struct wlmim_cursor_options cursor_options; /** Cursor handle. */ wlmim_cursor_t *cursor_ptr; /** wlroots seat. */ struct wlr_seat *wlr_seat_ptr; /** Reference to the config dict. */ bspl_dict_t *config_dict_ptr; /** The compositor's root. */ wlmtk_root_t *root_ptr; }; /** Wraps an input device. */ struct wlmim_device { /** List node, as an element of @ref wlmim_t::devices. */ bs_dllist_node_t dlnode; /** The input device. */ struct wlr_input_device *wlr_input_device_ptr; /** Handle to the wlmaker actual device. */ void *handle_ptr; /** Listener for the `destroy` signal of `wlr_input_device`. */ struct wl_listener destroy_listener; /** Back-link to input manager. */ wlmim_t *input_manager_ptr; }; /** Internal struct holding a keybinding. */ struct _wlmim_keybinding_t { /** Node within @ref wlmim_t::keybindings. */ bs_dllist_node_t dlnode; /** The key binding: Modifier and keysym to bind to. */ const struct wlmim_keybinding_combo *keybinding_combo_ptr; /** Callback for when this modifier + key is encountered. */ wlmim_keybinding_callback_t callback; }; /** Argument to @ref _wlmim_process_keybinding. */ struct _wlmim_process_arg { /** Keysym. */ xkb_keysym_t keysym; /** Modifiers currently set. */ uint32_t modifiers; }; static void _wlmim_handle_new_input_device( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmim_handle_destroy_input_device( struct wl_listener *listener_ptr, void *data_ptr); static bool _wlmim_device_register( wlmim_t *input_manager_ptr, struct wlr_input_device *wlr_input_device_ptr, void *handle_ptr); static void _wlmim_device_unregister( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmim_device_update_capabilities( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static bool _wlmim_process_keybinding( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmim_unbind_node( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /* == Data ================================================================= */ const uint32_t wlmim_modifiers_default_mask = ( WLR_MODIFIER_SHIFT | // Excluding: WLR_MODIFIER_CAPS. WLR_MODIFIER_CTRL | WLR_MODIFIER_ALT | WLR_MODIFIER_MOD2 | WLR_MODIFIER_MOD3 | WLR_MODIFIER_LOGO | WLR_MODIFIER_MOD5); /** Enum definition for input configuration sections. */ static const bspl_desc_t _wlmim_manager_config[] = { BSPL_DESC_DICT("Touchpad", false, wlmim_t, pointer_options, pointer_options, wlmim_pointer_config_touchpad), BSPL_DESC_DICT("Cursor", false, wlmim_t, cursor_options, cursor_options, wlmim_cursor_options_desc), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmim_t *wlmim_input_manager_create( struct wlr_backend *wlr_backend_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_seat *wlr_seat_ptr, bspl_dict_t *config_dict_ptr, const struct wlmim_cursor_style *cursor_style_ptr, wlmtk_root_t *root_ptr) { wlmim_t *input_manager_ptr = logged_calloc(1, sizeof(*input_manager_ptr)); if (NULL == input_manager_ptr) return NULL; input_manager_ptr->wlr_seat_ptr = wlr_seat_ptr; input_manager_ptr->config_dict_ptr = BS_ASSERT_NOTNULL( bspl_dict_ref(config_dict_ptr)); input_manager_ptr->root_ptr = root_ptr; wl_signal_init(&input_manager_ptr->events.cursor_position_updated); wl_signal_init(&input_manager_ptr->events.activity); wl_signal_init(&input_manager_ptr->events.deactivate_task_list); if (!bspl_decode_dict( config_dict_ptr, _wlmim_manager_config, input_manager_ptr)) { wlmim_input_manager_destroy(input_manager_ptr); return NULL; } if (NULL != wlr_seat_ptr && NULL != wlr_output_layout_ptr) { input_manager_ptr->cursor_ptr = wlmim_cursor_create( input_manager_ptr, cursor_style_ptr, &input_manager_ptr->cursor_options, wlr_output_layout_ptr, wlr_seat_ptr, root_ptr); if (NULL == input_manager_ptr->cursor_ptr) { wlmim_input_manager_destroy(input_manager_ptr); return NULL; } } if (NULL != wlr_backend_ptr) { wlmtk_util_connect_listener_signal( &wlr_backend_ptr->events.new_input, &input_manager_ptr->backend_new_input_device_listener, _wlmim_handle_new_input_device); } return input_manager_ptr; } /* ------------------------------------------------------------------------- */ void wlmim_input_manager_destroy(wlmim_t *input_manager_ptr) { bs_dllist_for_each( &input_manager_ptr->keybindings, _wlmim_unbind_node, input_manager_ptr); bs_dllist_for_each( &input_manager_ptr->devices, _wlmim_device_unregister, input_manager_ptr); if (NULL != input_manager_ptr->cursor_ptr) { wlmim_cursor_destroy(input_manager_ptr->cursor_ptr); input_manager_ptr->cursor_ptr = NULL; } bspl_dict_unref(input_manager_ptr->config_dict_ptr); free(input_manager_ptr); } /* ------------------------------------------------------------------------- */ struct wlmim_events *wlmim_events(wlmim_t *input_manager_ptr) { return &input_manager_ptr->events; } /* ------------------------------------------------------------------------- */ bool wlmim_set_style( wlmim_t *input_manager_ptr, const struct wlmim_cursor_style *style_ptr) { if (NULL != input_manager_ptr->cursor_ptr) { return wlmim_cursor_set_style( input_manager_ptr->cursor_ptr, style_ptr); } return true; } /* ------------------------------------------------------------------------- */ struct wlr_cursor *wlmim_wlr_cursor(wlmim_t *input_manager_ptr) { return wlmim_cursor_wlr_cursor(input_manager_ptr->cursor_ptr); } /* ------------------------------------------------------------------------- */ void wlmim_report_activity(wlmim_t *input_manager_ptr) { wl_signal_emit(&input_manager_ptr->events.activity, NULL); } /* ------------------------------------------------------------------------- */ void wlmim_set_keyboard_group_index( wlmim_t *input_manager_ptr, uint32_t index) { input_manager_ptr->last_keyboard_group_index = index; } /* ------------------------------------------------------------------------- */ uint32_t wlmim_get_keyboard_group_index( wlmim_t *input_manager_ptr) { return input_manager_ptr->last_keyboard_group_index; } /* ------------------------------------------------------------------------- */ wlmim_keybinding_t *wlmim_bind_key( wlmim_t *input_manager_ptr, const struct wlmim_keybinding_combo *keybinding_combo_ptr, wlmim_keybinding_callback_t callback) { wlmim_keybinding_t *keybinding_ptr = logged_calloc( 1, sizeof(*keybinding_ptr)); if (NULL == keybinding_ptr) return NULL; keybinding_ptr->keybinding_combo_ptr = keybinding_combo_ptr; keybinding_ptr->callback = callback; bs_dllist_push_back( &input_manager_ptr->keybindings, &keybinding_ptr->dlnode); return keybinding_ptr; } /* ------------------------------------------------------------------------- */ void wlmim_unbind_key( wlmim_t *input_manager_ptr, wlmim_keybinding_t *keybinding_ptr) { bs_dllist_remove( &input_manager_ptr->keybindings, &keybinding_ptr->dlnode); free(keybinding_ptr); } /* ------------------------------------------------------------------------- */ bool wlmim_process_key( wlmim_t *im_ptr, xkb_keysym_t keysym, uint32_t modifiers) { if (bs_will_log(BS_DEBUG)) { char keysym_name[128] = {}; xkb_keysym_get_name(keysym, keysym_name, sizeof(keysym_name)); bs_log(BS_DEBUG, "Process key '%s' (sym %d, modifiers %"PRIx32")", keysym_name, keysym, modifiers); } struct _wlmim_process_arg a = { .keysym = keysym, .modifiers = modifiers }; return bs_dllist_any(&im_ptr->keybindings, _wlmim_process_keybinding, &a); } /* ------------------------------------------------------------------------- */ const struct wlmim_cursor_options *wlmim_cursor_options( wlmim_t *input_manager_ptr) { return &input_manager_ptr->cursor_options; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Handler for the `new_input` signal raised by `wlr_backend`. * * @param listener_ptr * @param data_ptr */ void _wlmim_handle_new_input_device( struct wl_listener *listener_ptr, void *data_ptr) { struct wlr_input_device *wlr_input_device_ptr = data_ptr; wlmim_t *input_manager_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_t, backend_new_input_device_listener); void *handle_ptr = NULL; switch (wlr_input_device_ptr->type) { case WLR_INPUT_DEVICE_KEYBOARD: handle_ptr = wlmim_keyboard_create( input_manager_ptr, input_manager_ptr->config_dict_ptr, wlr_keyboard_from_input_device(wlr_input_device_ptr), input_manager_ptr->wlr_seat_ptr, wlmtk_root_element(input_manager_ptr->root_ptr)); if (NULL == handle_ptr) { // Abort if we cannot create the keyboard for the device. bs_log(BS_FATAL, "Failed wlmim_keyboard_create()"); break; } if (!_wlmim_device_register( input_manager_ptr, wlr_input_device_ptr, handle_ptr)) { bs_log(BS_FATAL, "Failed _wlim_device_register()"); wlmim_keyboard_destroy(handle_ptr); } break; case WLR_INPUT_DEVICE_TOUCH: break; case WLR_INPUT_DEVICE_TABLET_PAD: break; case WLR_INPUT_DEVICE_POINTER: handle_ptr = wlmim_pointer_create( wlr_input_device_ptr, &input_manager_ptr->pointer_options); if (NULL == handle_ptr) { // Abort if we cannot create the keyboard for the device. bs_log(BS_FATAL, "Failed wlmim_pointer_create()"); break; } if (!_wlmim_device_register( input_manager_ptr, wlr_input_device_ptr, handle_ptr)) { bs_log(BS_ERROR, "Failed _wlim_device_register()"); wlmim_keyboard_destroy(handle_ptr); } else if (wlmim_pointer_enabled(handle_ptr) && NULL != input_manager_ptr->cursor_ptr) { wlmim_cursor_attach_input_device( input_manager_ptr->cursor_ptr, wlr_input_device_ptr); } break; case WLR_INPUT_DEVICE_SWITCH: break; default: bs_log(BS_INFO, "Input manager %p: Unhandled new input device type %d", input_manager_ptr,wlr_input_device_ptr->type); } // If the KEYBOARD capability isn't set, keys won't be forwarded... uint32_t capabilities = WL_SEAT_CAPABILITY_POINTER; bs_dllist_for_each( &input_manager_ptr->devices, _wlmim_device_update_capabilities, &capabilities); wlr_seat_set_capabilities(input_manager_ptr->wlr_seat_ptr, capabilities); } /* ------------------------------------------------------------------------- */ /** * Handler for the `destroy` signal raised by `wlr_input_device`. * * @param listener_ptr * @param data_ptr */ void _wlmim_handle_destroy_input_device( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { struct wlmim_device *device_ptr = BS_CONTAINER_OF( listener_ptr, struct wlmim_device, destroy_listener); _wlmim_device_unregister( &device_ptr->dlnode, device_ptr->input_manager_ptr); } /* ------------------------------------------------------------------------- */ /** * Creates and registers the device node. * * @param input_manager_ptr * @param wlr_input_device_ptr * @param handle_ptr * * @return true on success. */ bool _wlmim_device_register( wlmim_t *input_manager_ptr, struct wlr_input_device *wlr_input_device_ptr, void *handle_ptr) { struct wlmim_device *device_ptr = logged_calloc(1, sizeof(*device_ptr)); if (NULL == device_ptr) return false; device_ptr->input_manager_ptr = input_manager_ptr; device_ptr->wlr_input_device_ptr = wlr_input_device_ptr; device_ptr->handle_ptr = handle_ptr; wlmtk_util_connect_listener_signal( &wlr_input_device_ptr->events.destroy, &device_ptr->destroy_listener, _wlmim_handle_destroy_input_device); bs_dllist_push_back( &input_manager_ptr->devices, &device_ptr->dlnode); return true; } /* ------------------------------------------------------------------------- */ /** * Unregisters and destroys the device node. * * @param dlnode_ptr * @param ud_ptr */ void _wlmim_device_unregister( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { struct wlmim_device *device_ptr = BS_CONTAINER_OF( dlnode_ptr, struct wlmim_device, dlnode); wlmim_t *input_manager_ptr = ud_ptr; switch (device_ptr->wlr_input_device_ptr->type) { case WLR_INPUT_DEVICE_KEYBOARD: wlmim_keyboard_destroy(device_ptr->handle_ptr); break; case WLR_INPUT_DEVICE_POINTER: if (wlmim_pointer_enabled(device_ptr->handle_ptr) && NULL != input_manager_ptr->cursor_ptr) { wlmim_cursor_detach_input_device( input_manager_ptr->cursor_ptr, device_ptr->wlr_input_device_ptr); } wlmim_pointer_destroy(device_ptr->handle_ptr); break; default: break; } bs_dllist_remove( &device_ptr->input_manager_ptr->devices, &device_ptr->dlnode); wlmtk_util_disconnect_listener(&device_ptr->destroy_listener); free(device_ptr); } /* ------------------------------------------------------------------------- */ /** * Updates the capabilities bitmask. Iterator for `bs_dllist_for_each`. * * @param dlnode_ptr * @param ud_ptr Points to a uint32_t capabilities bitmask. */ void _wlmim_device_update_capabilities( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { uint32_t *capabilities_ptr = ud_ptr; struct wlmim_device *device_ptr = BS_CONTAINER_OF( dlnode_ptr, struct wlmim_device, dlnode); if (device_ptr->wlr_input_device_ptr->type == WLR_INPUT_DEVICE_KEYBOARD) { *capabilities_ptr |= WL_SEAT_CAPABILITY_KEYBOARD; } } /* ------------------------------------------------------------------------- */ /** * Process one keybinding, as iterator to `bs_dllist_all`. * * @param dlnode_ptr * @param ud_ptr * * @return true if the keybinding matched, and the callback returned true. */ bool _wlmim_process_keybinding(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmim_keybinding_t *keybinding_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmim_keybinding_t, dlnode); struct _wlmim_process_arg *arg_ptr = ud_ptr; const struct wlmim_keybinding_combo *keybinding_combo_ptr = keybinding_ptr->keybinding_combo_ptr; uint32_t mask = keybinding_combo_ptr->modifiers_mask; if (!mask) mask = UINT32_MAX; xkb_keysym_t bound_ks = keybinding_combo_ptr->keysym; if ((arg_ptr->modifiers & mask) != keybinding_combo_ptr->modifiers) { return false; } if (!keybinding_combo_ptr->ignore_case && arg_ptr->keysym != bound_ks) { return false; } if (keybinding_combo_ptr->ignore_case && arg_ptr->keysym != xkb_keysym_to_lower(bound_ks) && arg_ptr->keysym != xkb_keysym_to_upper(bound_ks)) return false; return keybinding_ptr->callback(keybinding_combo_ptr); } /* ------------------------------------------------------------------------- */ /** Unbinds keybinding at the node, as iterator to `bs_dllist_for_each`. */ void _wlmim_unbind_node( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmim_keybinding_t *keybinding_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmim_keybinding_t, dlnode); wlmim_unbind_key(ud_ptr, keybinding_ptr); } /* == Unit Tests =========================================================== */ static void _wlmim_test_bind(bs_test_t *test_ptr); /** Test cases for the input manager. */ static const bs_test_case_t _wlmim_test_cases[] = { { true, "bind", _wlmim_test_bind }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmim_test_set = BS_TEST_SET( true, "server", _wlmim_test_cases); /** Test helper: Callback for a keybinding. */ bool _wlmim_test_binding_callback( __UNUSED__ const struct wlmim_keybinding_combo *keybinding_combo_ptr) { return true; } /* ------------------------------------------------------------------------- */ /** Tests key bindings. */ void _wlmim_test_bind(bs_test_t *test_ptr) { wlmim_t im = {}; struct wlmim_keybinding_combo binding_a = { .modifiers = WLR_MODIFIER_CTRL, .modifiers_mask = WLR_MODIFIER_CTRL | WLR_MODIFIER_SHIFT, .keysym = XKB_KEY_A, .ignore_case = true }; struct wlmim_keybinding_combo binding_b = { .keysym = XKB_KEY_b }; wlmim_keybinding_t *kb1_ptr = wlmim_bind_key( &im, &binding_a, _wlmim_test_binding_callback); BS_TEST_VERIFY_NEQ(test_ptr, NULL, kb1_ptr); wlmim_keybinding_t *kb2_ptr = wlmim_bind_key( &im, &binding_b, _wlmim_test_binding_callback); BS_TEST_VERIFY_NEQ(test_ptr, NULL, kb2_ptr); // First binding. Ctrl-A, permitting other modifiers except Ctrl. BS_TEST_VERIFY_TRUE( test_ptr, wlmim_process_key(&im, XKB_KEY_A, WLR_MODIFIER_CTRL)); BS_TEST_VERIFY_TRUE( test_ptr, wlmim_process_key(&im, XKB_KEY_a, WLR_MODIFIER_CTRL)); BS_TEST_VERIFY_TRUE( test_ptr, wlmim_process_key( &im, XKB_KEY_a, WLR_MODIFIER_CTRL | WLR_MODIFIER_ALT)); BS_TEST_VERIFY_FALSE( test_ptr, wlmim_process_key( &im, XKB_KEY_a, WLR_MODIFIER_CTRL | WLR_MODIFIER_SHIFT)); BS_TEST_VERIFY_FALSE( test_ptr, wlmim_process_key(&im, XKB_KEY_A, 0)); // Second binding. Triggers only on lower-case 'b'. BS_TEST_VERIFY_TRUE( test_ptr, wlmim_process_key(&im, XKB_KEY_b, 0)); BS_TEST_VERIFY_FALSE( test_ptr, wlmim_process_key(&im, XKB_KEY_B, 0)); wlmim_unbind_key(&im, kb2_ptr); wlmim_unbind_key(&im, kb1_ptr); } /* == End of manager.c ===================================================== */ wlmaker-0.8.1/src/input/pointer.h0000644000175100017510000000537615222270710016360 0ustar runnerrunner/* ========================================================================= */ /** * @file pointer.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_POINTER_H__ #define __WLMAKER_POINTER_H__ #include #include #include #include struct wlr_input_device; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Options to a pointer. */ struct wlmim_pointer_options { /** Configuration for pointers directed by touchpads. */ struct { /** Whether to enable touchpads. True by default. */ bool enabled; /** Disable the touchpad while typing. */ bool disable_while_typing; /** Click method: None, ButtonAreas or ClickFinger. */ enum libinput_config_click_method click_method; /** Whether tap-to-click is enabled. */ bool tap_to_click; /** Scroll method: NoScroll, TwoFingers, Edge, OnButtonDown. */ enum libinput_config_scroll_method scroll_method; /** Natural scrolling, where the content is "moved" by touchpad. */ bool natural_scrolling; } touchpad; }; /** Forward declaration: Pointer handle. */ typedef struct _wlmim_pointer_t wlmim_pointer_t; /** * Creates a pointer, and configures it as specified. * * @param wlr_input_device_ptr * @param options_ptr * * @return A pointer handle or NULL on error. */ wlmim_pointer_t *wlmim_pointer_create( struct wlr_input_device *wlr_input_device_ptr, const struct wlmim_pointer_options *options_ptr); /** * Destroys the pointer. * * @param pointer_ptr */ void wlmim_pointer_destroy(wlmim_pointer_t *pointer_ptr); /** @return @ref wlmim_pointer_t::enabled. */ bool wlmim_pointer_enabled(wlmim_pointer_t *pointer_ptr); /** Plist descriptor for the "Touchpad" section. */ extern const bspl_desc_t wlmim_pointer_config_touchpad[]; /** Unit test set for pointers. */ extern const bs_test_set_t wlmim_pointer_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // __WLMAKER_POINTER_H__ /* == End of pointer.h ================= */ wlmaker-0.8.1/src/input/cursor.c0000644000175100017510000005404315222270710016203 0ustar runnerrunner/* ========================================================================= */ /** * @file cursor.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "cursor.h" #include #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #include #undef WLR_USE_UNSTABLE #include "keyboard.h" #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** State and tools for handling wlmaker cursors. */ struct _wlmim_cursor_t { /** Points to a `wlr_cursor`. */ struct wlr_cursor *wlr_cursor_ptr; /** Points to a `wlr_xcursor_manager`. */ struct wlr_xcursor_manager *wlr_xcursor_manager_ptr; /** Cursor options. */ struct wlmim_cursor_options options; /** The toolkit wrapper for above. */ wlmtk_pointer_t *pointer_ptr; /** Listener for the `motion` event of `wlr_cursor`. */ struct wl_listener motion_listener; /** Listener for the `motion_absolute` event of `wlr_cursor`. */ struct wl_listener motion_absolute_listener; /** Listener for the `button` event of `wlr_cursor`. */ struct wl_listener button_listener; /** Listener for the `axis` event of `wlr_cursor`. */ struct wl_listener axis_listener; /** Listener for the `frame` event of `wlr_cursor`. */ struct wl_listener frame_listener; /** Listener for the `request_set_cursor` event of `wlr_seat`. */ struct wl_listener seat_request_set_cursor_listener; /** Back-link to input manager. */ wlmim_t *input_manager_ptr; /** WLR seat. */ struct wlr_seat *wlr_seat_ptr; /** Root of the compositor. */ wlmtk_root_t *root_ptr; /** Whether the current left-button press emulates a right button. */ bool left_button_emulates_right; }; static void _wlmim_cursor_handle_motion( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmim_cursor_handle_motion_absolute( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmim_cursor_handle_button( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmim_cursor_handle_axis( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmim_cursor_handle_frame( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmim_cursor_handle_seat_request_set_cursor( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmim_cursor_process_motion( wlmim_cursor_t *cursor_ptr, uint32_t time_msec); char *_wlmim_cursor_get_theme_name(const struct wlmim_cursor_style *style_ptr); static int _wlmim_cursor_theme_handler( void *ud_ptr, const char *section_ptr, const char *name_ptr, const char *value_ptr); /* == Data ================================================================= */ const bspl_desc_t wlmim_cursor_style_desc[] = { BSPL_DESC_BOOL( "OverrideSystemConfiguration", false, struct wlmim_cursor_style, override_system_configuration, override_system_configuration, false), BSPL_DESC_STRING( "Name", true, struct wlmim_cursor_style, name_ptr, name_ptr, "default"), BSPL_DESC_UINT64( "Size", true, struct wlmim_cursor_style, size, size, 24), BSPL_DESC_SENTINEL() }; const bspl_desc_t wlmim_cursor_options_desc[] = { BSPL_DESC_ENUM("EmulateRightButtonModifier", false, struct wlmim_cursor_options, emulate_right_button_modifier, emulate_right_button_modifier, 0, wlmim_keyboard_modifiers), BSPL_DESC_ENUM("MoveWindowModifier", false, struct wlmim_cursor_options, move_window_modifier, move_window_modifier, 0, wlmim_keyboard_modifiers), BSPL_DESC_SENTINEL(), }; /** Name of the system-wide configuration file for cursor themes. */ static const char *_wlmim_cursor_system_config_file = "/usr/share/icons/default/index.theme"; /** The debian specific versino of the system-wide cursor configuration. */ static const char *_wlmim_cursor_system_config_alternative_file = "/etc/alternatives/x-cursor-theme"; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmim_cursor_t *wlmim_cursor_create( wlmim_t *input_manager_ptr, const struct wlmim_cursor_style *style_ptr, const struct wlmim_cursor_options *options_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_seat *wlr_seat_ptr, wlmtk_root_t *root_ptr) { wlmim_cursor_t *cursor_ptr = logged_calloc(1, sizeof(wlmim_cursor_t)); if (NULL == cursor_ptr) return NULL; cursor_ptr->input_manager_ptr = input_manager_ptr; cursor_ptr->wlr_seat_ptr = wlr_seat_ptr; cursor_ptr->root_ptr = root_ptr; cursor_ptr->options = *options_ptr; // tinywl: wlr_cursor is a utility tracking the cursor image shown on // screen. cursor_ptr->wlr_cursor_ptr = wlr_cursor_create(); if (NULL == cursor_ptr->wlr_cursor_ptr) { bs_log(BS_ERROR, "Failed wlr_cursor_create()"); wlmim_cursor_destroy(cursor_ptr); return NULL; } wlr_cursor_attach_output_layout( cursor_ptr->wlr_cursor_ptr, wlr_output_layout_ptr); wlmim_cursor_set_style(cursor_ptr, style_ptr); cursor_ptr->pointer_ptr = wlmtk_pointer_create( cursor_ptr->wlr_cursor_ptr, cursor_ptr->wlr_xcursor_manager_ptr); if (NULL == cursor_ptr->pointer_ptr) { wlmim_cursor_destroy(cursor_ptr); return NULL; } // tinywl: wlr_cursor *only* displays an image on screen. It does not move // around when the pointer moves. However, we can attach input devices to // it, and it will generate aggregate events for all of them. In these // events, we can choose how we want to process them, forwarding them to // clients and moving the cursor around. More detail on this process is // described in the input handling blog post: // // https://drewdevault.com/2018/07/17/Input-handling-in-wlroots.html wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.motion, &cursor_ptr->motion_listener, _wlmim_cursor_handle_motion); wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.motion_absolute, &cursor_ptr->motion_absolute_listener, _wlmim_cursor_handle_motion_absolute); wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.button, &cursor_ptr->button_listener, _wlmim_cursor_handle_button); wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.axis, &cursor_ptr->axis_listener, _wlmim_cursor_handle_axis); wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_cursor_ptr->events.frame, &cursor_ptr->frame_listener, _wlmim_cursor_handle_frame); wlmtk_util_connect_listener_signal( &cursor_ptr->wlr_seat_ptr->events.request_set_cursor, &cursor_ptr->seat_request_set_cursor_listener, _wlmim_cursor_handle_seat_request_set_cursor); return cursor_ptr; } /* ------------------------------------------------------------------------- */ void wlmim_cursor_destroy(wlmim_cursor_t *cursor_ptr) { wlmtk_util_disconnect_listener( &cursor_ptr->seat_request_set_cursor_listener); if (NULL != cursor_ptr->wlr_cursor_ptr) { wlmtk_util_disconnect_listener(&cursor_ptr->frame_listener); wlmtk_util_disconnect_listener(&cursor_ptr->axis_listener); wlmtk_util_disconnect_listener(&cursor_ptr->button_listener); wlmtk_util_disconnect_listener(&cursor_ptr->motion_absolute_listener); wlmtk_util_disconnect_listener(&cursor_ptr->motion_listener); } if (NULL != cursor_ptr->pointer_ptr) { wlmtk_pointer_destroy(cursor_ptr->pointer_ptr); cursor_ptr->pointer_ptr = NULL; } if (NULL != cursor_ptr->wlr_xcursor_manager_ptr) { wlr_xcursor_manager_destroy(cursor_ptr->wlr_xcursor_manager_ptr); cursor_ptr->wlr_xcursor_manager_ptr = NULL; } if (NULL != cursor_ptr->wlr_cursor_ptr) { wlr_cursor_destroy(cursor_ptr->wlr_cursor_ptr); cursor_ptr->wlr_cursor_ptr = NULL; } free(cursor_ptr); } /* ------------------------------------------------------------------------- */ bool wlmim_cursor_set_style( wlmim_cursor_t *cursor_ptr, const struct wlmim_cursor_style *style_ptr) { char *theme_name_ptr = _wlmim_cursor_get_theme_name(style_ptr); if (NULL == theme_name_ptr) return false; struct wlr_xcursor_manager *wxm_ptr = wlr_xcursor_manager_create( theme_name_ptr, style_ptr->size); if (NULL == wxm_ptr) { bs_log(BS_ERROR, "Failed wlr_xcursor_manager_create(\"%s\", %"PRIu64")", theme_name_ptr, style_ptr->size); free(theme_name_ptr); return false; } if (!wlr_xcursor_manager_load(wxm_ptr, 1.0)) { bs_log(BS_ERROR, "Failed wlr_xcursor_manager_load() for %s, %"PRIu64, theme_name_ptr, style_ptr->size); wlr_xcursor_manager_destroy(wxm_ptr); free(theme_name_ptr); return false; } free(theme_name_ptr); if (NULL != cursor_ptr->pointer_ptr) { wlmtk_pointer_set_xcursor_manager(cursor_ptr->pointer_ptr, wxm_ptr); } if (NULL != cursor_ptr->wlr_xcursor_manager_ptr) { wlr_xcursor_manager_destroy(cursor_ptr->wlr_xcursor_manager_ptr); } cursor_ptr->wlr_xcursor_manager_ptr = wxm_ptr; return true; } /* ------------------------------------------------------------------------- */ struct wlr_cursor *wlmim_cursor_wlr_cursor(wlmim_cursor_t *cursor_ptr) { return cursor_ptr->wlr_cursor_ptr; } /* ------------------------------------------------------------------------- */ void wlmim_cursor_attach_input_device( wlmim_cursor_t *cursor_ptr, struct wlr_input_device *wlr_input_device_ptr) { wlr_cursor_attach_input_device( cursor_ptr->wlr_cursor_ptr, wlr_input_device_ptr); } /* ------------------------------------------------------------------------- */ void wlmim_cursor_detach_input_device( wlmim_cursor_t *cursor_ptr, struct wlr_input_device *wlr_input_device_ptr) { wlr_cursor_detach_input_device( cursor_ptr->wlr_cursor_ptr, wlr_input_device_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Handler for the `motion` event of `wlr_cursor`. * * @param listener_ptr * @param data_ptr Points to a `wlr_pointer_motion_event`. */ void _wlmim_cursor_handle_motion( struct wl_listener *listener_ptr, void *data_ptr) { wlmim_cursor_t *cursor_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_cursor_t, motion_listener); struct wlr_pointer_motion_event *wlr_pointer_motion_event_ptr = data_ptr; wlmim_report_activity(cursor_ptr->input_manager_ptr); wlr_cursor_move( cursor_ptr->wlr_cursor_ptr, &wlr_pointer_motion_event_ptr->pointer->base, wlr_pointer_motion_event_ptr->delta_x, wlr_pointer_motion_event_ptr->delta_y); _wlmim_cursor_process_motion( cursor_ptr, wlr_pointer_motion_event_ptr->time_msec); } /* ------------------------------------------------------------------------- */ /** * Handler for the `motion_absolute` event of `wlr_cursor`. * * @param listener_ptr * @param data_ptr Points to a `wlr_pointer_motion_absolute_event`. */ void _wlmim_cursor_handle_motion_absolute( struct wl_listener *listener_ptr, void *data_ptr) { wlmim_cursor_t *cursor_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_cursor_t, motion_absolute_listener); struct wlr_pointer_motion_absolute_event *wlr_pointer_motion_absolute_event_ptr = data_ptr; wlmim_report_activity(cursor_ptr->input_manager_ptr); wlr_cursor_warp_absolute( cursor_ptr->wlr_cursor_ptr, &wlr_pointer_motion_absolute_event_ptr->pointer->base, wlr_pointer_motion_absolute_event_ptr->x, wlr_pointer_motion_absolute_event_ptr->y); _wlmim_cursor_process_motion( cursor_ptr, wlr_pointer_motion_absolute_event_ptr->time_msec); } /* ------------------------------------------------------------------------- */ /** * Handler for the `button` event of `wlr_cursor`. * * @param listener_ptr * @param data_ptr Points to a `wlr_pointer_button_event`. */ void _wlmim_cursor_handle_button( struct wl_listener *listener_ptr, void *data_ptr) { wlmim_cursor_t *cursor_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_cursor_t, button_listener); struct wlr_pointer_button_event *wlr_pointer_button_event_ptr = data_ptr; wlmim_report_activity(cursor_ptr->input_manager_ptr); struct wlr_keyboard *wlr_keyboard_ptr = wlr_seat_get_keyboard( cursor_ptr->wlr_seat_ptr); uint32_t modifiers = 0; if (NULL != wlr_keyboard_ptr) { modifiers = wlr_keyboard_get_modifiers(wlr_keyboard_ptr); } // TODO(kaeser@gubbe.ch): This deserves unit testing. uint32_t mod_mask = cursor_ptr->options.emulate_right_button_modifier; struct wlr_pointer_button_event ev = *wlr_pointer_button_event_ptr; if (0 != mod_mask && ev.button == BTN_LEFT) { if (ev.state == WL_POINTER_BUTTON_STATE_PRESSED && (modifiers & mod_mask) == mod_mask) { ev.button = BTN_RIGHT; cursor_ptr->left_button_emulates_right = true; modifiers &= ~mod_mask; } else if (ev.state == WL_POINTER_BUTTON_STATE_RELEASED && cursor_ptr->left_button_emulates_right) { ev.button = BTN_RIGHT; cursor_ptr->left_button_emulates_right = false; modifiers &= ~mod_mask; } } wlmtk_root_pointer_button(cursor_ptr->root_ptr, &ev, modifiers); } /* ------------------------------------------------------------------------- */ /** * Handler for the `axis` event of `wlr_cursor`. * * @param listener_ptr * @param data_ptr Points to a `wlr_pointer_axis_event`. */ void _wlmim_cursor_handle_axis( struct wl_listener *listener_ptr, void *data_ptr) { wlmim_cursor_t *cursor_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_cursor_t, axis_listener); struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr = data_ptr; wlmim_report_activity(cursor_ptr->input_manager_ptr); wlmtk_root_pointer_axis( cursor_ptr->root_ptr, wlr_pointer_axis_event_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `frame` event of `wlr_cursor`. * * @param listener_ptr * @param data_ptr */ void _wlmim_cursor_handle_frame( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmim_cursor_t *cursor_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_cursor_t, frame_listener); /* Notify the client with pointer focus of the frame event. */ wlr_seat_pointer_notify_frame(cursor_ptr->wlr_seat_ptr); } /* ------------------------------------------------------------------------- */ /** * Handler for the `request_set_cursor` event of `wlr_seat`. * * This event is raised when a client provides a cursor image. It is accepted * only if the client also has the pointer focus. * * @param listener_ptr * @param data_ptr Points to a `wlr_seat_pointer_request_set_cursor_event`. */ void _wlmim_cursor_handle_seat_request_set_cursor( struct wl_listener *listener_ptr, void *data_ptr) { wlmim_cursor_t *cursor_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_cursor_t, seat_request_set_cursor_listener); struct wlr_seat_pointer_request_set_cursor_event *wlr_seat_pointer_request_set_cursor_event_ptr = data_ptr; struct wlr_seat_client *focused_wlr_seat_client_ptr = cursor_ptr->wlr_seat_ptr->pointer_state.focused_client; if (focused_wlr_seat_client_ptr == wlr_seat_pointer_request_set_cursor_event_ptr->seat_client) { wlr_cursor_set_surface( cursor_ptr->wlr_cursor_ptr, wlr_seat_pointer_request_set_cursor_event_ptr->surface, wlr_seat_pointer_request_set_cursor_event_ptr->hotspot_x, wlr_seat_pointer_request_set_cursor_event_ptr->hotspot_y); } else { bs_log(BS_WARNING, "request_set_cursor called without pointer focus."); } } /* ------------------------------------------------------------------------- */ /** * Processes the cursor motion: Lookups up the view & surface under the * pointer and sets (respectively: clears) the pointer focus. Also sets the * default cursor image, if no view is given (== no client-side cursor). * * @param cursor_ptr * @param time_msec */ void _wlmim_cursor_process_motion( wlmim_cursor_t *cursor_ptr, uint32_t time_msec) { wl_signal_emit_mutable( &wlmim_events(cursor_ptr->input_manager_ptr)->cursor_position_updated, cursor_ptr->wlr_cursor_ptr); // TODO(kaeser@gubbe.ch): also make this an event-based callback. wlmtk_root_pointer_motion( cursor_ptr->root_ptr, cursor_ptr->wlr_cursor_ptr->x, cursor_ptr->wlr_cursor_ptr->y, time_msec, cursor_ptr->pointer_ptr); } /* ------------------------------------------------------------------------- */ /** * Returns a copy of the cursor theme's name to use, from configured style. * * This will either be a copy of @ref wlmim_cursor_style::name_ptr, or what is * described by the system-wide cursor theme files. * * @param style_ptr * * @return Cursor theme name, as string. Must be released via free(). */ char *_wlmim_cursor_get_theme_name(const struct wlmim_cursor_style *style_ptr) { char *theme_name_ptr; if (!style_ptr->override_system_configuration) { if (0 == ini_parse(_wlmim_cursor_system_config_file, _wlmim_cursor_theme_handler, &theme_name_ptr) || 0 == ini_parse(_wlmim_cursor_system_config_alternative_file, _wlmim_cursor_theme_handler, &theme_name_ptr)) { return theme_name_ptr; } bs_log(BS_WARNING, "System-wide cursor theme configuration not found, " "neither in %s nor %s. Falling back...", _wlmim_cursor_system_config_file, _wlmim_cursor_system_config_alternative_file); } return logged_strdup(style_ptr->name_ptr); } /* ------------------------------------------------------------------------- */ /** inih library parser callback for the system-wide cursor theme files. */ int _wlmim_cursor_theme_handler( void *ud_ptr, const char *section_ptr, const char *name_ptr, const char *value_ptr) { // Skip anything other than the "Inherits" key in "[Icon Theme]". if (0 != strcmp(section_ptr, "Icon Theme") || 0 != strcmp(name_ptr, "Inherits")) return 1; char **theme_name_ptr_ptr = ud_ptr; *theme_name_ptr_ptr = logged_strdup(value_ptr); if (NULL == *theme_name_ptr_ptr) return 0; return 1; } /* == Unit tests =========================================================== */ static void _wlmim_cursor_test_theme_name(bs_test_t *test_ptr); static void _wlmim_cursor_test_config(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t _wlmim_cursor_test_cases[] = { { 1, "theme_name", _wlmim_cursor_test_theme_name }, { 1, "config", _wlmim_cursor_test_config }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmim_cursor_test_set = BS_TEST_SET( true, "cursor", _wlmim_cursor_test_cases); /* ------------------------------------------------------------------------- */ /** Tests obtaining the theme name. */ void _wlmim_cursor_test_theme_name(bs_test_t *test_ptr) { char *theme_name_ptr; int rv = ini_parse(bs_test_data_path(test_ptr, "input/cursor-index.theme"), _wlmim_cursor_theme_handler, &theme_name_ptr); BS_TEST_VERIFY_EQ(test_ptr, 0, rv); BS_TEST_VERIFY_STREQ(test_ptr, "ThemeName", theme_name_ptr); free(theme_name_ptr); struct wlmim_cursor_style style = { .override_system_configuration = true, .name_ptr = "OverrideName" }; theme_name_ptr = _wlmim_cursor_get_theme_name(&style); BS_TEST_VERIFY_STREQ(test_ptr, "OverrideName", theme_name_ptr); free(theme_name_ptr); } /* ------------------------------------------------------------------------- */ /** Tests that parsing the configuration populates the options. */ void _wlmim_cursor_test_config(bs_test_t *test_ptr) { bspl_dict_t *d = bspl_dict_from_object( bspl_create_object_from_plist_string( "{" "EmulateRightButtonModifier=Logo;" "MoveWindowModifier = Ctrl;" "}")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, d); struct wlmim_cursor_options opt = {}; BS_TEST_VERIFY_TRUE( test_ptr, bspl_decode_dict(d, wlmim_cursor_options_desc, &opt)); BS_TEST_VERIFY_EQ( test_ptr, WLR_MODIFIER_LOGO, opt.emulate_right_button_modifier); BS_TEST_VERIFY_EQ( test_ptr, WLR_MODIFIER_CTRL, opt.move_window_modifier); bspl_dict_unref(d); } /* == End of cursor.c ====================================================== */ wlmaker-0.8.1/src/input/manager.h0000644000175100017510000001423515222270710016304 0ustar runnerrunner/* ========================================================================= */ /** * @file manager.h * * TODO(kaeser@gubbe.ch): Minor cleanups: * - Make wlr_seat (and similar) as accessors, and use in device modules. * - Add abstract device module with a dtor method. * - Make the config dict parsing part of the file here. * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_INPUT_MANAGER_H__ #define __WLMAKER_INPUT_MANAGER_H__ #include #include #include #include #include #include #include "toolkit/toolkit.h" struct wlr_backend; struct wlr_output_layout; struct wlr_seat; #ifdef __cplusplus extern "C" { #endif // __cplusplus struct wlmim_cursor_style; /** Forward declaration. */ typedef struct _wlmim_t wlmim_t; /** Events provided by the input manager. */ struct wlmim_events { /** * Signals when the cursor's position is updated. * * Will be called from @ref _wlmim_cursor_handle_motion and * @ref _wlmim_cursor_handle_motion_absolute * handlers, after issuing wlr_cursor_move(), respectively * wlr_cursor_warp_absolute(). * * Offers struct wlr_cursor as argument. */ struct wl_signal cursor_position_updated; /** * Signals when there has been any input activity. * * Useful to eg. reset an idle timer. */ struct wl_signal activity; /** * Hack: Signals when ALT modifier is lost, indicating that the * task list should be de-activated. * * TODO(kaeser@gubbe.ch): Handle this better -- should respect the * modifiers of the task list actions, and be more generalized. */ struct wl_signal deactivate_task_list; }; /** Handle for a key binding. */ typedef struct _wlmim_keybinding_t wlmim_keybinding_t; /** Specifies the key + modifier to bind. */ struct wlmim_keybinding_combo { /** Modifiers required. See `enum wlr_keyboard_modifiers`. */ uint32_t modifiers; /** Modifier mask: Only masked modifiers are considered. */ uint32_t modifiers_mask; /** XKB Keysym to trigger on. */ xkb_keysym_t keysym; /** Whether to ignore case when matching. */ bool ignore_case; }; /** * Callback for a key binding. * * @param kc The key combo that triggered the callback. * * @return true if the key can be considered "consumed". */ typedef bool (*wlmim_keybinding_callback_t)( const struct wlmim_keybinding_combo *kc); /** * Creates an input manager: Registers input devices and ensures event routing * will be set up as desired. * * @param wlr_backend_ptr * @param wlr_output_layout_ptr * @param wlr_seat_ptr * @param config_dict_ptr * @param cursor_style_ptr * @param root_ptr * * @return Pointer to the input manager handle. */ wlmim_t *wlmim_input_manager_create( struct wlr_backend *wlr_backend_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_seat *wlr_seat_ptr, bspl_dict_t *config_dict_ptr, const struct wlmim_cursor_style *cursor_style_ptr, wlmtk_root_t *root_ptr); /** * Destroys the input manager. * * @param input_manager_ptr */ void wlmim_input_manager_destroy(wlmim_t *input_manager_ptr); /** @return Pointer to wlmim_t::events. */ struct wlmim_events *wlmim_events(wlmim_t *input_manager_ptr); /** * Updates the cursor style of the input manager. * * @param input_manager_ptr * @param style_ptr * * @return true on success. */ bool wlmim_set_style( wlmim_t *input_manager_ptr, const struct wlmim_cursor_style *style_ptr); /** @return Pointer to struct wlr_cursor of @ref wlmim_t::cursor_ptr. */ struct wlr_cursor *wlmim_wlr_cursor(wlmim_t *input_manager_ptr); /** * Reports input activity. Used to reset idle timers. * * @param input_manager_ptr */ void wlmim_report_activity(wlmim_t *input_manager_ptr); /** Sets @ref wlmim_t::last_keyboard_group_index. */ void wlmim_set_keyboard_group_index( wlmim_t *input_manager_ptr, uint32_t index); /** @return @ref wlmim_t::last_keyboard_group_index. */ uint32_t wlmim_get_keyboard_group_index( wlmim_t *input_manager_ptr); /** * Binds a particular key combination to a callback. * * @param input_manager_ptr * @param key_combo_ptr * @param callback * * @return The key binding handle or NULL on error. */ wlmim_keybinding_t *wlmim_bind_key( wlmim_t *input_manager_ptr, const struct wlmim_keybinding_combo *key_combo_ptr, wlmim_keybinding_callback_t callback); /** * Releases a key binding. @see wlmim_bind_key. * * @param input_manager_ptr * @param keybinding_ptr */ void wlmim_unbind_key( wlmim_t *input_manager_ptr, wlmim_keybinding_t *keybinding_ptr); /** * Processes key combo: Call back if a matching binding is found. * * @param input_manager_ptr * @param keysym * @param modifiers * * @return true if a binding was found AND the callback returned true. */ bool wlmim_process_key( wlmim_t *input_manager_ptr, xkb_keysym_t keysym, uint32_t modifiers); /** @return A pointer to @ref _wlmim_t::cursor_options. */ const struct wlmim_cursor_options *wlmim_cursor_options( wlmim_t *input_manager_ptr); /** All modifiers to use by default. */ extern const uint32_t wlmim_modifiers_default_mask; /** Unit test set. */ extern const bs_test_set_t wlmim_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // __WLMAKER_INPUT_MANAGER_H__ /* == End of manager.h ===================================================== */ wlmaker-0.8.1/src/input/CMakeLists.txt0000644000175100017510000000311215222270710017251 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) set(public_header_files cursor.h keyboard.h manager.h) add_library(input STATIC cursor.c keyboard.c pointer.c manager.c) set_target_properties( input PROPERTIES VERSION 1.0 PUBLIC_HEADER "${public_header_files}" ) add_dependencies( input toolkit) target_compile_options( input PRIVATE "${WAYLAND_SERVER_CFLAGS}" "${WAYLAND_SERVER_CFLAGS_OTHER}" ) target_include_directories( input PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${Libinput_INCLUDE_DIRS}" "${WAYLAND_SERVER_INCLUDE_DIRS}" "${XKBCOMMON_INCLUDE_DIRS}" ) target_include_directories( input PRIVATE "${WLROOTS_INCLUDE_DIRS}" ) target_link_libraries( input PUBLIC toolkit PRIVATE Libinput::Libinput PkgConfig::WAYLAND_SERVER PkgConfig::WLROOTS PkgConfig::XKBCOMMON inih libbase libbase_plist) if(iwyu_path_and_options) set_target_properties( input PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8.1/src/input/keyboard.c0000644000175100017510000004463215222270710016471 0ustar runnerrunner/* ========================================================================= */ /** * @file keyboard.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "keyboard.h" #include #include #include #include #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #undef WLR_USE_UNSTABLE #include #include "toolkit/toolkit.h" /* == Declarations ========================================================= */ /** Keyboard handle. */ struct _wlmim_keyboard_t { /** Configuration dictionnary, just the "Keyboard" section. */ bspl_dict_t *config_dict_ptr; /** Listener for the `modifiers` signal of `wl_keyboard`. */ struct wl_listener modifiers_listener; /** Listener for the `key` signal of `wl_keyboard`. */ struct wl_listener key_listener; /** Back-link to the input manager. */ wlmim_t *input_manager_ptr; /** The wlroots keyboard structure. */ struct wlr_keyboard *wlr_keyboard_ptr; /** The wlroots seat. */ struct wlr_seat *wlr_seat_ptr; /** Root element, for forwarding events. */ wlmtk_element_t *root_element_ptr; }; static bspl_dict_t *_wlmim_keyboard_populate_rules( bspl_dict_t *dict_ptr, struct xkb_rule_names *rules_ptr); static bool _wlmim_keyboard_populate_repeat( bspl_dict_t *dict_ptr, int32_t *rate_ptr, int32_t *delay_ptr); static int _wlmim_keyboard_config_ini_handler( void *user_ptr, const char *section_ptr, const char *name_ptr, const char *value_ptr); static void handle_key(struct wl_listener *listener_ptr, void *data_ptr); static void handle_modifiers(struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ const bspl_enum_desc_t wlmim_keyboard_modifiers[] = { BSPL_ENUM("Shift", WLR_MODIFIER_SHIFT), // Caps? Maybe not: BSPL_ENUM("Caps", WLR_MODIFIER_CAPS), BSPL_ENUM("Ctrl", WLR_MODIFIER_CTRL), BSPL_ENUM("Alt", WLR_MODIFIER_ALT), BSPL_ENUM("Mod2", WLR_MODIFIER_MOD2), BSPL_ENUM("Mod3", WLR_MODIFIER_MOD3), BSPL_ENUM("Logo", WLR_MODIFIER_LOGO), BSPL_ENUM("Mod5", WLR_MODIFIER_MOD5), BSPL_ENUM_SENTINEL(), }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmim_keyboard_t *wlmim_keyboard_create( wlmim_t *input_manager_ptr, bspl_dict_t *wlmaker_config_dict_ptr, struct wlr_keyboard *wlr_keyboard_ptr, struct wlr_seat *wlr_seat_ptr, wlmtk_element_t *root_element_ptr) { wlmim_keyboard_t *keyboard_ptr = logged_calloc( 1, sizeof(wlmim_keyboard_t)); if (NULL == keyboard_ptr) return NULL; keyboard_ptr->input_manager_ptr = input_manager_ptr; keyboard_ptr->wlr_keyboard_ptr = wlr_keyboard_ptr; keyboard_ptr->wlr_seat_ptr = wlr_seat_ptr; keyboard_ptr->root_element_ptr = root_element_ptr; // Retrieve configuration. bspl_dict_t *config_dict_ptr = bspl_dict_get_dict( wlmaker_config_dict_ptr, "Keyboard"); if (NULL == config_dict_ptr) { bs_log(BS_ERROR, "Failed to retrieve \"Keyboard\" dict from config."); wlmim_keyboard_destroy(keyboard_ptr); return NULL; } keyboard_ptr->config_dict_ptr = BS_ASSERT_NOTNULL( bspl_dict_ref(config_dict_ptr)); struct xkb_rule_names xkb_rule; bspl_dict_t *rmlvo_dict_ptr = _wlmim_keyboard_populate_rules( keyboard_ptr->config_dict_ptr, &xkb_rule); if (NULL == rmlvo_dict_ptr) { bs_log(BS_ERROR, "No rule data found in 'Keyboard' dict."); wlmim_keyboard_destroy(keyboard_ptr); return NULL; } // Set keyboard layout. struct xkb_context *xkb_context_ptr = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *xkb_keymap_ptr = xkb_keymap_new_from_names( xkb_context_ptr, &xkb_rule, XKB_KEYMAP_COMPILE_NO_FLAGS); if (NULL == xkb_keymap_ptr) { bs_log(BS_ERROR, "Failed xkb_keymap_new_from_names(%p, { .rules = %s, " ".model = %s, .layout = %s, variant = %s, .options = %s }, " "XKB_KEYMAP_COMPILE_NO_NO_FLAGS)", xkb_context_ptr, xkb_rule.rules, xkb_rule.model, xkb_rule.layout, xkb_rule.variant, xkb_rule.options); bspl_dict_unref(rmlvo_dict_ptr); wlmim_keyboard_destroy(keyboard_ptr); return NULL; } bspl_dict_unref(rmlvo_dict_ptr); wlr_keyboard_set_keymap(keyboard_ptr->wlr_keyboard_ptr, xkb_keymap_ptr); xkb_keymap_unref(xkb_keymap_ptr); xkb_context_unref(xkb_context_ptr); // Repeat rate and delay. int32_t rate, delay; if (!_wlmim_keyboard_populate_repeat( keyboard_ptr->config_dict_ptr, &rate, &delay)) { bs_log(BS_ERROR, "No repeat data found in 'Keyboard' dict."); wlmim_keyboard_destroy(keyboard_ptr); return NULL; } wlr_keyboard_set_repeat_info(keyboard_ptr->wlr_keyboard_ptr, rate, delay); wlmtk_util_connect_listener_signal( &keyboard_ptr->wlr_keyboard_ptr->events.key, &keyboard_ptr->key_listener, handle_key); wlmtk_util_connect_listener_signal( &keyboard_ptr->wlr_keyboard_ptr->events.modifiers, &keyboard_ptr->modifiers_listener, handle_modifiers); // Set (or restore) keyboard layout group in XKB state, and update modifiers. xkb_state_update_mask( keyboard_ptr->wlr_keyboard_ptr->xkb_state, 0, // depressed_mods 0, // latched_mods 0, // locked_mods 0, // depressed_layout 0, // latched_layout // locked_layout wlmim_get_keyboard_group_index(keyboard_ptr->input_manager_ptr)); wlr_keyboard_ptr->modifiers.group = wlmim_get_keyboard_group_index( keyboard_ptr->input_manager_ptr); wlr_seat_keyboard_notify_modifiers( wlr_seat_ptr, &wlr_keyboard_ptr->modifiers); // Also, re-trigger client's XKB state machine by an explicit "Enter". if (NULL != wlr_seat_ptr->keyboard_state.focused_surface) { wlr_seat_keyboard_enter( wlr_seat_ptr, wlr_seat_ptr->keyboard_state.focused_surface, wlr_keyboard_ptr->keycodes, wlr_keyboard_ptr->num_keycodes, &wlr_keyboard_ptr->modifiers); } wlr_seat_set_keyboard(wlr_seat_ptr, keyboard_ptr->wlr_keyboard_ptr); return keyboard_ptr; } /* ------------------------------------------------------------------------- */ void wlmim_keyboard_destroy(wlmim_keyboard_t *keyboard_ptr) { wlmtk_util_disconnect_listener(&keyboard_ptr->key_listener); wlmtk_util_disconnect_listener(&keyboard_ptr->modifiers_listener); if (NULL != keyboard_ptr->config_dict_ptr) { bspl_dict_unref(keyboard_ptr->config_dict_ptr); keyboard_ptr->config_dict_ptr = NULL; } free(keyboard_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Populates the XKB rules struct from the config dict. * * @param dict_ptr * @param rules_ptr * * @return A referenced bspl_dict_t holding rules details, or NULL on error. */ bspl_dict_t *_wlmim_keyboard_populate_rules( bspl_dict_t *dict_ptr, struct xkb_rule_names *rules_ptr) { bspl_dict_t *rmlvo = NULL; const char *fname_ptr = bspl_dict_get_string_value( dict_ptr, "XkbConfigurationFile"); if (NULL != fname_ptr) { rmlvo = bspl_dict_create(); if (0 != ini_parse( fname_ptr, _wlmim_keyboard_config_ini_handler, rmlvo)) { bs_log(BS_WARNING, "Failed to parse \"XkbConfigurationFile\" at " "%s, falling back to \"XkbRMLVO\" section.", fname_ptr); bspl_dict_unref(rmlvo); rmlvo = NULL; } } if (NULL == rmlvo) { rmlvo = bspl_dict_ref(bspl_dict_get_dict(dict_ptr, "XkbRMLVO")); } if (NULL == rmlvo) { bs_log(BS_ERROR, "No \"XkbConfigurationFile\" nor \"XkbRMLVO\" dict " "found in \"Keyboard\" dict."); return NULL; } rules_ptr->rules = bspl_dict_get_string_value(rmlvo, "Rules"); rules_ptr->model = bspl_dict_get_string_value(rmlvo, "Model"); rules_ptr->layout = bspl_dict_get_string_value(rmlvo, "Layout"); rules_ptr->variant = bspl_dict_get_string_value(rmlvo, "Variant"); rules_ptr->options = bspl_dict_get_string_value(rmlvo, "Options"); return rmlvo; } /* ------------------------------------------------------------------------- */ /** * Retrieves and converts the 'Repeat' parameters from the config dict. * * @param dict_ptr * @param rate_ptr * @param delay_ptr * * @return true on success. */ bool _wlmim_keyboard_populate_repeat( bspl_dict_t *dict_ptr, int32_t *rate_ptr, int32_t *delay_ptr) { dict_ptr = bspl_dict_get_dict(dict_ptr, "Repeat"); if (NULL == dict_ptr) { bs_log(BS_ERROR, "No 'Repeat' dict in 'Keyboard' dict."); return false; } uint64_t value; if (!bs_strconvert_uint64( bspl_dict_get_string_value(dict_ptr, "Delay"), &value, 10) || value > INT32_MAX) { bs_log(BS_ERROR, "Invalid value for 'Delay': %s", bspl_dict_get_string_value(dict_ptr, "Delay")); return false; } *delay_ptr = value; if (!bs_strconvert_uint64( bspl_dict_get_string_value(dict_ptr, "Rate"), &value, 10) || value > INT32_MAX) { bs_log(BS_ERROR, "Invalid value for 'Rate': %s", bspl_dict_get_string_value(dict_ptr, "Rate")); return false; } *rate_ptr = value; return true; } /* ------------------------------------------------------------------------- */ /** * inih parser callback. Fills XKB config file values into the dict. * * @param user_ptr Points to a `bspl_dict_t`. * @param section_ptr * @param name_ptr * @param value_ptr * * @return 0 on success. */ int _wlmim_keyboard_config_ini_handler( void *user_ptr, __UNUSED__ const char *section_ptr, const char *name_ptr, const char *value_ptr) { bspl_dict_t *rmlvo_dict_ptr = user_ptr; struct field_definition { const char *n; const char *k; }; struct field_definition definitions[] = { { .n = "XKBMODEL", .k = "Model" }, { .n = "XKBLAYOUT", .k = "Layout" }, { .n = "XKBVARIANT", .k = "Variant" }, { .n = "XKBOPTIONS", .k = "Options" }, { .n = "BACKSPACE", .k = NULL }, { .n = NULL } }; for (const struct field_definition *def_ptr = &definitions[0]; NULL != def_ptr->n; ++def_ptr) { if (0 != strcmp(def_ptr->n, name_ptr)) continue; if (NULL == def_ptr->k) return 1; // Trim from left, and remove double-quote. while ('\0' != *value_ptr && isblank(*value_ptr)) ++value_ptr; if ('"' == *value_ptr) ++value_ptr; // Trim from right, and also remove double-quote. size_t l = strlen(value_ptr); while (0 < l && isblank(value_ptr[l-1])) --l; if (0 < l && '"' == value_ptr[l-1]) --l; char *copied = logged_malloc(l+1); if (NULL == copied) return 1; memcpy(copied, value_ptr, l); copied[l] = '\0'; bspl_string_t *s = bspl_string_create(copied); free(copied); if (NULL == s) return 0; bspl_dict_add(rmlvo_dict_ptr, def_ptr->k, bspl_object_from_string(s)); bspl_string_unref(s); return 1; } bs_log(BS_WARNING, "Unknown name: \"%s\"", name_ptr); return 1; } /* ------------------------------------------------------------------------- */ /** * Handles `key` signals, ie. key presses. * * @param listener_ptr * @param data_ptr Points to a `wlr_keyboard_key_event`. */ void handle_key(struct wl_listener *listener_ptr, void *data_ptr) { wlmim_keyboard_t *keyboard_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_keyboard_t, key_listener); struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr = data_ptr; wlmim_report_activity(keyboard_ptr->input_manager_ptr); // TODO(kaeser@gubbe.ch): Omit consumed modifiers, see xkbcommon.h. uint32_t modifiers = wlr_keyboard_get_modifiers( keyboard_ptr->wlr_keyboard_ptr); // TODO(kaeser@gubbe.ch): Handle this better -- should respect the // modifiers of the task list actions, and be more generalized. if ((modifiers & WLR_MODIFIER_ALT) != WLR_MODIFIER_ALT) { wl_signal_emit( &wlmim_events(keyboard_ptr->input_manager_ptr)->deactivate_task_list, NULL); } // Translates libinput keycode -> xkbcommon. uint32_t keycode = wlr_keyboard_key_event_ptr->keycode + 8; // For key presses: Pass them on to the server, for potential key bindings. bool processed = false; const xkb_keysym_t *key_syms; int key_syms_count = xkb_state_key_get_syms( keyboard_ptr->wlr_keyboard_ptr->xkb_state, keycode, &key_syms); for (int i = 0; i < key_syms_count; ++i) { enum xkb_key_direction direction = wlr_keyboard_key_event_ptr->state == WL_KEYBOARD_KEY_STATE_RELEASED ? XKB_KEY_UP : XKB_KEY_DOWN; xkb_state_update_key( keyboard_ptr->wlr_keyboard_ptr->xkb_state, keycode, direction); if (WL_KEYBOARD_KEY_STATE_PRESSED == wlr_keyboard_key_event_ptr->state && wlmim_process_key(keyboard_ptr->input_manager_ptr, key_syms[i], modifiers)) { processed |= true; } else { processed |= wlmtk_element_keyboard_sym( keyboard_ptr->root_element_ptr, key_syms[i], direction, modifiers); } } if (processed) return; wlmtk_element_keyboard_event( keyboard_ptr->root_element_ptr, wlr_keyboard_key_event_ptr); } /* ------------------------------------------------------------------------- */ /** * Handles `modifiers` signals, ie. updates to the modifiers. * * @param listener_ptr * @param data_ptr Points to wlr_keyboard. */ void handle_modifiers(struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmim_keyboard_t *keyboard_ptr = BS_CONTAINER_OF( listener_ptr, wlmim_keyboard_t, modifiers_listener); wlmim_report_activity(keyboard_ptr->input_manager_ptr); wlmim_set_keyboard_group_index( keyboard_ptr->input_manager_ptr, xkb_state_serialize_layout( keyboard_ptr->wlr_keyboard_ptr->xkb_state, XKB_STATE_LAYOUT_EFFECTIVE)); uint32_t modifiers = wlr_keyboard_get_modifiers( keyboard_ptr->wlr_keyboard_ptr); if ((modifiers & WLR_MODIFIER_ALT) != WLR_MODIFIER_ALT) { wl_signal_emit( &wlmim_events(keyboard_ptr->input_manager_ptr)->deactivate_task_list, NULL); } wlr_seat_set_keyboard( keyboard_ptr->wlr_seat_ptr, keyboard_ptr->wlr_keyboard_ptr); wlr_seat_keyboard_notify_modifiers( keyboard_ptr->wlr_seat_ptr, &keyboard_ptr->wlr_keyboard_ptr->modifiers); } /* == Unit tests =========================================================== */ static void _wlmim_keyboard_test_rmlvo(bs_test_t *test_ptr); static void _wlmim_keyboard_test_keyboard_file(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmim_keyboard_test_cases[] = { { true, "rmlvo", _wlmim_keyboard_test_rmlvo }, { true, "keyboard_file", _wlmim_keyboard_test_keyboard_file }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmim_keyboard_test_set = BS_TEST_SET( true, "keyboard", wlmim_keyboard_test_cases); /* ------------------------------------------------------------------------- */ /** Tests keyboard rules are loaded from a given RMLVO dict. */ void _wlmim_keyboard_test_rmlvo(bs_test_t *test_ptr) { struct xkb_rule_names r = {}; bspl_dict_t *d = bspl_dict_from_object( bspl_create_object_from_plist_string( "{XkbRMLVO={Rules=R;Model=M;Layout=L;Variant=V;Options=O}}")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, d); bspl_dict_t *rmlvo = _wlmim_keyboard_populate_rules(d, &r); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, rmlvo); BS_TEST_VERIFY_STREQ(test_ptr, "R", r.rules); BS_TEST_VERIFY_STREQ(test_ptr, "M", r.model); BS_TEST_VERIFY_STREQ(test_ptr, "L", r.layout); BS_TEST_VERIFY_STREQ(test_ptr, "V", r.variant); BS_TEST_VERIFY_STREQ(test_ptr, "O", r.options); bspl_dict_unref(rmlvo); bspl_dict_unref(d); } /* ------------------------------------------------------------------------- */ /** Tests keyboard rules are loaded from XKB configuration file. */ void _wlmim_keyboard_test_keyboard_file(bs_test_t *test_ptr) { struct xkb_rule_names r = {}; bspl_dict_t *d = bspl_dict_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, d); bspl_object_t *o = bspl_object_from_string( bspl_string_create(bs_test_data_path(test_ptr, "keyboard"))); BS_TEST_VERIFY_TRUE_OR_RETURN( test_ptr, bspl_dict_add(d, "XkbConfigurationFile", o)); bspl_object_unref(o); bspl_dict_t *rmlvo = _wlmim_keyboard_populate_rules(d, &r); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, rmlvo); BS_TEST_VERIFY_STREQ(test_ptr, "pc105", r.model); BS_TEST_VERIFY_STREQ(test_ptr, "us,ch", r.layout); BS_TEST_VERIFY_STREQ(test_ptr, "intl,", r.variant); BS_TEST_VERIFY_STREQ(test_ptr, "grp:shift_caps_toggle", r.options); bspl_dict_unref(rmlvo); bspl_dict_unref(d); } /* == End of keyboard.c ==================================================== */ wlmaker-0.8.1/src/action.h0000644000175100017510000000754115222270710015012 0ustar runnerrunner/* ========================================================================= */ /** * @file action.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_ACTION_H__ #define __WLMAKER_ACTION_H__ #include #include #include #include "task_list.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** wlmaker actions. Can be bound to keys. Also @see wlmaker_action_desc. */ typedef enum { WLMAKER_ACTION_NONE, WLMAKER_ACTION_QUIT, WLMAKER_ACTION_LOCK_SCREEN, WLMAKER_ACTION_LOCK_INHIBIT_BEGIN, WLMAKER_ACTION_LOCK_INHIBIT_END, WLMAKER_ACTION_LAUNCH_TERMINAL, WLMAKER_ACTION_SHELL_EXECUTE, WLMAKER_ACTION_EXECUTE, WLMAKER_ACTION_WORKSPACE_TO_PREVIOUS, WLMAKER_ACTION_WORKSPACE_TO_NEXT, WLMAKER_ACTION_WORKSPACE_ADD, WLMAKER_ACTION_WORKSPACE_DESTROY_LAST, WLMAKER_ACTION_TASK_TO_PREVIOUS, WLMAKER_ACTION_TASK_TO_NEXT, WLMAKER_ACTION_WINDOW_RAISE, WLMAKER_ACTION_WINDOW_LOWER, WLMAKER_ACTION_WINDOW_TOGGLE_FULLSCREEN, WLMAKER_ACTION_WINDOW_TOGGLE_MAXIMIZED, WLMAKER_ACTION_WINDOW_MAXIMIZE, WLMAKER_ACTION_WINDOW_UNMAXIMIZE, WLMAKER_ACTION_WINDOW_FULLSCREEN, WLMAKER_ACTION_WINDOW_SHADE, WLMAKER_ACTION_WINDOW_UNSHADE, WLMAKER_ACTION_WINDOW_CLOSE, WLMAKER_ACTION_WINDOW_TO_NEXT_WORKSPACE, WLMAKER_ACTION_WINDOW_TO_PREVIOUS_WORKSPACE, WLMAKER_ACTION_ROOT_MENU, WLMAKER_ACTION_THEME_LOAD_FROM_FILE, WLMAKER_ACTION_OUTPUT_MAGNIFY, WLMAKER_ACTION_OUTPUT_REDUCE, WLMAKER_ACTION_OUTPUT_SAVE_STATE, // Note: Keep these numbered consecutively. WLMAKER_ACTION_SWITCH_TO_VT1, WLMAKER_ACTION_SWITCH_TO_VT2, WLMAKER_ACTION_SWITCH_TO_VT3, WLMAKER_ACTION_SWITCH_TO_VT4, WLMAKER_ACTION_SWITCH_TO_VT5, WLMAKER_ACTION_SWITCH_TO_VT6, WLMAKER_ACTION_SWITCH_TO_VT7, WLMAKER_ACTION_SWITCH_TO_VT8, WLMAKER_ACTION_SWITCH_TO_VT9, WLMAKER_ACTION_SWITCH_TO_VT10, WLMAKER_ACTION_SWITCH_TO_VT11, WLMAKER_ACTION_SWITCH_TO_VT12, } wlmaker_action_t; extern const char *wlmaker_action_config_dict_key; extern const bspl_enum_desc_t wlmaker_action_desc[]; /** Forward declaration: Handle for bound actions. */ typedef struct _wlmaker_action_handle_t wlmaker_action_handle_t; /** * Binds the actions specified in the config dictionary. * * @param server_ptr * @param keybindings_dict_ptr * @param add_logo Whether to add `Logo` to all modifiers. * * @return A bound action handle, or NULL on error. */ wlmaker_action_handle_t *wlmaker_action_bind_keys( wlmaker_server_t *server_ptr, bspl_dict_t *keybindings_dict_ptr, bool add_logo); /** * Unbinds actions previously bound by @ref wlmaker_action_bind_keys. * * @param handle_ptr */ void wlmaker_action_unbind_keys(wlmaker_action_handle_t *handle_ptr); /** * Executes the given action on wlmaker. * * @param server_ptr * @param action * @param arg_ptr */ void wlmaker_action_execute( wlmaker_server_t *server_ptr, wlmaker_action_t action, void *arg_ptr); /** Unit test set. */ extern const bs_test_set_t wlmaker_action_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __ACTION_H__ */ /* == End of action.h ====================================================== */ wlmaker-0.8.1/src/CMakeLists.txt0000644000175100017510000001111615222270710016115 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) # Further dependency versions, as submodules: # * drm at libdrm-2.4.117 # * hwdata at v0.371 # * libdisplay-info at 0.2.0 # * pixman at pixman-0.43.0 pkg_check_modules(WAYLAND_SERVER REQUIRED IMPORTED_TARGET wayland-server>=1.22.0) # We build for wlroots 0.18, 0.19 and (preparing for) 0.20. pkg_check_modules(WLROOTS IMPORTED_TARGET wlroots-0.20>=0.20) if(NOT WLROOTS_FOUND) pkg_check_modules(WLROOTS IMPORTED_TARGET wlroots-0.19>=0.19) if(NOT WLROOTS_FOUND) pkg_check_modules(WLROOTS REQUIRED IMPORTED_TARGET wlroots-0.18>=0.18) endif() endif() pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb>=1.15) # XWayland considered optional. pkg_check_modules(XWAYLAND xwayland>=22.1.9) add_subdirectory(backend) add_subdirectory(input) add_subdirectory(toolkit) set(public_header_files action.h action_item.h background.h wlmbacktrace.h clip.h config.h corner.h dock.h files.h icon_manager.h idle.h input_observation.h launcher.h layer_panel.h layer_shell.h lock_mgr.h root_menu.h server.h subprocess_monitor.h task_list.h tl_menu.h xdg_decoration.h xdg_popup.h xdg_shell.h xdg_toplevel.h xwl.h xwl_surface.h) add_library(wlmaker_lib STATIC) target_sources(wlmaker_lib PRIVATE action.c action_item.c background.c wlmbacktrace.c clip.c config.c corner.c dock.c files.c icon_manager.c idle.c input_observation.c launcher.c layer_panel.c layer_shell.c lock_mgr.c root_menu.c server.c subprocess_monitor.c task_list.c tl_menu.c x11_cursor.xpm xdg_decoration.c xdg_popup.c xdg_shell.c xdg_toplevel.c xwl.c xwl_surface.c ) set_target_properties( wlmaker_lib PROPERTIES VERSION 1.0 PUBLIC_HEADER "${public_header_files}") if(iwyu_path_and_options) set_target_properties( wlmaker_lib PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() add_dependencies( wlmaker_lib backend embedded_configuration embedded_root_menu embedded_state embedded_theme input libbase libbase_plist protocol_headers toolkit wlmaker_lib) target_link_libraries( wlmaker_lib backend embedded_configuration embedded_root_menu embedded_state embedded_theme libbase libbase_plist input toolkit wlmaker_protocols $<$:${LIBBACKTRACE}> PkgConfig::CAIRO PkgConfig::LIBXDGBASEDIR PkgConfig::WAYLAND_SERVER PkgConfig::WLROOTS PkgConfig::XCB) target_include_directories( wlmaker_lib PUBLIC # Keep wlroots first -- multiple versions may interfere (#117). "${WLROOTS_INCLUDE_DIRS}" "${PROJECT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}/third_party/protocols" "${PROJECT_BINARY_DIR}/protocols" "${CAIRO_INCLUDE_DIRS}" "${LIBXDGBASEDIR_INCLUDE_DIRS}" "${WAYLAND_SERVER_INCLUDE_DIRS}" "${XCB_INCLUDE_DIRS}" ) target_compile_definitions( wlmaker_lib PRIVATE "WLMAKER_SOURCE_DIR=\"${PROJECT_SOURCE_DIR}\"") target_compile_definitions( wlmaker_lib PRIVATE "WLMAKER_BINARY_DIR=\"${PROJECT_BINARY_DIR}\"") if(wmmenugen_executable) target_compile_definitions( wlmaker_lib PRIVATE "WLMAKER_WMMENUGEN_PATH=\"${wmmenugen_executable}\"") endif() if(XWAYLAND_FOUND) target_compile_definitions(wlmaker_lib PUBLIC WLMAKER_HAVE_XWAYLAND) endif() if(LIBBACKTRACE) target_compile_definitions(wlmaker_lib PRIVATE WLMAKER_HAVE_LIBBACKTRACE) endif() add_executable( wlmaker wlmaker.c) add_dependencies(wlmaker wlmaker_lib) target_compile_options( wlmaker PRIVATE "${WAYLAND_SERVER_CFLAGS}" "${WAYLAND_SERVER_CFLAGS_OTHER}") target_compile_definitions( wlmaker PRIVATE "WLMAKER_VERSION_MAJOR=\"${WLMAKER_VERSION_MAJOR}\"" "WLMAKER_VERSION_MINOR=\"${WLMAKER_VERSION_MINOR}\"" "WLMAKER_VERSION_FULL=\"${WLMAKER_VERSION_FULL}\"") target_link_libraries( wlmaker PRIVATE libbase libbase_plist wlmaker_lib) if(iwyu_path_and_options) set_target_properties( wlmaker PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() install(TARGETS wlmaker DESTINATION "${CMAKE_INSTALL_BINDIR}") wlmaker-0.8.1/src/corner.c0000644000175100017510000004363215222270710015021 0ustar runnerrunner/* ========================================================================= */ /** * @file corner.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "corner.h" #include #include #include #include #include #include #define WLR_USE_UNSTABLE #include #include #include #include #undef WLR_USE_UNSTABLE #include "action.h" #include "server.h" #include "toolkit/toolkit.h" // #include "input/cursor.h" /* == Declarations ========================================================= */ /** * State of the hot-corner handler. * * The hot corner compoment tracks output layout and pointer position. When the * pointer enters any of the 4 corners of the output's bounding rectangle, a * timer with a 'cooldown' period is armed. If the pointer is moved before the * cooldown expires, the timer is disarmed, and nothing happens. * * If the pointer stays in the corner until the timer fires, we do consider the * corner as 'activated'. */ struct _wlmaker_corner_t { /** Back-link to server. Required to execute actions. */ wlmaker_server_t *server_ptr; /** Listener for wlr_output_layout::events::change. */ struct wl_listener output_layout_changed_listener; /** Listener for when the cursor position was updated. */ struct wl_listener cursor_position_updated_listener; /** Current extents of the output, cached for convience. */ struct wlr_box extents; /** Pointer X coordinate, rounded to pixel position. */ int pointer_x; /** Pointer Y coordinate, rounded to pixel position. */ int pointer_y; /** Timer: Armed when the corner is occupied, triggers action. */ struct wl_event_source *timer_event_source_ptr; /** The cursor's current corner. 0 if not currently in a corner. */ unsigned current_corner; /** * Tracks whether the corner was occoppied and the timer had fired. * * Required to trigger 'leave' actions when the corner is cleared. */ bool corner_triggered; /** Configuration: Wait time before triggering 'Enter',. */ uint64_t trigger_delay_msec; /** Action when entering the top-left corner. */ wlmaker_action_t top_left_enter_action; /** Action when leaving the top-left corner. */ wlmaker_action_t top_left_leave_action; /** Action when entering the top-right corner. */ wlmaker_action_t top_right_enter_action; /** Action when leaving the top-right corner. */ wlmaker_action_t top_right_leave_action; /** Action when entering the bottom-left corner. */ wlmaker_action_t bottom_left_enter_action; /** Action when leaving the bottom-left corner. */ wlmaker_action_t bottom_left_leave_action; /** Action when entering the bottom-right corner. */ wlmaker_action_t bottom_right_enter_action; /** Action when leaving the bottom-right corner. */ wlmaker_action_t bottom_right_leave_action; }; static void _wlmaker_corner_clear(wlmaker_corner_t *corner_ptr); static void _wlmaker_corner_occupy( wlmaker_corner_t *corner_ptr, unsigned position); static void _wlmaker_corner_update_layout( wlmaker_corner_t *corner_ptr, struct wlr_box *extents_ptr); static void _wlmaker_corner_evaluate( wlmaker_corner_t *corner_ptr); static int _wlmaker_corner_handle_timer(void *data_ptr); static void _wlmaker_corner_handle_output_layout_changed( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_corner_handle_position_updated( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Descriptor for the 'HotConfig' config dictionary. */ static const bspl_desc_t _wlmaker_corner_config_desc[] = { BSPL_DESC_UINT64( "TriggerDelay", true, wlmaker_corner_t, trigger_delay_msec, trigger_delay_msec, 500), BSPL_DESC_ENUM( "TopLeftEnter", false, wlmaker_corner_t, top_left_enter_action,top_left_enter_action, WLMAKER_ACTION_NONE, wlmaker_action_desc), BSPL_DESC_ENUM( "TopLeftLeave", false, wlmaker_corner_t, top_left_leave_action, top_left_leave_action, WLMAKER_ACTION_NONE, wlmaker_action_desc), BSPL_DESC_ENUM( "TopRightEnter", false, wlmaker_corner_t, top_right_enter_action, top_right_enter_action, WLMAKER_ACTION_NONE, wlmaker_action_desc), BSPL_DESC_ENUM( "TopRightLeave", false, wlmaker_corner_t, top_right_leave_action, top_right_leave_action, WLMAKER_ACTION_NONE, wlmaker_action_desc), BSPL_DESC_ENUM( "BottomLeftEnter", false, wlmaker_corner_t, bottom_left_enter_action, bottom_left_enter_action, WLMAKER_ACTION_NONE, wlmaker_action_desc), BSPL_DESC_ENUM( "BottomLeftLeave", false, wlmaker_corner_t, bottom_left_leave_action, bottom_left_leave_action, WLMAKER_ACTION_NONE, wlmaker_action_desc), BSPL_DESC_ENUM( "BottomRightEnter", false, wlmaker_corner_t, bottom_right_enter_action, bottom_right_enter_action, WLMAKER_ACTION_NONE, wlmaker_action_desc), BSPL_DESC_ENUM( "BottomRightLeave", false, wlmaker_corner_t, bottom_right_leave_action, bottom_right_leave_action, WLMAKER_ACTION_NONE, wlmaker_action_desc), BSPL_DESC_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_corner_t *wlmaker_corner_create( bspl_dict_t *hot_corner_config_dict_ptr, struct wl_event_loop *wl_event_loop_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_cursor *wlr_cursor_ptr, struct wl_signal *cursor_position_updated_ptr, wlmaker_server_t *server_ptr) { wlmaker_corner_t *corner_ptr = logged_calloc(1, sizeof(wlmaker_corner_t)); if (NULL == corner_ptr) return NULL; corner_ptr->server_ptr = server_ptr; if (!bspl_decode_dict( hot_corner_config_dict_ptr, _wlmaker_corner_config_desc, corner_ptr)) { bs_log(BS_ERROR, "Failed to parse 'HotConfig' dict."); wlmaker_corner_destroy(corner_ptr); return NULL; } corner_ptr->timer_event_source_ptr = wl_event_loop_add_timer( wl_event_loop_ptr, _wlmaker_corner_handle_timer, corner_ptr); if (NULL == corner_ptr->timer_event_source_ptr) { bs_log(BS_ERROR, "Failed wl_event_loop_add_timer(%p, %p, %p)", wl_event_loop_ptr, _wlmaker_corner_handle_timer, corner_ptr); wlmaker_corner_destroy(corner_ptr); return NULL; } struct wlr_box extents; wlr_output_layout_get_box(wlr_output_layout_ptr, NULL, &extents); corner_ptr->pointer_x = wlr_cursor_ptr->x; corner_ptr->pointer_y = wlr_cursor_ptr->y; _wlmaker_corner_update_layout(corner_ptr, &extents); wlmtk_util_connect_listener_signal( &wlr_output_layout_ptr->events.change, &corner_ptr->output_layout_changed_listener, _wlmaker_corner_handle_output_layout_changed); wlmtk_util_connect_listener_signal( cursor_position_updated_ptr, &corner_ptr->cursor_position_updated_listener, _wlmaker_corner_handle_position_updated); return corner_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_corner_destroy(wlmaker_corner_t *corner_ptr) { wlmtk_util_disconnect_listener( &corner_ptr->cursor_position_updated_listener); wlmtk_util_disconnect_listener( &corner_ptr->output_layout_changed_listener); if (NULL != corner_ptr->timer_event_source_ptr) { wl_event_source_remove(corner_ptr->timer_event_source_ptr); corner_ptr->timer_event_source_ptr = NULL; } free(corner_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Clears the hot-corner tracking and activation. */ void _wlmaker_corner_clear(wlmaker_corner_t *corner_ptr) { if (0 == corner_ptr->current_corner) return; // Disarms the timer. wl_event_source_timer_update(corner_ptr->timer_event_source_ptr, 0); if (corner_ptr->corner_triggered) { wlmaker_action_t action = WLMAKER_ACTION_NONE; switch (corner_ptr->current_corner) { case WLR_EDGE_TOP | WLR_EDGE_LEFT: action = corner_ptr->top_left_leave_action; break; case WLR_EDGE_TOP | WLR_EDGE_RIGHT: action = corner_ptr->top_right_leave_action; break; case WLR_EDGE_BOTTOM | WLR_EDGE_LEFT: action = corner_ptr->bottom_right_leave_action; break; case WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT: action = corner_ptr->bottom_left_leave_action; break; default: break; } wlmaker_action_execute(corner_ptr->server_ptr, action, NULL); corner_ptr->corner_triggered = false; } corner_ptr->current_corner = 0; } /* ------------------------------------------------------------------------- */ /** * Starts occupation of a corner. * * @param corner_ptr * @param position * */ void _wlmaker_corner_occupy( wlmaker_corner_t *corner_ptr, unsigned position) { // guard clauses: Ignore non-positions and if re-occupying same corner. if (0 == position) return; if (position == corner_ptr->current_corner) return; // A different corner? First clear an existing corner. if (position != corner_ptr->current_corner && 0 != corner_ptr->current_corner) { _wlmaker_corner_clear(corner_ptr); } // Occupy: Store the active corner and (re-arm) event timer. corner_ptr->current_corner = position; wl_event_source_timer_update(corner_ptr->timer_event_source_ptr, corner_ptr->trigger_delay_msec); } /* ------------------------------------------------------------------------- */ /** Updates the output extents. Triggers a re-evaluation. */ void _wlmaker_corner_update_layout( wlmaker_corner_t *corner_ptr, struct wlr_box *extents_ptr) { corner_ptr->extents = *extents_ptr; _wlmaker_corner_evaluate(corner_ptr); } /* ------------------------------------------------------------------------- */ /** (Re)evaluates hot corner state from layout extents and pointer position. */ void _wlmaker_corner_evaluate( wlmaker_corner_t *corner_ptr) { if (0 >= corner_ptr->extents.width || 0>= corner_ptr->extents.height) { bs_log(BS_INFO, "Zero extents found, clearing corner setup."); _wlmaker_corner_clear(corner_ptr); return; } struct wlr_box *e_ptr = &corner_ptr->extents; unsigned position = WLR_EDGE_NONE; if (corner_ptr->pointer_x == e_ptr->x) { position |= WLR_EDGE_LEFT; } else if (corner_ptr->pointer_x >= e_ptr->x + e_ptr->width - 1) { position |= WLR_EDGE_RIGHT; } if (corner_ptr->pointer_y == e_ptr->y) { position |= WLR_EDGE_TOP; } else if (corner_ptr->pointer_y >= e_ptr->y + e_ptr->height - 1) { position |= WLR_EDGE_BOTTOM; } switch (position) { case WLR_EDGE_TOP | WLR_EDGE_LEFT: case WLR_EDGE_TOP | WLR_EDGE_RIGHT: case WLR_EDGE_BOTTOM | WLR_EDGE_LEFT: case WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT: _wlmaker_corner_occupy(corner_ptr, position); break; default: _wlmaker_corner_clear(corner_ptr); } } /* ------------------------------------------------------------------------- */ /** Handles timer callbacks: Sends 'enter' event and registers triggering. */ int _wlmaker_corner_handle_timer(void *data_ptr) { wlmaker_corner_t *corner_ptr = data_ptr; corner_ptr->corner_triggered = true; wlmaker_action_t action = WLMAKER_ACTION_NONE; switch (corner_ptr->current_corner) { case WLR_EDGE_TOP | WLR_EDGE_LEFT: action = corner_ptr->top_left_enter_action; break; case WLR_EDGE_TOP | WLR_EDGE_RIGHT: action = corner_ptr->top_right_enter_action; break; case WLR_EDGE_BOTTOM | WLR_EDGE_LEFT: action = corner_ptr->bottom_right_enter_action; break; case WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT: action = corner_ptr->bottom_left_enter_action; break; default: break; } wlmaker_action_execute(corner_ptr->server_ptr, action, NULL); return 0; } /* ------------------------------------------------------------------------- */ /** * Handles `change` events of `struct wlr_output_layout`. * * Will recompute the output's layout settings and re-evaluate the current * cursor position. * * @param listener_ptr * @param data_ptr Points to a struct wlr_output_layout. */ void _wlmaker_corner_handle_output_layout_changed( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_corner_t *corner_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_corner_t, output_layout_changed_listener); struct wlr_output_layout *wlr_output_layout_ptr = data_ptr; struct wlr_box extents; wlr_output_layout_get_box(wlr_output_layout_ptr, NULL, &extents); _wlmaker_corner_update_layout(corner_ptr, &extents); } /* ------------------------------------------------------------------------- */ /** * Handles @ref wlmim_events::cursor_position_updated signal callbacks. * * Stores the pointer's position in @ref wlmaker_corner_t. If the position is * different than before, triggers a re-evaluation of whether a corner is * occupied. * * @param listener_ptr * @param data_ptr Points to a `struct wlr_cursor`. */ void _wlmaker_corner_handle_position_updated( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_corner_t *corner_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_corner_t, cursor_position_updated_listener); struct wlr_cursor *wlr_cursor_ptr = data_ptr; // Optimization: Ignore updates that are moves within the same pixel. if (corner_ptr->pointer_x == wlr_cursor_ptr->x && corner_ptr->pointer_y == wlr_cursor_ptr->y) return; corner_ptr->pointer_x = wlr_cursor_ptr->x; corner_ptr->pointer_y = wlr_cursor_ptr->y; _wlmaker_corner_evaluate(corner_ptr); } /* == Unit tests =========================================================== */ static void _wlmaker_corner_test(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t wlmaker_corner_test_cases[] = { { true, "test", _wlmaker_corner_test }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t wlmaker_corner_test_set = BS_TEST_SET( true, "corner", wlmaker_corner_test_cases); /* ------------------------------------------------------------------------- */ /** Exercises the hot corner module. */ void _wlmaker_corner_test(bs_test_t *test_ptr) { bspl_object_t *obj_ptr = bspl_create_object_from_plist_string( "{" "TriggerDelay = 500;" "}"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); struct wl_event_loop *wl_event_loop_ptr = wl_event_loop_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_event_loop_ptr); struct wl_display *wl_display_ptr = wl_display_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, wl_display_ptr); struct wlr_output_layout *wlr_output_layout_ptr = wlr_output_layout_create( wl_display_ptr); struct wlr_cursor wlr_cursor = {}; struct wl_signal position_updated; wl_signal_init(&position_updated); wlmaker_server_t server = {}; wlmaker_corner_t *c_ptr = wlmaker_corner_create( bspl_dict_from_object(obj_ptr), wl_event_loop_ptr, wlr_output_layout_ptr, &wlr_cursor, &position_updated, &server); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, c_ptr); BS_TEST_VERIFY_EQ(test_ptr, 500, c_ptr->trigger_delay_msec); BS_TEST_VERIFY_EQ( test_ptr, 0, c_ptr->current_corner); // Set dimensions. Pointer still at (0, 0), that's top-left. struct wlr_output output = { .width = 640, .height = 480, .scale = 1 }; wlmtk_test_wlr_output_init(&output); wlr_output_layout_add(wlr_output_layout_ptr, &output, 0, 0); BS_TEST_VERIFY_EQ( test_ptr, WLR_EDGE_TOP | WLR_EDGE_LEFT, c_ptr->current_corner); BS_TEST_VERIFY_FALSE(test_ptr, c_ptr->corner_triggered); // Move pointer to wlr_cursor.x = 639; wlr_cursor.y = 479; wl_signal_emit(&position_updated, &wlr_cursor); BS_TEST_VERIFY_EQ( test_ptr, WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT, c_ptr->current_corner); BS_TEST_VERIFY_FALSE(test_ptr, c_ptr->corner_triggered); // Pretend the timer expired. _wlmaker_corner_handle_timer(c_ptr); BS_TEST_VERIFY_TRUE(test_ptr, c_ptr->corner_triggered); // Move pointer: Clears triggers. wlr_cursor.x = 320; wlr_cursor.y = 240; wl_signal_emit(&position_updated, &wlr_cursor); BS_TEST_VERIFY_EQ( test_ptr, 0, c_ptr->current_corner); BS_TEST_VERIFY_FALSE(test_ptr, c_ptr->corner_triggered); wlmaker_corner_destroy(c_ptr); wl_display_destroy(wl_display_ptr); wl_event_loop_destroy(wl_event_loop_ptr); bspl_object_unref(obj_ptr); } /* == End of corner.c ====================================================== */ wlmaker-0.8.1/src/launcher.h0000644000175100017510000000403015222270710015324 0ustar runnerrunner/* ========================================================================= */ /** * @file launcher.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LAUNCHER_H__ #define __LAUNCHER_H__ #include #include #include "files.h" #include "subprocess_monitor.h" #include "toolkit/toolkit.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: Launcher handle. */ typedef struct _wlmaker_launcher_t wlmaker_launcher_t; /** * Creates an application launcher, configured from a plist dict. * * @param style_ptr * @param dict_ptr * @param monitor_ptr * @param files_ptr * * @return Pointer to the launcher handle or NULL on error. */ wlmaker_launcher_t *wlmaker_launcher_create_from_plist( const struct wlmtk_tile_style *style_ptr, bspl_dict_t *dict_ptr, wlmaker_subprocess_monitor_t *monitor_ptr, wlmaker_files_t *files_ptr); /** * Destroys the application launcher. * * @param launcher_ptr */ void wlmaker_launcher_destroy(wlmaker_launcher_t *launcher_ptr); /** @return A pointer to the @ref wlmtk_tile_t superclass of `launcher_ptr`. */ wlmtk_tile_t *wlmaker_launcher_tile(wlmaker_launcher_t *launcher_ptr); /** Unit test set. */ extern const bs_test_set_t wlmaker_launcher_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LAUNCHER_H__ */ /* == End of launcher.h ==================================================== */ wlmaker-0.8.1/src/tl_menu.c0000644000175100017510000003310215222270710015163 0ustar runnerrunner/* ========================================================================= */ /** * @file tl_menu.c * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "tl_menu.h" #include #include #include #include #include "action.h" #include "action_item.h" #include "server.h" /* == Declarations ========================================================= */ /** State of a toplevel's window menu. */ struct _wlmaker_tl_menu_t { /** Pointer to the window's @ref wlmtk_menu_t. */ wlmtk_menu_t *menu_ptr; /** Pointer to the submenu of `move_to_ws_ai_ptr`. */ wlmtk_menu_t *workspaces_submenu_ptr; /** Holds @ref wlmaker_tl_menu_ws_item_t::dlnode items. */ bs_dllist_t submenu_items; /** Back-link to server. */ wlmaker_server_t *server_ptr; /** Back-link to the window. */ wlmtk_window_t *window_ptr; /** Listener for @ref wlmtk_window_events_t::state_changed. */ struct wl_listener window_state_changed_listener; /** Listener for @ref wlmtk_root_events_t::workspace_changed. */ struct wl_listener workspace_changed_listener; /** Action item for 'Maximize'. */ wlmaker_action_item_t *maximize_ai_ptr; /** Action item for 'Unmaximize'. */ wlmaker_action_item_t *unmaximize_ai_ptr; /** Action item for 'Fullscreen'. */ wlmaker_action_item_t *fullscreen_ai_ptr; /** Action item for 'Shade'. */ wlmaker_action_item_t *shade_ai_ptr; /** Action item for 'Unshade'. */ wlmaker_action_item_t *unshade_ai_ptr; /** Menu item for attaching the workspaces submenu. */ wlmaker_action_item_t *move_to_ws_ai_ptr; /** Action item for 'close'. */ wlmaker_action_item_t *close_ai_ptr; }; /** Item holder. */ typedef struct { /** Element of @ref wlmaker_tl_menu_t::submenu_items. */ bs_dllist_node_t dlnode; /** Composed from a menu item. */ wlmtk_menu_item_t *menu_item_ptr; /** Window to move. */ wlmtk_window_t *window_ptr; /** Workspace to move it to. */ wlmtk_workspace_t *workspace_ptr; /** Listener for @ref wlmtk_menu_item_events_t::triggered. */ struct wl_listener triggered_listener; /** Listener for @ref wlmtk_menu_item_events_t::destroy. */ struct wl_listener destroy_listener; } wlmaker_tl_menu_ws_item_t; static void _wlmaker_tl_menu_workspace_iterator_create_item( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmaker_tl_menu_ws_items_iterator_enable_workspace( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void _wlmaker_tl_menu_handle_window_state_changed( struct wl_listener *listener_ptr, void *data_ptr); static void _wlmaker_tl_menu_handle_workspace_changed( struct wl_listener *listener_ptr, void *data_ptr); static void _destroy(wlmaker_tl_menu_ws_item_t *ws_item_ptr); static void _item_handle_triggered( struct wl_listener *listener_ptr, void *data_ptr); static void _item_handle_destroy( struct wl_listener *listener_ptr, void *data_ptr); /* == Data ================================================================= */ /** Menu items for the XDG toplevel's window menu. */ static const wlmaker_action_item_desc_t _tl_menu_items[] = { { "Maximize", WLMAKER_ACTION_WINDOW_MAXIMIZE, NULL, offsetof(wlmaker_tl_menu_t, maximize_ai_ptr) }, { "Unmaximize", WLMAKER_ACTION_WINDOW_UNMAXIMIZE, NULL, offsetof(wlmaker_tl_menu_t, unmaximize_ai_ptr) }, { "Fullscreen", WLMAKER_ACTION_WINDOW_TOGGLE_FULLSCREEN, NULL, offsetof(wlmaker_tl_menu_t, fullscreen_ai_ptr) }, { "Shade", WLMAKER_ACTION_WINDOW_SHADE, NULL, offsetof(wlmaker_tl_menu_t, shade_ai_ptr) }, { "Unshade", WLMAKER_ACTION_WINDOW_UNSHADE, NULL, offsetof(wlmaker_tl_menu_t, unshade_ai_ptr) }, { "Move to workspace ...", WLMAKER_ACTION_NONE, NULL, offsetof(wlmaker_tl_menu_t, move_to_ws_ai_ptr) }, { "Close", WLMAKER_ACTION_WINDOW_CLOSE, NULL, offsetof(wlmaker_tl_menu_t, close_ai_ptr) }, { NULL, 0, NULL, 0 } // Sentinel. }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ wlmaker_tl_menu_t *wlmaker_tl_menu_create( wlmtk_window_t *window_ptr, wlmaker_server_t *server_ptr) { wlmaker_tl_menu_t *tl_menu_ptr = logged_calloc( 1, sizeof(wlmaker_tl_menu_t)); if (NULL == tl_menu_ptr) return NULL; tl_menu_ptr->server_ptr = server_ptr; tl_menu_ptr->menu_ptr = wlmtk_window_menu(window_ptr); tl_menu_ptr->window_ptr = window_ptr; for (const wlmaker_action_item_desc_t *desc_ptr = &_tl_menu_items[0]; NULL != desc_ptr->text_ptr; ++desc_ptr) { wlmaker_action_item_t *ai_ptr = wlmaker_action_item_create_from_desc( desc_ptr, tl_menu_ptr, wlmtk_menu_style_to_ref(server_ptr->style_ptr->menu_style_ptr), server_ptr); if (NULL == ai_ptr) { bs_log(BS_ERROR, "Failed wlmaker_action_item_create_from_desc()"); wlmaker_tl_menu_destroy(tl_menu_ptr); return NULL; } wlmtk_menu_add_item( tl_menu_ptr->menu_ptr, wlmaker_action_item_menu_item(ai_ptr)); } tl_menu_ptr->workspaces_submenu_ptr = wlmtk_menu_create( wlmtk_menu_style_to_ref(server_ptr->style_ptr->menu_style_ptr)); if (NULL == tl_menu_ptr->workspaces_submenu_ptr) { wlmaker_tl_menu_destroy(tl_menu_ptr); return NULL; } wlmtk_menu_item_set_submenu( wlmaker_action_item_menu_item(tl_menu_ptr->move_to_ws_ai_ptr), tl_menu_ptr->workspaces_submenu_ptr); // Connect state listener and initialize state. wlmtk_util_connect_listener_signal( &wlmtk_window_events(window_ptr)->state_changed, &tl_menu_ptr->window_state_changed_listener, _wlmaker_tl_menu_handle_window_state_changed); _wlmaker_tl_menu_handle_window_state_changed( &tl_menu_ptr->window_state_changed_listener, window_ptr); wlmtk_util_connect_listener_signal( &wlmtk_root_events(server_ptr->root_ptr)->workspace_changed, &tl_menu_ptr->workspace_changed_listener, _wlmaker_tl_menu_handle_workspace_changed); _wlmaker_tl_menu_handle_workspace_changed( &tl_menu_ptr->workspace_changed_listener, NULL); return tl_menu_ptr; } /* ------------------------------------------------------------------------- */ void wlmaker_tl_menu_destroy(wlmaker_tl_menu_t *tl_menu_ptr) { wlmtk_util_disconnect_listener( &tl_menu_ptr->workspace_changed_listener); wlmtk_util_disconnect_listener( &tl_menu_ptr->window_state_changed_listener); free(tl_menu_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Handles state changes: Updates the menu items accordingly. */ void _wlmaker_tl_menu_handle_window_state_changed( struct wl_listener *listener_ptr, void *data_ptr) { wlmaker_tl_menu_t *tl_menu_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_tl_menu_t, window_state_changed_listener); wlmtk_window_t *window_ptr = data_ptr; wlmtk_menu_item_set_enabled( wlmaker_action_item_menu_item(tl_menu_ptr->shade_ai_ptr), !wlmtk_window_is_shaded(window_ptr)); wlmtk_menu_item_set_enabled( wlmaker_action_item_menu_item(tl_menu_ptr->unshade_ai_ptr), wlmtk_window_is_shaded(window_ptr)); wlmtk_menu_item_set_enabled( wlmaker_action_item_menu_item(tl_menu_ptr->fullscreen_ai_ptr), !wlmtk_window_is_fullscreen(window_ptr)); wlmtk_menu_item_set_enabled( wlmaker_action_item_menu_item(tl_menu_ptr->maximize_ai_ptr), !wlmtk_window_is_maximized(window_ptr)); wlmtk_menu_item_set_enabled( wlmaker_action_item_menu_item(tl_menu_ptr->unmaximize_ai_ptr), wlmtk_window_is_maximized(window_ptr)); // Refresh the list of workspaces. bs_dllist_for_each( &tl_menu_ptr->submenu_items, _wlmaker_tl_menu_ws_items_iterator_enable_workspace, NULL); } /* ------------------------------------------------------------------------- */ /** Handles workspace changes: Refreshes the workspace menu. */ void _wlmaker_tl_menu_handle_workspace_changed( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_tl_menu_t *tl_menu_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_tl_menu_t, workspace_changed_listener); // Flush, then rebuild the workspaces submenu. while (0 < wlmtk_menu_items_size(tl_menu_ptr->workspaces_submenu_ptr)) { wlmtk_menu_remove_item( tl_menu_ptr->workspaces_submenu_ptr, wlmtk_menu_item_at(tl_menu_ptr->workspaces_submenu_ptr, 0)); } wlmtk_root_for_each_workspace( tl_menu_ptr->server_ptr->root_ptr, _wlmaker_tl_menu_workspace_iterator_create_item, tl_menu_ptr); // Disable the currently-active workspace. bs_dllist_for_each( &tl_menu_ptr->submenu_items, _wlmaker_tl_menu_ws_items_iterator_enable_workspace, NULL); } /* ------------------------------------------------------------------------- */ /** Destroys the item holder. */ void _destroy(wlmaker_tl_menu_ws_item_t *ws_item_ptr) { if (NULL != ws_item_ptr->menu_item_ptr) { wlmtk_menu_item_destroy(ws_item_ptr->menu_item_ptr); ws_item_ptr->menu_item_ptr = NULL; } free(ws_item_ptr); } /* ------------------------------------------------------------------------- */ /** Creates a menu item for each workspace. */ void _wlmaker_tl_menu_workspace_iterator_create_item( bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { wlmtk_workspace_t *workspace_ptr = wlmtk_workspace_from_dlnode(dlnode_ptr); wlmaker_tl_menu_t *tl_menu_ptr = ud_ptr; const char *name_ptr; int index; wlmtk_workspace_get_details(workspace_ptr, &name_ptr, &index); wlmaker_tl_menu_ws_item_t *ws_item_ptr = logged_calloc( 1, sizeof(wlmaker_tl_menu_ws_item_t)); if (NULL == ws_item_ptr) return; ws_item_ptr->workspace_ptr = workspace_ptr; ws_item_ptr->window_ptr = tl_menu_ptr->window_ptr; ws_item_ptr->menu_item_ptr = wlmtk_menu_item_create( wlmtk_menu_style_to_ref( tl_menu_ptr->server_ptr->style_ptr->menu_style_ptr)); if (NULL == ws_item_ptr->menu_item_ptr) { _destroy(ws_item_ptr); return; } wlmtk_menu_item_set_text(ws_item_ptr->menu_item_ptr, name_ptr); wlmtk_util_connect_listener_signal( &wlmtk_menu_item_events(ws_item_ptr->menu_item_ptr)->triggered, &ws_item_ptr->triggered_listener, _item_handle_triggered); wlmtk_util_connect_listener_signal( &wlmtk_menu_item_events(ws_item_ptr->menu_item_ptr)->destroy, &ws_item_ptr->destroy_listener, _item_handle_destroy); wlmtk_menu_add_item( tl_menu_ptr->workspaces_submenu_ptr, ws_item_ptr->menu_item_ptr); bs_dllist_push_back(&tl_menu_ptr->submenu_items, &ws_item_ptr->dlnode); } /* ------------------------------------------------------------------------- */ /** Enables workspace items, except the one the window is currently on. */ void _wlmaker_tl_menu_ws_items_iterator_enable_workspace( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { wlmaker_tl_menu_ws_item_t *ws_item_ptr = BS_CONTAINER_OF( dlnode_ptr, wlmaker_tl_menu_ws_item_t, dlnode); wlmtk_menu_item_set_enabled( ws_item_ptr->menu_item_ptr, (wlmtk_window_get_workspace(ws_item_ptr->window_ptr) != ws_item_ptr->workspace_ptr)); } /* ------------------------------------------------------------------------- */ /** Handler for @ref wlmtk_menu_item_events_t::triggered. Moves the window. */ void _item_handle_triggered( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_tl_menu_ws_item_t *ws_item_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_tl_menu_ws_item_t, triggered_listener); wlmtk_workspace_unmap_window( wlmtk_window_get_workspace(ws_item_ptr->window_ptr), ws_item_ptr->window_ptr); wlmtk_workspace_map_window( ws_item_ptr->workspace_ptr, ws_item_ptr->window_ptr); } /* ------------------------------------------------------------------------- */ /** Handler for @ref wlmtk_menu_item_events_t::destroy. Destroy. */ void _item_handle_destroy( struct wl_listener *listener_ptr, __UNUSED__ void *data_ptr) { wlmaker_tl_menu_ws_item_t *ws_item_ptr = BS_CONTAINER_OF( listener_ptr, wlmaker_tl_menu_ws_item_t, destroy_listener); ws_item_ptr->menu_item_ptr = NULL; _destroy(ws_item_ptr); } /* == End of tl_menu.c ===================================================== */ wlmaker-0.8.1/dependencies/0000755000175100017510000000000015222270711015215 5ustar runnerrunnerwlmaker-0.8.1/dependencies/pixman/0000755000175100017510000000000015222270711016511 5ustar runnerrunnerwlmaker-0.8.1/dependencies/README.md0000644000175100017510000000031715222270711016475 0ustar runnerrunner# wlmaker-dependencies - Dependencies for Wayland Maker Wayland Maker dependencies, when building on systems without pre-packaged versions. See https://github.com/phkaeser/wlmaker for further information. wlmaker-0.8.1/dependencies/libbacktrace/0000755000175100017510000000000015222270711017623 5ustar runnerrunnerwlmaker-0.8.1/dependencies/.gitmodules0000644000175100017510000000137715222270711017402 0ustar runnerrunner[submodule "wlroots"] path = wlroots url = https://gitlab.freedesktop.org/wlroots/wlroots.git branch = main update = rebase [submodule "libbacktrace"] path = libbacktrace url = https://github.com/ianlancetaylor/libbacktrace.git [submodule "wayland"] path = wayland url = https://gitlab.freedesktop.org/wayland/wayland.git [submodule "libdrm"] path = libdrm url = https://gitlab.freedesktop.org/mesa/libdrm.git [submodule "libxkbcommon"] path = libxkbcommon url = https://github.com/xkbcommon/libxkbcommon.git [submodule "wayland-protocols"] path = wayland-protocols url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git [submodule "pixman"] path = pixman url = https://gitlab.freedesktop.org/pixman/pixman.git wlmaker-0.8.1/dependencies/LICENSE0000644000175100017510000002613515222270711016231 0ustar runnerrunner Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. wlmaker-0.8.1/dependencies/.git0000644000175100017510000000004515222270710015777 0ustar runnerrunnergitdir: ../.git/modules/dependencies wlmaker-0.8.1/dependencies/wayland-protocols/0000755000175100017510000000000015222270711020676 5ustar runnerrunnerwlmaker-0.8.1/dependencies/libxkbcommon/0000755000175100017510000000000015222270711017701 5ustar runnerrunnerwlmaker-0.8.1/dependencies/wlroots/0000755000175100017510000000000015222270711016726 5ustar runnerrunnerwlmaker-0.8.1/dependencies/.gitignore0000644000175100017510000000006315222270711017204 0ustar runnerrunner**/*~ **/CMakeFiles/* **/CMakeCache.txt **/build/* wlmaker-0.8.1/dependencies/wayland/0000755000175100017510000000000015222270711016654 5ustar runnerrunnerwlmaker-0.8.1/dependencies/libdrm/0000755000175100017510000000000015222270711016466 5ustar runnerrunnerwlmaker-0.8.1/dependencies/CMakeLists.txt0000644000175100017510000001046115222270711017757 0ustar runnerrunner# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Default arguments: # cmake -DCMAKE_INSTALL_PREFIX:PATH=${HOME}/.local -B build CMAKE_MINIMUM_REQUIRED(VERSION 3.13) PROJECT(wlmaker VERSION 0.1 DESCRIPTION "Wayland Maker - Dependencies" LANGUAGES C) # TODO(kaeser@gubbe.ch): Add a target for refreshing all submodules. # See https://cliutils.gitlab.io/modern-cmake/chapters/projects/submodule.html # If not found: Try 'pip3 install --user meson' FIND_PROGRAM(MESON_EXECUTABLE NAMES meson REQUIRED) FIND_PROGRAM(NINJA_EXECUTABLE NAMES ninja REQUIRED) FIND_PACKAGE(PkgConfig REQUIRED) # https://github.com/phkaeser/libbase # Initialize: git submodule update --init --recursive --merge # Checkout: git submodule update --checkout --recursive --merge # Update: git submodule update --remote --merge # Update: (cd libbase && git pull) # ADD_SUBDIRECTORY(libbase) # wlroots -- build and configure from the git submodule. # Note: This will *NOT* automatically install the library. INCLUDE(ExternalProject) ExternalProject_Add(dep-libbacktrace SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libbacktrace" INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CONFIGURE_COMMAND /configure --prefix= --datadir=/share --datarootdir=/share BUILD_COMMAND make INSTALL_COMMAND make install ) # Wayland # Packages needed: xsltproc xmlto ExternalProject_Add(wayland SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wayland" INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CONFIGURE_COMMAND ${MESON_EXECUTABLE} --prefix= -Ddocumentation=false BUILD_COMMAND ${NINJA_EXECUTABLE} -C INSTALL_COMMAND ${NINJA_EXECUTABLE} -C install ) # libdrm - libdrm-2.4.129 ExternalProject_Add(libdrm SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libdrm" INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CONFIGURE_COMMAND ${MESON_EXECUTABLE} --prefix= BUILD_COMMAND ${NINJA_EXECUTABLE} -C INSTALL_COMMAND ${NINJA_EXECUTABLE} -C install ) # xkbcommon - xkbcommon-1.8.0 # Packages needed: libxcb-xkb-dev ExternalProject_Add(libxkbcommon SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libxkbcommon" INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CONFIGURE_COMMAND ${MESON_EXECUTABLE} --prefix= -Denable-bash-completion=false BUILD_COMMAND ${NINJA_EXECUTABLE} -C INSTALL_COMMAND ${NINJA_EXECUTABLE} -C install ) # wayland-protocols - 1.47 ExternalProject_Add(wayland-protocols SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wayland-protocols" INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CONFIGURE_COMMAND ${MESON_EXECUTABLE} --prefix= BUILD_COMMAND ${NINJA_EXECUTABLE} -C INSTALL_COMMAND ${NINJA_EXECUTABLE} -C install ) # pixman - pixman-0.46.0 ExternalProject_Add(pixman SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/pixman" INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CONFIGURE_COMMAND ${MESON_EXECUTABLE} --prefix= BUILD_COMMAND ${NINJA_EXECUTABLE} -C INSTALL_COMMAND ${NINJA_EXECUTABLE} -C install ) # XWayland as optional dependency, configure wlroots accordingly. PKG_CHECK_MODULES(XWAYLAND xwayland>=22.1.9) IF(XWAYLAND_FOUND) SET(WLROOTS_XWAYLAND "-Dxwayland=enabled") ELSE(XWAYLAND_FOUND) SET(WLROOTS_XWAYLAND "-Dxwayland=disabled") ENDIF(XWAYLAND_FOUND) # wlroots - 0.20.0 ExternalProject_Add(wlroots SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/wlroots" INSTALL_DIR ${CMAKE_INSTALL_PREFIX} CONFIGURE_COMMAND ${MESON_EXECUTABLE} --prefix= -Dexamples=true -Dbackends=drm,libinput,x11 ${WLROOTS_XWAYLAND} BUILD_COMMAND ${NINJA_EXECUTABLE} -C INSTALL_COMMAND ${NINJA_EXECUTABLE} -C install ) wlmaker-0.8.1/LICENSE0000644000175100017510000002613515222270710013602 0ustar runnerrunner Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.wlmaker-0.8.1/include/0000755000175100017510000000000015222270710014211 5ustar runnerrunnerwlmaker-0.8.1/include/backend/0000755000175100017510000000000015222270710015600 5ustar runnerrunnerwlmaker-0.8.1/include/backend/backend.h0000644000175100017510000000664015222270710017346 0ustar runnerrunner/* ========================================================================= */ /** * @file backend.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMBE_BACKEND_H__ #define __WLMBE_BACKEND_H__ #include #include #include #include struct wl_display; struct wlr_output_layout; struct wlr_scene; /** Forward declaration. */ typedef struct _wlmbe_backend_t wlmbe_backend_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates the backend drivers. * * @param wl_display_ptr * @param wlr_scene_ptr * @param wlr_output_layout_ptr * @param width * @param height * @param config_dict_ptr * @param state_fname_ptr * * @return the server backend state, or NULL on error. */ wlmbe_backend_t *wlmbe_backend_create( struct wl_display *wl_display_ptr, struct wlr_scene *wlr_scene_ptr, struct wlr_output_layout *wlr_output_layout_ptr, int width, int height, bspl_dict_t *config_dict_ptr, const char *state_fname_ptr); /** * Destroys the server backend. * * @param backend_ptr */ void wlmbe_backend_destroy(wlmbe_backend_t *backend_ptr); /** * Switches to the given virtual terminal, if a wlroots session is available. * * Logs if wlr_session_change_vt() fails, but ignores the errors. * * @param backend_ptr * @param vt_num */ void wlmbe_backend_switch_to_vt(wlmbe_backend_t *backend_ptr, unsigned vt_num); /** Accessor. TODO(kaeser@gubbe.ch): Eliminate. */ struct wlr_backend *wlmbe_backend_wlr(wlmbe_backend_t *backend_ptr); /** Accessor. TODO(kaeser@gubbe.ch): Eliminate. */ struct wlr_compositor *wlmbe_backend_compositor(wlmbe_backend_t *backend_ptr); /** * Returns the primary output. Currently that is the first output found * in the output layout. * * @param wlr_output_layout_ptr * * @return A pointer to the `struct wlr_output` for the primary output. */ struct wlr_output *wlmbe_primary_output( struct wlr_output_layout *wlr_output_layout_ptr); /** * Returns the number of outputs active in the output layout. * * @param wlr_output_layout_ptr * * @return Number of outputs. */ size_t wlmbe_num_outputs(struct wlr_output_layout *wlr_output_layout_ptr); /** Magnifies all backend outputs by @ref _wlmbke_backend_magnification. */ void wlmbe_backend_magnify(wlmbe_backend_t *backend_ptr); /** Reduces all backend outputs by @ref _wlmbke_backend_magnification. */ void wlmbe_backend_reduce(wlmbe_backend_t *backend_ptr); /** Saves the output's ephemeral state into the state file. */ bool wlmbe_backend_save_ephemeral_output_configs(wlmbe_backend_t *backend_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmbe_backend_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMBE_BACKEND_H__ */ /* == End of backend.h ===================================================== */ wlmaker-0.8.1/include/backend/output.h0000644000175100017510000000573215222270710017320 0ustar runnerrunner/* ========================================================================= */ /** * @file output.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMBE_OUTPUT_H__ #define __WLMBE_OUTPUT_H__ #include #include #include "output_config.h" /** Handle for an output device. */ typedef struct _wlmbe_output_t wlmbe_output_t; struct wlr_output; struct wlr_allocator; struct wlr_renderer; struct wlr_scene; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates an output device from `wlr_output_ptr`. * * @param wlr_output_ptr * @param wlr_allocator_ptr * @param wlr_renderer_ptr * @param wlr_scene_ptr * @param config_ptr * @param width * @param height * * @return The output device handle or NULL on error. */ wlmbe_output_t *wlmbe_output_create( struct wlr_output *wlr_output_ptr, struct wlr_allocator *wlr_allocator_ptr, struct wlr_renderer *wlr_renderer_ptr, struct wlr_scene *wlr_scene_ptr, wlmbe_output_config_t *config_ptr, int width, int height); /** * Destroys the output device handle, as created by @ref wlmbe_output_create. * * @param output_ptr */ void wlmbe_output_destroy(wlmbe_output_t *output_ptr); /** @return A long description string, @see wlmbe_output_t::description_ptr. */ const char *wlmbe_output_description(wlmbe_output_t *output_ptr); /** Returns @ref wlmbe_output_t::wlr_output_ptr. */ struct wlr_output *wlmbe_wlr_output_from_output(wlmbe_output_t *output_ptr); /** * Gets the attributes of this output. * * @param output_ptr * * @return pointer to @ref wlmbe_output_config_t::attributes of * @ref wlmbe_output_t::output_config_ptr. */ const wlmbe_output_config_attributes_t *wlmbe_output_attributes( wlmbe_output_t *output_ptr); /** Updates attributes from wlr_output, and from the given position data. */ void wlmbe_output_update_attributes( wlmbe_output_t *output_ptr, int x, int y, bool has_position); /** Returns a pointer to @ref wlmbe_output_t::dlnode. */ bs_dllist_node_t *wlmbe_dlnode_from_output(wlmbe_output_t *output_ptr); /** Returns the @ref wlmbe_output_t for @ref wlmbe_output_t::dlnode. */ wlmbe_output_t *wlmbe_output_from_dlnode(bs_dllist_node_t *dlnode_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMBE_OUTPUT_H__ */ /* == End of output.h ====================================================== */ wlmaker-0.8.1/include/backend/output_manager.h0000644000175100017510000000454415222270710021012 0ustar runnerrunner/* ========================================================================= */ /** * @file output_manager.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMBE_OUTPUT_MANAGER_H__ #define __WLMBE_OUTPUT_MANAGER_H__ struct wl_display; struct wlr_backend; struct wlr_output_layout; struct wlr_scene; /** Forward declaration: Handle for output managers. */ typedef struct _wlmbe_output_manager_t wlmbe_output_manager_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Instantiates output managers for both `wlr-output-management-unstable-v1` * and `xdg-output-unstable-v1`. Both will listen for output changes in * `wlr_output_layout_ptr` and update the respective output configurations * is requested so. * * @param wl_display_ptr * @param wlr_scene_ptr * @param wlr_output_layout_ptr * @param wlr_backend_ptr * * @return Handle for the output managers, or NULL on error. Must be destroyed * by @ref wlmbe_output_manager_destroy. */ wlmbe_output_manager_t *wlmbe_output_manager_create( struct wl_display *wl_display_ptr, struct wlr_scene *wlr_scene_ptr, struct wlr_output_layout *wlr_output_layout_ptr, struct wlr_backend *wlr_backend_ptr); /** * Destroy the output manager, returned from @ref wlmbe_output_manager_create. * * @param output_manager_ptr */ void wlmbe_output_manager_destroy( wlmbe_output_manager_t *output_manager_ptr); /** * Scales all outputs by the provided scale factor. * * @param output_manager_ptr * @param scale */ void wlmbe_output_manager_scale( wlmbe_output_manager_t *output_manager_ptr, double scale); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMBE_OUTPUT_MANAGER_H__ */ /* == End of output_manager.h ============================================== */ wlmaker-0.8.1/include/backend/output_config.h0000644000175100017510000001744215222270710020646 0ustar runnerrunner/* ========================================================================= */ /** * @file output_config.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMBE_OUTPUT_CONFIG_H__ #define __WLMBE_OUTPUT_CONFIG_H__ #include #include #include #include #include /** Forward declaration. */ typedef struct _wlmbe_output_config_t wlmbe_output_config_t; struct wlr_output; struct wlr_output_layout; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** An output's position. */ typedef struct { /** Horizontal, in pixels. */ int32_t x; /** Vertical, in pixels. */ int32_t y; } wlmbe_output_config_position_t; /** An output's position. */ typedef struct { /** Width, in pixels. */ int32_t width; /** Height, in pixels. */ int32_t height; /** Refresh rate, in mHz. Set 0, to let backend pick a preferred value. */ int32_t refresh; } wlmbe_output_config_mode_t; /** Description of an output, useful to identify an output. */ typedef struct { /** Name of this output. */ char *name_ptr; /** Whether a 'Name' entry was present. */ bool has_name; /** Manufacturer of this output. That is 'make' in WLR speech. */ char *manufacturer_ptr; /** Whether the 'Manufacturer' entry was present. */ bool has_manufacturer; /** The model of this output. */ char *model_ptr; /** Whether the 'Model' entry was present. */ bool has_model; /** The serial of this output. */ char *serial_ptr; /** Whether the 'Serial' entry was present. */ bool has_serial; } wlmbe_output_description_t; /** Attributes of the output. */ typedef struct { /** Default transformation for the output(s). */ enum wl_output_transform transformation; /** Default scaling factor to use for the output(s). */ double scale; /** Whether this output is enabled. */ bool enabled; /** Position of this output. */ wlmbe_output_config_position_t position; /** Whether the 'Position' field was present. */ bool has_position; /** Mode of this output. */ wlmbe_output_config_mode_t mode; /** Whether the 'Mode' field was present. */ bool has_mode; } wlmbe_output_config_attributes_t; /** Returns the base pointer from the @ref wlmbe_output_config_t::dlnode. */ wlmbe_output_config_t *wlmbe_output_config_from_dlnode( bs_dllist_node_t *dlnode_ptr); /** Returns the base pointer from the @ref wlmbe_output_config_t::dlnode. */ bs_dllist_node_t *wlmbe_dlnode_from_output_config( wlmbe_output_config_t *config_ptr); /** Returns pointer to @ref wlmbe_output_config_t::attributes. */ const wlmbe_output_config_attributes_t *wlmbe_output_config_attributes( wlmbe_output_config_t *config_ptr); /** Updates the attributes from wlr_output_ptr and the given position info. */ void wlmbe_output_config_update_attributes( wlmbe_output_config_t *config_ptr, struct wlr_output *wlr_output_ptr, int x, int y, bool has_position); /** * Applies `attributes_ptr` to the configuration. Respects presence flags. * * @param config_ptr * @param attributes_ptr */ void wlmbe_output_config_apply_attributes( wlmbe_output_config_t *config_ptr, const wlmbe_output_config_attributes_t *attributes_ptr); /** * Creates a new output config from `wlr_output`. * * @param wlr_output_ptr * * @return New output configuration or NULL on error. */ wlmbe_output_config_t *wlmbe_output_config_create_from_wlr( struct wlr_output *wlr_output_ptr); /** * Returns whether the backend configuration equals the wlr_output attributes. * * @param dlnode_ptr To @ref wlmbe_output_config_t::dlnode. * @param ud_ptr To a `struct wlr_output`. * * @return true if it equals. */ bool wlmbe_output_config_equals( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /** * Returns if the backend configuration fnmatches the wlr_output attributes. * * @param dlnode_ptr To @ref wlmbe_output_config_t::dlnode. * @param ud_ptr To a `struct wlr_output`. * * @return true if it matches. */ bool wlmbe_output_config_fnmatches( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /** * Creates a new output config from the plist dictionnary `dict_ptr`. * * @param dict_ptr * * @return New output configuration or NULL on error. */ wlmbe_output_config_t *wlmbe_output_config_create_from_plist( bspl_dict_t *dict_ptr); /** * Creates a plist dict from the output config. * * @param config_ptr * * @return A plist dict or NULL on error. */ bspl_dict_t *wlmbe_output_config_create_into_plist( const wlmbe_output_config_t *config_ptr); /** Destroys the output configuration. */ void wlmbe_output_config_destroy(wlmbe_output_config_t *config_ptr); /** * Initializes the output description from the plist dictionary. * * @param desc_ptr * @param dict_ptr Dictionary describing the output. It may contain * keys and values beyond of what's described in * @ref _wlmbe_output_description_desc. */ bool wlmbe_output_description_init_from_plist( wlmbe_output_description_t *desc_ptr, bspl_dict_t *dict_ptr); /** Un-initializes the output description. */ void wlmbe_output_description_fini(wlmbe_output_description_t *desc_ptr); /** * Returns whether the output description equals the `wlr_output`'s attributes. * * @param desc_ptr * @param wlr_output_ptr * * @return True if all fields (Name, Manufacturer, Model, Serial) have the * same presence in both description and `wlr_output_ptr`, and (if present) * their values are equal. */ bool wlmbe_output_description_equals( wlmbe_output_description_t *desc_ptr, struct wlr_output *wlr_output_ptr); /** * Returns whether the output description matches the `wlr_output`'s attributes. * * @param desc_ptr * @param wlr_output_ptr * * @return For all fields present in the description, returns the aggregate AND * of whether a fnmatch() of the field matches the corresponding `wlr_output` * field. */ bool wlmbe_output_description_fnmatches( wlmbe_output_description_t *desc_ptr, struct wlr_output *wlr_output_ptr); /** * Returns the first `wlr_output_layout` output that matches `desc_ptr`. * * @param desc_ptr * @param wlr_output_layout_ptr * * @return A pointer to the first `wlr_output` found in `wlr_output_layout_ptr` * that matches `desc_ptr`. See @ref wlmbe_output_description_fnmatches for * matching details. */ struct wlr_output *wlmbe_output_description_first_fnmatch( wlmbe_output_description_t *desc_ptr, struct wlr_output_layout *wlr_output_layout_ptr); /** Unit tests for the output module. */ extern const bs_test_set_t wlmbe_output_config_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMBE_OUTPUT_CONFIG_H__ */ /* == End of output_config.h ================================================== */ wlmaker-0.8.1/include/toolkit/0000755000175100017510000000000015222270710015676 5ustar runnerrunnerwlmaker-0.8.1/include/toolkit/menu.h0000644000175100017510000001473715222270710017027 0ustar runnerrunner/* ========================================================================= */ /** * @file menu.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_MENU_H__ #define __WLMTK_MENU_H__ /** Forward declaration: Menu handle. */ typedef struct _wlmtk_menu_t wlmtk_menu_t; /** Forward declaration: Reference to the menu's style. */ typedef struct _wlmtk_menu_style_ref_t wlmtk_menu_style_ref_t; enum wlmtk_menu_mode; // IWYU pragma: keep #include #include #include #include #include #include "base.h" #include "box.h" // IWYU pragma: keep #include "element.h" #include "menu_item.h" // IWYU pragma: keep #include "style.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Style definition for the menu. */ struct wlmtk_menu_style { /** Margin. */ struct wlmtk_margin_style margin; /** Border. */ struct wlmtk_margin_style border; /** Item's style. */ struct wlmtk_menu_item_style item; }; /** Modes of the menu. */ enum wlmtk_menu_mode { /** Normal (window) mode of a menu: Left button click triggers items. */ WLMTK_MENU_MODE_NORMAL, /** * Right-click mode of menu: Menu is invoked while right button is pressed. * Releasing the right button triggers items. */ WLMTK_MENU_MODE_RIGHTCLICK }; /** Events of the popup menu. */ typedef struct { /** * The menu was opened or closed. Check through @ref wlmtk_menu_is_open. * The signal's argument is a pointer to the menu that opened/closed. */ struct wl_signal open_changed; /** Popup menu requests to be closed. */ struct wl_signal request_close; /** The dtor has been called. */ struct wl_signal destroy; } wlmtk_menu_events_t; /** * Creates a menu. * * @param style_ref_ptr * * @return Pointer to the menu state or NULL on error. */ wlmtk_menu_t *wlmtk_menu_create( wlmtk_menu_style_ref_t *style_ref_ptr); /** * Destroys the menu. * * @param menu_ptr */ void wlmtk_menu_destroy(wlmtk_menu_t *menu_ptr); /** @return pointer to the menu's @ref wlmtk_element_t superclass. */ wlmtk_element_t *wlmtk_menu_element(wlmtk_menu_t *menu_ptr); /** @return pointer to the menu's @ref wlmtk_base_t superclass. */ wlmtk_base_t *wlmtk_menu_base(wlmtk_menu_t *menu_ptr); /** @return a pointer to @ref wlmtk_menu_t::events. */ wlmtk_menu_events_t *wlmtk_menu_events(wlmtk_menu_t *menu_ptr); /** * Sets the menu's style. * * @param menu_ptr * @param style_ref_ptr Reference to the new style. * * @return true on success. */ bool wlmtk_menu_set_style( wlmtk_menu_t *menu_ptr, wlmtk_menu_style_ref_t *style_ref_ptr); /** * Opens the menu: Makes it visible or invisible, and resets state * if needed. * * @param menu_ptr * @param opened */ void wlmtk_menu_set_open(wlmtk_menu_t *menu_ptr, bool opened); /** @return whether the menu is open, ie. visible. */ bool wlmtk_menu_is_open(wlmtk_menu_t *menu_ptr); /** * Sets the mode of the menu. * * @param menu_ptr * @param mode */ void wlmtk_menu_set_mode(wlmtk_menu_t *menu_ptr, enum wlmtk_menu_mode mode); /** @return mode of the menu. @see wlmtk_menu_set_mode. */ enum wlmtk_menu_mode wlmtk_menu_get_mode(wlmtk_menu_t *menu_ptr); /** * Adds a menu item to the menu. * * @param menu_ptr * @param menu_item_ptr */ void wlmtk_menu_add_item(wlmtk_menu_t *menu_ptr, wlmtk_menu_item_t *menu_item_ptr); /** * Removes a menu item from the menu. * * @param menu_ptr * @param menu_item_ptr */ void wlmtk_menu_remove_item(wlmtk_menu_t *menu_ptr, wlmtk_menu_item_t *menu_item_ptr); /** * Registers `menu_item_ptr` as this submenu's parent item. * * @protected Must be called only from @ref wlmtk_menu_item_set_submenu. * * @param menu_ptr * @param menu_item_ptr */ void wlmtk_menu_set_parent_item(wlmtk_menu_t *menu_ptr, wlmtk_menu_item_t *menu_item_ptr); /** @return the parent menu item, if this is a submenu. NULL otherwise. */ wlmtk_menu_item_t *wlmtk_menu_get_parent_item(wlmtk_menu_t *menu_ptr); /** * Requests that menu_item_ptr be highlighted. * * @param menu_ptr * @param menu_item_ptr */ void wlmtk_menu_request_item_highlight( wlmtk_menu_t *menu_ptr, wlmtk_menu_item_t *menu_item_ptr); /** * @param menu_ptr * * @return the size of @ref wlmtk_menu_t::items. */ size_t wlmtk_menu_items_size(wlmtk_menu_t *menu_ptr); /** * @param menu_ptr * @param i * * @return the i-th element of @ref wlmtk_menu_t::items, or NULL if i is out * of bounds. */ wlmtk_menu_item_t *wlmtk_menu_item_at(wlmtk_menu_t *menu_ptr, size_t i); /** Creates a holder for the menu style, with initialized reference. * * @return Pointer to @ref wlmtk_menu_style. To destroy, call * @ref wlmtk_menu_style_ref_release on @ref wlmtk_menu_style_to_ref. */ struct wlmtk_menu_style *wlmtk_menu_style_create(void); /** @return the @ref wlmtk_menu_style_ref_t for the style. */ wlmtk_menu_style_ref_t *wlmtk_menu_style_to_ref( struct wlmtk_menu_style *menu_style_ptr); /** Retains a reference. @return a read-only style. */ const struct wlmtk_menu_style *wlmtk_menu_style_ref_retain( wlmtk_menu_style_ref_t *ref_ptr); /** Releases a reference. */ void wlmtk_menu_style_ref_release(wlmtk_menu_style_ref_t *ref_ptr); /** Decodes dict at `object_ptr` into @ref wlmtk_menu_style at `value_ptr` */ bool wlmtk_menu_style_decode( bspl_object_t *object_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Initializer. */ bool wlmtk_menu_style_decode_init(void *dst_ptr); /** Fini. */ void wlmtk_menu_style_decode_fini(void *dst_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_menu_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_MENU_H__ */ /* == End of menu.h ======================================================== */ wlmaker-0.8.1/include/toolkit/menu_item.h0000644000175100017510000001443415222270710020037 0ustar runnerrunner/* ========================================================================= */ /** * @file menu_item.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_MENU_ITEM_H__ #define __WLMTK_MENU_ITEM_H__ #include #include #include #include #include #include "element.h" #include "style.h" /** Forward declaration: State of the menu item. */ typedef struct _wlmtk_menu_item_t wlmtk_menu_item_t; /** Menu item style. */ struct wlmtk_menu_item_style { /** Fill style. */ wlmtk_style_fill_t fill; /** Fill style when disabled. */ wlmtk_style_fill_t highlighted_fill; /** Style of the font used in the menu item. */ wlmtk_style_font_t font; /** Height of the menu item, in pixels. */ uint64_t height; /** Width of the bezel, in pixels. */ uint64_t bezel_width; /** Text color. */ uint32_t enabled_text_color; /** Text color when highlighted. */ uint32_t highlighted_text_color; /** Text color when disabled. */ uint32_t disabled_text_color; /** Width of the item. */ uint64_t width; }; #include "menu.h" // IWYU pragma: keep #ifdef __cplusplus extern "C" { #endif // __cplusplus /** States a menu item can be in. */ typedef enum { WLMTK_MENU_ITEM_ENABLED, WLMTK_MENU_ITEM_HIGHLIGHTED, WLMTK_MENU_ITEM_DISABLED } wlmtk_menu_item_state_t; /** Events of the menu item. */ typedef struct { /** The menu item was triggered, by a click or key action. */ struct wl_signal triggered; /** The menu item is being destroyed. */ struct wl_signal destroy; } wlmtk_menu_item_events_t; /** * Creates a menu item. * * Note: Menu items are created as 'visible' elements. * * @param style_ref_ptr Reference of the menu's style. * * @return Pointer to the menu item state, or NULL on failure. */ wlmtk_menu_item_t *wlmtk_menu_item_create( wlmtk_menu_style_ref_t *style_ref_ptr); /** * Destroys the menu item. * * @param menu_item_ptr */ void wlmtk_menu_item_destroy(wlmtk_menu_item_t *menu_item_ptr); /** Returns pointer to the menu item's @ref wlmtk_menu_item_t::events. */ wlmtk_menu_item_events_t *wlmtk_menu_item_events( wlmtk_menu_item_t *menu_item_ptr); /** * Udpates the style of this item. * * @param menu_item_ptr * @param style_ref_ptr Reference of the menu's style. * * @return true on success. */ bool wlmtk_menu_item_set_style( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_style_ref_t *style_ref_ptr); /** * Sets the menu this item belongs to. * * Private: Should only be called by @ref wlmtk_menu_add_item. * * @param menu_item_ptr * @param menu_ptr May be NULL, to detach from menu. */ void wlmtk_menu_item_set_parent_menu( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_t *menu_ptr); /** * Sets the submenu for this menu item. * * @param menu_item_ptr * @param submenu_ptr The submenu to set for the item. The item will * take ownership of `submenu_ptr`, and destroy it * when the item ist destroyed. Unless the submenu * is detached again, by calling with a NULL * argument. */ void wlmtk_menu_item_set_submenu( wlmtk_menu_item_t *menu_item_ptr, wlmtk_menu_t *submenu_ptr); /** * Returns the submenu for this menu item. * * @param menu_item_ptr * * @return the submenu of the item, or NULL. */ wlmtk_menu_t *wlmtk_menu_item_get_submenu( wlmtk_menu_item_t *menu_item_ptr); /** * Sets the menu's mode for this item. * * @param menu_item_ptr * @param mode */ void wlmtk_menu_item_set_mode( wlmtk_menu_item_t *menu_item_ptr, enum wlmtk_menu_mode mode); /** @return the mode of this item. */ enum wlmtk_menu_mode wlmtk_menu_item_get_mode( wlmtk_menu_item_t *menu_item_ptr); /** @return The state of the item, @ref wlmtk_menu_item_t::state. */ wlmtk_menu_item_state_t wlmtk_menu_item_get_state( wlmtk_menu_item_t *menu_item_ptr); /** * Sets or updates the text for the menu item. * * @param menu_item_ptr * @param text_ptr */ bool wlmtk_menu_item_set_text( wlmtk_menu_item_t *menu_item_ptr, const char *text_ptr); /** * Sets whether the menu item is enabled or disabled. * * @param menu_item_ptr * @param enabled */ void wlmtk_menu_item_set_enabled( wlmtk_menu_item_t *menu_item_ptr, bool enabled); /** * Sets the menu item as highlighted. * * @param menu_item_ptr * @param highlighted * * @return false if the menu was not enabled, ie. could not be highlighted. */ bool wlmtk_menu_item_set_highlighted( wlmtk_menu_item_t *menu_item_ptr, bool highlighted); /** * Triggers the menu item's action. * * @param menu_item_ptr */ void wlmtk_menu_item_trigger(wlmtk_menu_item_t *menu_item_ptr); /** Returns pointer to @ref wlmtk_menu_item_t::dlnode. */ bs_dllist_node_t *wlmtk_dlnode_from_menu_item( wlmtk_menu_item_t *menu_item_ptr); /** Returns the base @ref wlmtk_menu_item_t from `dlnode_ptr`. */ wlmtk_menu_item_t *wlmtk_menu_item_from_dlnode(bs_dllist_node_t *dlnode_ptr); /** Returns a pointer to the superclass @ref wlmtk_element_t. */ wlmtk_element_t *wlmtk_menu_item_element(wlmtk_menu_item_t *menu_item_ptr); /** Descriptor for decoding the "Item" style dictionary. */ extern const bspl_desc_t wlmtk_menu_item_style_desc[]; /** Unit test cases. */ extern const bs_test_set_t wlmtk_menu_item_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_MENU_ITEM_H__ */ /* == End of menu_item.h =================================================== */ wlmaker-0.8.1/include/toolkit/layer.h0000644000175100017510000000742015222270710017166 0ustar runnerrunner/* ========================================================================= */ /** * @file layer.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_LAYER_H__ #define __WLMTK_LAYER_H__ #include #include /** Forward declaration: Layer state. */ typedef struct _wlmtk_layer_t wlmtk_layer_t; /** Forward declaration: Layer state. */ typedef struct _wlmtk_layer_output_t wlmtk_layer_output_t; #include "element.h" #include "panel.h" // IWYU pragma: keep #include "workspace.h" // IWYU pragma: keep /** Forward declaration: wlr output layout. */ struct wlr_output_layout; /** Forward declaration: wlr output. */ struct wlr_output; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a layer. Layers contain panels, such as layer shells. * * @param wlr_output_layout_ptr The output layout. * * @return Pointer to the layer handle or NULL on error. */ wlmtk_layer_t *wlmtk_layer_create( struct wlr_output_layout *wlr_output_layout_ptr); /** * Destroys the layer. * * @param layer_ptr */ void wlmtk_layer_destroy(wlmtk_layer_t *layer_ptr); /** @return Pointer to super @ref wlmtk_element_t of the layer. */ wlmtk_element_t *wlmtk_layer_element(wlmtk_layer_t *layer_ptr); /** * Adds the panel to the output within the specified layer. This will trigger * an update to the layer's layout, and a call to request_size of each panel * of that output. * * @param layer_ptr * @param panel_ptr * @param wlr_output_ptr */ bool wlmtk_layer_add_panel( wlmtk_layer_t *layer_ptr, wlmtk_panel_t *panel_ptr, struct wlr_output *wlr_output_ptr); /** * Removes the panel from the layer. * * @param layer_ptr * @param panel_ptr */ void wlmtk_layer_remove_panel(wlmtk_layer_t *layer_ptr, wlmtk_panel_t *panel_ptr); /** * Calls @ref wlmtk_panel_compute_dimensions for each contained panel. * * The Wayland protocol spells it is 'undefined' how panels (layer shells) * are stacked and configured within a layer. For wlmaker, we'll configure * the panels in sequence as they were added (found in the container, back * to front). * * @param layer_ptr */ void wlmtk_layer_reconfigure(wlmtk_layer_t *layer_ptr); /** * Calls @ref wlmtk_panel_compute_dimensions for each contained panel. * * The Wayland protocol spells it is 'undefined' how panels (layer shells) * are stacked and configured within a layer. For wlmaker, we'll configure * the panels in sequence as they were added (found in the container, back * to front). * * @param layer_output_ptr */ void wlmtk_layer_output_reconfigure(wlmtk_layer_output_t *layer_output_ptr); /** * Sets the parent workspace for the layer. * * Should only be called from @ref wlmtk_workspace_t methods. * * @param layer_ptr * @param workspace_ptr NULL to clear the workspace reference. */ void wlmtk_layer_set_workspace(wlmtk_layer_t *layer_ptr, wlmtk_workspace_t *workspace_ptr); /** Layer unit test. */ extern const bs_test_set_t wlmtk_layer_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_LAYER_H__ */ /* == End of layer.h ======================================================= */ wlmaker-0.8.1/include/toolkit/util.h0000644000175100017510000001143115222270710017024 0ustar runnerrunner/* ========================================================================= */ /** * @file util.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_UTIL_H__ #define __WLMTK_UTIL_H__ #include #include #include #include #include #include struct wl_list; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Information regarding a client. Drawn from `struct wl_client`. */ typedef struct { /** Process ID. */ pid_t pid; /** User ID. */ uid_t uid; /** Group ID. */ gid_t gid; } wlmtk_util_client_t; /** Record for recording a signal, suitable for unit testing. */ typedef struct { /** Listener that will get connected to the signal. */ struct wl_listener listener; /** Counts number of calls since connect or last clear. */ size_t calls; /** The |data_ptr| argument of the most recent call. */ void *last_data_ptr; } wlmtk_util_test_listener_t; /** For recording a signal taking a struct wlr_box arg. For unit testing. */ typedef struct { /** Listener that will get connected to the signal. */ struct wl_listener listener; /** Counts number of calls since connect or last clear. */ size_t calls; /** Value of the last call. */ struct wlr_box box; } wlmtk_util_test_wlr_box_listener_t; /** * Iterates over `list_ptr` and calls func() for each element. * * Permits removal of `link_ptr` in `func()`. Similar to wl_list_for_each_safe, * but not as a macro, and returning true if all invocations succeeded. * * @return true if none of the `func()` invocations returned false. */ bool wlmtk_util_wl_list_for_each( struct wl_list *list_ptr, bool (*func)(struct wl_list *link_ptr, void *ud_ptr), void *ud_ptr); /** * Sets |notifier_func| as the notifier for |listener_ptr|, and registers it * with |signal_ptr|. * * This is merely a convenience helper for the usual two-liner of boilerplate. * To disconnect from the listener signal, call `wl_list_remove` on a reference * to the `link` element of `*listener_ptr`. * * @param signal_ptr * @param listener_ptr * @param notifier_func */ // TODO(kaeser@gubbe.ch): Either swap arguments (listener first) or rename, // eg. . wlm_util_connect_signal_to_listener(...). void wlmtk_util_connect_listener_signal( struct wl_signal *signal_ptr, struct wl_listener *listener_ptr, void (*notifier_func)(struct wl_listener *, void *)); /** * Disconnects a listener from the signal. * * Does that in a safe way: Will only disconnect, if the `link` is set. * * @param listener_ptr */ void wlmtk_util_disconnect_listener( struct wl_listener *listener_ptr); /** * Connects test listener to signal. @see wlmtk_util_connect_listener_signal. * * @param signal_ptr * @param test_listener_ptr */ void wlmtk_util_connect_test_listener( struct wl_signal *signal_ptr, wlmtk_util_test_listener_t *test_listener_ptr); /** * Clears @ref wlmtk_util_test_listener_t::calls and * @ref wlmtk_util_test_listener_t::last_data_ptr. * * @param test_listener_ptr */ void wlmtk_util_clear_test_listener( wlmtk_util_test_listener_t *test_listener_ptr); /** * Disconnects a test listener. * * @param test_listener_ptr */ void wlmtk_util_disconnect_test_listener( wlmtk_util_test_listener_t *test_listener_ptr); /** * Connects the wlr_box test listener to signal. * * @param signal_ptr * @param test_wlr_box_listener_ptr */ void wlmtk_util_connect_test_wlr_box_listener( struct wl_signal *signal_ptr, wlmtk_util_test_wlr_box_listener_t *test_wlr_box_listener_ptr); /** * Clears @ref wlmtk_util_test_wlr_box_listener_t::calls and box. * * @param test_wlr_box_listener_ptr */ void wlmtk_util_clear_test_wlr_box_listener( wlmtk_util_test_wlr_box_listener_t *test_wlr_box_listener_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_util_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_UTIL_H__ */ /* == End of util.h ======================================================== */ wlmaker-0.8.1/include/toolkit/workspace.h0000644000175100017510000002167215222270710020055 0ustar runnerrunner/* ========================================================================= */ /** * @file workspace.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_WORKSPACE_H__ #define __WLMTK_WORKSPACE_H__ #include #include #include /** State of the workspace. */ typedef struct _wlmtk_workspace_t wlmtk_workspace_t; struct wlmtk_tile_style; #include "element.h" #include "layer.h" // IWYU pragma: keep #include "root.h" // IWYU pragma: keep #include "window.h" // IWYU pragma: keep /** Forward declaration: wlr output layout. */ struct wlr_output; struct wlr_output_layout; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Indicates which layer the view shall be rendered in. * * See `enum layer` at: * https://wayland.app/protocols/wlr-layer-shell-unstable-v1. */ typedef enum { WLMTK_WORKSPACE_LAYER_BACKGROUND = 0, WLMTK_WORKSPACE_LAYER_BOTTOM = 1, WLMTK_WORKSPACE_LAYER_TOP = 3, WLMTK_WORKSPACE_LAYER_OVERLAY = 4, } wlmtk_workspace_layer_t; /** * Creates a workspace. * * @param wlr_output_layout_ptr Output layout. Must outlive the workspace. * @param name_ptr * @param tile_style_ptr * * @return Pointer to the workspace state, or NULL on error. Must be free'd * via @ref wlmtk_workspace_destroy. */ wlmtk_workspace_t *wlmtk_workspace_create( struct wlr_output_layout *wlr_output_layout_ptr, const char *name_ptr, const struct wlmtk_tile_style *tile_style_ptr); /** * Destroys the workspace. Will destroy any stil-contained element. * * @param workspace_ptr */ void wlmtk_workspace_destroy(wlmtk_workspace_t *workspace_ptr); /** * Sets or updates workspace details. * * @param workspace_ptr * @param index */ void wlmtk_workspace_set_details( wlmtk_workspace_t *workspace_ptr, int index); /** * Retrieves the naming details of this workspace. * * @param workspace_ptr * @param name_ptr_ptr * @param index_ptr */ void wlmtk_workspace_get_details( wlmtk_workspace_t *workspace_ptr, const char **name_ptr_ptr, int *index_ptr); /** * Returns the workspace'soutput extents available for maximized windows. * * @param workspace_ptr * @param wlr_output_ptr Output to lookup the extents for. May be NULL, * in which case the primary output is used. * * @return A `struct wlr_box` that lines out the available space and position. */ struct wlr_box wlmtk_workspace_get_maximize_extents( wlmtk_workspace_t *workspace_ptr, struct wlr_output *wlr_output_ptr); /** * Returns the extents of the workspace available for fullscreen windows. * * @param workspace_ptr * @param wlr_output_ptr Output to lookup the extents for. May be NULL, * in which case the primary output is used. * * @return A `struct wlr_box` that lines out the available space and position. */ struct wlr_box wlmtk_workspace_get_fullscreen_extents( wlmtk_workspace_t *workspace_ptr, struct wlr_output *wlr_output_ptr); /** * Confines the window to remain entirely within workspace extents. * * A no-op if window_ptr is not mapped to workspace_ptr. * * @param workspace_ptr * @param window_ptr */ void wlmtk_workspace_confine_within( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr); /** Returns @ref wlmtk_workspace_t::wlr_output_layout_ptr. */ struct wlr_output_layout *wlmtk_workspace_get_wlr_output_layout( wlmtk_workspace_t *workspace_ptr); /** * Enabled or disables the workspace. * * An enabled workspace can have keyboard focus and activated windows. When * re-enabling a workspace, the formerly activated window will get re-activated * and re-gains keyboard focus. * * @param workspace_ptr * @param enabled */ void wlmtk_workspace_enable(wlmtk_workspace_t *workspace_ptr, bool enabled); /** @return whether this workspace is enabled. */ bool wlmtk_workspace_enabled(wlmtk_workspace_t *workspace_ptr); /** * Maps the window: Adds it to the workspace container and makes it visible. * * @param workspace_ptr * @param window_ptr */ void wlmtk_workspace_map_window(wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr); /** * Unmaps the window: Sets it as invisible and removes it from the container. * * @param workspace_ptr * @param window_ptr */ void wlmtk_workspace_unmap_window(wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr); /** * Sets the window's position. * * @param workspace_ptr * @param window_ptr * @param x * @param y */ void wlmtk_workspace_set_window_position( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr, int x, int y); /** * Returns pointer to the @ref wlmtk_layer_t handle serving `layer`. * * @param workspace_ptr * @param layer * * @return Pointer to the layer state. */ wlmtk_layer_t *wlmtk_workspace_get_layer( wlmtk_workspace_t *workspace_ptr, wlmtk_workspace_layer_t layer); /** * Returns the `bs_dllist_t` of currently mapped windows. * * @param workspace_ptr * * @return A pointer to the list. Note that the list should not be manipulated * directly. It's contents can change on @ref wlmtk_workspace_map_window * or @ref wlmtk_workspace_unmap_window calls. */ bs_dllist_t *wlmtk_workspace_get_windows_dllist( wlmtk_workspace_t *workspace_ptr); /** * Promotes the window to the fullscreen layer (or back). * * To be called by @ref wlmtk_window_commit_fullscreen. * * @param workspace_ptr * @param window_ptr * @param fullscreen */ void wlmtk_workspace_window_to_fullscreen( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr, bool fullscreen); /** * Initiates a 'move' for the window. * * @param workspace_ptr * @param window_ptr */ void wlmtk_workspace_begin_window_move( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr); /** * Initiates a 'resize' for the window. * * @param workspace_ptr * @param window_ptr * @param edges */ void wlmtk_workspace_begin_window_resize( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr, uint32_t edges); /** Acticates `window_ptr`. Will de-activate an earlier window. */ void wlmtk_workspace_activate_window( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr); /** @return Pointer to the activated @ref wlmtk_window_t, if any. */ wlmtk_window_t *wlmtk_workspace_get_activated_window( wlmtk_workspace_t *workspace_ptr); /** * Activates the @ref wlmtk_window_t *before* the currently activated one. * * Intended to permit cycling through tasks. Will activate the window, but not * raise it. See @ref wlmtk_workspace_activate_next_window. * * @param workspace_ptr */ void wlmtk_workspace_activate_previous_window( wlmtk_workspace_t *workspace_ptr); /** * Activates the @ref wlmtk_window_t *after* the currently activated one. * * Intended to permit cycling through tasks. Will activate the window, but not * raise it. See @ref wlmtk_workspace_activate_previous_window. * * @param workspace_ptr */ void wlmtk_workspace_activate_next_window( wlmtk_workspace_t *workspace_ptr); /** Raises `window_ptr`: Will show it atop all other windows. */ void wlmtk_workspace_raise_window( wlmtk_workspace_t *workspace_ptr, wlmtk_window_t *window_ptr); /** @return Pointer to wlmtk_workspace_t::super_container::super_element. */ wlmtk_element_t *wlmtk_workspace_element(wlmtk_workspace_t *workspace_ptr); /** @return pointer to the anchor @ref wlmtk_root_t of `workspace_ptr`. */ wlmtk_root_t *wlmtk_workspace_get_root(wlmtk_workspace_t *workspace_ptr); /** * Sets the anchor @ref wlmtk_root_t of `workspace_ptr`. * * @protected Must only be called from @ref wlmtk_root_t. * * @param workspace_ptr * @param root_ptr */ void wlmtk_workspace_set_root( wlmtk_workspace_t *workspace_ptr, wlmtk_root_t *root_ptr); /** @return Pointer to @ref wlmtk_workspace_t::dlnode. */ bs_dllist_node_t *wlmtk_dlnode_from_workspace( wlmtk_workspace_t *workspace_ptr); /** @return Poitner to the @ref wlmtk_workspace_t of the `dlnode_ptr`. */ wlmtk_workspace_t *wlmtk_workspace_from_dlnode( bs_dllist_node_t *dlnode_ptr); /** Unit tests for the workspace. */ extern const bs_test_set_t wlmtk_workspace_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_WORKSPACE_H__ */ /* == End of workspace.h =================================================== */ wlmaker-0.8.1/include/toolkit/output_tracker.h0000644000175100017510000001045315222270710021125 0ustar runnerrunner/* ========================================================================= */ /** * @file output_tracker.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright (c) 2026 Google LLC and Philipp Kaeser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMAKER_OUTPUT_TRACKER_H__ #define __WLMAKER_OUTPUT_TRACKER_H__ #include struct wlr_output; struct wlr_output_layout; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** State of the output tracker. */ typedef struct _wlmtk_output_tracker_t wlmtk_output_tracker_t; /** * Called when `wlr_output_ptr` got added to the layout. * * @param wlr_output_ptr * @param ud_ptr The `userdata_ptr` of @ref wlmtk_output_tracker_create. * * @return NULL for error, or a pointer that will be passed to * @ref wlmtk_output_tracker_output_update_callback_t, respectively to * @ref wlmtk_output_tracker_output_destroy_callback_t. */ typedef void *(*wlmtk_output_tracker_output_create_callback_t)( struct wlr_output *wlr_output_ptr, void *ud_ptr); /** * Called on layout updates, when `wlr_output_ptr` remains in layout. * * That can happen eg. when resolution or position changes; or when an * unrelated output is added or removed. * * @param wlr_output_ptr * @param ud_ptr * @param output_ptr */ typedef void (*wlmtk_output_tracker_output_update_callback_t)( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr); /** * Called when `wlr_output_ptr` got removed from the layout. * * @param wlr_output_ptr * @param ud_ptr * @param output_ptr */ typedef void (*wlmtk_output_tracker_output_destroy_callback_t)( struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr); /** * Creates an output tracker. * * @param wlr_output_layout_ptr * @param userdata_ptr Will be passed to the callbacks below. * @param create_fn * @param update_fn * @param destroy_fn * * @return A tracker handle, or NULL on error. */ wlmtk_output_tracker_t *wlmtk_output_tracker_create( struct wlr_output_layout *wlr_output_layout_ptr, void *userdata_ptr, wlmtk_output_tracker_output_create_callback_t create_fn, wlmtk_output_tracker_output_update_callback_t update_fn, wlmtk_output_tracker_output_destroy_callback_t destroy_fn); /** * Destroys the output tracker. * * @ref wlmtk_output_tracker_output_destroy_callback_t will be called for any * yet-remaining output. * * @param tracker_ptr */ void wlmtk_output_tracker_destroy(wlmtk_output_tracker_t *tracker_ptr); /** * Returns the "created output" for `wlr_output_ptr`. * * @param tracker_ptr * @param wlr_output_ptr * * @return Return value of @ref wlmtk_output_tracker_output_create_callback_t * that was called for the value of `wlr_output_ptr`. Or NULL, if * `wlr_output_ptr` is not tracked. */ void *wlmtk_output_tracker_get_output( wlmtk_output_tracker_t *tracker_ptr, struct wlr_output *wlr_output_ptr); /** Returns @ref wlmtk_output_tracker_t::wlr_output_layout_ptr. */ struct wlr_output_layout *wlmtk_output_tracker_get_layout( wlmtk_output_tracker_t *tracker_ptr); /** * Runs `callback` for all tracked outputs of the given tracker. * * @param tracker_ptr * @param callback Will be called for each output. * @param arg_ptr */ void wlmtk_output_tracker_for_each( wlmtk_output_tracker_t *tracker_ptr, void (*callback)(struct wlr_output *wlr_output_ptr, void *ud_ptr, void *output_ptr, void *arg_ptr), void *arg_ptr); /** Output tracker unit test. */ extern const bs_test_set_t wlmtk_output_tracker_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // __WLMAKER_OUTPUT_TRACKER_H__ /* == End of output_tracker.h ============================================== */ wlmaker-0.8.1/include/toolkit/resizebar_area.h0000644000175100017510000000460315222270710021030 0ustar runnerrunner/* ========================================================================= */ /** * @file resizebar_area.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_RESIZEBAR_AREA_H__ #define __WLMTK_RESIZEBAR_AREA_H__ #include #include // for bool #include // for uint32_t /** Forward declaration: Element of the resizebar. */ typedef struct _wlmtk_resizebar_area_t wlmtk_resizebar_area_t ; struct wlmtk_resizebar_style; #include "element.h" #include "window.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a resizebar button. * * @param window_ptr * @param edges * * @return Pointer to the resizebar button. */ wlmtk_resizebar_area_t *wlmtk_resizebar_area_create( wlmtk_window_t *window_ptr, uint32_t edges); /** * Destroys the resizebar element. * * @param resizebar_area_ptr */ void wlmtk_resizebar_area_destroy( wlmtk_resizebar_area_t *resizebar_area_ptr); /** * Redraws the element, with updated position and width. * * @param resizebar_area_ptr * @param gfxbuf_ptr * @param position * @param width * @param style_ptr * * @return true on success. */ bool wlmtk_resizebar_area_redraw( wlmtk_resizebar_area_t *resizebar_area_ptr, bs_gfxbuf_t *gfxbuf_ptr, unsigned position, unsigned width, const struct wlmtk_resizebar_style *style_ptr); /** Returns the button's super_buffer.super_element address. */ wlmtk_element_t *wlmtk_resizebar_area_element( wlmtk_resizebar_area_t *resizebar_area_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_resizebar_area_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_RESIZEBAR_AREA_H__ */ /* == End of resizebar_area.h ============================================== */ wlmaker-0.8.1/include/toolkit/titlebar.h0000644000175100017510000000760015222270710017660 0ustar runnerrunner/* ========================================================================= */ /** * @file titlebar.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_TITLEBAR_H__ #define __WLMTK_TITLEBAR_H__ #include #include #include #include /** Forward declaration: Title bar. */ typedef struct _wlmtk_titlebar_t wlmtk_titlebar_t; struct wlmtk_titlebar_style; #include "element.h" #include "window.h" // IWYU pragma: keep /** Properties of the titlebar: Which buttons to show. */ typedef enum { /** Whether the 'iconify' button is shown. */ WLMTK_TITLEBAR_PROPERTY_ICONIFY = UINT32_C(1) << 0, /** Whether the 'close' button is shown. */ WLMTK_TITLEBAR_PROPERTY_CLOSE = UINT32_C(1) << 1 } wlmtk_titlebar_property_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a title bar, suitable as a window title. * * @param window_ptr * @param style_ptr * * @return Pointer to the title bar state, or NULL on error. Must be free'd * by calling @ref wlmtk_titlebar_destroy. */ wlmtk_titlebar_t *wlmtk_titlebar_create( wlmtk_window_t *window_ptr, const struct wlmtk_titlebar_style *style_ptr); /** * Destroys the title bar. * * @param titlebar_ptr */ void wlmtk_titlebar_destroy(wlmtk_titlebar_t *titlebar_ptr); /** * Sets the width of the title bar. * * @param titlebar_ptr * @param width * * @return Whether the operation was successful. */ bool wlmtk_titlebar_set_width( wlmtk_titlebar_t *titlebar_ptr, unsigned width); /** * Sets the style of the title bar. * * @param titlebar_ptr * @param style_ptr * * @return true on success. */ bool wlmtk_titlebar_set_style( wlmtk_titlebar_t *titlebar_ptr, const struct wlmtk_titlebar_style *style_ptr); /** * Sets the properties of the title bar. * * @param titlebar_ptr * @param properties See @ref wlmtk_titlebar_property_t. */ void wlmtk_titlebar_set_properties( wlmtk_titlebar_t *titlebar_ptr, uint32_t properties); /** * Sets whether the title bar is activated. * * @param titlebar_ptr * @param activated */ void wlmtk_titlebar_set_activated( wlmtk_titlebar_t *titlebar_ptr, bool activated); /** Returns whether the title bar is activated. */ bool wlmtk_titlebar_is_activated(wlmtk_titlebar_t *titlebar_ptr); /** * Updates the title text of the titlebar. * * @param titlebar_ptr * @param title_ptr Expected to remain valid until the next call of * @ref wlmtk_titlebar_set_title or until the * `titlebar_ptr` is destroyed. */ void wlmtk_titlebar_set_title( wlmtk_titlebar_t *titlebar_ptr, const char *title_ptr); /** * Returns the super Element of the titlebar. * * @param titlebar_ptr * * @return Pointer to the @ref wlmtk_element_t base instantiation to * titlebar_ptr. */ wlmtk_element_t *wlmtk_titlebar_element(wlmtk_titlebar_t *titlebar_ptr); /** Descriptor for decoding the "TitleBar" dict below "Window". */ extern const bspl_desc_t wlmtk_titlebar_style_desc[]; /** Unit test cases. */ extern const bs_test_set_t wlmtk_titlebar_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_TITLEBAR_H__ */ /* == End of titlebar.h ==================================================== */ wlmaker-0.8.1/include/toolkit/root.h0000644000175100017510000001634415222270710017042 0ustar runnerrunner/* ========================================================================= */ /** * @file root.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_ROOT_H__ #define __WLMTK_ROOT_H__ /** Forward declaration: Root element (technically: container). */ typedef struct _wlmtk_root_t wlmtk_root_t; /** Forward declaration: wlr output layout. */ #include #include #include #include #include "element.h" #include "input.h" #include "menu.h" #include "surface.h" // IWYU pragma: keep #include "window.h" // IWYU pragma: keep #include "workspace.h" // IWYU pragma: keep struct wlr_output_layout; /** Forward declaration: Wlroots scene. */ struct wlr_scene; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Signals available for the @ref wlmtk_root_t class. */ typedef struct { /** * Signal: Raised when the current workspace is changed. * Data: Pointer to the new `wlmaker_workspace_t`, or NULL. */ struct wl_signal workspace_changed; /** Triggers whenever @ref wlmtk_root_unlock succeeds. */ struct wl_signal unlock_event; /** Triggers when a window is mapped to a workspace. */ struct wl_signal window_mapped; /** Triggers when a window is unmapped from a workspace. */ struct wl_signal window_unmapped; /** An unclaimed pointer event. Arg: @ref wlmtk_button_event_t. */ struct wl_signal unclaimed_button_event; } wlmtk_root_events_t; /** * Creates the root element. * * @param wlr_scene_ptr * @param wlr_output_layout_ptr * * @return Handle of the root element or NULL on error. */ wlmtk_root_t *wlmtk_root_create( struct wlr_scene *wlr_scene_ptr, struct wlr_output_layout *wlr_output_layout_ptr); /** * Destroys the root element. * * @param root_ptr */ void wlmtk_root_destroy(wlmtk_root_t *root_ptr); /** * Gets the set of events available in root. To bind listeners to. * * @param root_ptr * * @return Pointer to @ref wlmtk_root_t::events. */ wlmtk_root_events_t *wlmtk_root_events(wlmtk_root_t *root_ptr); /** * Handles a pointer motion event. * * @param root_ptr * @param x * @param y * @param time_msec * @param pointer_ptr * * @return Whether there was an element under the pointer. */ bool wlmtk_root_pointer_motion( wlmtk_root_t *root_ptr, double x, double y, uint32_t time_msec, wlmtk_pointer_t *pointer_ptr); /** * Handles a button event: Translates to button down/up/click/dblclick events. * * Each button activity (button pressed or released) will directly trigger a * corresponding BUTTON_DOWN or BUTTON_UP event. Depending on timing and * motion, a "released" event may also triccer a CLICK, DOUBLE_CLICK or * DRAG event. * These events will be forwarded to the event currently having pointer focus. * * TODO(kaeser@gubbe.ch): Implement DOUBLE_CLICK and DRAG events, and make it * well tested. * * @param root_ptr * @param event_ptr * @param modifiers * * @return Whether the button was consumed. */ bool wlmtk_root_pointer_button( wlmtk_root_t *root_ptr, const struct wlr_pointer_button_event *event_ptr, uint32_t modifiers); /** * Handles a pointer axis event. * * @param root_ptr * @param wlr_pointer_axis_event_ptr * * @return Whether the axis event was consumed. */ bool wlmtk_root_pointer_axis( wlmtk_root_t *root_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); /** * Adds a workspace. * * @param root_ptr * @param workspace_ptr */ void wlmtk_root_add_workspace( wlmtk_root_t *root_ptr, wlmtk_workspace_t *workspace_ptr); /** * Removes the workspace. * * @param root_ptr * @param workspace_ptr */ void wlmtk_root_remove_workspace( wlmtk_root_t *root_ptr, wlmtk_workspace_t *workspace_ptr); /** * Returns a pointer to the currently-active workspace. * * @param root_ptr */ wlmtk_workspace_t *wlmtk_root_get_current_workspace(wlmtk_root_t *root_ptr); /** * Destroys the last workspace -- if it's not the only remaining workspace, not * the current workspace, and if there's no windows contained in it. * * @param root_ptr */ void wlmtk_root_destroy_last_workspace(wlmtk_root_t *root_ptr); /** * Switches to the next workspace. * * @param root_ptr */ void wlmtk_root_switch_to_next_workspace(wlmtk_root_t *root_ptr); /** * Switches to the previous workspace. * * @param root_ptr */ void wlmtk_root_switch_to_previous_workspace(wlmtk_root_t *root_ptr); /** * Runs |func()| for each workspace. * * @param root_ptr * @param func * @param ud_ptr */ void wlmtk_root_for_each_workspace( wlmtk_root_t *root_ptr, void (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr); /** * Locks the root, using the provided element. * * The root must not be locked already. If locked successfully, the root will * keep a reference to `element_ptr`. The lock must call @ref wlmtk_root_unlock * to unlock root, and for releasing the reference. * * @param root_ptr * @param element_ptr * * @return Whether the lock was established. */ bool wlmtk_root_lock( wlmtk_root_t *root_ptr, wlmtk_element_t *element_ptr); /** * Unlocks the root, and releases the reference from @ref wlmtk_root_lock. * * Unlocking can only be done with `element_ptr` matching the `element_ptr` * argument from @ref wlmtk_root_lock. * * @param root_ptr * @param element_ptr * * @return Whether the lock was lifted. */ bool wlmtk_root_unlock( wlmtk_root_t *root_ptr, wlmtk_element_t *element_ptr); /** @return Whether root is locked. */ bool wlmtk_root_locked(wlmtk_root_t *root_ptr); /** * Releases the lock reference, but keeps the root locked. * * This is in accordance with the session lock protocol specification [1], * stating the session should remain locked if the client dies. * This call is a no-op if `element_ptr` is not currently the lock of * `root_ptr`. * * [1] https://wayland.app/protocols/ext-session-lock-v1 * * @param root_ptr * @param element_ptr */ void wlmtk_root_lock_unreference( wlmtk_root_t *root_ptr, wlmtk_element_t *element_ptr); /** @returns pointer to the root's @ref wlmtk_element_t. (Temporary) */ wlmtk_element_t *wlmtk_root_element(wlmtk_root_t *root_ptr); /** Updates the style for all windows contained in root. */ bool wlmtk_root_set_style( wlmtk_root_t *root_ptr, wlmtk_window_style_ref_t *window_style_ref_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_root_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_ROOT_H__ */ /* == End of root.h ======================================================== */ wlmaker-0.8.1/include/toolkit/element.h0000644000175100017510000004166215222270710017511 0ustar runnerrunner/* ========================================================================= */ /** * @file element.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_ELEMENT_H__ #define __WLMTK_ELEMENT_H__ #include #include #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE struct wlr_scene_tree; /** Forward declaration: Element. */ typedef struct _wlmtk_element_t wlmtk_element_t; /** Forward declaration: Element virtual method table. */ typedef struct _wlmtk_element_vmt_t wlmtk_element_vmt_t; /** Forward declaration: Container. */ typedef struct _wlmtk_container_t wlmtk_container_t; /** Forward declaration: Wlroots keyboard event. */ struct wlr_keyboard_key_event; #include "input.h" #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Events of the element. */ typedef struct { /** Pointer entered the element. Arg: @ref wlmtk_pointer_t. */ struct wl_signal pointer_enter; /** Pointer left the element: Pointer focus lost. */ struct wl_signal pointer_leave; /** Pointer motion within element. @ref wlmtk_pointer_motion_event_t. */ struct wl_signal pointer_motion; } wlmtk_element_events_t; /** Virtual method table for the element. */ struct _wlmtk_element_vmt_t { /** Abstract: Destroys the implementation of the element. */ void (*destroy)(wlmtk_element_t *element_ptr); /** Abstract: Creates element's scene graph API node, child to wlr_scene_tree_ptr. */ struct wlr_scene_node *(*create_scene_node)( wlmtk_element_t *element_ptr, struct wlr_scene_tree *wlr_scene_tree_ptr); /** Abstract: Gets dimensions of the element, relative to the element's position. */ void (*get_dimensions)( wlmtk_element_t *element_ptr, int *x1_ptr, int *y1_ptr, int *x2_ptr, int *y2_ptr); /** * Offers the pointer motion to this element. * * @pure This method is called from @ref wlmtk_element_pointer_motion. * * @param element_ptr * @param motion_event_ptr * * @return Whether the motion is accepted by this element (or any of it's * children). In that case, the caller should accept this element as * having pointer focus. */ bool (*pointer_accepts_motion)( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr); /** * Cancels a held pointer grab. * * Required to have an implementation by any element that requests a * pointer grab through @ref wlmtk_container_pointer_grab. * * Private: Must only to be called by the parent container. * * @param element_ptr */ void (*pointer_grab_cancel)(wlmtk_element_t *element_ptr); /** * Indicates pointer button event. * * @param element_ptr * @param button_event_ptr * * @return true If the button event was consumed. */ bool (*pointer_button)( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); /** * Indicates a pointer axis event. * * @param element_ptr * @param wlr_pointer_axis_event_ptr * * @return true If the axis event was consumed. */ bool (*pointer_axis)( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); /** * Blurs (de-activates) keyboard focus for the element. Propagates to child * elements, where available. * * @param element_ptr */ void (*keyboard_blur)(wlmtk_element_t *element_ptr); /** * Handler for keyboard events. * * This handler is suitable for passing keyboard events on to Wayland * clients, which may have their own keymap and state tracking. * * @param element_ptr * @param wlr_keyboard_key_event_ptr * * @return true if the key was handled. */ bool (*keyboard_event)( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr); /** * Handler for already-translated keys. * * This handler is intended for toolkit elements reacting on key strokes, * and expects the parent to have translated the key event into (a series * of) keysym events. * * @param element_ptr * @param keysym * @param direction * @param modifiers * * @return true if the key was processed. */ bool (*keyboard_sym)( wlmtk_element_t *element_ptr, xkb_keysym_t keysym, enum xkb_key_direction direction, uint32_t modifiers); /** * Redraws this element. For containers, will (re)arrange children. * * @param element_ptr */ void (*layout)(wlmtk_element_t *element_ptr); }; /** State of an element. */ struct _wlmtk_element_t { /** The node of elements. */ bs_dllist_node_t dlnode; /** Virtual method table for the element. */ wlmtk_element_vmt_t vmt; /** Events available from the element. */ wlmtk_element_events_t events; /** The container this element belongs to, if any. */ wlmtk_container_t *parent_container_ptr; /** Points to the wlroots scene graph API node, if attached. */ struct wlr_scene_node *wlr_scene_node_ptr; /** Listener for the `destroy` signal of `wlr_scene_node_ptr`. */ struct wl_listener wlr_scene_node_destroy_listener; /** Details of last @ref wlmtk_element_pointer_motion call. */ wlmtk_pointer_motion_event_t last_pointer_motion_event; /** * X position of the element in pixels, relative to parent container. * * This value may be stale, if @ref wlmtk_element_t::wlr_scene_node_ptr is * set and had been updated directly. Therefore, consider the value as * "private", and access only through @ref wlmtk_element_get_position. */ int x; /** * Y position of the element, relative to the container. * * Same observations apply as for @ref wlmtk_element_t::x. */ int y; /** Whether the element is visible (drawn, when part of a scene graph). */ bool visible; /** Whether the pointer is currently within the element's bounds. */ bool pointer_inside; /** Whether to inhibit blurring in @ref wlmtk_element_pointer_blur. */ bool inhibit_pointer_blur; }; /** * Initializes the element. * * @param element_ptr * * @return true on success. */ bool wlmtk_element_init(wlmtk_element_t *element_ptr); /** * Extends the element's virtual methods. * * @param element_ptr * @param element_vmt_ptr * * @return The previous virtual method table. */ wlmtk_element_vmt_t wlmtk_element_extend( wlmtk_element_t *element_ptr, const wlmtk_element_vmt_t *element_vmt_ptr); /** * Cleans up the element. * * @param element_ptr */ void wlmtk_element_fini( wlmtk_element_t *element_ptr); /** Gets the dlnode from the element. */ bs_dllist_node_t *wlmtk_dlnode_from_element( wlmtk_element_t *element_ptr); /** Gets the element from the dlnode. */ wlmtk_element_t *wlmtk_element_from_dlnode( bs_dllist_node_t *dlnode_ptr); /** * Sets the parent container for the element. * * Will call @ref wlmtk_element_attach_to_scene_graph to align the scene graph * with the new (or deleted) parent. * * Private: Should only be called by wlmtk_container_add_element, respectively * wlmtk_container_remove_element ("friends"). * * @param element_ptr * @param parent_container_ptr Pointer to the parent container, or NULL if * the parent should be cleared. */ void wlmtk_element_set_parent_container( wlmtk_element_t *element_ptr, wlmtk_container_t *parent_container_ptr); /** * Attaches or detaches the element to the parent's wlroots scene tree. * * If the element has a parent, and that parent is itself attached to the * wlroots scene tree, this will either re-parent an already existing node, * or invoke @ref wlmtk_element_vmt_t::create_scene_node to create and attach a * new node to the paren'ts tree. * Otherwise, it will clear any existing node. * * The function is idempotent. * * Private: Should only called by wlmtk_container_t methods, when there are * changes to wlmtk_container_t::wlr_scene_tree. * * @param element_ptr */ void wlmtk_element_attach_to_scene_graph( wlmtk_element_t *element_ptr); /** * Sets the element's visibility. * * @param element_ptr * @param visible */ void wlmtk_element_set_visible(wlmtk_element_t *element_ptr, bool visible); /** Calls @ref wlmtk_container_invalidate_layout on the parent container. */ void wlmtk_element_invalidate_parent_layout(wlmtk_element_t *element_ptr); /** * Returns the position of the element. * * @param element_ptr * @param x_ptr Optional, may be NULL. * @param y_ptr Optional, may be NULL. */ void wlmtk_element_get_position( wlmtk_element_t *element_ptr, int *x_ptr, int *y_ptr); /** * Sets the position of the element. * * @param element_ptr * @param x * @param y */ void wlmtk_element_set_position( wlmtk_element_t *element_ptr, int x, int y); /** * Gets the dimensions of the element in pixels, relative to the position. * * @param element_ptr * @param left_ptr Leftmost position. May be NULL. * @param top_ptr Topmost position. May be NULL. * @param right_ptr Rightmost position. Ma be NULL. * @param bottom_ptr Bottommost position. May be NULL. */ static inline void wlmtk_element_get_dimensions( wlmtk_element_t *element_ptr, int *left_ptr, int *top_ptr, int *right_ptr, int *bottom_ptr) { element_ptr->vmt.get_dimensions( element_ptr, left_ptr, top_ptr, right_ptr, bottom_ptr); } /** * Gets the element's dimensions in pixel as wlr_box, relative to the position. * * @param element_ptr * * @return A struct wlr_box that specifies the top-left corner of the element * relative to it's position, and the element's total width and height. */ static inline struct wlr_box wlmtk_element_get_dimensions_box( wlmtk_element_t *element_ptr) { struct wlr_box box; element_ptr->vmt.get_dimensions( element_ptr, &box.x, &box.y, &box.width, &box.height); box.width -= box.x; box.height -= box.y; return box; } /** * Moves the pointer to the new position. * * Calls @ref wlmtk_element_vmt_t::pointer_accepts_motion, if the element is * visible. Triggers @ref wlmtk_element_events_t::pointer_enter or * @ref wlmtk_element_events_t::pointer_leave if the motion is accepted. * * @return true if the element is visible and the motion is within the * element's "pointer focus area", ie. in the area it accepts pointer events. */ bool wlmtk_element_pointer_motion( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *pointer_motion_ptr); /** * Processes the button event. Calls @ref wlmtk_element_vmt_t::pointer_button. * * @param element_ptr * @param button_event_ptr * * @return true if the button event was consumed. */ bool wlmtk_element_pointer_button( wlmtk_element_t *element_ptr, const wlmtk_button_event_t *button_event_ptr); /** * Processes the axis event. Calls @ref wlmtk_element_vmt_t::pointer_axis. * * @param element_ptr * @param wlr_pointer_axis_event_ptr * * @return true if the axis event was consumed. */ bool wlmtk_element_pointer_axis( wlmtk_element_t *element_ptr, struct wlr_pointer_axis_event *wlr_pointer_axis_event_ptr); /** * Sets pointer focus for the element. * * If not focussed already: Request pointer focus from the parent through * @ref wlmtk_container_request_pointer_focus. If that succeeds, will * raise @ref wlmtk_element_events_t::pointer_enter. * * @param element_ptr * @param motion_event_ptr * * @return true if the element obtained focus. */ bool wlmtk_element_pointer_focus( wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr); /** * Informs the element that it does not (or no longer) have pointer focus. * * Raises @ref wlmtk_element_events_t::pointer_leave, if it had pointer focus, * and calls @ref wlmtk_element_pointer_blur on the parent (if available). * * @param element_ptr */ void wlmtk_element_pointer_blur(wlmtk_element_t *element_ptr); /** * Cancels pointer grab. Calls @ref wlmtk_element_vmt_t::pointer_grab_cancel. * * @param element_ptr */ void wlmtk_element_pointer_grab_cancel(wlmtk_element_t *element_ptr); /** Calls @ref wlmtk_element_vmt_t::keyboard_blur. */ static inline void wlmtk_element_keyboard_blur( wlmtk_element_t *element_ptr) { element_ptr->vmt.keyboard_blur(element_ptr); } /** Calls @ref wlmtk_element_vmt_t::keyboard_event. */ static inline bool wlmtk_element_keyboard_event( wlmtk_element_t *element_ptr, struct wlr_keyboard_key_event *wlr_keyboard_key_event_ptr) { return element_ptr->vmt.keyboard_event( element_ptr, wlr_keyboard_key_event_ptr); } /** Calls @ref wlmtk_element_vmt_t::keyboard_sym. */ static inline bool wlmtk_element_keyboard_sym( wlmtk_element_t *element_ptr, xkb_keysym_t keysym, enum xkb_key_direction direction, uint32_t modifiers) { return element_ptr->vmt.keyboard_sym( element_ptr, keysym, direction, modifiers); } /** Calls @ref wlmtk_element_vmt_t::layout, if given. */ static inline void wlmtk_element_layout(wlmtk_element_t *element_ptr) { element_ptr->vmt.layout(element_ptr); } /** * Virtual method: Calls the dtor of the element's implementation. * * The implementation is required to call @ref wlmtk_element_fini(). * * @param element_ptr */ static inline void wlmtk_element_destroy( wlmtk_element_t *element_ptr) { element_ptr->vmt.destroy(element_ptr); } /** Unit tests for the element. */ extern const bs_test_set_t wlmtk_element_test_set; /** Fake element, useful for unit test. */ typedef struct { /** State of the element. */ wlmtk_element_t element; /** Original VMT. */ wlmtk_element_vmt_t orig_vmt; /** Dimensions of the fake element, in pixels. */ struct wlr_box dimensions; /** @ref wlmtk_element_vmt_t::pointer_accepts_motion() was called. */ bool pointer_accepts_motion_called; /** Indicates @ref wlmtk_element_vmt_t::pointer_button() was called. */ bool pointer_button_called; /** Last button event received. */ wlmtk_button_event_t pointer_button_event; /** Indicates @ref wlmtk_element_vmt_t::pointer_axis() was called. */ bool pointer_axis_called; /** Indicates @ref wlmtk_element_vmt_t::pointer_grab_cancel() was called. */ bool pointer_grab_cancel_called; /** Whether the fake element has keyboare focus. */ bool has_keyboard_focus; /** Indicates that @ref wlmtk_element_vmt_t::keyboard_event() was called. */ bool keyboard_event_called; /** Indicates that @ref wlmtk_element_vmt_t::keyboard_sym() was called. */ bool keyboard_sym_called; /** Indicates that @ref wlmtk_element_vmt_t::layout() was called. */ bool layout_called; /** Last axis event received. */ struct wlr_pointer_axis_event wlr_pointer_axis_event; } wlmtk_fake_element_t; /** * Ctor for the fake element, useful for tests. * * @return A pointer to @ref wlmtk_fake_element_t. Should be destroyed via * @ref wlmtk_element_destroy, by passing the pointer to * @ref wlmtk_fake_element_t::element as argument. */ wlmtk_fake_element_t *wlmtk_fake_element_create(void); /** * Updates @ref wlmtk_fake_element_t::dimensions and propagates the layout * changes to the parent (if set). * * @param fake_element_ptr * @param width * @param height */ void wlmtk_fake_element_set_dimensions( wlmtk_fake_element_t *fake_element_ptr, int width, int height); /** * Sets @ref wlmtk_fake_element_t::has_keyboard_focus and calls @ref * wlmtk_container_set_keyboard_focus_element for the parent (if set). * * @param fake_element_ptr */ void wlmtk_fake_element_grab_keyboard(wlmtk_fake_element_t *fake_element_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_ELEMENT_H__ */ /* == End of element.h ===================================================== */ wlmaker-0.8.1/include/toolkit/style.h0000644000175100017510000001325215222270710017212 0ustar runnerrunner/* ========================================================================= */ /** * @file style.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_STYLE_H__ #define __WLMTK_STYLE_H__ #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Specifies the type of coloring to use for the fill. */ typedef enum { /** Horizontal color gradient. */ WLMTK_STYLE_COLOR_SOLID, /** Horizontal color gradient. */ WLMTK_STYLE_COLOR_HGRADIENT, /** Vertical color gradient. */ WLMTK_STYLE_COLOR_VGRADIENT, /** * Diagonal color gradient, Cairo style. * * Colors are interpolated from top-left to bottom-right corner. Areas of * equal color value are arranged perpendicular to that diagonal. * This produces a smooth color flow across all rectangle edges. */ WLMTK_STYLE_COLOR_DGRADIENT, /** * Alternative diagonal color gradient, Window Maker style. * * Colors are interpolated from top-left to bottom-right corner. Areas of * equal color value are aligned with the other diagonal -- from top-right * to bottom-left. * This may produce a steep gradient along the thin axis of long & thin * rectangles, but is similar to what Window Maker uses. */ WLMTK_STYLE_COLOR_ADGRADIENT, } wlmtk_style_fill_type_t; /** Specifies the color for a solid fill. */ typedef struct { /** Color to start from, as ARGB 8888. Left, for the HGRADIENT type. */ uint32_t color; } wlmtk_style_color_solid_data_t; /** Specifies the two colors to span a gradient between. */ typedef struct { /** Color to start from, as ARGB 8888. Left, for the HGRADIENT type. */ uint32_t from; /** Color to end with, as ARGB 8888. Right, for the HGRADIENT type. */ uint32_t to; } wlmtk_style_color_gradient_data_t; /** Specification for the fill of the titlebar. */ typedef struct { /** The type of fill to apply. */ wlmtk_style_fill_type_t type; /** Parameters for the fill. */ union { /** Solid color. */ wlmtk_style_color_solid_data_t solid; /** Horizontal color gradient. */ wlmtk_style_color_gradient_data_t hgradient; /** Vertical color gradient. */ wlmtk_style_color_gradient_data_t vgradient; /** Diagonal color gradient. */ wlmtk_style_color_gradient_data_t dgradient; /** Alternative diagonal color gradient. */ wlmtk_style_color_gradient_data_t adgradient; } param; } wlmtk_style_fill_t; /** Hardcoded max length of the font face name. */ #define WLMTK_STYLE_FONT_FACE_LENGTH 128 /** Weight of a font. */ typedef enum { WLMTK_FONT_WEIGHT_NORMAL, WLMTK_FONT_WEIGHT_BOLD, } wlmtk_style_font_weight_t; /** Style of font. */ typedef struct { /** Font face family name. */ char face[WLMTK_STYLE_FONT_FACE_LENGTH]; /** Weight. */ wlmtk_style_font_weight_t weight; /** Size of the font, in pixels per "em" square side. */ uint64_t size; } wlmtk_style_font_t; /** Specifies color and width of a margin. */ struct wlmtk_margin_style { /** Width of the margin. */ uint64_t width; /** Color of the margin. */ uint32_t color; }; /** Style options for the titlebar. */ struct wlmtk_titlebar_style { /** Fill style for when the titlebar is focussed (activated). */ wlmtk_style_fill_t focussed_fill; /** Fill style for when the titlebar is blurred (not activated). */ wlmtk_style_fill_t blurred_fill; /** Color of the title text when focussed. */ uint32_t focussed_text_color; /** Color of the title text when blurred. */ uint32_t blurred_text_color; /** Height of the title bar, in pixels. */ uint64_t height; /** Width of the bezel. */ uint64_t bezel_width; /** Style of the margin within the resizebar. */ struct wlmtk_margin_style margin; /** Font style for the titlebar's title. */ wlmtk_style_font_t font; }; /** Translates the font weight from toolkit into cairo enum. */ cairo_font_weight_t wlmtk_style_font_weight_cairo_from_wlmtk( wlmtk_style_font_weight_t weight); /** * Custom decoder for fill style struct from a plist dict. * * @param object_ptr * @param desc_value_ptr * @param value_ptr * * @return true on success. */ bool wlmtk_style_decode_fill( bspl_object_t *object_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Plist decoding descriptor of a margin's style. */ extern const bspl_desc_t wlmtk_style_margin_desc[]; /** Plist decoding descriptor for decoding "Font" sections. */ extern const bspl_desc_t wlmtk_style_font_desc[]; /** Unit test cases. */ extern const bs_test_set_t wlmtk_style_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_STYLE_H__ */ /* == End of style.h ================================================== */ wlmaker-0.8.1/include/toolkit/box.h0000644000175100017510000000662015222270710016643 0ustar runnerrunner/* ========================================================================= */ /** * @file box.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_BOX_H__ #define __WLMTK_BOX_H__ #include #include "libbase/libbase.h" #include "container.h" // IWYU pragma: keep #include "element.h" #include "style.h" /** Forward declaration: Box. */ typedef struct _wlmtk_box_t wlmtk_box_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Orientation of the box. */ typedef enum { /** Horizontal box layout. The container's "front" is on the left. */ WLMTK_BOX_HORIZONTAL, /** Vertical box layout. The container's "front" is the top. */ WLMTK_BOX_VERTICAL, } wlmtk_box_orientation_t; /** State of the box. */ struct _wlmtk_box_t { /** Super class of the box. */ wlmtk_container_t super_container; /** Virtual method table of the superclass' element. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Orientation of the box. */ wlmtk_box_orientation_t orientation; /** Container for the box's elements. */ wlmtk_container_t element_container; /** Container for margin elements. */ wlmtk_container_t margin_container; /** Margin style. */ struct wlmtk_margin_style style; }; /** * Initializes the box with the provided virtual method table. * * @param box_ptr * @param orientation * @param style_ptr * * @return true on success. */ bool wlmtk_box_init( wlmtk_box_t *box_ptr, wlmtk_box_orientation_t orientation, const struct wlmtk_margin_style *style_ptr); /** * Un-initializes the box. * * @param box_ptr */ void wlmtk_box_fini(wlmtk_box_t *box_ptr); /** * Adds `element_ptr` to the front of the box. * * @param box_ptr * @param element_ptr */ void wlmtk_box_add_element_front(wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr); /** * Adds `element_ptr` to the back of the box. * * @param box_ptr * @param element_ptr */ void wlmtk_box_add_element_back(wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr); /** * Removes `element_ptr` from the box. * * Requires that element_ptr is an element of the box. * * @param box_ptr * @param element_ptr */ void wlmtk_box_remove_element(wlmtk_box_t *box_ptr, wlmtk_element_t *element_ptr); /** @return Pointer to the superclass' @ref wlmtk_element_t of `box_ptr`. */ wlmtk_element_t *wlmtk_box_element(wlmtk_box_t *box_ptr); /** Updates the box style and updates the layout of the contained elements. */ void wlmtk_box_set_style( wlmtk_box_t *box_ptr, const struct wlmtk_margin_style *style_ptr); /** Unit tests. */ extern const bs_test_set_t wlmtk_box_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_BOX_H__ */ /* == End of box.h ========================================================= */ wlmaker-0.8.1/include/toolkit/titlebar_title.h0000644000175100017510000000563715222270710021071 0ustar runnerrunner/* ========================================================================= */ /** * @file titlebar_title.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_TITLEBAR_TITLE_H__ #define __WLMTK_TITLEBAR_TITLE_H__ /** Forward declaration. */ typedef struct _wlmtk_titlebar_title_t wlmtk_titlebar_title_t; struct wlmtk_titlebar_style; #include #include #include "element.h" #include "window.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a title bar title. * * @param window_ptr * * @return Title handle. */ wlmtk_titlebar_title_t *wlmtk_titlebar_title_create( wlmtk_window_t *window_ptr); /** * Destroys the titlebar title. * * @param titlebar_title_ptr */ void wlmtk_titlebar_title_destroy( wlmtk_titlebar_title_t *titlebar_title_ptr); /** * Redraws the title section of the title bar. * * @param titlebar_title_ptr * @param focussed_gfxbuf_ptr Titlebar background when focussed. * @param blurred_gfxbuf_ptr Titlebar background when blurred. * @param position Position of title telative to titlebar. * @param width Width of title. * @param activated Whether the title bar should start focussed. * @param title_ptr Title, or NULL. * @param style_ptr * * @return true on success. */ bool wlmtk_titlebar_title_redraw( wlmtk_titlebar_title_t *titlebar_title_ptr, bs_gfxbuf_t *focussed_gfxbuf_ptr, bs_gfxbuf_t *blurred_gfxbuf_ptr, int position, int width, bool activated, const char *title_ptr, const struct wlmtk_titlebar_style *style_ptr); /** * Sets activation status of the titlebar's title. * * @param titlebar_title_ptr * @param activated */ void wlmtk_titlebar_title_set_activated( wlmtk_titlebar_title_t *titlebar_title_ptr, bool activated); /** * Returns the superclass @ref wlmtk_element_t for the titlebar title. * * @param titlebar_title_ptr * * @return Pointer to the super element. */ wlmtk_element_t *wlmtk_titlebar_title_element( wlmtk_titlebar_title_t *titlebar_title_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_titlebar_title_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_TITLEBAR_TITLE_H__ */ /* == End of titlebar_title.h ============================================== */ wlmaker-0.8.1/include/toolkit/rectangle.h0000644000175100017510000000504215222270710020014 0ustar runnerrunner/* ========================================================================= */ /** * @file rectangle.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_RECTANGLE_H__ #define __WLMTK_RECTANGLE_H__ #include #include #include "element.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: Rectangle state. */ typedef struct _wlmtk_rectangle_t wlmtk_rectangle_t; /** * Creates a rectangle. Useful for margins and borders. * * @param width * @param height * @param color * * @return Pointer to the rectangle state or NULL on error. */ wlmtk_rectangle_t *wlmtk_rectangle_create( int width, int height, uint32_t color); /** * Destroys the rectangle. * * @param rectangle_ptr */ void wlmtk_rectangle_destroy(wlmtk_rectangle_t *rectangle_ptr); /** * Sets (or updates) the size of the rectangle. * * @param rectangle_ptr * @param width * @param height */ void wlmtk_rectangle_set_size( wlmtk_rectangle_t *rectangle_ptr, int width, int height); /** * Sets (or updates) the color of the rectangle. * * @param rectangle_ptr * @param color */ void wlmtk_rectangle_set_color( wlmtk_rectangle_t *rectangle_ptr, uint32_t color); /** Returns the superclass @ref wlmtk_element_t of the rectangle. */ wlmtk_element_t *wlmtk_rectangle_element(wlmtk_rectangle_t *rectangle_ptr); /** * Gets the @ref wlmtk_rectangle_t instance from it's element superclass. * * Requires `element_ptr` as pointer to @ref wlmtk_rectangle_t::super_element. * * @param element_ptr * * @return The pointer to the @ref wlmtk_rectangle_t instance. */ wlmtk_rectangle_t *wlmtk_rectangle_from_element(wlmtk_element_t *element_ptr); /** Unit tests. */ extern const bs_test_set_t wlmtk_rectangle_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_RECTANGLE_H__ */ /* == End of rectangle.h =================================================== */ wlmaker-0.8.1/include/toolkit/titlebar_button.h0000644000175100017510000000600315222270710021247 0ustar runnerrunner/* ========================================================================= */ /** * @file titlebar_button.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_TITLEBAR_BUTTON_H__ #define __WLMTK_TITLEBAR_BUTTON_H__ #include #include #include #include #include "element.h" #include "window.h" /** Forward declaration. */ typedef struct _wlmtk_titlebar_button_t wlmtk_titlebar_button_t; struct wlmtk_titlebar_style; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Function pointer to method for drawing the button contents. */ typedef void (*wlmtk_titlebar_button_draw_t)( cairo_t *cairo_ptr, int siye, uint32_t color); /** * Creates a button for the titlebar. * * @param click_handler * @param window_ptr * @param draw * * @return Pointer to the titlebar button, or NULL on error. */ wlmtk_titlebar_button_t *wlmtk_titlebar_button_create( void (*click_handler)(wlmtk_window_t *window_ptr), wlmtk_window_t *window_ptr, wlmtk_titlebar_button_draw_t draw); /** * Destroys the titlebar button. * * @param titlebar_button_ptr */ void wlmtk_titlebar_button_destroy( wlmtk_titlebar_button_t *titlebar_button_ptr); /** * Sets the activation status (focussed / blurred) of the titlebar button. * * @param titlebar_button_ptr * @param activated */ void wlmtk_titlebar_button_set_activated( wlmtk_titlebar_button_t *titlebar_button_ptr, bool activated); /** * Redraws the titlebar button for given textures, position and style. * * @param titlebar_button_ptr * @param focussed_gfxbuf_ptr * @param blurred_gfxbuf_ptr * @param position * @param style_ptr * * @return true on success. */ bool wlmtk_titlebar_button_redraw( wlmtk_titlebar_button_t *titlebar_button_ptr, bs_gfxbuf_t *focussed_gfxbuf_ptr, bs_gfxbuf_t *blurred_gfxbuf_ptr, int position, const struct wlmtk_titlebar_style *style_ptr); /** * Returns the titlebar button's super element. * * @param titlebar_button_ptr * * @return Pointer to the superclass @ref wlmtk_element_t. */ wlmtk_element_t *wlmtk_titlebar_button_element( wlmtk_titlebar_button_t *titlebar_button_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_titlebar_button_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_TITLEBAR_BUTTON_H__ */ /* == End of titlebar_button.h ============================================= */ wlmaker-0.8.1/include/toolkit/window.h0000644000175100017510000003466215222270710017371 0ustar runnerrunner/* ========================================================================= */ /** * @file window.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_WINDOW2_H__ #define __WLMTK_WINDOW2_H__ #include #include #include #include #include struct wlr_output; /** Forward declaration: Window. */ typedef struct _wlmtk_window_t wlmtk_window_t; /** Reference to the window's style. */ typedef struct _wlmtk_window_style_ref_t wlmtk_window_style_ref_t; #include "element.h" #include "menu.h" #include "resizebar.h" // IWYU pragma: keep #include "style.h" #include "util.h" #include "workspace.h" // IWYU pragma: keep #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Signals available for the @ref wlmtk_window_t class. */ typedef struct { /** * Signals that the window state (maximize, iconify, ...) changed. * * Window state can be retrieved from: * - @ref wlmtk_window_is_maximized * - @ref wlmtk_window_is_fullscreen * - @ref wlmtk_window_is_shaded * * The signal is also raised when the window's workspace is changed. * Retrieve through @ref wlmtk_window_get_workspace. * * data_ptr points to the window state (@ref wlmtk_window_t). */ struct wl_signal state_changed; /** Signals that @ref wlmtk_window_t::activated changed. */ struct wl_signal set_activated; /** * Signals that the window was requested to be closed. * * Applies only to windows with @ref WLMTK_WINDOW_PROPERTY_CLOSABLE. */ struct wl_signal request_close; /** * Signals that the window's size is requested to change. * * Takes a `struct wlr_box*` as argument. */ struct wl_signal request_size; /** * Signals that the window is desiring to switch to fullscreen. * * Takes a `bool` as argument, specifying whether to enable fullscreen. */ struct wl_signal request_fullscreen; /** * Signals that the window is desiring to switch to maximized. * * Takes a `bool` as argument, specifying whether to enable maximized. */ struct wl_signal request_maximized; } wlmtk_window_events_t; /** Style options for the window. */ struct wlmtk_window_style { /** The titlebar's style. */ struct wlmtk_titlebar_style titlebar; /** The resizebar's style. */ struct wlmtk_resizebar_style resizebar; /** Style of the window border. */ struct wlmtk_margin_style border; /** Style of the margins between titlebar, window and resizebar. */ struct wlmtk_margin_style margin; }; /** Window properties. */ typedef enum { /** Can be resized. Server-side decorations will show resize-bar. */ WLMTK_WINDOW_PROPERTY_RESIZABLE = UINT32_C(1) << 0, /** Can be iconified. Server-side decorations include icnonify button. */ WLMTK_WINDOW_PROPERTY_ICONIFIABLE = UINT32_C(1) << 1, /** Can be closed. Server-side decorations include close button. */ WLMTK_WINDOW_PROPERTY_CLOSABLE = UINT32_C(1) << 2, /** * Kludge: a window that closes on right-click-release. * The window's element must pointer_grab. * TODO(kaeser@gubbe.ch): This should be... better. */ WLMTK_WINDOW_PROPERTY_RIGHTCLICK = UINT32_C(1) << 3 } wlmtk_window_property_t; /** * Creates a window. * * @param content_element_ptr * @param style_ref_ptr * @param menu_style_ref_ptr * * @return The window handle, or NULL on error. */ wlmtk_window_t *wlmtk_window_create( wlmtk_element_t *content_element_ptr, wlmtk_window_style_ref_t *style_ref_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr); /** * Destroys the window. * * @param window_ptr */ void wlmtk_window_destroy(wlmtk_window_t *window_ptr); /** * Gets the set of events available to a window, for binding listeners. * * @param window_ptr * * @return Pointer to this window's @ref wlmtk_window_t::events. */ wlmtk_window_events_t *wlmtk_window_events(wlmtk_window_t *window_ptr); /** * Returns the element for adding the window into a container. * * @param window_ptr * * @return Pointer to the super element of @ref wlmtk_window_t::bordered. */ wlmtk_element_t *wlmtk_window_element(wlmtk_window_t *window_ptr); /** * Returns the window, where `element_ptr` is @ref wlmtk_window_element. * * @param element_ptr * * @return the @ref wlmtk_window_t from the element pointer. */ wlmtk_window_t *wlmtk_window_from_element(wlmtk_element_t *element_ptr); /** * Returns the bounding box for the window. * * @param window_ptr * * @return the box. */ struct wlr_box wlmtk_window_get_bounding_box(wlmtk_window_t *window_ptr); /** Helper: Indicates that the position has changed. */ void wlmtk_window_position_changed(wlmtk_window_t *window_ptr); /** * Sets properties for the window. * * @param window_ptr * @param properties @see wlmtk_window_property_t. */ void wlmtk_window_set_properties( wlmtk_window_t *window_ptr, uint32_t properties); /** * Sets client information for the window. * * @param window_ptr * @param client_ptr The client's information will be copied into * @ref wlmtk_window_t::client, and does not need * to outlive this call. */ void wlmtk_window_set_client( wlmtk_window_t *window_ptr, const wlmtk_util_client_t *client_ptr); /** * Returns a pointer to @ref wlmtk_window_t::client. * * @param window_ptr * * @return Client information. Remains walid until `window_ptr` is destroyed. */ const wlmtk_util_client_t *wlmtk_window_get_client_ptr( wlmtk_window_t *window_ptr); /** * Sets the WLR output for the window. Used for fullscreen requests. * * See also @ref wlmtk_window_get_wlr_output. * * @param window_ptr * @param wlr_output_ptr Output to consider when requesting a window as * fullscreen. Can be NULL to indicate no preference. */ void wlmtk_window_set_preferred_wlr_output( wlmtk_window_t *window_ptr, struct wlr_output *wlr_output_ptr); /** * Gets the struct wlr_output that the window is on, or it prefers. * * * @param window_ptr * * @return The preferred output, if @ref wlmtk_window_set_preferred_wlr_output * was called with `wlr_output_ptr` other than NULL. Otherwise, returns a * pointer to the `struct wlr_output` the window is currently placed on, or * NULL if the window is not mapped or there are no outputs. */ struct wlr_output *wlmtk_window_get_wlr_output( wlmtk_window_t *window_ptr); /** * Sets the title for the window. * * If `title_ptr` is NULL, a generic name is set. * * @param window_ptr * @param title_ptr May be NULL. * * @return true on success. */ bool wlmtk_window_set_title( wlmtk_window_t *window_ptr, const char *title_ptr); /** * Returns the title of the window. * * @param window_ptr * * @returns Pointer to the window title. Will remain valid until the next call * to @ref wlmtk_window_set_title, or until the window is destroyed. Will * never be NULL. */ const char *wlmtk_window_get_title(wlmtk_window_t *window_ptr); /** * Sets the window as activated, depending on the argument's value. * * An activated window will have keyboard focus and would have distinct * decorations to indicate state. * * @param window_ptr * @param activated */ void wlmtk_window_set_activated( wlmtk_window_t *window_ptr, bool activated); /** @return whether the window is currently activated. */ bool wlmtk_window_is_activated(wlmtk_window_t *window_ptr); /** * Requests the window's size to be updated. * * @param window_ptr * @param box_ptr Only the `width` and `height` are considered. */ void wlmtk_window_request_size( wlmtk_window_t *window_ptr, const struct wlr_box *box_ptr); /** * Sets the window's committed size. Should be called when the content size * is changed, eg. on surface commit. * * @param window_ptr * @param width * @param height */ void wlmtk_window_commit_size( wlmtk_window_t *window_ptr, int width, int height); /** * Returns the window's (committed) size. That may be different than the * dimensions of the window's @ref wlmtk_window_t::content_element_ptr. * * @param window_ptr * * @return The size box. Only `width` and `height` will be used. */ struct wlr_box wlmtk_window_get_size(wlmtk_window_t *window_ptr); /** * Sets the resizing edges. Keeps the opposite edges firm when resizing. * * @param window_ptr * @param edges */ void wlmtk_window_set_resize_edges( wlmtk_window_t *window_ptr, uint32_t edges); /** @return resizing edges. */ uint32_t wlmtk_window_get_resize_edges(wlmtk_window_t *window_ptr); /** * Requests to close the window. * * @param window_ptr */ void wlmtk_window_request_close(wlmtk_window_t *window_ptr); /** * Requests to minimize (iconify) the window. * * @param window_ptr */ void wlmtk_window_request_minimize(wlmtk_window_t *window_ptr); /** * Requests the window to be fullscreen (or end fullscreen). * * @param window_ptr * @param fullscreen */ void wlmtk_window_request_fullscreen(wlmtk_window_t *window_ptr, bool fullscreen); /** @return whether the window currently is in fullscreen mode. */ bool wlmtk_window_is_fullscreen(wlmtk_window_t *window_ptr); /** * Commits the window as fullscreen: Client has comitted the surface in * fullscreen state. * * @param window_ptr * @param fullscreen */ void wlmtk_window_commit_fullscreen( wlmtk_window_t *window_ptr, bool fullscreen); /** * Requests the window to be maximized (or end maximized). * * Guarantees to emit a @ref wlmtk_window_events_t::request_maximized signal, * either with the provided `maximized` value, or with what the compositor * deems acceptable. * * @param window_ptr * @param maximized */ void wlmtk_window_request_maximized( wlmtk_window_t *window_ptr, bool maximized); /** @return whether the window currently is in maximized mode. */ bool wlmtk_window_is_maximized(wlmtk_window_t *window_ptr); /** * Commits the `maximized` mode for the window. * * The call is idempotent: Once the window is committed, further calls with * the same `maximized` value will return straight away. * * @param window_ptr * @param maximized */ void wlmtk_window_commit_maximized( wlmtk_window_t *window_ptr, bool maximized); /** * Requests the window to be shaded (rolled up) or not. * * @param window_ptr * @param shaded */ void wlmtk_window_request_shaded(wlmtk_window_t *window_ptr, bool shaded); /** @return whether the window currently is shaded. */ bool wlmtk_window_is_shaded(wlmtk_window_t *window_ptr); /** * En-/Disables the window menu. * * @param window_ptr * @param enabled */ void wlmtk_window_menu_set_enabled(wlmtk_window_t *window_ptr, bool enabled); /** * Returns a pointer to the window menu's state. * * @param window_ptr * * @return A pointer to the @ref wlmtk_menu_t of the window menu. */ wlmtk_menu_t *wlmtk_window_menu(wlmtk_window_t *window_ptr); /** * Sets whether to have server-side decorations for this window. * * @param window_ptr * @param decorated */ void wlmtk_window_set_server_side_decorated( wlmtk_window_t *window_ptr, bool decorated); /** * Sets window options (currently: the modifier to move when dragging). * * @param window_ptr * @param move_modifier */ void wlmtk_window_set_options( wlmtk_window_t *window_ptr, uint32_t move_modifier); /** * Sets @ref wlmtk_window_t::workspace_ptr. * * Protected method, to be called only from @ref wlmtk_workspace_t. * * @param window_ptr * @param workspace_ptr */ void wlmtk_window_set_workspace( wlmtk_window_t *window_ptr, wlmtk_workspace_t *workspace_ptr); /** @return The value of @ref wlmtk_window_t::workspace_ptr. */ wlmtk_workspace_t *wlmtk_window_get_workspace(wlmtk_window_t *window_ptr); /** @return pointer to @ref wlmtk_window_t::dlnode. */ bs_dllist_node_t *wlmtk_dlnode_from_window(wlmtk_window_t *window_ptr); /** @return the @ref wlmtk_window_t for @ref wlmtk_window_t::dlnode. */ wlmtk_window_t *wlmtk_window_from_dlnode(bs_dllist_node_t *dlnode_ptr); /** * Applies the style held by the reference. * * @param window_ptr * @param style_ref_ptr * @param menu_style_ref_ptr * * @return true on success. */ bool wlmtk_window_set_style( wlmtk_window_t *window_ptr, wlmtk_window_style_ref_t *style_ref_ptr, wlmtk_menu_style_ref_t *menu_style_ref_ptr); /** Creates a holder for the window style, with initialized reference. * * @return Pointer to @ref wlmtk_window_style. To destroy, call * @ref wlmtk_window_style_ref_release on @ref wlmtk_window_style_to_ref. */ struct wlmtk_window_style *wlmtk_window_style_create(void); /** @return the @ref wlmtk_window_style_ref_t for the style. */ wlmtk_window_style_ref_t *wlmtk_window_style_to_ref( struct wlmtk_window_style *window_style_ptr); /** Retains a reference. @return a read-only style. */ const struct wlmtk_window_style *wlmtk_window_style_ref_retain( wlmtk_window_style_ref_t *ref_ptr); /** Releases a reference. */ void wlmtk_window_style_ref_release(wlmtk_window_style_ref_t *ref_ptr); /** Decodes dict at `object_ptr` into @ref wlmtk_window_style at `value_ptr` */ bool wlmtk_window_style_decode( bspl_object_t *object_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Initializer. */ bool wlmtk_window_style_decode_init(void *dst_ptr); /** Fini. */ void wlmtk_window_style_decode_fini(void *dst_ptr); /** Window unit test cases. */ extern const bs_test_set_t wlmtk_window_test_set; /** * Creates a window, with default styles, for testing. * * @param content_element_ptr * * @return See @ref wlmtk_window_create. */ wlmtk_window_t *wlmtk_test_window_create(wlmtk_element_t *content_element_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_WINDOW2_H__ */ /* == End of window.h ===================================================== */ wlmaker-0.8.1/include/toolkit/input.h0000644000175100017510000000674715222270710017224 0ustar runnerrunner/* ========================================================================= */ /** * @file input.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_INPUT_H__ #define __WLMTK_INPUT_H__ #include struct wlr_cursor; struct wlr_xcursor_manager; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: Button event. */ typedef struct _wlmtk_button_event_t wlmtk_button_event_t; /** Forward declaration: Pointer. */ typedef struct _wlmtk_pointer_t wlmtk_pointer_t; /** Button state. */ typedef enum { WLMTK_BUTTON_DOWN, WLMTK_BUTTON_UP, WLMTK_BUTTON_CLICK, WLMTK_BUTTON_DOUBLE_CLICK, } wlmtk_button_event_type_t; /** Cursor types. */ typedef enum { /** Default. */ WLMTK_POINTER_CURSOR_DEFAULT = 0, /** Resizing, southern border. */ WLMTK_POINTER_CURSOR_RESIZE_S, /** Resizing, south-eastern corner. */ WLMTK_POINTER_CURSOR_RESIZE_SE, /** Resizing, south-western corner. */ WLMTK_POINTER_CURSOR_RESIZE_SW, /** Sentinel: Maximum value. */ WLMTK_POINTER_CURSOR_MAX, } wlmtk_pointer_cursor_t; /** Button events. */ struct _wlmtk_button_event_t { /** Button for which the event applies: linux/input-event-codes.h */ uint32_t button; /** Type of the event: DOWN, UP, ... */ wlmtk_button_event_type_t type; /** Time of the button event, in milliseconds. */ uint32_t time_msec; /** Modifiers that were active when the button event happened. */ uint32_t keyboard_modifiers; }; /** Motion event. */ typedef struct { /** X position. */ double x; /** Y position. */ double y; /** Time of the motion event, in milliseconds. */ uint32_t time_msec; /** Pointer that's associated with this motion event. */ wlmtk_pointer_t *pointer_ptr; } wlmtk_pointer_motion_event_t; /** * Creates the pointer handler. * * @param wlr_cursor_ptr * @param wlr_xcursor_manager_ptr * * @return A @ref wlmtk_pointer_t or NULL on error. */ wlmtk_pointer_t *wlmtk_pointer_create( struct wlr_cursor *wlr_cursor_ptr, struct wlr_xcursor_manager *wlr_xcursor_manager_ptr); /** * Destroys the pointer handler. * * @param pointer_ptr */ void wlmtk_pointer_destroy(wlmtk_pointer_t *pointer_ptr); /** * Updates the XCursor manager, and with that: The cursor theme. * * @param pointer_ptr * @param wlr_xcursor_manager_ptr */ void wlmtk_pointer_set_xcursor_manager( wlmtk_pointer_t *pointer_ptr, struct wlr_xcursor_manager *wlr_xcursor_manager_ptr); /** * Sets the cursor for the pointer. */ void wlmtk_pointer_set_cursor( wlmtk_pointer_t *pointer_ptr, wlmtk_pointer_cursor_t cursor); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_INPUT_H__ */ /* == End of input.h ======================================================= */ wlmaker-0.8.1/include/toolkit/resizebar.h0000644000175100017510000000612715222270710020043 0ustar runnerrunner/* ========================================================================= */ /** * @file resizebar.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_RESIZEBAR_H__ #define __WLMTK_RESIZEBAR_H__ /** Forward declaration: Title bar. */ typedef struct _wlmtk_resizebar_t wlmtk_resizebar_t; #include #include #include #include #include "element.h" #include "style.h" /** Style options for the resizebar. */ struct wlmtk_resizebar_style { /** Fill style for the complete resizebar. */ wlmtk_style_fill_t fill; /** Height of the resize bar. */ uint64_t height; /** Width of the corners. */ uint64_t corner_width; /** Width of the bezel. */ uint64_t bezel_width; }; #include "window.h" // IWYU pragma: keep #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates the resize bar. * * @param window_ptr * @param style_ptr Style. Must outlive the resize bar, or until next * call to @ref wlmtk_resizebar_set_style. * * @return Pointer to the resizebar state, or NULL on error. */ wlmtk_resizebar_t *wlmtk_resizebar_create( wlmtk_window_t *window_ptr, const struct wlmtk_resizebar_style *style_ptr); /** * Destroys the resize bar. * * @param resizebar_ptr */ void wlmtk_resizebar_destroy(wlmtk_resizebar_t *resizebar_ptr); /** * Sets the width of the resize bar. * * @param resizebar_ptr * @param width * * @return true on success. */ bool wlmtk_resizebar_set_width( wlmtk_resizebar_t *resizebar_ptr, unsigned width); /** * Sets the resizebar's style. * * @param resizebar_ptr * @param style_ptr Style. Must outlive the resize bar, or until next * call to @ref wlmtk_resizebar_set_style. * * @return true on success. */ bool wlmtk_resizebar_set_style( wlmtk_resizebar_t *resizebar_ptr, const struct wlmtk_resizebar_style *style_ptr); /** * Returns the super Element of the resizebar. * * @param resizebar_ptr * * @return Pointer to the element. */ wlmtk_element_t *wlmtk_resizebar_element(wlmtk_resizebar_t *resizebar_ptr); extern const bspl_desc_t wlmtk_resizebar_style_desc[]; /** Unit test cases. */ extern const bs_test_set_t wlmtk_resizebar_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_RESIZEBAR_H__ */ /* == End of resizebar.h ================================================== */ wlmaker-0.8.1/include/toolkit/bordered.h0000644000175100017510000000630115222270710017635 0ustar runnerrunner/* ========================================================================= */ /** * @file bordered.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_BORDERED_H__ #define __WLMTK_BORDERED_H__ #include #include "libbase/libbase.h" #include "element.h" #include "container.h" #include "rectangle.h" #include "style.h" /** Forward declaration: Bordered container state. */ typedef struct _wlmtk_bordered_t wlmtk_bordered_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** State of the bordered container. */ struct _wlmtk_bordered_t { /** Super class of the bordered. */ wlmtk_container_t super_container; /** Virtual method table of the super element before extending it. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Points to the element that will be enclosed by the border. */ wlmtk_element_t *element_ptr; /** Style of the border. */ struct wlmtk_margin_style style; /** Border element at the northern side. Includes east + west corners. */ wlmtk_rectangle_t *northern_border_rectangle_ptr; /** Border element at the eastern side. */ wlmtk_rectangle_t *eastern_border_rectangle_ptr; /** Border element at the southern side. Includes east + west corners. */ wlmtk_rectangle_t *southern_border_rectangle_ptr; /** Border element at the western side. */ wlmtk_rectangle_t *western_border_rectangle_ptr; }; /** * Initializes the bordered element. * * The bordered element positions the element within such that north-western * corner is at (0, 0). * * @param bordered_ptr * @param element_ptr * @param style_ptr * * @return true on success. */ bool wlmtk_bordered_init(wlmtk_bordered_t *bordered_ptr, wlmtk_element_t *element_ptr, const struct wlmtk_margin_style *style_ptr); /** * Un-initializes the bordered element. * * @param bordered_ptr */ void wlmtk_bordered_fini(wlmtk_bordered_t *bordered_ptr); /** * Updates the style. * * @param bordered_ptr * @param style_ptr */ void wlmtk_bordered_set_style(wlmtk_bordered_t *bordered_ptr, const struct wlmtk_margin_style *style_ptr); /** Returns the superclass @ref wlmtk_element_t for `bordered_ptr`. */ wlmtk_element_t *wlmtk_bordered_element(wlmtk_bordered_t *bordered_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_bordered_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_BORDERED_H__ */ /* == End of bordered.h ==================================================== */ wlmaker-0.8.1/include/toolkit/base.h0000644000175100017510000000557115222270710016771 0ustar runnerrunner/* ========================================================================= */ /** * @file base.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_BASE_H__ #define __WLMTK_BASE_H__ /** Forward declaration: State of a base element. */ typedef struct _wlmtk_base_t wlmtk_base_t; #include #include #include "container.h" // IWYU pragma: keep #include "element.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Base: Holds a base element with stacked & floating elements on top. */ struct _wlmtk_base_t { /** Super class of the base. Holds element and popups. */ wlmtk_container_t super_container; /** Virtual method table of the superclass' element. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Points to the element contained at the base. */ wlmtk_element_t *content_element_ptr; }; /** * Initializes the base with the given element. * * @param base_ptr * @param element_ptr is added to @ref wlmtk_base_t::super_container * until @ref wlmtk_base_fini is called. Will take * ownership. May be NULL. * * @return true on success. */ bool wlmtk_base_init( wlmtk_base_t *base_ptr, wlmtk_element_t *element_ptr); /** * Un-initializes the base. * * @param base_ptr */ void wlmtk_base_fini(wlmtk_base_t *base_ptr); /** @return Pointer to the superclass @ref wlmtk_element_t of the base. */ wlmtk_element_t *wlmtk_base_element(wlmtk_base_t *base_ptr); /** * Sets @ref wlmtk_base_t::content_element_ptr. Any earlier content element * will be destroyed. * * @param base_ptr * @param content_element_ptr */ void wlmtk_base_set_content_element( wlmtk_base_t *base_ptr, wlmtk_element_t *content_element_ptr); /** Adds a stacked element (eg. a popup) */ void wlmtk_base_push_element( wlmtk_base_t *base_ptr, wlmtk_element_t *element_ptr); /** Removes a stacked element. */ void wlmtk_base_pop_element( wlmtk_base_t *base_ptr, wlmtk_element_t *element_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_base_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_BASE_H__ */ /* == End of base.h ======================================================== */ wlmaker-0.8.1/include/toolkit/image.h0000644000175100017510000000375315222270710017141 0ustar runnerrunner/* ========================================================================= */ /** * @file image.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_IMAGE_H__ #define __WLMTK_IMAGE_H__ #include #include "element.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: State of a toolkit image. */ typedef struct _wlmtk_image_t wlmtk_image_t; /** * Creates a toolkit image: An element showing the image. * * @param image_path_ptr * * @return Pointer to the toolkit image, or NULL on error. */ wlmtk_image_t *wlmtk_image_create( const char *image_path_ptr); /** * Creates a toolkit image, scaled while preserving aspect ratio. * * @param image_path_ptr * @param width * @param height * * @return Pointer to the toolkit image or NULL on error. */ wlmtk_image_t *wlmtk_image_create_scaled( const char *image_path_ptr, int width, int height); /** * Destroys the toolkit image. * * @param image_ptr */ void wlmtk_image_destroy(wlmtk_image_t *image_ptr); /** @return the parent @ref wlmtk_element_t of `image_ptr`. */ wlmtk_element_t *wlmtk_image_element(wlmtk_image_t *image_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_image_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __IMAGE_H__ */ /* == End of image.h ================================================== */ wlmaker-0.8.1/include/toolkit/popup.h0000644000175100017510000000454615222270710017223 0ustar runnerrunner/* ========================================================================= */ /** * @file popup.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_POPUP_H__ #define __WLMTK_POPUP_H__ #include /** Forward declaration: Popup. */ typedef struct _wlmtk_popup_t wlmtk_popup_t; #include "container.h" // IWYU pragma: keep #include "element.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * State of a popup. * * A popup contains a @ref wlmtk_element_t, and may contain further popups. * These further popups will be stacked above the principal element, in order * of them being added. */ struct _wlmtk_popup_t { /** Super class of the panel. */ wlmtk_container_t super_container; /** And the popup container. Popups can contain child popups. */ wlmtk_container_t popup_container; /** The contained element. */ wlmtk_element_t *element_ptr; }; /** * Initializes the popup. * * @param popup_ptr * @param element_ptr * * @return true on success. */ bool wlmtk_popup_init( wlmtk_popup_t *popup_ptr, wlmtk_element_t *element_ptr); /** * Un-initializes the popup. Will remove it from the parent container. * * @param popup_ptr */ void wlmtk_popup_fini(wlmtk_popup_t *popup_ptr); /** Returns the base @ref wlmtk_element_t. */ wlmtk_element_t *wlmtk_popup_element(wlmtk_popup_t *popup_ptr); /** * Adds a further popup to `popup_ptr`. * * @param popup_ptr * @param further_popup_ptr */ void wlmtk_popup_add_popup(wlmtk_popup_t *popup_ptr, wlmtk_popup_t *further_popup_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_POPUP_H__ */ /* == End of popup.h ======================================================= */ wlmaker-0.8.1/include/toolkit/gfxbuf.h0000644000175100017510000000554615222270710017342 0ustar runnerrunner/* ========================================================================= */ /** * @file gfxbuf.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_GFXBUF_H__ #define __WLMTK_GFXBUF_H__ #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE struct wlr_buffer; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a wlroots buffer tied to a libbase graphics buffer. * * This creates a libbase graphics buffer, and wraps it as `struct wlr_buffer`. * * @param width * @param height * * @return A struct wlr_buffer. Must be released using wlr_buffer_drop(). */ struct wlr_buffer *bs_gfxbuf_create_wlr_buffer( unsigned width, unsigned height); /** * Drops a WLR buffer, and sets the pointer to NULL. * * @param wlr_buffer_ptr_ptr Points to a pointer to a WLR buffer. The pointer * to the WLR buffer may be NULL; in that case, the * call is a no-op. */ void wlr_buffer_drop_nullify(struct wlr_buffer **wlr_buffer_ptr_ptr); /** * Returns the libbase graphics buffer for the `struct wlr_buffer`. * * @param wlr_buffer_ptr Pointer to a `struct wlr_buffer`. Must be a * `wlr_buffer` that was previously created by * @ref bs_gfxbuf_create_wlr_buffer. * * @return Pointer to the libbase graphics buffer. The `wlr_buffer_ptr` must * outlive the `bs_gfxbuf_t*`. Must not be destroyed. */ bs_gfxbuf_t *bs_gfxbuf_from_wlr_buffer(struct wlr_buffer *wlr_buffer_ptr); /** * Returns a `cairo_t` for the WLR buffer backed by a libbase graphics buffer. * * @param wlr_buffer_ptr Pointer to a `struct wlr_buffer`. Must be a * `wlr_buffer` that was previously created by * @ref bs_gfxbuf_create_wlr_buffer. * * @return Pointer to the the cairo. The `wlr_buffer_ptr` must outlive the * `cairo_t*`. It must be destroyed using `cairo_destroy`. */ cairo_t *cairo_create_from_wlr_buffer(struct wlr_buffer *wlr_buffer_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_GFXBUF_H__ */ /* == End of gfxbuf.h ====================================================== */ wlmaker-0.8.1/include/toolkit/toolkit.h0000644000175100017510000000346715222270710017546 0ustar runnerrunner/* ========================================================================= */ /** * @file toolkit.h * * See @ref toolkit_page for documentation. * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_TOOLKIT_H__ #define __WLMTK_TOOLKIT_H__ // IWYU pragma: begin_exports #include "base.h" #include "bordered.h" #include "box.h" #include "buffer.h" #include "button.h" #include "container.h" #include "dock.h" #include "element.h" #include "fsm.h" #include "gfxbuf.h" #include "image.h" #include "input.h" #include "layer.h" #include "menu.h" #include "menu_item.h" #include "output_tracker.h" #include "panel.h" #include "popup.h" #include "primitives.h" #include "rectangle.h" #include "resizebar.h" #include "resizebar_area.h" #include "root.h" #include "style.h" #include "surface.h" #include "test.h" #include "tile.h" #include "titlebar.h" #include "titlebar_button.h" #include "titlebar_title.h" #include "util.h" #include "window.h" #include "workspace.h" // IWYU pragma: end_exports #ifdef __cplusplus extern "C" { #endif // __cplusplus #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_TOOLKIT_H__ */ /* == End of toolkit.h ===================================================== */ wlmaker-0.8.1/include/toolkit/tile.h0000644000175100017510000001301015222270710016777 0ustar runnerrunner/* ========================================================================= */ /** * @file tile.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_TILE_H__ #define __WLMTK_TILE_H__ /** Forward declaration: State of a tile. */ typedef struct _wlmtk_tile_t wlmtk_tile_t; /** Forward declaration: Tile virtual method table. */ typedef struct _wlmtk_tile_vmt_t wlmtk_tile_vmt_t; #include #include #include #include #include "buffer.h" #include "container.h" #include "element.h" #include "style.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Style options for the tile. */ struct wlmtk_tile_style { /** Fill style for the tile's background. */ wlmtk_style_fill_t fill; /** Size of the tile, in pixels. Tiles are of quadratic shape. */ uint64_t size; /** Content size of what's in the tile, in pixels. */ uint64_t content_size; /** Width of the bezel. */ uint64_t bezel_width; } ; /** Virtual method table for the tile. */ struct _wlmtk_tile_vmt_t { /** Updates the tile's content size. */ bool (*set_content_size)(wlmtk_tile_t *tile_ptr, uint64_t content_size); }; /** State of a tile. */ struct _wlmtk_tile_t { /** A tile is a container. Holds a background and contents. */ wlmtk_container_t super_container; /** The tile's virtual method table. */ wlmtk_tile_vmt_t vmt; /** Element of @ref wlmtk_dock_t::tiles. */ bs_dllist_node_t dlnode; /** The tile background is modelled as @ref wlmtk_buffer_t. */ wlmtk_buffer_t buffer; /** Style to be used for this tile. */ struct wlmtk_tile_style style; /** Holds the tile's background, used in @ref wlmtk_tile_t::buffer. */ struct wlr_buffer *background_wlr_buffer_ptr; /** References the content element from @ref wlmtk_tile_set_content. */ wlmtk_element_t *content_element_ptr; /** References the content element from @ref wlmtk_tile_set_overlay. */ wlmtk_element_t *overlay_element_ptr; }; /** * Initializes the tile. * * @param tile_ptr * @param style_ptr * * @return true on success. */ bool wlmtk_tile_init( wlmtk_tile_t *tile_ptr, const struct wlmtk_tile_style *style_ptr); /** * Extends the tile's virtual methods. * * @param tile_ptr * @param tile_vmt_ptr * * @return The previous virtual method table. */ wlmtk_tile_vmt_t wlmtk_tile_extend( wlmtk_tile_t *tile_ptr, const wlmtk_tile_vmt_t *tile_vmt_ptr); /** * Un-initializes the tile. * * @param tile_ptr */ void wlmtk_tile_fini(wlmtk_tile_t *tile_ptr); /** * Updates the style for the tile. * * @param tile_ptr * @param style_ptr * * @return true on success. */ bool wlmtk_tile_set_style( wlmtk_tile_t *tile_ptr, const struct wlmtk_tile_style *style_ptr); /** * Sets (overwrites) the default tile's background buffer. * * This permits specific tiles, eg. a Dock Clip to include active elements in * the background, or change the bezel or texture. * * @param tile_ptr * @param wlr_buffer_ptr Points to a `struct wlr_buffer`. The tile will * add a buffer lock, so the caller may safely * drop or unlock the buffer. * The buffer must match the tile's size. * * @return false if the buffer did not match the tile size. */ bool wlmtk_tile_set_background_buffer( wlmtk_tile_t *tile_ptr, struct wlr_buffer *wlr_buffer_ptr); /** * Sets `element_ptr` as the content of `tile_ptr`. * * TODO(kaeser@gubbe.ch): Flesh out the behaviour -- permit only 1 content? * Does the tile claim ownerwhip? How to reset the content? * * @param tile_ptr * @param element_ptr */ void wlmtk_tile_set_content( wlmtk_tile_t *tile_ptr, wlmtk_element_t *element_ptr); /** * Sets `element_ptr` as the overlay of `tile_ptr`. * * TODO(kaeser@gubbe.ch): Flesh out the behaviour -- permit only 1 overlay? * Does the tile claim ownerwhip? How to reset the overlay? * * @param tile_ptr * @param element_ptr */ void wlmtk_tile_set_overlay( wlmtk_tile_t *tile_ptr, wlmtk_element_t *element_ptr); /** @return the superclass' @ref wlmtk_element_t of `tile_ptr`. */ wlmtk_element_t *wlmtk_tile_element(wlmtk_tile_t *tile_ptr); /** @return The @ref wlmtk_tile_t for `dlnode_ptr`. */ wlmtk_tile_t *wlmtk_tile_from_dlnode(bs_dllist_node_t *dlnode_ptr); /** @return @ref wlmtk_tile_t::dlnode from `tile_ptr`. */ bs_dllist_node_t *wlmtk_dlnode_from_tile(wlmtk_tile_t *tile_ptr); /** Plist decoding descriptor of a tile style. */ extern const bspl_desc_t wlmtk_tile_style_desc[]; /** Unit test cases for @ref wlmtk_tile_t. */ extern const bs_test_set_t wlmtk_tile_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_TILE_H__ */ /* == End of tile.h ======================================================== */ wlmaker-0.8.1/include/toolkit/test.h0000644000175100017510000000434115222270710017030 0ustar runnerrunner/* ========================================================================= */ /** * @file test.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_TEST_H__ #define __WLMTK_TEST_H__ #include // IWYU pragma: keep #include /** Forward declaration. */ struct wlr_output; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Unit test comparator: Whether _box matches expected dimensions. */ #define WLMTK_TEST_VERIFY_WLRBOX_EQ(_test, _x, _y, _width, _height, _box) \ do { \ struct wlr_box __box = (_box); \ int __x = (_x), __y = (_y), __width = (_width), __height = (_height); \ if (__x != __box.x || __y != __box.y || \ __height != __box.height || __width != __box.width) { \ bs_test_fail_at( \ (_test), __FILE__, __LINE__, \ "Expecting {%d, %d, %d, %d}, got '%s' {%d, %d, %d, %d}", \ __x, __y, __width, __height, \ #_box, __box.x, __box.y, __box.width, __box.height); \ } \ } while (false) /** Initializes a struct wlr_output sufficient for testing. */ void wlmtk_test_wlr_output_init(struct wlr_output *wlr_output_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_TEST_H__ */ /* == End of test.h ================================================== */ wlmaker-0.8.1/include/toolkit/container.h0000644000175100017510000002031415222270710020031 0ustar runnerrunner/* ========================================================================= */ /** * @file container.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_CONTAINER_H__ #define __WLMTK_CONTAINER_H__ #include #include #include "libbase/libbase.h" struct wlr_scene_tree; /** Forward declaration: Container. */ typedef struct _wlmtk_container_t wlmtk_container_t; /** Forward declaration: Container virtual method table. */ typedef struct _wlmtk_container_vmt_t wlmtk_container_vmt_t; #include "element.h" #include "input.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** State of the container. */ struct _wlmtk_container_t { /** Super class of the container. */ wlmtk_element_t super_element; /** Virtual method table of the super element before extending it. */ wlmtk_element_vmt_t orig_super_element_vmt; /** * Elements contained here. * * `head_ptr` is the topmost element, and `tail_ptr` the bottom-most one. */ bs_dllist_t elements; /** Scene tree. */ struct wlr_scene_tree *wlr_scene_tree_ptr; /** Listener for the `destroy` signal of `wlr_scene_tree_ptr->node`. */ struct wl_listener wlr_scene_tree_node_destroy_listener; /** Listener for @ref wlmtk_element_events_t::pointer_leave. */ struct wl_listener element_pointer_leave_listener; /** Stores the element with current pointer focus. May be NULL. */ wlmtk_element_t *pointer_focus_element_ptr; /** Stores the element with current pointer grab. May be NULL. */ wlmtk_element_t *pointer_grab_element_ptr; /** Stores the element which received WLMTK_BUTTON_DOWN for BTN_LEFT. */ wlmtk_element_t *left_button_element_ptr; /** Stores the element with current keyboard focus. May be NULL. */ wlmtk_element_t *keyboard_focus_element_ptr; /** @private Stores whether the layout had been invalidated. */ bool invalidated_layout; }; /** * Initializes the container with the provided virtual method table. * * @param container_ptr * * @return true on success. */ bool wlmtk_container_init(wlmtk_container_t *container_ptr); /** * Initializes the container, and attach to WLR sene graph. * * @param container_ptr * @param root_wlr_scene_tree_ptr * * @return true on success. */ bool wlmtk_container_init_attached( wlmtk_container_t *container_ptr, struct wlr_scene_tree *root_wlr_scene_tree_ptr); /** * Un-initializes the container. * * Any element still in `elements` will be destroyed. * * @param container_ptr */ void wlmtk_container_fini( wlmtk_container_t *container_ptr); /** * Adds `element_ptr` to the container. * * Requires that `element_ptr` is not added to a container yet. The element * will be added at the top of the container. * * @param container_ptr * @param element_ptr */ void wlmtk_container_add_element( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr); /** * Adds `element_ptr` to the container atop the reference's position. * * If reference_element_ptr is NULL, the element will be added at the back. * * @param container_ptr * @param reference_element_ptr Must be an element of this container. * @param element_ptr */ void wlmtk_container_add_element_atop( wlmtk_container_t *container_ptr, wlmtk_element_t *reference_element_ptr, wlmtk_element_t *element_ptr); /** * Removes `element_ptr` from the container. * * Expects that `container_ptr` is `element_ptr`'s parent container. In case * `element_ptr` holds a pointer grab, @ref wlmtk_element_pointer_grab_cancel * will be called. * * @param container_ptr * @param element_ptr */ void wlmtk_container_remove_element( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr); /** * Places `element_ptr` at the top (head) of the container. * * Expects that `container_ptr` is `element_ptr`'s parent container. * * @param container_ptr * @param element_ptr */ void wlmtk_container_raise_element_to_top( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr); /** * Requests that `element_ptr` be given pointer focus from `container_ptr`. * * @param container_ptr * @param element_ptr Must be in @ref wlmtk_container_t::elements. * @param motion_event_ptr The motion event (in particular: pointer) that * led to request pointer focus. * FIXME -> Must change to pointer! * * @return false if the request fails. */ // TODO(kaeser@gubbe.ch): Unify handling with pointer grab. bool wlmtk_container_request_pointer_focus( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr, wlmtk_pointer_motion_event_t *motion_event_ptr); /** * Requests a pointer grab from `container_ptr` for `element_ptr`. * * Will cancel any existing grab held by elements other than `element_ptr`, and * propagates the grab to the parent container. * When a pointer grab is held, pointer events will be routed exclusively to * the element holding the pointer grab. * * @param container_ptr * @param element_ptr Must be a child of this container. * */ // TODO(kaeser@gubbe.ch): Merge with @ref wlmtk_container_request_pointer_focus. void wlmtk_container_pointer_grab( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr); /** * Releases a pointer grab in `container_ptr` held by `element_ptr`. * * If the grab is held by an element other than `element_ptr`, nothing is done. * Otherwise, the pointer grab is released, and the release is propagated to * the parent container. * * @param container_ptr * @param element_ptr Must be a child of this container. */ void wlmtk_container_pointer_grab_release( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr); /** * Sets or disables keyboard focus for `element_ptr` for the container. * * If `enabled`, calls @ref wlmtk_element_keyboard_blur for the element * currently having keyboard focus, and updates * @ref wlmtk_container_t::keyboard_focus_element_ptr to `element_ptr`. * Otherwise, evaluates whether the focus is currently held by `element_ptr`, * and (if yes) clears this container's focus. * Propagates the event to the container's parent. * * @param container_ptr * @param element_ptr * @param enabled */ void wlmtk_container_set_keyboard_focus_element( wlmtk_container_t *container_ptr, wlmtk_element_t *element_ptr, bool enabled); /** * Informs the container (and parents) that the layout needs to be recomputed. * * Must be called if an element is added or removed, or if any of the child * elements changes visibility or dimensions. Propagates to the parent * container(s). * * @param container_ptr */ void wlmtk_container_invalidate_layout(wlmtk_container_t *container_ptr); /** * Returns the wlroots scene graph tree for this node. * * Private: Should be called only by wlmtk_element_t. * * @param container_ptr * * @return The scene tree, or NULL if not attached to a scene graph. */ struct wlr_scene_tree *wlmtk_container_wlr_scene_tree( wlmtk_container_t *container_ptr); /** Unit tests for the container. */ extern const bs_test_set_t wlmtk_container_test_set; /** Constructor for a fake container with a scene tree. */ wlmtk_container_t *wlmtk_container_create_fake_parent(void); /** Destructor for that fake container. */ void wlmtk_container_destroy_fake_parent(wlmtk_container_t *container_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_CONTAINER_H__ */ /* == End of container.h =================================================== */ wlmaker-0.8.1/include/toolkit/panel.h0000644000175100017510000001716515222270710017160 0ustar runnerrunner/* ========================================================================= */ /** * @file panel.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_PANEL_H__ #define __WLMTK_PANEL_H__ #include #include #include #include "container.h" // IWYU pragma: keep #include "element.h" /** Forward declaration: An element of a layer, we call it: Panel. */ typedef struct _wlmtk_panel_t wlmtk_panel_t; /** Forward declaration: The panel's virtual method table. */ typedef struct _wlmtk_panel_vmt_t wlmtk_panel_vmt_t; /** Forward declaration: The panel's positioning parameters. */ typedef struct _wlmtk_panel_positioning_t wlmtk_panel_positioning_t; #include "layer.h" // IWYU pragma: keep #ifdef __cplusplus extern "C" { #endif // __cplusplus /** The panel's virtual method table. */ struct _wlmtk_panel_vmt_t { /** * Requests the panel to change to the specified size. * * This may be implemented as an asynchronous implementation. Once the * panel has committed the adapted size, @ref wlmtk_panel_commit should * be called with the corresponding serial. * * @param content_ptr * @param width_ptr * @param height * * @return WLR Layer Shell configuration serial. */ uint32_t (*request_size)( wlmtk_panel_t *panel_ptr, int width, int height); }; /** The panel's positioning parameters. */ struct _wlmtk_panel_positioning_t { /** Desired width of the panel. */ int desired_width; /** Desired height of the panel. */ int desired_height; /** Edges the panel is anchored to. See `enum wlr_edges`. */ uint32_t anchor; /** Margin on the left of the panel. */ int margin_left; /** Margin on the right of the panel. */ int margin_right; /** Margin on the top of the panel. */ int margin_top; /** Margin on the bottom of the panel. */ int margin_bottom; /** Size of the exclusive zone, in pixels. -1 to request all. */ int exclusive_zone; }; /** State of the panel. */ struct _wlmtk_panel_t { /** Super class of the panel. */ wlmtk_container_t super_container; /** The panel's virtual method table. */ wlmtk_panel_vmt_t vmt; /** Popup container. Panels may contain popups. */ wlmtk_container_t popup_container; /** The layer that this panel belongs to. NULL if none. */ wlmtk_layer_t *layer_ptr; /** The layer output this panel is associated with. */ wlmtk_layer_output_t *layer_output_ptr; /** Node of @ref wlmtk_layer_output_t::panels. */ bs_dllist_node_t dlnode; /** Positioning parameters. */ wlmtk_panel_positioning_t positioning; }; /** * Initializes the panel. * * @param panel_ptr * @param positioning_ptr * * @return true on success. */ bool wlmtk_panel_init( wlmtk_panel_t *panel_ptr, const wlmtk_panel_positioning_t *positioning_ptr); /** * Un-initializes the panel. * * @param panel_ptr */ void wlmtk_panel_fini(wlmtk_panel_t *panel_ptr); /** * Extends the panel by the specified virtual methods. * * @param panel_ptr * @param panel_vmt_ptr * * @return The original virtual method table. */ wlmtk_panel_vmt_t wlmtk_panel_extend( wlmtk_panel_t *panel_ptr, const wlmtk_panel_vmt_t *panel_vmt_ptr); /** @return pointer to the super @ref wlmtk_element_t of the panel. */ wlmtk_element_t *wlmtk_panel_element(wlmtk_panel_t *panel_ptr); /** @return Pointer to @ref wlmtk_panel_t::dlnode. */ bs_dllist_node_t *wlmtk_dlnode_from_panel(wlmtk_panel_t *panel_ptr); /** @return Pointer to @ref wlmtk_panel_t for the given dlnode. */ wlmtk_panel_t *wlmtk_panel_from_dlnode(bs_dllist_node_t *dlnode_ptr); /** * Sets the layer for the `panel_ptr`. * * @protected This method must only be called from @ref wlmtk_layer_t. * * @param panel_ptr * @param layer_ptr */ void wlmtk_panel_set_layer(wlmtk_panel_t *panel_ptr, wlmtk_layer_t *layer_ptr); /** @return the wlmtk_layer_t this panel belongs to. Or NULL, if unmapped. */ wlmtk_layer_t *wlmtk_panel_get_layer(wlmtk_panel_t *panel_ptr); /** * Sets the layer output for the `panel_ptr`. * * @protected This method must only be called from @ref wlmtk_layer_output_t. * * @param panel_ptr * @param layer_output_ptr */ void wlmtk_panel_set_layer_output( wlmtk_panel_t *panel_ptr, wlmtk_layer_output_t *layer_output_ptr); /** @return the wlmtk_layer_output_t the panel belongs to. NULL if unmapped. */ wlmtk_layer_output_t *wlmtk_panel_get_layer_output(wlmtk_panel_t *panel_ptr); /** Requests new size. See @ref wlmtk_panel_vmt_t::request_size. */ static inline uint32_t wlmtk_panel_request_size( wlmtk_panel_t *panel_ptr, int width, int height) { return panel_ptr->vmt.request_size(panel_ptr, width, height); } /** * Reports a commit for the given serial, and updates positioning. * * @param panel_ptr * @param serial * @param positioning_ptr */ void wlmtk_panel_commit( wlmtk_panel_t *panel_ptr, uint32_t serial, const wlmtk_panel_positioning_t *positioning_ptr); /** * Computes the requested dimension for the panel. * * @param panel_ptr * @param full_area_ptr * @param usable_area_ptr Are that remains usable from the output and layer * after factoring in other panels. *usable_area_ptr * will be updated with this panel's exclusive area * (if any) subtracted. * * @return A wlr_box with the requested position and size for this panel. The * caller is advised to issue a call to @ref wlmtk_panel_request_size call and * update the panel element's position with the box' information. */ struct wlr_box wlmtk_panel_compute_dimensions( const wlmtk_panel_t *panel_ptr, const struct wlr_box *full_area_ptr, struct wlr_box *usable_area_ptr); /** Unit test cases of panel. */ extern const bs_test_set_t wlmtk_panel_test_set; /** Forward declaration: Fake panel, for tests. */ typedef struct _wlmtk_fake_panel_t wlmtk_fake_panel_t; /** State of fake panel. */ struct _wlmtk_fake_panel_t { /** Superclass: panel. */ wlmtk_panel_t panel; /** Serial to return on next request_size call. */ uint32_t serial; /** `width` argument eof last @ref wlmtk_panel_vmt_t::request_size call. */ int requested_width; /** `height` argument of last @ref wlmtk_panel_vmt_t::request_size call. */ int requested_height; }; /** Creates a fake panel, for tests. */ wlmtk_fake_panel_t *wlmtk_fake_panel_create( const wlmtk_panel_positioning_t *positioning_ptr); /** Destroys the fake panel. */ void wlmtk_fake_panel_destroy(wlmtk_fake_panel_t *fake_panel_ptr); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_PANEL_H__ */ /* == End of panel.h ======================================================= */ wlmaker-0.8.1/include/toolkit/button.h0000644000175100017510000000622715222270710017371 0ustar runnerrunner/* ========================================================================= */ /** * @file button.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_BUTTON_H__ #define __WLMTK_BUTTON_H__ #include #include #include #include "buffer.h" #include "element.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: State of a button. */ typedef struct _wlmtk_button_t wlmtk_button_t; /** Virtual method table of the button. */ typedef struct { /** Optional: Called when the button has been clicked. */ void (*clicked)(wlmtk_button_t *button_ptr); } wlmtk_button_vmt_t; /** State of a button. */ struct _wlmtk_button_t { /** Super class of the button: A buffer. */ wlmtk_buffer_t super_buffer; /** Original virtual method table of the superclass element. */ wlmtk_element_vmt_t orig_super_element_vmt; /** The virtual method table. */ wlmtk_button_vmt_t vmt; /** WLR buffer holding the button in released state. */ struct wlr_buffer *released_wlr_buffer_ptr; /** WLR buffer holding the button in pressed state. */ struct wlr_buffer *pressed_wlr_buffer_ptr; /** Listens to when we obtain pointer focus. */ struct wl_listener pointer_enter_listener; /** Listens to when we lose pointer focus. */ struct wl_listener pointer_leave_listener; /** Whether the button is currently pressed. */ bool pressed; }; /** * Initializes the button. * * @param button_ptr * * @return true on success. */ bool wlmtk_button_init(wlmtk_button_t *button_ptr); /** * Extends the button's virtual methods. * * @param button_ptr * @param button_vmt_ptr * * @return The original virtual method table. */ wlmtk_button_vmt_t wlmtk_button_extend( wlmtk_button_t *button_ptr, const wlmtk_button_vmt_t *button_vmt_ptr); /** * Cleans up the button. * * @param button_ptr */ void wlmtk_button_fini(wlmtk_button_t *button_ptr); /** * Sets (or updates) the button textures. * * @param button_ptr * @param released_wlr_buffer_ptr * @param pressed_wlr_buffer_ptr */ void wlmtk_button_set( wlmtk_button_t *button_ptr, struct wlr_buffer *released_wlr_buffer_ptr, struct wlr_buffer *pressed_wlr_buffer_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_button_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_BUTTON_H__ */ /* == End of button.h ====================================================== */ wlmaker-0.8.1/include/toolkit/primitives.h0000644000175100017510000001060015222270710020237 0ustar runnerrunner/* ========================================================================= */ /** * @file primitives.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_PRIMITIVES_H__ #define __WLMTK_PRIMITIVES_H__ #include #include #include #include #include "style.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Completely fills the cairo with the specified style. * * @param cairo_ptr A cairo, backed by an image surface. * @param fill_ptr */ void wlmaker_primitives_cairo_fill( cairo_t *cairo_ptr, const wlmtk_style_fill_t *fill_ptr); /** * Fills the cairo with the specified style at the specified rectangle. * * @param cairo_ptr A cairo, backed by an image surface. * @param x * @param y * @param width * @param height * @param fill_ptr */ void wlmaker_primitives_cairo_fill_at( cairo_t *cairo_ptr, int x, int y, unsigned width, unsigned height, const wlmtk_style_fill_t *fill_ptr); /** * Sets the bezel color. * * Note: Window Maker draws the bezel by adding 80 (0x50) to each R, G, B of * the underlying title for the illuminated side; respectively by subtracting * 40 (0x28) on the non-illuminated side. * We are using cairo's overlaying with the respective "alpha" values below, * which leads to different results. * * @param cairo_ptr * @param illuminated */ void wlmaker_primitives_set_bezel_color( cairo_t *cairo_ptr, bool illuminated); /** * Draws a bezel into the cairo. * * @param cairo_ptr A cairo, backed by an image surface. * @param bezel_width * @param raised Whether the bezel is to highlight a raised (true) * or pressed (false) state. */ void wlmaker_primitives_draw_bezel( cairo_t *cairo_ptr, double bezel_width, bool raised); /** * Draws a bezel into the cairo, at specified position and width/height. * * @param cairo_ptr A cairo, backed by an image surface. * @param x * @param y * @param width * @param height * @param bezel_width * @param raised Whether the bezel is to highlight a raised (true) * or pressed (false) state. */ void wlmaker_primitives_draw_bezel_at( cairo_t *cairo_ptr, int x, int y, unsigned width, unsigned height, double bezel_width, bool raised); /** * Draws the "minimize" icon, as used in the title bar. * * @param cairo_ptr * @param size * @param color */ void wlmaker_primitives_draw_minimize_icon( cairo_t *cairo_ptr, int size, uint32_t color); /** * Draws the "close" icon, as used in the title bar. * * @param cairo_ptr * @param size * @param color */ void wlmaker_primitives_draw_close_icon( cairo_t *cairo_ptr, int size, uint32_t color); /** * Draws the window title into the `cairo_t`. * * @param cairo_ptr * @param font_style_ptr The font style to use. * @param title_ptr Title string, or NULL. * @param color As an ARGB 8888 value. */ void wlmaker_primitives_draw_window_title( cairo_t *cairo_ptr, const wlmtk_style_font_t *font_style_ptr, const char *title_ptr, uint32_t color); /** * Draws the text with given parameters into the `cairo_t` at (x, y). * * @param cairo_ptr * @param x * @param y * @param font_style_ptr * @param color * @param text_ptr */ void wlmaker_primitives_draw_text( cairo_t *cairo_ptr, int x, int y, const wlmtk_style_font_t *font_style_ptr, uint32_t color, const char *text_ptr); /** Unit tests. */ extern const bs_test_set_t wlmaker_primitives_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_PRIMITIVES_H__ */ /* == End of primitives.h ================================================== */ wlmaker-0.8.1/include/toolkit/buffer.h0000644000175100017510000000565515222270710017333 0ustar runnerrunner/* ========================================================================= */ /** * @file buffer.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_BUFFER_H__ #define __WLMTK_BUFFER_H__ #include #include #include #include "element.h" #include "input.h" /** Forward declaration: Buffer state. */ typedef struct _wlmtk_buffer_t wlmtk_buffer_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** State of a texture-backed buffer. */ struct _wlmtk_buffer_t { /** Super class of the buffer: An element. */ wlmtk_element_t super_element; /** Virtual method table of the super element before extending it. */ wlmtk_element_vmt_t orig_super_element_vmt; /** Cursor to set when we have pointer focus. */ wlmtk_pointer_cursor_t pointer_cursor; /** WLR buffer holding the contents. */ struct wlr_buffer *wlr_buffer_ptr; /** Scene graph API node. Only set after calling `create_scene_node`. */ struct wlr_scene_buffer *wlr_scene_buffer_ptr; /** Listener for the `destroy` signal of `wlr_scene_buffer_ptr->node`. */ struct wl_listener wlr_scene_buffer_node_destroy_listener; /** Listener for @ref wlmtk_element_events_t::pointer_enter. */ struct wl_listener element_pointer_enter_listener; }; /** * Initializes the buffer. * * @param buffer_ptr * * @return true on success. */ bool wlmtk_buffer_init(wlmtk_buffer_t *buffer_ptr); /** * Cleans up the buffer. * * @param buffer_ptr */ void wlmtk_buffer_fini(wlmtk_buffer_t *buffer_ptr); /** * Sets (or updates) buffer contents. * * @param buffer_ptr * @param wlr_buffer_ptr A WLR buffer to use for the update. That buffer * will get locked by @ref wlmtk_buffer_t for the * duration of it's use. */ void wlmtk_buffer_set( wlmtk_buffer_t *buffer_ptr, struct wlr_buffer *wlr_buffer_ptr); /** @return the superclass' @ref wlmtk_element_t of `buffer_ptr`. */ wlmtk_element_t *wlmtk_buffer_element(wlmtk_buffer_t *buffer_ptr); /** Unit test cases. */ extern const bs_test_set_t wlmtk_buffer_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_BUFFER_H__ */ /* == End of buffer.h ====================================================== */ wlmaker-0.8.1/include/toolkit/dock.h0000644000175100017510000000662415222270710016777 0ustar runnerrunner/* ========================================================================= */ /** * @file dock.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_DOCK_H__ #define __WLMTK_DOCK_H__ #include #include #include #define WLR_USE_UNSTABLE #include #undef WLR_USE_UNSTABLE #include "element.h" #include "panel.h" #include "style.h" #include "tile.h" /** Forward declaration: Dock handle. */ typedef struct _wlmtk_dock_t wlmtk_dock_t; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Positioning options for the dock. */ typedef struct { /** Principal edge the dock aligns to. Must not be WLR_EDGE_NONE. */ enum wlr_edges edge; /** Dock anchor, along @ref wlmtk_dock_positioning_t::edge. */ enum wlr_edges anchor; } wlmtk_dock_positioning_t; /** Styling information for the dock. */ struct wlmtk_dock_style { /** The margin's style of the dock's box. */ struct wlmtk_margin_style margin; }; /** * Creates a dock. A dock contains icons, launchers and the likes. * * The dock is an implementation of a @ref wlmtk_panel_t. * * @param dock_positioning_ptr * @param style_ptr * * @return The dock handle, or NULL on error. Must be destroyed calling * @ref wlmtk_dock_destroy. */ wlmtk_dock_t *wlmtk_dock_create( const wlmtk_dock_positioning_t *dock_positioning_ptr, const struct wlmtk_dock_style *style_ptr); /** * Destroys the dock. * * @param dock_ptr */ void wlmtk_dock_destroy(wlmtk_dock_t *dock_ptr); /** * Sets the style for the dock, including all contained tiles. * * @param dock_ptr * @param style_ptr * @param tile_style_ptr * * @return true on success. */ bool wlmtk_dock_set_style( wlmtk_dock_t *dock_ptr, const struct wlmtk_dock_style *style_ptr, const struct wlmtk_tile_style *tile_style_ptr); /** * Adds the tile to the dock. * * @param dock_ptr * @param tile_ptr */ void wlmtk_dock_add_tile( wlmtk_dock_t *dock_ptr, wlmtk_tile_t *tile_ptr); /** * Removes the tile from the dock. * * @param dock_ptr * @param tile_ptr */ void wlmtk_dock_remove_tile( wlmtk_dock_t *dock_ptr, wlmtk_tile_t *tile_ptr); /** @return Pointer to the superclass @ref wlmtk_panel_t of `dock_ptr`. */ wlmtk_panel_t *wlmtk_dock_panel(wlmtk_dock_t *dock_ptr); /** @return Pointer to the superclass @ref wlmtk_element_t of `dock_ptr`. */ wlmtk_element_t *wlmtk_dock_element(wlmtk_dock_t *dock_ptr); /** Plist decoding descriptor of the dock's style. */ extern const bspl_desc_t wlmtk_dock_style_desc[]; /** Unit tests for @ref wlmtk_dock_t. */ extern const bs_test_set_t wlmtk_dock_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_DOCK_H__ */ /* == End of dock.h ======================================================== */ wlmaker-0.8.1/include/toolkit/fsm.h0000644000175100017510000000563515222270710016645 0ustar runnerrunner/* ========================================================================= */ /** * @file fsm.h * Event-driven finite state machine. * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_FSM_H__ #define __WLMTK_FSM_H__ #include #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration. */ typedef struct _wlmtk_fsm_t wlmtk_fsm_t; /** State machine definition. */ typedef struct { /** State before receiving the event. */ int state; /** Event. */ int event; /** Upon having (state, event): State to transition to. */ int to_state; /** Handler for the activity at (state, event). */ bool (*handler)(wlmtk_fsm_t *fsm_ptr, void *ud_ptr); } wlmtk_fsm_transition_t; /** Finite state machine. State. */ struct _wlmtk_fsm_t { /** The transitions table. */ const wlmtk_fsm_transition_t *transitions; /** Current state. */ int state; }; /** Sentinel element for state transition table. */ #define WLMTK_FSM_TRANSITION_SENTINEL { \ .state = -1, \ .event = -1, \ .to_state = -1, \ .handler = NULL, \ } /** * Initializes the finite-state machine. * * @param fsm_ptr * @param transitions * @param initial_state */ void wlmtk_fsm_init( wlmtk_fsm_t *fsm_ptr, const wlmtk_fsm_transition_t *transitions, int initial_state); /** * Handles an event for the finite-state machine. * * Will search for the transition matching (current state, event) and call the * associate handler. * * @param fsm_ptr * @param event * @param ud_ptr * * @return If a matching transition was found: The return value of the * associated handler (or true, if no handler was given). Otherwise, * returns false. */ bool wlmtk_fsm_event( wlmtk_fsm_t *fsm_ptr, int event, void *ud_ptr); /** Unit tests for the finite-state machine. */ extern const bs_test_set_t wlmtk_fsm_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_FSM_H__ */ /* == End of fsm.h ====================================================== */ wlmaker-0.8.1/include/toolkit/surface.h0000644000175100017510000000716115222270710017504 0ustar runnerrunner/* ========================================================================= */ /** * @file surface.h * * @copyright * Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __WLMTK_SURFACE_H__ #define __WLMTK_SURFACE_H__ #include #include #include /** Forward declaration: State of a toolkit's WLR surface. */ typedef struct _wlmtk_surface_t wlmtk_surface_t; /** Forward declaration: Virtual method table of the toolkit's WLR surface. */ typedef struct _wlmtk_surface_vmt_t wlmtk_surface_vmt_t; #include "element.h" /** Forward declaration. */ struct wlr_seat; struct wlr_surface; struct wlr_xdg_surface; #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a toolkit surface from the `wlr_surface_ptr`. * * @param wlr_surface_ptr * @param wlr_seat_ptr * * @return A pointer to the @ref wlmtk_surface_t. Must be destroyed by calling * @ref wlmtk_surface_destroy. */ wlmtk_surface_t *wlmtk_surface_create( struct wlr_surface *wlr_surface_ptr, struct wlr_seat *wlr_seat_ptr); /** * Creates a toolkit surface from the XDG surface `wlr_xdg_surface_ptr`. * * TODO(kaeser@gubbe.ch): Merge with @ref wlmtk_surface_create. * * @param wlr_xdg_surface_ptr * @param wlr_seat_ptr * * @return A pointer to the @ref wlmtk_surface_t. Must be destroyed by calling * @ref wlmtk_surface_destroy. */ wlmtk_surface_t *wlmtk_xdg_surface_create( struct wlr_xdg_surface *wlr_xdg_surface_ptr, struct wlr_seat *wlr_seat_ptr); /** * Destroys the toolkit surface. * * @param surface_ptr */ void wlmtk_surface_destroy(wlmtk_surface_t *surface_ptr); /** * Returns a pointer to the surface's element superclass instance. * * @param surface_ptr * * @return Pointer to the corresponding @ref wlmtk_element_t. */ wlmtk_element_t *wlmtk_surface_element(wlmtk_surface_t *surface_ptr); /** * Returns committed size of the surface. * * @param surface_ptr * @param width_ptr * @param height_ptr */ void wlmtk_surface_get_size( wlmtk_surface_t *surface_ptr, int *width_ptr, int *height_ptr); /** * Activates the surface. * * @param surface_ptr * @param activated */ void wlmtk_surface_set_activated( wlmtk_surface_t *surface_ptr, bool activated); /** @return Whether the surface is activated. */ bool wlmtk_surface_is_activated(wlmtk_surface_t *surface_ptr); /** Connects a listener and handler to the `map` signal of `wlr_surface`. */ void wlmtk_surface_connect_map_listener_signal( wlmtk_surface_t *surface_ptr, struct wl_listener *listener_ptr, wl_notify_func_t handler); /** Connects a listener and handler to the `unmap` signal of `wlr_surface`. */ void wlmtk_surface_connect_unmap_listener_signal( wlmtk_surface_t *surface_ptr, struct wl_listener *listener_ptr, wl_notify_func_t handler); /** Unit test cases. */ extern const bs_test_set_t wlmtk_surface_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __WLMTK_SURFACE_H__ */ /* == End of surface.h ===================================================== */ wlmaker-0.8.1/.git/0000755000175100017510000000000015222270710013427 5ustar runnerrunnerwlmaker-0.8.1/.git/index0000644000175100017510000007266115222270710014475 0ustar runnerrunnerDIRC?jIqÈ©´‹jIqÈ©´‹|-$�¤ééNÈDAøüPP{`åfµ,.github/workflows/build-for-debian-forky.ymljIqȬŒ(jIqȬŒ(|-%�¤éé ?§x€¶ªü Ž~‹º“ð¬7 {²½V4.github/workflows/build-for-debian-trixie-wlr019.ymljIqȬŒ(jIqȬŒ(|-&�¤éé zÛÿ·—÷™;ÀÃT’yé†\ƒ4.github/workflows/build-for-debian-trixie-wlr020.ymljIqȬŒ(jIqȬŒ(|-'�¤éé‘ô`Ú—‚Vâ`liCU‘g­ÃÕ-.github/workflows/build-for-debian-trixie.ymljIqȬŒ(jIqȬŒ(|-(�¤éé,TŸ¹š7¤…Nº¥ý^Š2Àž¯(.github/workflows/build-for-fedora41.ymljIqȬŒ(jIqȬŒ(|-)�¤ééØ9EêÛE¡‡;Ò}÷Å”å�l 5'.github/workflows/build-for-freebsd.ymljIqȬŒ(jIqȬŒ(|-*�¤éé¬S ¹=Ùm‰P©Yùa³†È¦nèL3.github/workflows/build-for-opensuse-tumbleweed.ymljIqȬŒ(jIqȬŒ(|-+�¤ééÕÇžœ]ۦ钱¯L õ蓜ºªÞ%.github/workflows/package-release.ymljIqȬŒ(jIqȬŒ(|-,�¤éé ¾÷¦ÜþŠ2±‰v§+M}Í"@ ý+!.github/workflows/publish-doc.ymljIqȰþjIqȰþ|--�¤éé|�Ù: ßgéÎèïš¾,àQüЧŸ .gitignorejIqȰþjIqȰþ|-.�¤éé \þ­&WKk6¼µ7d²hs&õ T .gitmodulesjIqȰþjIqȰþ|-/�¤éé˜èaT†À-QbÜjåa88cÖCMakeLists.txtjIqȰþjIqȰþ|-0�¤éé“v5�ì×0d)î&‚ýá­T –CODE_OF_CONDUCT.mdjIqȰþjIqȰþ|-1�¤ééÅÑ„›½Ÿ‰”SÈÅÒ+äý 2HÏÕCONTRIBUTING.mdjIqȰþjIqȰþ|-2�¤éé,]zJ>¢BL ûä�EZíª”ÙH5LICENSEjIqȰþjIqȰþ|-3�¤éé*  ®=buI¤Úm„T�æšQÐNOTICEjIqȰþjIqȰþ|-4�¤ééÉ®ìÕ:ï°ËÕl]6œ+ÞÛ÷ README.mdjIqȰþjIqȰþ|-5�¤éé]«?TוhÈþ¢+»´K;U² RELEASE.mdjIqȰþjIqȰþ|-6�¤éé ?‹Dý9cà:u‡Ÿádò¶@eëSTYLE.mdjIqȰþjIqȰþ|-8�¤ééöÅä¼Uâ°Ð-ó§ò„íX­ ÊTemplates/TEMPLATE.cjIqÈÀ¬ÅjIqÈÀ¬Å|-9�¤éé‡CÛKÿo�×¾�Ââ¢U?ñ©Templates/TEMPLATE.hjIqÈÀ¬ÅjIqÈÀ¬Å|-;�¤éé ‹3¡þ.äüìMɰvOÖÇÆZ(tapps/CMakeLists.txtjIqÈÃ4ŒjIqÈÃ4Œ|-<�¤ééÂ7¿ m¼ ô—¯X1�ß_�Ábapps/example_toplevel.cjIqÈÃ4ŒjIqÈÃ4Œ|->�¤éé õ}RÖéÓʹ Â46ß[eíaË� apps/libwlclient/CMakeLists.txtjIqÈÆ CjIqÈÆ C|-?�¤éé…©–3ZkØ¢zýÍ�¨9¶E é¨íapps/libwlclient/client.cjIqÈÆ CjIqÈÆ C|-@�¤éé2"7Ö ÇZæ‡lÊ|èjy&k%üapps/libwlclient/dblbuf.cjIqÈÆ CjIqÈÆ C|-A�¤éé â³ñÂQÁ²õûIäXE0L)×<apps/libwlclient/dblbuf.hjIqÈÆ CjIqÈÆ C|-B�¤éé,ñ œçnCÙ°wx0?ÊÎ(apps/libwlclient/icon.cjIqÈÆ CjIqÈÆ C|-C�¤éé ˆ¢§ÁzãÕi²)ö;俌X|apps/libwlclient/icon.hjIqÈÆ CjIqÈÆ C|-D�¤éé‚7‰›‰G:Î@eÖQ­: dñûapps/libwlclient/libwlclient.hjIqÈÆ CjIqÈÆ C|-E�¤ééK!ˆ¡\Í2›g©_îSˆ¢¯[ú:tapps/libwlclient/xdg_toplevel.cjIqÈÆ CjIqÈÆ C|-F�¤éé Ï#¥@Šó¸~sÚèß;Q•&“IÁÍapps/libwlclient/xdg_toplevel.hjIqÈÏ‚�jIqÈÏ‚�|-H�¤ééBí› ãˆ÷O,×â`QL÷ Ö,^apps/primitives/CMakeLists.txtjIqÈÔÖjIqÈÔÖ|-Á�¤éé ¤ê‹Ç×`Aæ¯-›C)e6$¦=ªapps/primitives/primitives.cjIqÈÔÖjIqÈÔÖ|-Â�¤éév‡sçØuž¦& ‡x¬¿ž6�‰®!apps/primitives/primitives.hjIqÈÔÖjIqÈÔÖ|-Ã�¤éé*05nŨ«Šÿ¼4bÎØ›JTùE!apps/primitives/segment_display.cjIqÈÔÖjIqÈÔÖ|-Ä�¤éé ¸²ÐkAÀ£óþ‡ª[MIW\m^Û !apps/primitives/segment_display.hjIqÈÔÖjIqÈÔÖ|-Å�¤éé|f�°+xx�;Κ¦5ÅÀ%ZôÝ\±)apps/primitives/segment_display_16x24.pngjIqÈÔÖjIqÈÔÖ|-Æ�¤ééb>tγ߈ ìèÝ+.V‰±¼–û'apps/primitives/segment_display_6x8.pngjIqÈÔÖjIqÈÔÖ|-Ç�¤é霬Ö_Õ6Žôp±«žfn#M™Í’(apps/primitives/segment_display_7x10.pngjIqÈÔÖjIqÈÔÖ|-È�¤éé¢?ù|ñ�9þ|�y¶Ê•?ÿ“Bé§&apps/primitives/segment_display_test.cjIqÈÞÄÖjIqÈÞÄÖ|-É�¤ééÖáM¶´§0&ÔUºl�û'n ›Éaapps/wlm_graph_shared.cjIqÈÞÄÖjIqÈÞÄÖ|-Ê�¤ééÞãá= £~)‡K™f4$nõ dÐapps/wlm_graph_shared.hjIqÈÞÄÖjIqÈÞÄÖ|-Ë�¤ééW½¹×¢‰£Ýây©¤yk•÷¸Óapps/wlmbattery.cjIqÈÞÄÖjIqÈÞÄÖ|-Ì�¤éé2¡ ®$]Å[GéŸÜß2Õ&Ñeûapps/wlmclock.cjIqÈÞÄÖjIqÈÞÄÖ|-Í�¤ééµÒØÏ2_ÿ™×Çøb8*÷$ Tñ8ºapps/wlmcpugraph.cjIqÈÞÄÖjIqÈÞÄÖ|-Î�¤éé&åã$08€këbÂn‚¿ÀåNo}apps/wlmeyes.cjIqÈÞÄÖjIqÈÞÄÖ|-Ï�¤éé()5cŽg|‡ü"H›¤ªb©MuSAÜapps/wlmmemgraph.cjIqÈÞÄÖjIqÈÞÄÖ|-Ð�¤éé6ݲ¢Ê§ o‡‡Ýð¥S¡I!z.„apps/wlmnetgraph.cjIqÈÞÄÖjIqÈÞÄÖ|-Ñ�íééž·š.Ìë“êg&�mvÏ÷nqJbuild-cscope-index.shjIqÈÞÄÖjIqÈÞÄÖ|-Ó�¤éé �¯Š3odXM³ØÖÝ™ùåµ*�Wªcmake/EmbedBinary.cmakejIqÈïÛjIqÈïÛ|-Ô�¤éé K\Í\jcŸ=›óOëÕ+ƒ--~cmake/WaylandProtocol.cmakejIqÈïÛjIqÈïÛ|-Õ�¤ééµ&¶ÔPà ”˜ð»}Þx?æßOÉ¿šcmake/embed.cmake.injIqÈïÛjIqÈïÛ|-Ö�¤éé,_—&#ñQýZ¡•£ye¿3=¨Ðgcmake/embed_binary.c.injIqÈïÛjIqÈïÛ|-×�¤ééÕŸQr0šrè=DýùU»0)\@Ý›cmake/embed_binary.h.injIqÈïÛjIqÈïÛ|-ØàééÅ“q«ÊÐÕéT¹b} áÞCÓ]Îf dependenciesjIqÈïÛjIqÈïÛ|-Ú�¤ééRÕóß…© ˆÉMÍUo'Aš,ßÇ doc/BUILD.mdjIqÈõx³jIqÈõx³|-Û�¤éé C†? ¯h[äXLKPó?åžßðdoc/CMakeLists.txtjIqÈõx³jIqÈõx³|-Ü�¤ééÅì£ô\Ž#'š¥NóŒ/L÷]�ëdoc/Doxyfile.injIqÈýIbjIqÈýIb|-Ý�¤éé<¾§¼h Eݤ†Ìƒw‚kipdoc/FEATURES.mdjIqÈýIbjIqÈýIb|-Þ�¤éé^Ó…^�­üú ËtTV3m“§¥àdoc/ROADMAP.mdjIqÈýIbjIqÈýIb|-ß�¤éé!å;OSá%䟉cRÄ>šŸ¿=€Ð doc/RUN.mdjIqÈýIbjIqÈýIb|-à�¤éé 4 F)Œ¦a=‹ï�YÆÌÔþ„AÙdoc/commandline.mdjIqÈýIbjIqÈýIb|-á�¤é錃éüþ~Ÿn½/òZ9½MÀ†o¨, doc/config.mdjIqÈýIbjIqÈýIb|-â�¤éé N»µ½ûØÅë8F’9FO¬Ždoc/manual.cssjIqÈýIbjIqÈýIb|-ã�¤ééžìÙÂß¶˜¤ÖÂõúç@€+̲ doc/manual.mdjIqÈýIbjIqÈýIb|-ä�¤ééNE¶z{­=¡éå8HShÅrÇWädoc/protocols.mdjIqÈýIbjIqÈýIb|-å�¤ééoƒ‘ˆAô<Ç’iV/à|V¼¹¡doc/root_menu.mdjIqÈ ‹¨jIqÈ ‹¨|-æ�¤éé ¨ÐP'{XÀlÅN—9´3 )aÖÏ"doc/wlmaker-default-screenshot.pngjIqÈ ‹¨jIqÈ ‹¨|-ç�¤éé-„Ýð~Zîo²E{øžkàR©Ø&doc/wlmaker-header.pngjIqÈ ‹¨jIqÈ ‹¨|-é�¤éé ­ÀìDå5hÛú?[ì€üw#Ðetc/CMakeLists.txtjIqÈHfjIqÈHf|-ê�¤éé˜å9ècß\€DA R{¶!sHetc/Config.plistjIqÈHfjIqÈHf|-ë�¤éél}ºL)ñ}}ãÆÐ#½ª6B•etc/ExampleConfig.plistjIqÈHfjIqÈHf|-ì�¤éé)ñ¯)õ£nH“aGþ€ì Çfetc/HomeConfig.plistjIqÈHfjIqÈHf|-í�¤éé«�¯ˆ*HqÅïÄÆÆ�p¬ �Tetc/RootMenu.plistjIqÈHfjIqÈHf|-î�¤ééJ¡'^�_³½�¢dµÚ‘.ˆ"Yetc/RootMenuDebian.plist.injIqÈHfjIqÈHf|-ï�¤éé0oÉù³}´Dãೆ$�È À Ìnetc/State.plistjIqÈHfjIqÈHf|-ñ�¤éé�LR„�ájøë‚Ò\¨�ä>câYŸexamples/CMakeLists.txtjIqÈôcjIqÈôc|-ò�¤é鮬5.ö&}ËlÅ›ð+´ëM ž‚nexamples/README.mdjIqÈôcjIqÈôc|-óàéé_qTaý(JÎÑ>{#‘¦ KÊÍexamples/gtk-layer-shelljIqÈpojIqÈpo|-ö�¤éé  ¤‹¼£·ôxtï´iÀZ@§%Ä,yinclude/backend/backend.hjIqÈ2“jIqÈ2“|-÷�¤éé Ú �Âêð¶XçF¨K„$ÉH~ p;include/backend/output.hjIqÈ2“jIqÈ2“|-ø�¤éé"aæ!L¡dþãȼÃúùÚbv ;ginclude/backend/output_config.hjIqÈ2“jIqÈ2“|-ù�¤éé dïY¢x^6çw$ì±Ar�Fjw|Õö include/backend/output_manager.hjIqÈ2“jIqÈ2“|-û�¤éé y0þgÑÔ9Mx%áÅzÐúÒkØinclude/toolkit/base.hjIqÈ"‚œjIqÈ"‚œ|-ü�¤éé Á‚Ûóó]¿çüŒ«·¬¿¿>è« include/toolkit/bordered.hjIqÈ"‚œjIqÈ"‚œ|-ý�¤éé ��sÌëA�P;±ø{ycFµinclude/toolkit/box.hjIqÈ"‚œjIqÈ"‚œ|-þ�¤éé ­³œ£EP‘?ÙÛx²%ÿÑ‚´ ŒÛinclude/toolkit/buffer.hjIqÈ"‚œjIqÈ"‚œ|-ÿ�¤éé —g£š¼] òIŒàsb+Þø�include/toolkit/button.hjIqÈ"‚œjIqÈ"‚œ|.�¤éé Ì[^àúBwé©7$Ó1ý<&9�&>include/toolkit/container.hjIqÈ"‚œjIqÈ"‚œ|.�¤éé ”6=›³(�Ñ“�1mÿʸo³¤Finclude/toolkit/dock.hjIqÈ"‚œjIqÈ"‚œ|.�¤ééC²�nÓÿaä�ãÆÅŽy<Û À4include/toolkit/element.hjIqÈ"‚œjIqÈ"‚œ|.�¤éé �¤Ç/(h÷S°±ìÈ_°-~º âinclude/toolkit/fsm.hjIqÈ"‚œjIqÈ"‚œ|.�¤éé f “Y¾Õ^¨¥ Å(gsœ¤É¾A,yinclude/toolkit/gfxbuf.hjIqÈ"‚œjIqÈ"‚œ|.�¤éé뼌×Møï6‡Ê$çQ£Ñúý¸ÿœinclude/toolkit/image.hjIqÈ"‚œjIqÈ"‚œ|.�¤éé ço[ˆ’+T§.aÅ5‹™�Q­£‰ éinclude/toolkit/input.hjIqÈ"‚œjIqÈ"‚œ|.�¤ééE�d¦jÓ�8üë·aÁŽ—Íinclude/toolkit/layer.hjIqÈ"‚œjIqÈ"‚œ|.�¤éé߉¨ ¶×­¨ËeãCÿÀ³'sXÂinclude/toolkit/menu.hjIqÈ"‚œjIqÈ"‚œ|. �¤éérîÓ¸ »¤½ÃnBj~uVR—Ýinclude/toolkit/menu_item.hjIqÈ+5jIqÈ+5|. �¤éé+×±¿i¯ÌQ3ÿY[�bNËæy include/toolkit/output_tracker.hjIqÈ+5jIqÈ+5|. �¤ééu<šÂÉ/¾z&%-0MãÇL5qinclude/toolkit/panel.hjIqÈ+5jIqÈ+5|. �¤éé f‰¥‚)î8 ‡­A‡ùf#Ó ˜Ýinclude/toolkit/popup.hjIqÈ+5jIqÈ+5|. �¤é逬cë[æö}Ø&kTÒGf,^óinclude/toolkit/primitives.hjIqÈ+5jIqÈ+5|.�¤éé "|;jtô÷ƒ%ÛÔ†û'�Ú§»include/toolkit/rectangle.hjIqÈ+5jIqÈ+5|.�¤éé WV-ÃWØK‘ÚôŠâùÿñï°æ¦Winclude/toolkit/resizebar.hjIqÈ+5jIqÈ+5|.�¤éé ƒoì¿ì‹ i&›ë»,Ïè0݆¥ include/toolkit/resizebar_area.hjIqÈ+5jIqÈ+5|.�¤ééäÛu~PK–½G‹ßLØ�E��Ïinclude/toolkit/root.hjIqÈ+5jIqÈ+5|.�¤ééªÿ´u>6Rwí¾5$¾ØÃ½~¾¸Þfinclude/toolkit/style.hjIqÈ+5jIqÈ+5|.�¤ééq²cßðæ§ ¢Y¥ÒØG¯ïšinclude/toolkit/surface.hjIqÈ+5jIqÈ+5|.�¤ééáÞé@3¹Ø0Q |@Ç”#ºÏM£include/toolkit/test.hjIqÈ+5jIqÈ+5|.�¤ééLQ�=i;<áôþ<`\dÖê†include/toolkit/tile.hjIqÈ+5jIqÈ+5|.�¤éé€7Ö¡Êû”û“Iòh–%'ÿ<„Kinclude/toolkit/titlebar.hjIqÈ+5jIqÈ+5|.�¤éé ;8|œuùQžòÉô�ñóÑb!include/toolkit/titlebar_button.hjIqÈ+5jIqÈ+5|.�¤éé Ÿ�º>‚]Þ!™ªiÐL÷B‹ˆâ‡¿ include/toolkit/titlebar_title.hjIqÈ+5jIqÈ+5|.�¤éé7î÷Bkx·'Žg l‡Zé–Šinclude/toolkit/toolkit.hjIqÈ+5jIqÈ+5|.�¤ééâo_»xÄ3\+މðþMÇa’include/toolkit/util.hjIqÈ+5jIqÈ+5|.�¤éé9²›eb-޳ ¨®:Ml?BÆIêQinclude/toolkit/window.hjIqÈ+5jIqÈ+5|.�¤éé#ºßõ´´b^†ôZLŽORiNAµuinclude/toolkit/workspace.hjIqÈ+5jIqÈ+5|.�¤ééò¹WUk O1¹Ì&©&Ï¿¸@º³iwyu-mappings.impjIqÈ+5jIqÈ+5|.�¤éé5ï/úÕØLŠÂèˆ1òD�ÑÂ/9Zlibcairo-fontconfig.suppjIqÈ:R{jIqÈ:R{|.�¤ééG$R<•öBÁ̵Á3LKÅ–¼öù libdrm.suppjIqÈ:R{jIqÈ:R{|. �¤ééNh²¶*¼@¯c·àÅùÕ›óxf«libgallium.suppjIqÈ:R{jIqÈ:R{|."�¤éé}—4Ã� å¹ÛÓ×E58†ûˆprotocols/CMakeLists.txtjIqÈ?tÏjIqÈ?tÏ|.#�¤éél_.®DG9’�ÆcÏéOšÃA6É&protocols/ext-input-observation-v1.xmljIqÈ?tÏjIqÈ?tÏ|.$�¤éé ÷§Šh­Í[éƒ œ�O²™Î#\&protocols/wlmaker-icon-unstable-v1.xmljIqÈ?tÏjIqÈ?tÏ|.&�¤éé ¿¸žÊcþàôQ¾>£NVŽ¿;‰ø”share/CMakeLists.txtjIqÈBÆjIqÈBÆ|.(�¤éé 9ÿÌÐ85Œi´8"y¯»Ì¸share/Themes/Debian.plistjIqÈC jIqÈC |.)�¤éé A8¹S½r fC盼Ç2UcÚÙ¾”share/Themes/DebianDouble.plistjIqÈC jIqÈC |.*�¤éé B…]H(W=’9Ø�9£âã[,üug�share/Themes/DebianLarger.plistjIqÈC jIqÈC |.+�¤éé @ü’ “o"NZÞPÿ6€iÒR {Ñshare/Themes/Default.plistjIqÈC jIqÈC |.,�¤éé1÷ñÈ€åÇIªud±­Ìh ‚ ÐÝ&share/org.wlmaker.wlmaker.metainfo.xmljIqÈC jIqÈC |.-�¤éé Sµ/3²‰�J,ì¬k 9œy<share/wlmaker-48x48.pngjIqÈC jIqÈC |..�¤é镚›Õgc¦%O�yvxÕ9hôlzshare/wlmaker-64x64.pngjIqÈC jIqÈC |./�¤ééžêfRÓÆ'Ÿ–)Âp<³Ié:t¼share/wlmaker.desktop.injIqÈC jIqÈC |.0�¤éé1õæeß";½9-¾D†cÃ^k$ cshare/wlmaker.svgjIqÈC jIqÈC |.1�¤éé>øAÑòš^=~ÍsæõqŽ3ÌæÙ#share/wlmaker.wlmbattery.desktop.injIqÈC jIqÈC |.2�¤éééÞ¨µ„OH-Ç¥{6î]bdœµ!share/wlmaker.wlmclock.desktop.injIqÈC jIqÈC |.3�¤ééÖât³Ü»”ácó˜é9rc©Æ$share/wlmaker.wlmcpugraph.desktop.injIqÈC jIqÈC |.4�¤ééávðØ©=¡û6ÑCÆ¥ïàÊhjy share/wlmaker.wlmeyes.desktop.injIqÈC jIqÈC |.5�¤éé $ ñ†VŠy(�HÒÀ³¹úF$share/wlmaker.wlmmemgraph.desktop.injIqÈC jIqÈC |.6�¤ééÎ2�¶ëï¢ÿmxÕ'ˆl•‰ÿçhZ$share/wlmaker.wlmnetgraph.desktop.injIqÈJµ¬jIqÈJµ¬|.9�¤éé'ñÆ‘)±òNy’ÿëc s¯w,share/wlmaker/icons/README.mdjIqÈK¨jIqÈK¨|.:�¤éé{Ãzy†mª^$ òjX¨7an$share/wlmaker/icons/chrome-56x56.pngjIqÈK¨jIqÈK¨|.;�¤éé|¿´ŸnÓâ¿è)ƒ¸ð�Ê1§ý›ý&share/wlmaker/icons/chromium-56x56.pngjIqÈK¨jIqÈK¨|.<�¤éémDèYkÞ7¸àS0¸N²³�œÕ }Zô"share/wlmaker/icons/clip-56x56.pngjIqÈK¨jIqÈK¨|.=�¤éé !HÊ#Q”_HbC AÙD 20£Õs%share/wlmaker/icons/firefox-56x56.pngjIqÈK¨jIqÈK¨|.>�¤éé ÒF£òüé¤h¶ ÑøÀ´Þø™0Þä]&share/wlmaker/icons/terminal-56x56.pngjIqÈK¨jIqÈK¨|.?�¤éé %*â8û[5QARÁ¯_À^ÎÌshare/wlmbattery-64x64.pngjIqÈK¨jIqÈK¨|.@�¤éé$GÓ/Τ{¹6Ø)® »‚Ì8×–Oshare/wlmclock-64x64.pngjIqÈK¨jIqÈK¨|.A�¤éé ž`�%™åDmp3Wéüéî~„ˆshare/wlmcpugraph-64x64.pngjIqÈK¨jIqÈK¨|.B�¤éé ³Hâe>ÎúT!F°Î« ž©ü–=°share/wlmeyes-64x64.pngjIqÈK¨jIqÈK¨|.C�¤éé½�Àu~ª3õZ,…>ý/õ�ZÒLshare/wlmmemgraph-64x64.pngjIqÈK¨jIqÈK¨|.D�¤ééƺøoŒ¹;‚dÔÄMÑ6/`share/wlmnetgraph-64x64.pngjIqÈK¨jIqÈK¨|.F�¤ééNkp YÈ ±WLç\Ašž�plR|src/CMakeLists.txtjIqÈUeÙjIqÈUeÙ|.G�¤éé€éÇð“£²®Rᾨ@�ÌÑ$B src/action.cjIqÈUeÙjIqÈUeÙ|.H�¤ééaöV¿‘‘¹F*ˆ_ÊúÐJhÁ]ð src/action.hjIqÈUeÙjIqÈUeÙ|.I�¤éé,濬afyBB˜xlPëâbûÖsrc/action_item.cjIqÈUeÙjIqÈUeÙ|.J�¤éé \}ÞBf'ϼ*…'=`óü²-9¯src/action_item.hjIqÈUeÙjIqÈUeÙ|.L�¤ééA¨ä‰,êC¯yjÿößúaBßsrc/backend/CMakeLists.txtjIqÈZÞ…jIqÈZÞ…|.M�¤één”`ªÈ1)~«ø™iæþ7¥ôíOˆ<src/backend/backend.cjIqÈZÞ…jIqÈZÞ…|.N�¤éé.Ç0,6ú?ˆ¹Íù ÒøND‰src/backend/output.cjIqÈZÞ…jIqÈZÞ…|.O�¤ééoqŸ~g£'ë»®0mÞLâ>MYŠ…src/backend/output_config.cjIqÈZÞ…jIqÈZÞ…|.P�¤ééO&÷œê •9Å�dH}jeÝØ‰«�ìsrc/backend/output_manager.cjIqÈZÞ…jIqÈZÞ…|.Q�¤éé%qÙÙ®�<ý£�ÆÖ.Œ¡A,³csrc/background.cjIqÈZÞ…jIqÈZÞ…|.R�¤ééz×(ë……Øs­ °ËéË\¿f­*src/background.hjIqÈZÞ…jIqÈZÞ…|.S�¤éé¸Ç1æÆ(qÑ]ZQ@ˆ¨Û;C%src/backtrace.cjIqÈZÞ…jIqÈZÞ…|.T�¤éé�4˜«1‡Ê¶ªw3H­ ýª& src/clip.cjIqÈZÞ…jIqÈZÞ…|.U�¤éé¹ò Žˆ¬žsˆåGtš#·Sà¨ò src/clip.hjIqÈZÞ…jIqÈZÞ…|.V�¤éé-Ö¼P?Êç&—€ýÅ�¡JŸ >6 src/config.cjIqÈZÞ…jIqÈZÞ…|.W�¤éédÕuíßZ¦/pÐ÷–/7B¾J¥M src/config.hjIqÈZÞ…jIqÈZÞ…|.X�¤ééGš«H.?НTODG¬ÝFˆg;ƒk‡ src/corner.cjIqÈZÞ…jIqÈZÞ…|.Y�¤éé,{êåøí€œ<«Žk‰´„†åù&ò src/corner.hjIqÈhMjIqÈhM|.Z�¤éé: Vú’ãL„n…@C>ªÜ~ÀÇEÔä src/dock.cjIqÈhMjIqÈhM|.[�¤éé®ûß{n8´ç[Û”Ù‚wÚ src/dock.hjIqÈhMjIqÈhM|.\�¤ééO -á6"LÀ7ÚDíu8ùå$¹ê src/files.cjIqÈhMjIqÈhM|.]�¤éé òoèÝ€IêãZ:W�¸„¤8sÁ‚r src/files.hjIqÈhMjIqÈhM|.^�¤ééQa4¤¦¶—•b’Ž£qÙœGv*H7src/icon_manager.cjIqÈhMjIqÈhM|._�¤ééþ¿JJßvƒÈOöæµ,×}·k³src/icon_manager.hjIqÈhMjIqÈhM|.`�¤éé;ÈŒµgâu@Ãxk”ˆö¼±´)é src/idle.cjIqÈhMjIqÈhM|.a�¤éé —‚Ž9¢Ð&/‡*ˆ½¨@”—Søª src/idle.hjIqÈhMjIqÈhM|.c�¤ééJ¼Ï4Ô;€€Ò¸src/toolkit/buffer.cjIqÈ•àjIqÈ•à|.†�¤éé; [¹ÑÕQÔ©ZÞ•#»Ÿ%A£7ÛÈsrc/toolkit/button.cjIqÈ•àjIqÈ•à|.‡�¤éé8!T‡½Vë{†9.%I7ãûP™9src/toolkit/container.cjIqÈ•àjIqÈ•à|.ˆ�¤éé6ä 2›Ä’èoîëDúC„xQƒˆÚàÎsrc/toolkit/dock.cjIqÈ•àjIqÈ•à|.‰�¤éé’{b°×n V ¶ò5®Ž$T~Íjݶsrc/toolkit/element.cjIqÈ•àjIqÈ•à|.Š�¤ééM,61Y‹Ã¶¶Ô8Füó @�src/toolkit/fsm.cjIqÈ•àjIqÈ•à|.‹�¤éé‹ãöÎht_»9 íPˆd%]�`ïŸÐsrc/toolkit/gfxbuf.cjIqÈ•àjIqÈ•à|.Œ�¤ééj«ÇQ°�¿®¼‰©ð&:¾¦src/toolkit/image.cjIqÈ•àjIqÈ•à|.��¤éé¦XIÍÊóŸ|N€‘Œu´Yà�ñsrc/toolkit/input.cjIqÈ•àjIqÈ•à|.Ž�¤ééF<½1Ϭ² Å5wj³L[Óž#<src/toolkit/layer.cjIqÈ¥"ejIqÈ¥"e|.��¤é鑊j!Þ\TÑ290-%¤Uõ©cIÊÍ�src/toolkit/menu.cjIqÈ¥"ejIqÈ¥"e|.��¤éé³*¡»ï‚F·[-Ûß?#ͧÆÌjsrc/toolkit/menu_item.cjIqÈ¥"ejIqÈ¥"e|.‘�¤éé?\T5 êhJk†a, øÖò lsrc/toolkit/output_tracker.cjIqÈ¥"ejIqÈ¥"e|.’�¤ééFŠ;}Wæþj³n–ø v^J?S3�Ñsrc/toolkit/panel.cjIqÈ¥"ejIqÈ¥"e|.“�¤éé ¦«X†o™¤½2v¹šLœm%Dªsrc/toolkit/popup.cjIqÈ¥"ejIqÈ¥"e|.”�¤éé@��Eûíâr£¹óøöÛ‘Š”¡fßsrc/toolkit/primitives.cjIqÈ¥"ejIqÈ¥"e|.•�¤éé6OQøñCKh—�Þÿ•ZôâÙ5 Çsrc/toolkit/rectangle.cjIqÈ¥"ejIqÈ¥"e|.–�¤éé7Ö¯›ÈO3îìV~¡‘ŽT²I÷§-¸src/toolkit/resizebar.cjIqÈ¥"ejIqÈ¥"e|.—�¤éé2íðEo‹7cÎ<ùR±SøÔÃ:o%src/toolkit/resizebar_area.cjIqÈ¥"ejIqÈ¥"e|.˜�¤éé—í|‹¡½·ñ#F*6é©‹src/toolkit/root.cjIqÈ¥"ejIqÈ¥"e|.™�¤éé#Dj9¼ãìvê#¥rIªÝÀ÷^Hsrc/toolkit/style.cjIqÈ¥"ejIqÈ¥"e|.š�¤éé{£Ö¨7²VÎDÞ<¼†±ÛƱ½src/toolkit/surface.cjIqÈ¥"ejIqÈ¥"e|.›�¤éé*z]™©N=†ýß)žs8Ÿƒ+*src/toolkit/test.cjIqÈ´d¬jIqÈ´d¬|.œ�¤éé*ˆbÌ5ïCöã€Ø„GEb{Ôwò¬src/toolkit/tile.cjIqÈ´d¬jIqÈ´d¬|.��¤éé[®–LönðúçÙ‚ Ü‰^¼þj×src/toolkit/titlebar.cjIqÈ´d¬jIqÈ´d¬|.ž�¤éé7½'ÂÒ‘›Š¨Ëá·ã¸ÆLhA‘src/toolkit/titlebar_button.cjIqÈ´d¬jIqÈ´d¬|.Ÿ�¤ééFþbªôñh„¨0¾Ï¨õ؆�&žØÇsrc/toolkit/titlebar_title.cjIqÈ´d¬jIqÈ´d¬|. �¤ééHÎC�$ຠ®ü¼�.Ãc˜2r¾Qsrc/toolkit/toolkit.mdjIqÈ´d¬jIqÈ´d¬|.¡�¤éé%}®è×à=Ÿ°¢Œ4çhŸ®2Ò)Ô src/toolkit/util.cjIqÈ´d¬jIqÈ´d¬|.¢�¤éé]À&Ì‹?4¯ý:¶Wœ#‰¶W€Esrc/toolkit/window.cjIqÈ´d¬jIqÈ´d¬|.£�¤ééMÍSƒ§yéŠG —4h—ÒYJ/¸Ö?src/toolkit/workspace.cjIqÈæòjIqÈæò|.¤�¤ééB-Ð�—šáBˆ)Öòh·`ݾÒO src/wlmaker.cjIqÈæòjIqÈæò|.¥�¤ééÊ�úÅ‚í[c™‡Èíó„RSÅ!Ü£src/wlmbacktrace.cjIqÈæòjIqÈæò|.¦�¤ééåPEº�E:ÈéÁI××”IZµ®~®src/wlmbacktrace.hjIqÈæòjIqÈæò|.§�¤éé¨0V®Gþh··YÎaèýòÒwäsrc/x11_cursor.xpmjIqÈæòjIqÈæò|.¨�¤ééYʘ»×/úxY6�UëÂiL+ ǘÅsrc/xdg_decoration.cjIqÈæòjIqÈæò|.©�¤éé`ÊŸŒ¢Ö10¢cè/#…'ðµásrc/xdg_decoration.hjIqÈæòjIqÈæò|.ª�¤éé%ïùéì<Ök‰¬)B$�…¾ *src/xdg_popup.cjIqÈæòjIqÈæò|.«�¤éé=ƒ ùM½¸l„þ}äá" Šsrc/xdg_popup.hjIqÈæòjIqÈæò|.¬�¤éé«ÅP"Wtests/data/menu-generate.plistjIqÈÛ, jIqÈÛ, |.Å�¤éé6᧬EÌ5!� §îÅׇ*e^¼ÝVtests/data/menu-include.plistjIqÈÛ, jIqÈÛ, |.Æ�¤éé7´‚oø<–õÄþ•WÍ®K¶¾àTtests/data/menu.plistjIqÈßÀÇjIqÈßÀÇ|.É�¤ééæ�⛲ÑÖCK‹)®wZØÂäŒS‘tests/data/subdir/wlmaker/a.txtjIqÈßÀÇjIqÈßÀÇ|.Ë�¤ééŒÊ&Á– ÞãÃE~+UmªžŒf�Õ)tests/data/toolkit/menu_item_disabled.pngjIqÈácìjIqÈácì|.Ì�¤ééûS“¡ÀŒ:I²Bû(í±$¿¥b(tests/data/toolkit/menu_item_enabled.pngjIqÈácìjIqÈácì|.Í�¤ééü£Ì>Dôž`¨ÏÙñ?í¶€Ò½,tests/data/toolkit/menu_item_highlighted.pngjIqÈácìjIqÈácì|.Î�¤ééõÛHʽ„°;²ß:FÁu³ø¼R1tests/data/toolkit/menu_item_submenu_disabled.pngjIqÈácìjIqÈácì|.Ï�¤ééH>ÎØÑ]iN™NEìßS¸ñ>µ–æj¥1tests/data/toolkit/primitive_close_icon_large.pngjIqÈâ+~jIqÈâ+~|.Ó�¤éé‹&؇Ö/hÞíͳzg…ÿî.:Ͻp~pæ‡ahd ØÕ²ÖÿåÜ+tests/data/toolkit/title_button_blurred.pngjIqÈâ+~jIqÈâ+~|.â�¤éé´Ê¦9 ¨Sê±Û���ùKY#4tests/data/toolkit/title_button_focussed_pressed.pngjIqÈâ+~jIqÈâ+~|.ã�¤éé…5TÌïàºéºB[êZ‰d© ¥Ü5tests/data/toolkit/title_button_focussed_released.pngjIqÈâ+~jIqÈâ+~|.ä�¤éé%rw†y1ݱ93‡£½—O6Â+Üaµ%tests/data/toolkit/title_focussed.pngjIqÈâ+~jIqÈâ+~|.å�¤éé=Íî>¶�;—‚HåS9„À"·hðtests/data/wlmaker.desktopjIqÈâ+~jIqÈâ+~|.æ�¤éé˜Áp�?º­˜Ž"Bó—¥4eþtests/desktop_parser_test.cjIqÈâ+~jIqÈâ+~|.ç�¤éé/ [éïïS–Ývý³Ú‡Ö÷oätests/input_test.cjIqÈâ+~jIqÈâ+~|.è�¤ééÔ=âŠnròjºÁ•s_g#9ðÜtests/toolkit_test.cjIqÈâ+~jIqÈâ+~|.é�¤éé TÀ dÔ¬�¨ÍWãXëJ$"g1Û*tests/wlmaker_test.cjIqÈâ+~jIqÈâ+~|.ê�¤é驸»Íwª·Èm6‹RÛæÈÜ›[Îtests/wlmtool_test.cjIqÈð >jIqÈð >|.í�¤éé‘M+h@þ<Õ²ù…o’ÓpÄ¢v£$third_party/protocols/CMakeLists.txtjIqÈñ#‰jIqÈñ#‰|.î�¤ééHÖ/Õ�Ñ‚G¥¸ s•Ã)p™£€5third_party/protocols/wlr-layer-shell-unstable-v1.xmljIqÈñ#‰jIqÈñ#‰|.ð�¤ééAÇÙ~ö2Òh!2Á¹9Úq5õétool/CMakeLists.txtjIqÈñmÄjIqÈñmÄ|.ò�¤éé[ac +¬¹+ñ!0§&©–½"tool/desktop-parser/CMakeLists.txtjIqÈõújIqÈõú|.ó�¤é邘³k`Ž·Üf`dÙÄÀëRa~$tool/desktop-parser/desktop-parser.cjIqÈõújIqÈõú|.ô�¤ééDƒ á}Ø;vh»¦\Æ1ôùT1«Z tool/item.cjIqÈõújIqÈõú|.ö�¤éé ö â˜³«%üêüÐ oΉ)îCç K tool/item.hjIqÈõújIqÈõú|.÷�¤ééJ?ÆQƒ˜�ó•JO$ŠÓT¼¹ tool/menu.cjIqÈõújIqÈõú|.ø�¤éé®'›âµ;¢à)å9‚3Ã�kü tool/menu.hjIqÈõújIqÈõú|.ù�¤éé,Sæõuj §hyÒKßô6}xtool/wlmtool.cjIqÈõújIqÈõú|.ú�¤éép]7ò9JÅÿNfÉX¬k¾œÔ¿ø valgrind.mdTREEa319 15 z&þÙñ/)xô"»!àºEœë±doc14 0 zAÙŠí£‘ ]f¾ÎÈÒ£ÝG²etc7 0 øxä-�¡Â0Ì(‹‡s£ÿ!p» \src107 3 ÖÃÝ$àÂV颢åðŸYÈÉ`:‡input9 0 1Dåî‚gV-¿Ã'æf³¢Šgbackend5 0 "dUª*jøjm� ­,én‘ÓcÃãtoolkit35 0 nÏœŸ¹¸Ùko¯²]Õ(ì�m‹þ„apps28 2 KŒO@ ÛçñM~¤«‘Ü‘�J�òprimitives9 0 @—ª[§áÂH[g/ðUGC¢¿^Ðlibwlclient9 0 <‘ ‹©ê¹Þ£rk&¬ îÝ!tool9 1 x™’¹{ æç{4œ�mí#‹9adesktop-parser3 0 ¢D�Ê8ž3t]øß³Lrcmake5 0 K¸M�9õŠgü.eæÚ€~�›2Wshare28 2 ½¦aMëç[—ŒŸC#IÛ&<’â¤Themes4 0 ÍèÉ ) ­=iöÚR´À¼wlmaker6 1 ~23ŠÅ€í©9 ;Û1‰QÚ®�icons6 0 ÆEÛiQî· ¿„_'›qÚ¾Œtests44 1 ; ßîy‚sîŽhN2;§8°Ã«data37 4 ª‹õÉ»pßyUÊã< ôóž}óBÉÏ yqê+µsubdir1 1 ‚kS:þ«÷Aµ›O¸-�=[q Vwlmaker1 0 e¤WBZgœ¾šß 'Ax]<ê»D§toolkit26 0 ÛMQ[Yz@œÌïO¸‘t¼^r&.github9 1 ãŒð³}øeÔIâ/ACôQworkflows9 0 ß™üxLb%KÔx¾©f?)ÞÅinclude38 2 ¡SD“;R~\ÛìCoÐT…,,Àóbackend4 0 ЪMkÃοãD6,C�VI/3Mtoolkit34 0 ÎôK8±:öóy®®CVŠQ*#examples3 0 £ñ¨‡�ãÀ‘R ]¨<š j ÜíTemplates2 0 †–Z"ìÒ:°½»þ ªÏ–Esõ8§protocols3 0 ÏŽúä‹,:/Ç©âZAµððsubmodules3 0 Ô+h*TÆ•(x]ÞÀ˜é¤©-third_party2 1 geŸ åÀLOD‡²Ï…÷§e'protocols2 0 ý¼,ûòv9]õ•°o¸èˆð×ñ·˜Ž*†ž9±¸¿.ç<�'wlmaker-0.8.1/.git/HEAD0000644000175100017510000000005115222270710014047 0ustar runnerrunnerd08e0c08c7a14690c770a576be341f227e5ac2d8 wlmaker-0.8.1/.git/logs/0000755000175100017510000000000015222270710014373 5ustar runnerrunnerwlmaker-0.8.1/.git/logs/HEAD0000644000175100017510000000034615222270710015022 0ustar runnerrunner0000000000000000000000000000000000000000 d08e0c08c7a14690c770a576be341f227e5ac2d8 runner 1783198152 +0000 checkout: moving from master to refs/tags/v0.8.1 wlmaker-0.8.1/.git/shallow0000644000175100017510000000005115222270710015017 0ustar runnerrunnerd08e0c08c7a14690c770a576be341f227e5ac2d8 wlmaker-0.8.1/.git/info/0000755000175100017510000000000015222270707014370 5ustar runnerrunnerwlmaker-0.8.1/.git/info/exclude0000755000175100017510000000036015222270707015746 0ustar runnerrunner# git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ wlmaker-0.8.1/.git/modules/0000755000175100017510000000000015222270711015100 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/0000755000175100017510000000000015222270713017530 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/index0000644000175100017510000000172215222270711020562 0ustar runnerrunnerDIRC jIqÉ8ª¦jIqÉ8ª¦|/¼�¤éé3é�àÉ"e°�Q¾ÃÕ�-Äã� ¬m .gitignorejIqÉ8ª¦jIqÉ8ª¦|/½�¤ééÿÚ”5«¢*’ì.ûÑ¢“åù×ù! .gitmodulesjIqÉ8ª¦jIqÉ8ª¦|/¾�¤éé1%Âä¡]$öóÔjÄ=âv&{³CMakeLists.txtjIqÉ8ª¦jIqÉ8ª¦|/¿�¤éé,]&랟‹+K “fÝ©œo×Ó\dLICENSEjIqÉ8¹èNjIqÉ8¹èN|/À�¤ééÏõìaÎ~®*ÂÝýÁ³ô?Î¤Š �Ú README.mdjIqÉ8¹èNjIqÉ8¹èN|/Áàééy9!‡l˜ä—YM{¸›¸›-:- libbacktracejIqÉ8¹èNjIqÉ8¹èN|/Âàéé¨åá ‡?gõWÜpå@zôU?5íÙlibdrmjIqÉ8¹èNjIqÉ8¹èN|/Ãàéévt Eƒ®Ig^{¨!=1î ªÒ libxkbcommonjIqÉ8¹èNjIqÉ8¹èN|/ÄàééFef×ó¾B¼0¯T;8EUFf Ø÷pixmanjIqÉ8¹èNjIqÉ8¹èN|/Åàéésm¬g `Ü@mÄ›°kèxP†waylandjIqÉ8¹èNjIqÉ8¹èN|/Æàééˆ"0ѵxÐØ†˜fÚfÙ`Žù(wayland-protocolsjIqÉ8¹èNjIqÉ8¹èN|/ÇàééÁÓ…6É&F˜ÿ&„> AèJÄwlrootsTREE12 0 ½Æåõàî±nñ·ñÈßnðþfRv“ 1783198152 +0000 clone: from https://github.com/phkaeser/wlmaker-dependencies.git 563e30ef004d150d4cca20eb8553627f39b9ddc0 c59371abcad0d5e954b9627d0de1de43d35dce66 runner 1783198153 +0000 checkout: moving from main to c59371abcad0d5e954b9627d0de1de43d35dce66 wlmaker-0.8.1/.git/modules/dependencies/logs/refs/0000755000175100017510000000000015222270710021430 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/logs/refs/remotes/0000755000175100017510000000000015222270710023106 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/logs/refs/remotes/origin/0000755000175100017510000000000015222270710024375 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/logs/refs/remotes/origin/HEAD0000644000175100017510000000036615222270710025026 0ustar runnerrunner0000000000000000000000000000000000000000 563e30ef004d150d4cca20eb8553627f39b9ddc0 runner 1783198152 +0000 clone: from https://github.com/phkaeser/wlmaker-dependencies.git wlmaker-0.8.1/.git/modules/dependencies/logs/refs/heads/0000755000175100017510000000000015222270710022514 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/logs/refs/heads/main0000644000175100017510000000036615222270710023370 0ustar runnerrunner0000000000000000000000000000000000000000 563e30ef004d150d4cca20eb8553627f39b9ddc0 runner 1783198152 +0000 clone: from https://github.com/phkaeser/wlmaker-dependencies.git wlmaker-0.8.1/.git/modules/dependencies/shallow0000644000175100017510000000012215222270711021115 0ustar runnerrunner563e30ef004d150d4cca20eb8553627f39b9ddc0 c59371abcad0d5e954b9627d0de1de43d35dce66 wlmaker-0.8.1/.git/modules/dependencies/info/0000755000175100017510000000000015222270710020460 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/info/exclude0000755000175100017510000000036015222270710022036 0ustar runnerrunner# git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ wlmaker-0.8.1/.git/modules/dependencies/config0000644000175100017510000000176615222270713020732 0ustar runnerrunner[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true worktree = ../../../dependencies [remote "origin"] url = https://github.com/phkaeser/wlmaker-dependencies.git fetch = +refs/heads/main:refs/remotes/origin/main [branch "main"] remote = origin merge = refs/heads/main [gc] auto = 0 [http "https://github.com/"] extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUpoZFhSb2JtUWlMQ0poZW1NaU9sc2ljMmwwWlM4eU1EUTVNemcwTURrNU9TSmRMQ0psZUhBaU9qRTNPRE15TURFM05UQXNJbWxoZENJNk1UYzRNekU1T0RFMU1Dd2lhWE56SWpvaVoybDBhSFZpSWl3aWFuUnBJam9pT0RKbFpXTmxZMlV0T1RkbE5TMDBOakkwTFRreVlqWXROVFJoWVdZNE16aGhaVGhsSWl3aWMzVmlJam9pYVc1MFpXZHlZWFJwYjI0dk1UVXpOamdpTENKMlpYSWlPako5LmYtN0ljd1lxcDhaUUMzYlV4bWlFa3NzN0pqdzVFZ19tdVduU3JXWVMtMDNzM0ROS2Q0Wi1Kd3dzaHRvUnB0X0FnVm96amdaRGhmZDlOMElOU1ExVndn [url "https://github.com/"] insteadOf = git@github.com: insteadOf = org-130065133@github.com: wlmaker-0.8.1/.git/modules/dependencies/packed-refs0000644000175100017510000000016015222270710021631 0ustar runnerrunner# pack-refs with: peeled fully-peeled sorted 563e30ef004d150d4cca20eb8553627f39b9ddc0 refs/remotes/origin/main wlmaker-0.8.1/.git/modules/dependencies/description0000755000175100017510000000011115222270710021767 0ustar runnerrunnerUnnamed repository; edit this file 'description' to name the repository. wlmaker-0.8.1/.git/modules/dependencies/hooks/0000755000175100017510000000000015222270710020650 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/hooks/pre-commit.sample0000755000175100017510000000316115222270710024133 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=$(git hash-object -t tree /dev/null) fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --type=bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff-index --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- wlmaker-0.8.1/.git/modules/dependencies/hooks/push-to-checkout.sample0000755000175100017510000000533715222270710025270 0ustar runnerrunner#!/bin/sh # An example hook script to update a checked-out tree on a git push. # # This hook is invoked by git-receive-pack(1) when it reacts to git # push and updates reference(s) in its repository, and when the push # tries to update the branch that is currently checked out and the # receive.denyCurrentBranch configuration variable is set to # updateInstead. # # By default, such a push is refused if the working tree and the index # of the remote repository has any difference from the currently # checked out commit; when both the working tree and the index match # the current commit, they are updated to match the newly pushed tip # of the branch. This hook is to be used to override the default # behaviour; however the code below reimplements the default behaviour # as a starting point for convenient modification. # # The hook receives the commit with which the tip of the current # branch is going to be updated: commit=$1 # It can exit with a non-zero status to refuse the push (when it does # so, it must not modify the index or the working tree). die () { echo >&2 "$*" exit 1 } # Or it can make any necessary changes to the working tree and to the # index to bring them to the desired state when the tip of the current # branch is updated to the new commit, and exit with a zero status. # # For example, the hook can simply run git read-tree -u -m HEAD "$1" # in order to emulate git fetch that is run in the reverse direction # with git push, as the two-tree form of git read-tree -u -m is # essentially the same as git switch or git checkout that switches # branches while keeping the local changes in the working tree that do # not interfere with the difference between the branches. # The below is a more-or-less exact translation to shell of the C code # for the default behaviour for git's push-to-checkout hook defined in # the push_to_deploy() function in builtin/receive-pack.c. # # Note that the hook will be executed from the repository directory, # not from the working tree, so if you want to perform operations on # the working tree, you will have to adapt your code accordingly, e.g. # by adding "cd .." or using relative paths. if ! git update-index -q --ignore-submodules --refresh then die "Up-to-date check failed" fi if ! git diff-files --quiet --ignore-submodules -- then die "Working directory has unstaged changes" fi # This is a rough translation of: # # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX if git cat-file -e HEAD 2>/dev/null then head=HEAD else head=$(git hash-object -t tree --stdin &2 exit 1 } unset GIT_DIR GIT_WORK_TREE cd "$worktree" && if grep -q "^diff --git " "$1" then validate_patch "$1" else validate_cover_letter "$1" fi && if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" then git config --unset-all sendemail.validateWorktree && trap 'git worktree remove -ff "$worktree"' EXIT && validate_series fi wlmaker-0.8.1/.git/modules/dependencies/hooks/prepare-commit-msg.sample0000755000175100017510000000272415222270710025573 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first one removes the # "# Please enter the commit message..." help message. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. COMMIT_MSG_FILE=$1 COMMIT_SOURCE=$2 SHA1=$3 /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" # case "$COMMIT_SOURCE,$SHA1" in # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; # *) ;; # esac # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" # if test -z "$COMMIT_SOURCE" # then # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" # fi wlmaker-0.8.1/.git/modules/dependencies/hooks/update.sample0000755000175100017510000000710215222270710023340 0ustar runnerrunner#!/bin/sh # # An example hook script to block unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --type=bool hooks.allowunannotated) allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) denycreatebranch=$(git config --type=bool hooks.denycreatebranch) allowdeletetag=$(git config --type=bool hooks.allowdeletetag) allowmodifytag=$(git config --type=bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero=$(git hash-object --stdin &2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 wlmaker-0.8.1/.git/modules/dependencies/hooks/pre-rebase.sample0000755000175100017510000001144215222270710024105 0ustar runnerrunner#!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up to date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END wlmaker-0.8.1/.git/modules/dependencies/hooks/applypatch-msg.sample0000755000175100017510000000073615222270710025015 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup commitmsg="$(git rev-parse --git-path hooks/commit-msg)" test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} : wlmaker-0.8.1/.git/modules/dependencies/hooks/commit-msg.sample0000755000175100017510000000366415222270710024143 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines and messages that # would confuse 'git am'. ret=0 test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. ret=1 } comment_re="$( { git config --get-regexp "^core\.comment(char|string)\$" || echo '#' } | sed -n -e ' ${ s/^[^ ]* // s|[][*./\]|\\&|g s/^auto$/[#;@!$%^&|:]/ p }' )" scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$" comment_line="^${comment_re}.*" blank_line='^[ ]*$' # Disallow lines starting with "diff -" or "Index: " in the body of the # message. Stop looking if we see a scissors line. line="$(sed -n -e " # Skip comments and blank lines at the start of the file. /${scissors_line}/q /${comment_line}/d /${blank_line}/d # The first paragraph will become the subject header so # does not need to be checked. : subject n /${scissors_line}/q /${blank_line}/!b subject # Check the body of the message for problematic # prefixes. : body n /${scissors_line}/q /${comment_line}/b body /^diff -/{p;q;} /^Index: /{p;q;} b body " "$1")" if test -n "$line" then echo >&2 "Message contains a diff that will confuse 'git am'." echo >&2 "To fix this indent the diff." ret=1 fi exit $ret wlmaker-0.8.1/.git/modules/dependencies/hooks/pre-receive.sample0000755000175100017510000000104015222270710024257 0ustar runnerrunner#!/bin/sh # # An example hook script to make use of push options. # The example simply echoes all push options that start with 'echoback=' # and rejects all pushes when the "reject" push option is used. # # To enable this hook, rename this file to "pre-receive". if test -n "$GIT_PUSH_OPTION_COUNT" then i=0 while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" do eval "value=\$GIT_PUSH_OPTION_$i" case "$value" in echoback=*) echo "echo from the pre-receive-hook: ${value#*=}" >&2 ;; reject) exit 1 esac i=$((i + 1)) done fi wlmaker-0.8.1/.git/modules/dependencies/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315222270710025671 0ustar runnerrunner#!/usr/bin/perl use strict; use warnings; use IPC::Open2; # An example hook script to integrate Watchman # (https://facebook.github.io/watchman/) with git to speed up detecting # new and modified files. # # The hook is passed a version (currently 2) and last update token # formatted as a string and outputs to stdout a new update token and # all files that have been modified since the update token. Paths must # be relative to the root of the working tree and separated by a single NUL. # # To enable this hook, rename this file to "query-watchman" and set # 'git config core.fsmonitor .git/hooks/query-watchman' # my ($version, $last_update_token) = @ARGV; # Uncomment for debugging # print STDERR "$0 $version $last_update_token\n"; # Check the hook interface version if ($version ne 2) { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; } my $git_work_tree = get_working_dir(); my $json_pkg; eval { require JSON::XS; $json_pkg = "JSON::XS"; 1; } or do { require JSON::PP; $json_pkg = "JSON::PP"; }; launch_watchman(); sub launch_watchman { my $o = watchman_query(); if (is_work_tree_watched($o)) { output_result($o->{clock}, @{$o->{files}}); } } sub output_result { my ($clockid, @files) = @_; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # binmode $fh, ":utf8"; # print $fh "$clockid\n@files\n"; # close $fh; binmode STDOUT, ":utf8"; print $clockid; print "\0"; local $, = "\0"; print @files; } sub watchman_clock { my $response = qx/watchman clock "$git_work_tree"/; die "Failed to get clock id on '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; return $json_pkg->new->utf8->decode($response); } sub watchman_query { my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') or die "open2() failed: $!\n" . "Falling back to scanning...\n"; # In the query expression below we're asking for names of files that # changed since $last_update_token but not from the .git folder. # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the # output to file names only. Then we're using the "expression" term to # further constrain the results. my $last_update_line = ""; if (substr($last_update_token, 0, 1) eq "c") { $last_update_token = "\"$last_update_token\""; $last_update_line = qq[\n"since": $last_update_token,]; } my $query = <<" END"; ["query", "$git_work_tree", {$last_update_line "fields": ["name"], "expression": ["not", ["dirname", ".git"]] }] END # Uncomment for debugging the watchman query # open (my $fh, ">", ".git/watchman-query.json"); # print $fh $query; # close $fh; print CHLD_IN $query; close CHLD_IN; my $response = do {local $/; }; # Uncomment for debugging the watch response # open ($fh, ">", ".git/watchman-response.json"); # print $fh $response; # close $fh; die "Watchman: command returned no output.\n" . "Falling back to scanning...\n" if $response eq ""; die "Watchman: command returned invalid output: $response\n" . "Falling back to scanning...\n" unless $response =~ /^\{/; return $json_pkg->new->utf8->decode($response); } sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; $output = $json_pkg->new->utf8->decode($response); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # close $fh; # Watchman will always return all files on the first query so # return the fast "everything is dirty" flag to git and do the # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); $error = $o->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); return 0; } die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; return 1; } sub get_working_dir { my $working_dir; if ($^O =~ 'msys' || $^O =~ 'cygwin') { $working_dir = Win32::GetCwd(); $working_dir =~ tr/\\/\//; } else { require Cwd; $working_dir = Cwd::cwd(); } return $working_dir; } wlmaker-0.8.1/.git/modules/dependencies/hooks/post-update.sample0000755000175100017510000000027515222270710024327 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info wlmaker-0.8.1/.git/modules/dependencies/hooks/pre-merge-commit.sample0000755000175100017510000000064015222270710025227 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git merge" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message to # stderr if it wants to stop the merge commit. # # To enable this hook, rename this file to "pre-merge-commit". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" : wlmaker-0.8.1/.git/modules/dependencies/hooks/pre-push.sample0000755000175100017510000000253615222270710023627 0ustar runnerrunner#!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 wlmaker-0.8.1/.git/modules/dependencies/objects/0000755000175100017510000000000015222270711021157 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/objects/bd/0000755000175100017510000000000015222270711021544 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/objects/bd/c6e5f5e0eeb16ef113b71af1c81e00df6ef0fe0000444000175100017510000000063715222270711027331 0ustar runnerrunnerx+)JMU015a040031QÐKÏ,ÉLÏË/JexÙûà¤Rê†ÆÀ}‡¯NÐ=òx.÷š\$U¹ù)¥9©Å ·¦›®^¤5é�Þï‹‹& ?ýyý§"T™³obvªOfqI±^IE ƒê¡' cU¾}¾’uÄöQ¿ZµØf¨JOgW¿`W5¹×óæwk{ó NN»»rNþõË1)P%A®Ž.¾®z¹) _ß$ž«[§uèî߃›¿ØŸ[ÒÅÓxËÐÌr2“’“³KŠ“S*-ÛsfÈ<™)è[½cöŽÙºVºu)E¹ +ž>äj·Oÿ~§à©CÕ—P{Ó·7**²“’óssóóÊJøx\›×y¦ÇU¯P´5|ǹŠáT]AfEnbƒ[jÚõÏûœö¬±¶p uKã¼ñª¢<±2'1/…¡8WhMú!ž„;¹GfoÈ~Q ߆ªD· (¿$?9?§˜¡CÉ@ââÖŠ 7Úf¤ÝJ»™ÐÇúS¦8§(?¿¤˜áàåV³“jÂn3þ«‰¶Øñ:2¿ð:}(¹,wlmaker-0.8.1/.git/modules/dependencies/objects/info/0000755000175100017510000000000015222270710022111 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/objects/c5/0000755000175100017510000000000015222270711021466 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/objects/c5/9371abcad0d5e954b9627d0de1de43d35dce660000444000175100017510000000026615222270711027045 0ustar runnerrunnerx•ŽAjÃ0E³Ö)f_jfä(–K)ÝwÓM0’¾b“IJB¯ß�tõáñüXn·µ“uöÐ@!Å\v` È Yd “h–èÌ)?g˜ª ['ÏYSžcð³Ÿ�ª;¦1[Õè�ÅÄ8ò £÷¾”FßËz]k¥/ÅŽFï—ç~žï!`ˆËÉ4¹Ñ²xG/l™M|>ìùÿ­I¨Ø¶¸b£Ÿš´ƒz¡ßk+¥ï¯³÷Њ®AÕa*‡,É4îz È‘ÖH1ÎÕ­Œ“¶)“w9:/sÑ ?ÆV;|lûmo ®¬§v¸|ÿõýëQŠNËö¿/G1»lágD³Ôû}Cÿc�hÓCôXv=_á³ =aTxÞz­ãœ,Nd~ç Kë¦xœ340031QÐKÏ,ÉLÏË/JexÙûà¤Rê†ÆÀ}‡¯NÐ=òx.÷š\C„ªÜü”ÒœÔb†[S„MW/ÒšôFï÷ÅE“…Ÿþ¼þSªÌÙ71;Õ'³¸¤X¯¤¢„Aiñ’åÒÚS385óô¬ý÷æ©»#T¥�§³«_°+ƒšÜëyó»µ½y'§Ý]9'ÿú嘨’ WG_W½Ü†¯oÏÕ­Ó:t÷ïÁÍ_ìÏ-éâi¼ehf 9™II‰ÉÙ%E‰É© •–Ší93džL�ô­Þ1{Çl]+]„º”¢\†OrµÛ§ ¿SðÔ¡êK¨½éÛ›ÙIÉù¹¹ùy e%|<®Íë<ÓãªW(Ú¾ã\Åp ª® ³"71�Á-5íúç}N{ Ö‡X[¸†º¥qÞøUQžX™“˜—ÂPœ+´&ýO‡Ü#³7d¿¨�oCU¢[P”_’ŸœŸSÌСd qqkÅ…m3Òn¥ÝLècý©SœS”Ÿ_R̰üCš:‹úÌuÛeù†I¯TºµY³xœÓÒÒתãÒÒÒwöMÌNuËÌI-Öׂó�“3RõJ*J@"I¥™9)@Ijbv¿/xœ�PÍnà >7Oõ¾pŸ´'©v0ÄkP#ã¨íÛ�54¡J6Uãbéãû³OiÒžúÉa{¼8&’tülÚò"ÈÐ~´åc�'vDbzWêlÅ�î¾±Ç4 ÅŽø¬Šè1»L[ 4C0?ÎlXmc‚eÔ�°9UÝœÕÌ( sÁCiVÃÍaÓk˜tgÈ+ ÁåD¸9bU«î½ê¤Kæ@è«�‚ìøïî=³sãŸÃ{öÏ;dàEw� Ô¬Øs¾Ž:ïë)<û/ð7Z8ªüvž·È$dÈ¥í¡Ö¿ÿ�lÕoÒ£½z¨—›�sfrwïo|å è·–xœåWMsÚH½ó+ºˆ+�”%Œ�Ên¼qjeI¶ƒÄJ"¶OÔ 0±¤QF£ëòßñaðÇV\{à°'ÄÌëžî×ýZ£7`ò|.Øx"¡5áðàð#ô&,ay—„T@ã¶úýs\‡T�&ÍÚ› +´8‚sÎÇ …NǬ½ÁÝ‹hVÐÊ,FrBÁÈI„?Ë�}øFEÁx‡ú4 ¾Üª7ÿ@s^BJæ�q eAÑ+`Äð :‹h.�eñ4OÉ" S&'Õ1K':º¸YºàCIMŸã¿Ñ&ˆ¬žH™Ç­Öt:ÕI©ÎŸ•,PE«ã˜¶ØF[áûYB‹ýQ2�yç@r &"C 1!SàÈXPÜ“\;L²l¼É)½Ä¬�‚ K¹ÅÔ*4Lx€\‘ êFNP‡S#p‚}ôqå„^?„+Ã÷ 7tì<LϵœÐñ\üw†{—ŽkíEžð:Ë…ŠƒdŠC+ÂJ·ñE@EN#6bæ•�K2¦0æ?©È0È©HY¡*Y`x1zIXÊ$‘ÕÊ“¤ôŠ=‹ŽH™H b\¦4“Å1.F)¹¥ Yf׸´Ž„F§3èùö™s}Ü3‹“½» ¯kß·ô„G$í†%KâZmaÒu\§Ûï|û¯¾ãÛVã›íHéí£f­ç{_m3lLu�€Õî�Þ®Xv`úNOQõ+2ÇTcèV@ ãÍ)f’EŒuw ÷¼oœ#Õf³†¡‡žå=É1qŒ]'1K*+2!íÅI(ÊaÊã;iÅþª £„•’%…>f2!C�ñ"‘r­b©MH.Q@­\ðï4’EkíKŸÈ4QA9£J<#ŽE8†PÌá]Îò#ìÅBªÓ5­TâNiÁ³wµ3l$Û;÷�n£kž;°¯m³§\—PXÑÛܶq÷«ñÔ›ä;ybc˜—H_£w;6y6bã À†1ùI9ÔQæ­|² 9’B‰ÇÉPP$aÓc@ä›Pæ1‘ØJCþ•8l~ª¥”b5ÔüšÐè–—òEÛh xÁ¾_á^´4åò9|#Ša™¼}[™çe’¨‘jXÖ èŸZȃzþMc‰«8™&‚sY ÇEÓ+±á@Sä•¥*xZél+ÕV.Wdžj|N–ý½ë…ï�”’§(RÔQ2_·„r€‡ "æzÍqÍNß²ö ;-#IoÑjÍ£•öy#¦¹VÅÝJA"Š* ¼¾oÚÌê{w �š}ß·Ýpð°wßÚ4SêZI_®ì¶ÇÁ=¢pÂ�9ç}}zÝ®áZðùÁé—Ö5š†³nÄf'Ÿ7Áe,‰™Ø^oR©]Eù Œà´ït¬õéJ—Áo.¯ø­U•\|êaÖ8M Èð5AQ¤³"‘¨èfi"ù³Ãò/Xm{.)Žöž×?èíÃOÏR¾€¼®¡Ñ`ׄïŠÚÙí'vŠo íáYkë¿««ÓÓžG²fÑPC¤ÓŸ/`íèueX›íºØý4SwC 'úD«n®t÷2XÎL†Kñß/ÐÖ?üöo£çüú!ô`»ë‚ìŠñœÍR¢¤±xÐôQÏñ½@¼‚ä…Áÿ•ÙëÕ­�àwM®´…ßñêæ>ß߸-­.S$Џˆñ:žà}§wy>0/lórÐõ¬~Ç×WÆMGÅ2[¶ï—“ÃC½­jÖœ³õîàÌë»xqÅ:Ùaãªã{^ Ö¶uÍZ™Ÿ,F@\oÖìN`ÿª‡-øE¸ö�ŸxOÃØ¼+â·ÍáÁ ݵ½Fà ‹]÷—¥3¢ægq"E©>ÕõË\œàkwÇ>ËòRîÏÚm<ð1�÷;éϵ)jw½ÅxœÝZ[sÛÆ~÷¯Ør¦Si¦�4i牱䆭CiD¹n&“‡%° ·°È. Šýõ=—½�¤dwúVM¦5IìÙ³çò�Ÿù[ô²Ü)ñ^—ªsêÅ3OþCY§M'¾ž¿.Äßd7J{_¿~ýÍ“‹vÃпyõj¿ßÏ%m37vûªá­Ü«¸ðþúî§µX¬®ÄÛ›ÕÕò~y³Z‹w7wâÃúºw×·w7WÞâ×=uµ\ßß-ø€ß�€¯æâJÕºÓ(çæ/¼63¢™p;Ù4¢U²œtP¶uBv•(MWñ*Q+F§ aUoM5–øuáE᳕vƒÕ›¿Ò‰ ·T•ØÄZ•,ä+�o͸݉჆çL9¶ªŽõ2öD±Òô«·»A˜}§¬•`¡BŽÃÎXýoÚÏË9·bØÉAÀ¦[+aa·¥‡¼2ÔV6âšDŸ(1vx@Ò^ Y’” ˜žõb <àÔÊñÖ`ÐÁš¦Òªð¡!¥ < ~;v,+MÛšÎKòнv,‡7œ‹wÆ’ýh{“¬|4óRft'.ô%/5{e pŸ/¡ºãb0¢”àt|ÎKáŸÈV´²“[…ÎÃ}ÝXî¼b…Øï¼OûJ’�[f¯1š@Ê…MÈ=n§{”Të¬Ù+[¢è‹o_ÿþ’¶3`6|4n«£ÀMV¹ DnTF(5¸r"=Ó3¹üg3ÎĬÅÙÙeîuømò «eY‘LJ A[íPлÕÎQÀSœq�[NBm »•�‚�^íq¤õVÕÊZXN¿ÖdñO¸Ek* G“”UÁÁº+›‘LI(:3ˆF·w?:S{ /G‚S*°~È=äÅðEÈÿZoGK¿ƒ[•ÁÇÍæ_ §ªËîÀß�;Ɔò£¶¦…Ë�ì@ë� �Ã'e(ú¦ñk!›‡ÄÓzGÇ„´é5&”!åü1· pøzrà½à¤ŒÞåpÒR ‡>?öGc?�€Â¾$� ‡0ÒR è.#&›Î«•ɃÔ�Ü4!ÿ3\*M1KéCIF\èf€‡#¼±¥àaMf•Àµ…,´õ".àêQ¶=ì Ú!Ìy!>¹è{;?B25f™¬p¥¬~+>(�q³ãÀ=ÎÛÀŸÞKbÅ7Ò¡ó:JÅ ÷Àè‡èa¬Â­È]˜ û�.w€³¨�™V=hr%F1˜Æç‰P`acÃ'áÝœg“†UN9ˆ²¾„ÍLCIËôVw°Ë©ÏOñ8àT=IÿB›Ï[£ÙûŽÄûªaU+uÌOÕKK‘‚v¡c´ÊªæyÐ}"Ãm Z0N:ÙªËàt @dkYR‘(²�z¢ZG™:yý-B¹¯ñg=~œ1e³ý¢}Â…Zõ@aŸP Wž‰I†mC«à÷§”/²¤õ lÝØvã°ÃƒGà]¤9©çS�6"?¡ÁËTîž­9QAT¦í1Þ7 ŒYƒ)ž&/_VíÅ,žiæeq½�° ‹T h €q�^ØÈ†âhoq]Gäcì¼õfAnt• …v\J²¿+ž-E»ò=࿤ ¢npq”¤e%+R!wpƒj]áPsG…%¤¤éŸ`÷cåc¶¹Vnô"ƒ‘IdÖF»Ç-GGUžvl /=�üHˆ—J“z F˜ž5Ä#ÅõºÍè y[i?!ôÙÄŽåRNo;Â~Eôöl$"XÍV`o)ò\�ÏNSøˆ_Çc‡ ü,åÉ ˆøØm*v ÌFA<eT„ä t¾OJB§~!~ܶ4`o.×Hx³ôc úz.þŠ´ ·}�˜•X�\\}¬žmf²4ËQYA•™�BèL,Žx�C8%0¼^ `™~}Mµ×È5:Ó½$Ï;81~| ¬Çn±q2Ù ‡—µUðI±{0%ùI5÷ýnº-X9ÖcŸ ]‚ó~ÜÀZ°"jßHôø èÌ¥ÖÑ7žXä}[Nó#Y>ÙñL9'laý1sЭDÐý?ðÎ,Sý€ -Ç((è¸!º=Ÿ5óÐu¶“ŠX^PˆúhS×Èó ¨à—ÿÅØ�qÀeÏ fÂÉÐ죰«ìûÛMÓ�ÓÉʈ]^µ²‘ìÍÏf‡+’�ܺ7;È^ç¤Õ”�µô �Ò¡öå‰á.¡ 6�òàIdõ´ìxA8w¸¾Ú‚úLò¦Êù-öèŠPëæbY£ÿc/ä©0¦£S½eäVâÏr¾q¿H+rkkœ{IÃc”fDþÄŸÁóR4rïF=àQµå" Ê'Np„ŠÏÕVÜùV;É)“sáXÁ-1UÃTl‰�2…fÔgJh4RŽù’XWLQô^ˆéa«àË|Ѻ ûÄŠ¡à›¹¸SùdhN[·ò��í…uà6é‘¶E}Q=î7À­Žˆ •SߨâßÉA%Õ‡ãNâ{*£aÏM¶'n•Æ> ûwêX !ht‡qÂݣ˶Gˆ‹!�2±uß’1Ë™î\f;[5@‚�7g-Ç�Љ¹1z¤\eˆÐB•Ác¢99ãì� Ÿä´TO�V]"hEÿûÆ]=[ÝÜ/ß^Ï ù²7¦�ß)w¶Ož]œÉ”Ë’¿2Q¡õ”àCYQ�™‚N�5+‚’Ä9o&ƃ!„ŽP|‰]31ç-|Ö®l £QÒa;•Oéý’”­@Œ`Ó7AMtL¶NšD•{V‡ïs0ŸYž×Ó”ÐuÂ,™ÛTOå[œZY®—M¹|opÆJõQ¦�€��mõyˆ¾ép> 3 %¡ ½ßq†øujæÌßD¸•ŽC>è!RóŠ eªŽÏ-B¬Ãd6ˆ¬*ü·Å~'�ÈLJPÝ[èK2¡`ë;pD~&ê§p¼QUª«Æ6ÐÖIÄ`áþ/¸óÓÈÀaˆf8›L4­‚ž‰y€�ã� óÔ½ÅY¥®‚h+ ë™ ¾2W Ž\eÉid­–{†Á§ÑÞ™+#“Ý™úŒ6EJ›ššÅíH>�‹©Dòpëlš—8¹­šTáȺq–LTãh2–‰�ÊQ'0qÈ·Ôìø›îU tsñ¡ƒ*êÈiê6*5¶¿$1» ‰ó�Ã1‹Ì†YÙëÉÑUbú¸ãñ ‡©Þ&Ÿ>ÿ7­™§Y¤f0,‚©knyýÊ ¸(ÞÞP}ÙnÊ0m·ÔÞa!ÕÜåÀ©JñE¦A濳 �‚cK´…žŽÿà3„:2õ¨Ê â x£A¬ÚJË÷Jǽ‡¿ ø@a a1ãÑ•!ä˜rg7Bhx¡Æô%\cÈçf‘ÑàÔKÙœéû� “�a~8mÐ8DJjS­úmÔþö ºŸ`I'—Bá7-^O£6`eà%л"68©=™Ï†l ~óÕàL `Kýy.®´£Ö /mkñø'Øå“ ªº9pK�7¶X ȋԼ¤)X‘æsß%U/PW·¨ùÓ8¾œ8÷çZù³ÅZ,×3ñÃb½\ã~\Þÿxóá^|\ÜÝ-V÷Ë뵸¹Ë¯åoÞ‰Åêgñ÷åê èŽæàGœŽºtM¸RecÒ”A4'•§Ðä’©¨!²§ Ƽ_Þ¿¿.Àê«—ËÕ»»åê¯×?]¯î ñÓõÝÛAËÅË÷ËûŸ)„Þ-ïW×k~}`áeÜ.îÀaÞ/îÄ퇻ۛõ5W[¾-lðfôïaSM·t3Ã]á4\ÀsÖôV#=§×]øÅ_BÜl^ÊÓFç€áq\kGÈîL©c›Ì îïYi›_´ž6³{™Ãç`R\ô^Ë�nèò|‰•WýéÒƒeÀW ;AGè´³QK¸É‚ò‘A§¶�öUªË"Þv“Qnœü|6Þ/˜(àL¿Ñ"t¤ÜçñÞ"l9àŽnÇÏç£ç¤|àP&¸¬Ñ´±Ÿ�ke+·Ó>®¯¤—\¯ðn=»}†„bËW H`x¦‹r^h@hœ¹�Þ8®¶|gŽU<Öj¼5>ntÉšcĘ‘¿Ñ�wf†«ùÄàâÙ;ñ »1°[cª½nòÙá'(ʦï%N ‘Œ¨x-u3Z®F²©Ç.‘*‚gÞÁ[ Þܼ±r8‡HÐ�q^F¦ËêAÓ%ií_߀ ðF/7xñœßÍŢĚ€VÈ‹;/R¡Î’âã©û4]�/ Ÿ½n ,´ÜÃSPštN.Ûiæ ¼­V„'u¤¡ìJŇèy êÑï@q§Ú_-I16ktfÓø)ñ–W;È|ùª΃ùâû+46?š=vBÜJFƒ‘=3Áé|ôFK×d·!‘sûkâú¯HŒ’¾ÄtÒ-JBô4)ÊÂÀÏ„±gÒ5ã3&<ç;Ù¦Ž¶©T í ¯f\��KÛr­˜Òy´6Ý–ùÉ1`2t娬òµ8�ožl¤Ðɦ‘Ìï³hÌhcÔ…øzu…uõÜkpôûâöYþó º�¦€¨ÿúBþêþFªìã]üÝá‚¿F1�&Zm k,´áC˜j©“¯µj*' @@²3èoð–RAdÎ~ùu��&¾ÚB0ªú®/ë¤çââÊtˆï d9„ÿîRP·Nmªz‘?êể¬lgw³˜+îxþ/B©©g'`aãð‚ŠŸösÒ€âô,Ç D2Vn»ˆfö¡‡«Õ�J¯¬Ð iÐÄáÂ(GƒkÄàÖŠéͧùÕ„ÀÓñ>Þ[.Ü»ÆñLrH[îðÆšƒ!]&þr€¿_Å/¤7èytËú+=Êz¦iøù ¡âˆï\^~�"B?‚@ÀåË�Ï�×�oC cDEŠ#R×o64-““‘]d9„pÿÜ+§ï�»¯Ö×/AeZò% ý)îáß9C1ÙHíô '¼4ÈxŠ�ÿ�ô;o2ÛZ©‰ !ȉÖ@ÌÀѺí”ÊBwüfŸŸ–$¾îNÏ5ñ~\ìf¿ xœUޱÂ0 D÷|…%VÚì̬L Ìnã6Q'Š�Vý{b(Û=éîô.°¯*�£LìˆÇ@ÜÏ8¥/0ïM LÊ ë…Sb9¹ÝÀ¡¥Ó¾¸šDÕÞõî0‹ŠYØÚ”5«¢*’ì.ûÑ¢“åù×ù!é�àÉ"e°�Q¾ÃÕ�-Äã� ¬mõìaÎ~®*ÂÝýÁ³ô?Î¤Š �Ú¤‘M)ßB4°ÎÿͺLoV-Ä­$È€xîöÖ{ A ½ˆV¶s¦Tc·Ô:ó3œ·Ò‘U;6ºæÄm ÐÖ~BÀâÊûÇrZ«€”wlmaker-0.8.1/.git/modules/dependencies/objects/25/0000755000175100017510000000000015222270711021405 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/dependencies/objects/25/c2e4a15d24f6f3d46ac43de276060f267b16b30000444000175100017510000000277015222270711026531 0ustar runnerrunnerxåWßSã6îsþŠ�ÓKn°C€i{ô`jl>;u’ž2²­$:lË•åKR†ÿ½+;¿;0}à¡Oq¤ÝÕî·ß®VAÌØßßéþôLž­›Í%ìîìîÁ)糘B¯g6>4>@�…4ÍiEQrNÁÈHˆ?ë�møFEÎx »ú´”@s½ÕlÿŽV¼€„¬ 劜¢ –ÔátÒLK!äI3’†LÎËcÖFt4qµ6ÁIPš |¶>½/D–Ï¥ÌòƒNg±Xè¤ôTçbÖ‰«8òNÏ1mwhkèm)?Ncšç è_g°’¡3! ÐŘ,€ 3AqOråìB0ÉÒÙ6ä|*DPô0b¹,(ä¤Ö!|_±")4�!8Ã&Cg¸�6.œÑ™7Á…áû†;rì!x>˜žk9#Çsñß îœ;®µ q„Ðe&”ÿè$SÒH6¤ çMŽ`ŠÛêžÑ�MYˆq¥³‚Ì(Ìø*R 2*–«Læè^„Vb–0Id¹r—þ»¼ ˆE§¤ˆ%1+šÊüÄ\SÐ,³oœÛÇŽŒ^o2ðíçò``ŒÎ·nμ¾}ÛÑc’´c G�F¥Òw\§?îO|ûϱãÛVë›íØÓ»{íÆÀ÷¾Ú樵ˆÕAêݽ۰ì¡é;4/È C� _ jèoF‘ÈiÈhÞDážážŽ�S„Úl7Ðõ‘gy­kBs*þ˜A@õpÞ>#Š�u£¤²SÐ)Â>W¸‘8†¼Ȥýš†aÌ Éâ\Ÿ1“@g¼ƒ’¹V¢Ô ç$“X@�Lðï4”ygcKŸË$VN9Ó²x¦“p#±‚�Ëö�‹¹T§kÖ•€„æ<ýØ8Az ØÞ©oô[}{è¹ûÒ6Ç#ã¸gƒkàR% 5¼í‡:®ã~5žê I¾“':†yŽðµ×3“§S6»'€ž×(`ðó"бÌ;Ù¼‚+2H®ŠÇI± HÌþ¦€’whB‘ED"•4†"ø#hX`³ù¡–ŠÙ@msNÃk^Èu±Y•/è�Ë3^Ô4ᥠõyµ|+Œ°BÊàçŸKdz"ŽÛè’aY“áøØBîš#Ï¿j­åJŠ-bÁ¹ÌÑ›ŠôªØ°¡)ð Aa*xRÖê$­\ôÓ¯Úç‚aÚ?¹Þè�Bò‹ë(^m(¡ŠD¬ô†ãš½±e·ì%2-%ñ ¢zóhe‚:ÀöU¶ÎŽË`´¢†ª”Д94…#.github/workflows/build-for-bsd.ymljIqÊ0Ÿû*jIqÊ0Ÿû*|1#�¤éé�]™W (`ä¦%ÖT=g¿VËa·3%.github/workflows/build-for-linux.ymljIqÊ0Ÿû*jIqÊ0Ÿû*|1$�¤ééÇ4’W[›?ºC+š—±â÷ų[ .gitignorejIqÊ0Ÿû*jIqÊ0Ÿû*|1%�¤ééqå+`ºdäz®çA«•×9{ä¾Â BACKLOG.mdjIqÊ0Ÿû*jIqÊ0Ÿû*|1&�¤ééÝ- sê …q$Îsbdù@‰9JCMakeLists.txtjIqÊ0Ÿû*jIqÊ0Ÿû*|1'�¤éé“v5�ì×0d)î&‚ýá­T –CODE_OF_CONDUCT.mdjIqÊ0Ÿû*jIqÊ0Ÿû*|1(�¤éé,æ5¾zåˆÉ¦¨énö®+™:Ü CONTRIBUTING.mdjIqÊ0Ÿû*jIqÊ0Ÿû*|1)�¤ééÀÑOivˆÙ�ŸÕ÷Oç/Ä|êÖ­p Doxyfile.injIqÊ0Ÿû*jIqÊ0Ÿû*|1*�¤éé,]zJ>¢BL ûä�EZíª”ÙH5LICENSEjIqÊ0Ÿû*jIqÊ0Ÿû*|1+�¤ééä«Óö¤‰Jó‘Rì`âuþÐÖ7â README.mdjIqÊ0Ÿû*jIqÊ0Ÿû*|1,�¤éé ?‹Dý9cà:u‡Ÿádò¶@eëSTYLE.mdjIqÊ0¯)ÛjIqÊ0¯)Û|1/�¤éé÷µñäáÍüNÁj5GŒ›£'𽿌Winclude/libbase/arg.hjIqÊ0°öþjIqÊ0°öþ|10�¤ééYvƒå;�ÛÜ„¤–*žðü¼Binclude/libbase/assert.hjIqÊ0°öþjIqÊ0°öþ|11�¤éé50#$¼2G;’Ä…ïœì%P6rinclude/libbase/atomic.hjIqÊ0°öþjIqÊ0°öþ|12�¤ééÀ×x(ô̧ –™[†`%U´include/libbase/avltree.hjIqÊ0°öþjIqÊ0°öþ|13�¤éé �iCûÁK}kí#9²ÿ»nž É èinclude/libbase/def.hjIqÊ0°öþjIqÊ0°öþ|14�¤éé +Û n̶'ÉÅ ÿ®aãý>Þ½Ô^£include/libbase/dequeue.hjIqÊ0°öþjIqÊ0°öþ|15�¤éé.ÊÏÓbâ�:include/libbase/subprocess.hjIqÊ0¾2‹jIqÊ0¾2‹|1I�¤éé5±Z“^WT¤Â’«�†rwr¤jIinclude/libbase/test.hjIqÊ0¾2‹jIqÊ0¾2‹|1J�¤éé±ÌÆè¼:µ»R–­t´Í&ÏCUinclude/libbase/thread.hjIqÊ0¾2‹jIqÊ0¾2‹|1K�¤ééI#ÂMï‰g8HD­xw-:ÿke^include/libbase/time.hjIqÊ0¾2‹jIqÊ0¾2‹|1L�¤ééÀ<,å0'i; ¢zÓtÒþ¥2¼Öã~include/libbase/vector.hjIqÊ0¾2‹jIqÊ0¾2‹|1M�¤éé-�3x[Šæ ð}¢,é·”B¿�Ezàiwyu-mappings.impjIqÊ0¾2‹jIqÊ0¾2‹|1N�¤éér£Ü ¥í4Y!L–�`'µ¦¡ Úž libbase.pc.injIqÊ0¾2‹jIqÊ0¾2‹|1P�¤é鉨Œ’„†x2ñ6+1ït¿ŵz‡Îsrc/plist/analyzer.ljIqÊ0ØÓ×jIqÊ0ØÓ×|1^�¤éé³�w¨¿—â!áö�ãZ‰«O�èsrc/plist/conf.mdjIqÊ0ØÓ×jIqÊ0ØÓ×|1_�¤éé¾ñ²€à…»-�‚PóY�q8{_«†Âsrc/plist/decode.cjIqÊ0ØÓ×jIqÊ0ØÓ×|1`�¤ééx¢Õ¥Åd*¾BÁCê÷ŽŒÖ5±src/plist/grammar.yjIqÊ0ÛÔ jIqÊ0ÛÔ |1a�¤é鈼¿©Ë�{‚êmžü/VõÁÓîsrc/plist/model.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1b�¤éé,)óÁÃ*`ùé‘¥Z:L9m#mŒsrc/plist/parse.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1c�¤ééßì)l¹Κû‡œ°)d°®8ësrc/plist/parser_context.hjIqÊ0ÛÔ jIqÊ0ÛÔ |1d�¤ééùË®µÁ1lŒÿITð�Ÿ_ÌÁ src/ptr_set.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1e�¤ééN”§�Ý }”o ÿVÄŠ”7 ÍTsrc/ptr_stack.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1f�¤éé�ø.¨^•× ”ÖEë­I ŸhñÑsrc/ptr_vector.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1g�¤ééÞBCMØ?ik²ÕV¢ÌX•µæsD\Ä src/ref.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1h�¤éé;'s˜5l"ðH"vÊ/¬-€¥Eo src/sock.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1i�¤éé/4÷�bîP6\­Qfå>…²¤ò src/strutil.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1j�¤éé‡Z¸ {è®h]2ˆ*@å°(,ÊÎþsrc/subprocess.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1k�¤ééŠVi1TÅ‹gK’Ø¿~¦A–„“Ø src/test.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1l�¤éé~ÖZz¼’¯1_cùàÅëAjÿ\ src/thread.cjIqÊ0ÛÔ jIqÊ0ÛÔ |1m�¤éé ºÐ³²¥@¶ÎÒ ƒòŸ«1Êš¾ src/time.cjIqÊ0ëSjIqÊ0ëS|1o�¤éé ÏÄÆþd–sª=ó6,‡œ1UÀú`#tests/CMakeLists.txtjIqÊ0ïVwjIqÊ0ïVw|1q�¤é鬾†ÇÈ•†à‘* …¬ó Å•Ãtests/data/abcd.txtjIqÊ0ð"RjIqÊ0ð"R|1r�¤ééŸK‰ÜJ0¥ÛIìHZúd—¤nµ_ztests/data/array.plistjIqÊ0ð"RjIqÊ0ð"R|1s�¤éé­õô6]<¨9ÂKu…>Е±êitests/data/dict.plistjIqÊ0ð"RjIqÊ0ð"R|1t�¤ééW s{Ê[ÙÅŸ6Fzi%Ötests/data/gfxbuf_equals.pngjIqÊ0ð"RjIqÊ0ð"R|1u�¤éé¬muS‘ÿb\“ ÊÞç0_¾ÞÓtests/data/string.plistjIqÊ0ð"RjIqÊ0ð"R|1v�¤é邺º†¤Š¢cs‚´@VŒ£p�ƒhvtests/libbase_benchmark.cjIqÊ0ð"RjIqÊ0ð"R|1w�¤éé&ŠÂ\Ö‘ü¼UðÃl‘í–VO¨òNtests/libbase_test.cjIqÊ0ð"RjIqÊ0ð"R|1x�¤éé—΃°{Èϸ!´Öû5…Uùqvtests/plist_test.cjIqÊ0ð"RjIqÊ0ð"R|1y�¤éé¦Úæ>VÝ&ó«ÝgÍš>ÎÓ‹5tests/subprocess_test_failure.cjIqÊ0ð"RjIqÊ0ð"R|1z�¤ééVó!«Ç׋j"×ܧ±˜Ú¨štests/subprocess_test_hang.cjIqÊ0ð"RjIqÊ0ð"R|1{�¤ééß sìß'�(R—².?¸8ùµtests/subprocess_test_sigpipe.cjIqÊ0ð"RjIqÊ0ð"R|1|�¤ééˆ_&³Ë».Ý8ïµBUôa�¹Htests/subprocess_test_success.cjIqÊ0ð"RjIqÊ0ð"R|1~�¤ééjz_5*M~žg�a¼§=$ƒÉ=À›>tools/CMakeLists.txtjIqÊ0ùÞ7jIqÊ0ùÞ7|1�¤éé V÷­ìî‰F%�]‚zhÐH”Y¿tools/plist_parse.cTREEQ86 5 ‰[Vm2Ó•Ç2ZÁÔ"ÎÌòçüsrc29 1 ©Þl—Îö!w¸iæí_jD˜¼plist8 0 ²6Æã_aò\As愉”�W tests13 1 «jr?‹¿aæ¯Ká‚mYÃÎtNdata5 0 z² oRÂåÎYn‘ï¬�ïÇVãttools2 0 '×’Ów² Zop„Qÿm|W.github2 1 ñ<úŒÌG/I7¿^Û†dTÑ*b>workflows2 0 D=è½e¥¿)dî¸àY:Zƪ@include29 1 �TKäÿ@’‹;ß]ıo9ï.nlibbase29 1 wÁ:eº´±N²~?²Ùz �Ùplist3 0 ð‘©ß·DÞ"ËÂ:ƒ^L‰­Ëå’+hºäA›3—„1%€ŒYÊÜwlmaker-0.8.1/.git/modules/submodules/libbase/HEAD0000644000175100017510000000005115222270712021304 0ustar runnerrunner54e60555cae53ad99314c7579dce1ad38373ecaa wlmaker-0.8.1/.git/modules/submodules/libbase/logs/0000755000175100017510000000000015222270711021627 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/logs/HEAD0000644000175100017510000000074515222270712022262 0ustar runnerrunner0000000000000000000000000000000000000000 54e60555cae53ad99314c7579dce1ad38373ecaa runner 1783198153 +0000 clone: from https://github.com/phkaeser/libbase.git 54e60555cae53ad99314c7579dce1ad38373ecaa 54e60555cae53ad99314c7579dce1ad38373ecaa runner 1783198154 +0000 checkout: moving from main to 54e60555cae53ad99314c7579dce1ad38373ecaa wlmaker-0.8.1/.git/modules/submodules/libbase/logs/refs/0000755000175100017510000000000015222270711022566 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/logs/refs/remotes/0000755000175100017510000000000015222270711024244 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/logs/refs/remotes/origin/0000755000175100017510000000000015222270711025533 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/logs/refs/remotes/origin/HEAD0000644000175100017510000000035115222270711026156 0ustar runnerrunner0000000000000000000000000000000000000000 54e60555cae53ad99314c7579dce1ad38373ecaa runner 1783198153 +0000 clone: from https://github.com/phkaeser/libbase.git wlmaker-0.8.1/.git/modules/submodules/libbase/logs/refs/heads/0000755000175100017510000000000015222270711023652 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/logs/refs/heads/main0000644000175100017510000000035115222270711024520 0ustar runnerrunner0000000000000000000000000000000000000000 54e60555cae53ad99314c7579dce1ad38373ecaa runner 1783198153 +0000 clone: from https://github.com/phkaeser/libbase.git wlmaker-0.8.1/.git/modules/submodules/libbase/shallow0000644000175100017510000000005115222270711022253 0ustar runnerrunner54e60555cae53ad99314c7579dce1ad38373ecaa wlmaker-0.8.1/.git/modules/submodules/libbase/info/0000755000175100017510000000000015222270711021616 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/info/exclude0000755000175100017510000000036015222270711023174 0ustar runnerrunner# git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ wlmaker-0.8.1/.git/modules/submodules/libbase/config0000644000175100017510000000176215222270713022063 0ustar runnerrunner[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true worktree = ../../../../submodules/libbase [remote "origin"] url = https://github.com/phkaeser/libbase.git fetch = +refs/heads/main:refs/remotes/origin/main [branch "main"] remote = origin merge = refs/heads/main [gc] auto = 0 [http "https://github.com/"] extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUpoZFhSb2JtUWlMQ0poZW1NaU9sc2ljMmwwWlM4eU1EUTVNemcwTURrNU9TSmRMQ0psZUhBaU9qRTNPRE15TURFM05UQXNJbWxoZENJNk1UYzRNekU1T0RFMU1Dd2lhWE56SWpvaVoybDBhSFZpSWl3aWFuUnBJam9pT0RKbFpXTmxZMlV0T1RkbE5TMDBOakkwTFRreVlqWXROVFJoWVdZNE16aGhaVGhsSWl3aWMzVmlJam9pYVc1MFpXZHlZWFJwYjI0dk1UVXpOamdpTENKMlpYSWlPako5LmYtN0ljd1lxcDhaUUMzYlV4bWlFa3NzN0pqdzVFZ19tdVduU3JXWVMtMDNzM0ROS2Q0Wi1Kd3dzaHRvUnB0X0FnVm96amdaRGhmZDlOMElOU1ExVndn [url "https://github.com/"] insteadOf = git@github.com: insteadOf = org-130065133@github.com: wlmaker-0.8.1/.git/modules/submodules/libbase/packed-refs0000644000175100017510000000016015222270711022767 0ustar runnerrunner# pack-refs with: peeled fully-peeled sorted 54e60555cae53ad99314c7579dce1ad38373ecaa refs/remotes/origin/main wlmaker-0.8.1/.git/modules/submodules/libbase/description0000755000175100017510000000011115222270711023125 0ustar runnerrunnerUnnamed repository; edit this file 'description' to name the repository. wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/0000755000175100017510000000000015222270711022006 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/hooks/pre-commit.sample0000755000175100017510000000316115222270711025271 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=$(git hash-object -t tree /dev/null) fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --type=bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff-index --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/push-to-checkout.sample0000755000175100017510000000533715222270711026426 0ustar runnerrunner#!/bin/sh # An example hook script to update a checked-out tree on a git push. # # This hook is invoked by git-receive-pack(1) when it reacts to git # push and updates reference(s) in its repository, and when the push # tries to update the branch that is currently checked out and the # receive.denyCurrentBranch configuration variable is set to # updateInstead. # # By default, such a push is refused if the working tree and the index # of the remote repository has any difference from the currently # checked out commit; when both the working tree and the index match # the current commit, they are updated to match the newly pushed tip # of the branch. This hook is to be used to override the default # behaviour; however the code below reimplements the default behaviour # as a starting point for convenient modification. # # The hook receives the commit with which the tip of the current # branch is going to be updated: commit=$1 # It can exit with a non-zero status to refuse the push (when it does # so, it must not modify the index or the working tree). die () { echo >&2 "$*" exit 1 } # Or it can make any necessary changes to the working tree and to the # index to bring them to the desired state when the tip of the current # branch is updated to the new commit, and exit with a zero status. # # For example, the hook can simply run git read-tree -u -m HEAD "$1" # in order to emulate git fetch that is run in the reverse direction # with git push, as the two-tree form of git read-tree -u -m is # essentially the same as git switch or git checkout that switches # branches while keeping the local changes in the working tree that do # not interfere with the difference between the branches. # The below is a more-or-less exact translation to shell of the C code # for the default behaviour for git's push-to-checkout hook defined in # the push_to_deploy() function in builtin/receive-pack.c. # # Note that the hook will be executed from the repository directory, # not from the working tree, so if you want to perform operations on # the working tree, you will have to adapt your code accordingly, e.g. # by adding "cd .." or using relative paths. if ! git update-index -q --ignore-submodules --refresh then die "Up-to-date check failed" fi if ! git diff-files --quiet --ignore-submodules -- then die "Working directory has unstaged changes" fi # This is a rough translation of: # # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX if git cat-file -e HEAD 2>/dev/null then head=HEAD else head=$(git hash-object -t tree --stdin &2 exit 1 } unset GIT_DIR GIT_WORK_TREE cd "$worktree" && if grep -q "^diff --git " "$1" then validate_patch "$1" else validate_cover_letter "$1" fi && if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" then git config --unset-all sendemail.validateWorktree && trap 'git worktree remove -ff "$worktree"' EXIT && validate_series fi wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/prepare-commit-msg.sample0000755000175100017510000000272415222270711026731 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first one removes the # "# Please enter the commit message..." help message. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. COMMIT_MSG_FILE=$1 COMMIT_SOURCE=$2 SHA1=$3 /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" # case "$COMMIT_SOURCE,$SHA1" in # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; # *) ;; # esac # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" # if test -z "$COMMIT_SOURCE" # then # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" # fi wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/update.sample0000755000175100017510000000710215222270711024476 0ustar runnerrunner#!/bin/sh # # An example hook script to block unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --type=bool hooks.allowunannotated) allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) denycreatebranch=$(git config --type=bool hooks.denycreatebranch) allowdeletetag=$(git config --type=bool hooks.allowdeletetag) allowmodifytag=$(git config --type=bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero=$(git hash-object --stdin &2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/pre-rebase.sample0000755000175100017510000001144215222270711025243 0ustar runnerrunner#!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up to date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/applypatch-msg.sample0000755000175100017510000000073615222270711026153 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup commitmsg="$(git rev-parse --git-path hooks/commit-msg)" test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} : wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/commit-msg.sample0000755000175100017510000000366415222270711025301 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines and messages that # would confuse 'git am'. ret=0 test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. ret=1 } comment_re="$( { git config --get-regexp "^core\.comment(char|string)\$" || echo '#' } | sed -n -e ' ${ s/^[^ ]* // s|[][*./\]|\\&|g s/^auto$/[#;@!$%^&|:]/ p }' )" scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$" comment_line="^${comment_re}.*" blank_line='^[ ]*$' # Disallow lines starting with "diff -" or "Index: " in the body of the # message. Stop looking if we see a scissors line. line="$(sed -n -e " # Skip comments and blank lines at the start of the file. /${scissors_line}/q /${comment_line}/d /${blank_line}/d # The first paragraph will become the subject header so # does not need to be checked. : subject n /${scissors_line}/q /${blank_line}/!b subject # Check the body of the message for problematic # prefixes. : body n /${scissors_line}/q /${comment_line}/b body /^diff -/{p;q;} /^Index: /{p;q;} b body " "$1")" if test -n "$line" then echo >&2 "Message contains a diff that will confuse 'git am'." echo >&2 "To fix this indent the diff." ret=1 fi exit $ret wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/pre-receive.sample0000755000175100017510000000104015222270711025415 0ustar runnerrunner#!/bin/sh # # An example hook script to make use of push options. # The example simply echoes all push options that start with 'echoback=' # and rejects all pushes when the "reject" push option is used. # # To enable this hook, rename this file to "pre-receive". if test -n "$GIT_PUSH_OPTION_COUNT" then i=0 while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" do eval "value=\$GIT_PUSH_OPTION_$i" case "$value" in echoback=*) echo "echo from the pre-receive-hook: ${value#*=}" >&2 ;; reject) exit 1 esac i=$((i + 1)) done fi wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315222270711027027 0ustar runnerrunner#!/usr/bin/perl use strict; use warnings; use IPC::Open2; # An example hook script to integrate Watchman # (https://facebook.github.io/watchman/) with git to speed up detecting # new and modified files. # # The hook is passed a version (currently 2) and last update token # formatted as a string and outputs to stdout a new update token and # all files that have been modified since the update token. Paths must # be relative to the root of the working tree and separated by a single NUL. # # To enable this hook, rename this file to "query-watchman" and set # 'git config core.fsmonitor .git/hooks/query-watchman' # my ($version, $last_update_token) = @ARGV; # Uncomment for debugging # print STDERR "$0 $version $last_update_token\n"; # Check the hook interface version if ($version ne 2) { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; } my $git_work_tree = get_working_dir(); my $json_pkg; eval { require JSON::XS; $json_pkg = "JSON::XS"; 1; } or do { require JSON::PP; $json_pkg = "JSON::PP"; }; launch_watchman(); sub launch_watchman { my $o = watchman_query(); if (is_work_tree_watched($o)) { output_result($o->{clock}, @{$o->{files}}); } } sub output_result { my ($clockid, @files) = @_; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # binmode $fh, ":utf8"; # print $fh "$clockid\n@files\n"; # close $fh; binmode STDOUT, ":utf8"; print $clockid; print "\0"; local $, = "\0"; print @files; } sub watchman_clock { my $response = qx/watchman clock "$git_work_tree"/; die "Failed to get clock id on '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; return $json_pkg->new->utf8->decode($response); } sub watchman_query { my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') or die "open2() failed: $!\n" . "Falling back to scanning...\n"; # In the query expression below we're asking for names of files that # changed since $last_update_token but not from the .git folder. # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the # output to file names only. Then we're using the "expression" term to # further constrain the results. my $last_update_line = ""; if (substr($last_update_token, 0, 1) eq "c") { $last_update_token = "\"$last_update_token\""; $last_update_line = qq[\n"since": $last_update_token,]; } my $query = <<" END"; ["query", "$git_work_tree", {$last_update_line "fields": ["name"], "expression": ["not", ["dirname", ".git"]] }] END # Uncomment for debugging the watchman query # open (my $fh, ">", ".git/watchman-query.json"); # print $fh $query; # close $fh; print CHLD_IN $query; close CHLD_IN; my $response = do {local $/; }; # Uncomment for debugging the watch response # open ($fh, ">", ".git/watchman-response.json"); # print $fh $response; # close $fh; die "Watchman: command returned no output.\n" . "Falling back to scanning...\n" if $response eq ""; die "Watchman: command returned invalid output: $response\n" . "Falling back to scanning...\n" unless $response =~ /^\{/; return $json_pkg->new->utf8->decode($response); } sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; $output = $json_pkg->new->utf8->decode($response); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # close $fh; # Watchman will always return all files on the first query so # return the fast "everything is dirty" flag to git and do the # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); $error = $o->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); return 0; } die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; return 1; } sub get_working_dir { my $working_dir; if ($^O =~ 'msys' || $^O =~ 'cygwin') { $working_dir = Win32::GetCwd(); $working_dir =~ tr/\\/\//; } else { require Cwd; $working_dir = Cwd::cwd(); } return $working_dir; } wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/post-update.sample0000755000175100017510000000027515222270711025465 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/pre-merge-commit.sample0000755000175100017510000000064015222270711026365 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git merge" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message to # stderr if it wants to stop the merge commit. # # To enable this hook, rename this file to "pre-merge-commit". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" : wlmaker-0.8.1/.git/modules/submodules/libbase/hooks/pre-push.sample0000755000175100017510000000253615222270711024765 0ustar runnerrunner#!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 wlmaker-0.8.1/.git/modules/submodules/libbase/objects/0000755000175100017510000000000015222270711022314 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/objects/info/0000755000175100017510000000000015222270711023247 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/objects/pack/0000755000175100017510000000000015222270711023232 5ustar runnerrunner././@LongLink0000644000000000000000000000015500000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/submodules/libbase/objects/pack/pack-8d480dafbac9b96fa6268740ddd2277b436f23a8.revwlmaker-0.8.1/.git/modules/submodules/libbase/objects/pack/pack-8d480dafbac9b96fa6268740ddd2277b436f0000444000175100017510000000067415222270711031233 0ustar runnerrunnerRIDX1^!Y(Z*>34)F 'U&XNBEW[9M\-0#/8RP.K; VA57<2$ HC]6D: _ Oa`G%TS=L,@?JIQ "+�H ¯ºÉ¹o¦&‡@ÝÒ'{Co#¨df5Ô“¬ÖÚC%�YpÌ�âcÍý././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootwlmaker-0.8.1/.git/modules/submodules/libbase/objects/pack/pack-8d480dafbac9b96fa6268740ddd2277b436f23a8.packwlmaker-0.8.1/.git/modules/submodules/libbase/objects/pack/pack-8d480dafbac9b96fa6268740ddd2277b436f0000444000175100017510000051754315222270711031243 0ustar runnerrunnerPACKb›Exœ}SÉ®«FÝó-e“Ýk¦fˆ^¢Çd0ØÌûî iÌdl3›¯ä*Ë(%U-Žê”TçT�=Æ@”`y>g¡ÈY "�e`Šèœc„*,P¸@Ä=íq7‚\’Ä ¼” tAÓ(Ã’@­=LÊ¥‹Œ£ � ‘Ncyë�WVmu¿;ÅîÁš¥(Ò,KÞËæû9­eøìn=¾·ïÏK5–Sö‰n׿-Ãp¼¹[�ø᣹�M#y€æž»ÒœNÔÉJ¬íiÞjâ1Bæ©Ø Læ„ÜD7ªÈå‹ÓC›'Ý|ì¹1q;žò}¸*é_Ë󺛇�©x´öAH9Ï:y0Ìâ ö½è¨Ù�83H𔢋E1ìÏŒ”‡ðÕ˜ËQ €/ïVysq ­ØH“mï�øKµ^«Õß·¯;Ú1Œïÿ ôj§ƒ�÷ýúÕÅšY5ܺ� îpŸŽ8EÕâáüþ›Äÿñ ¹Xýª%xœ31½ôÌ’ŒÒ$†�6¿zθë{šï�»Ý–ÂrQ+ÉÎÐÀÀÌĬ$3=/¿(•Á„qRxôl{�]ÎÚ ³¦o|ôýèæh¨*'Ggow½Ü†§Ú »RžT±¯{î¸zêuËê'ûIBU9û&f§úd—ë•T”0èʰò¿âm•)T9Wœ”òÓ¡ÓÒ ¦ÒßÅ5Þß-ÞÙßÏ%Ô9dn™iÿ‰ë)šïÔXšþ2<\Â9 ®Ú/$ÈÓ)4ÄÓì„g¦ûªž2wœ\¶âeÞ7†uÚ3­îpC•ºäWT¦eæ¤êeæ1øg–uÜlœ•õ»ÿs}þ#5¯®­-€*óñtvõ ve¨ò²[ääÃùûI¯kÔ[¹USn y˜B•¹:ºøº‚¬[}ùÛ’N¯ÏƒÞ$<*ýw ù#¨¢à�H°šn~—¿–É­JÛç?Lù´Í!•ïµ 82ó’sJSRzC꽟üw˜Ôm}?öÈÆ|fË÷zyPS2Ë+Kus 2óÒ‹õ2s �+¢»ž-øP»Hçåö)Nû]«@çd&%%§ê$ƒü¸ø×Ò·&‘Š>Óú%Ô·.[È}kÄÞâ¢d‘•÷r¦Ÿ“ø¦(^¾#óÙÛø,—{ ²%©ÀXbX�Udß½?ñÙzï‡MŒ¹ò‘‡Ï•øAåóósŠÔ¯Ob¸\μ‰'Š#¿ %PþnM8¤;éö¤xœ31…òü¢ì´œüòbÛ{S—îÓLy·ã�h¤UÔ±Uòñͬxœ340031QH*ÍÌIÑMË/ÒM*NÑ«ÌÍa°:Àð-tÛ¹¾Ó [µ­šraŠ¥I«!ºâœÌ¼Ò °òؙἠO–©Ê] ±Mßv:q»1M±$¾3xœ}RMk1¼ï¯xä²—j7 ¡‡…Bp’ÞÚ€ í¡-FÏ»ŠµÒFOr1ôÇWÒ:‹�šž„4ó>fF–�ØÁ*j£`ë<|òˆ°úúïo››ªr¶«¦HC>„çVHü€«‘k{¿ Á˜�Ç—ˆþC¬ž� Œ‡…ç£%–¦@Ñ†È ÏX�(àD3‹�-{Þ(w.�Ná;ÐVš¨´í¡×(ŠÑ©h�šR)/ÀeÐÎR+�ÅwûÛ#þ[‡£®2o©ï øˆÕùhg·º�>�Å-nUÑF Á ! ÞÅ~€ûÏ|‡ç+HïˆØ”Ä%�GöºÑ|Þí¯››Íõť܄ž‡$qC‡dÈØÁ6%$H-Œ=zÒÙÁ:GV/ïÉÙþ,׬O9 TÀ4Ô5ÔÔ¾DîzshgÛÛZ ²�v}›?Bú�Lºß6Ixòž7Ø~žà ÷hZ¡ÉÙ‹ˆ“=L:�áÔ;�|ƒõžOƒ–)D®½;�J7`«9”؃,Am¾?®×Oë�O_þ%³9À¹â.q2–¦´_zÎ,9zöü­ú ¡¢ò-½Ixœ­SMkÜ0½ûW ¦ÇjíͰÐf“ÂBÒ……6”6,²/éþ÷Žl'­Úl’Cušyóæéi¤y‹œ©*Ø R‡>ËŒ.2€.¸&î¥åZ4è ø yË¥Îáz(µ±ø# óO³ï¦t1_Æ>›–{+ûï¼åëýxø3—0m'Ú�¯" ¹P\×£´c$B´L�óÙX«=) â‰N¶¼¦ÛVXJ®‹ØHb6ÉÀÎ�0zðd©�çJAÇŎʨªC]¡Ýl"¤æü|Ë;ÏjôºŠTü–#Û÷‡$™"�ÑId¸`iù“Heú}�iÝVaŸj铳¼Ý‡$ d)¸´æ «ðæïŒÁ:t15${ Š� ž\®ðÞ�àâËsá¥ÑîDL�w7o³´Öè­¬ƒÅØ¥äÁ7Ö„º�Å%Ýóˆ»ØwÆzX,æù«»»iRf÷‡Cþ{j»Ø)�ÓfØq<�ýœe¿@¬Z^· xœe�A Ã0 ïzJ Î#½å FV•DTML$CO}{CN>igVæßüeHEôuß)îk¥¦LHÿ:Чb”ÝUC#¸Œ¥Ž€JJhðNÑÙ¬¤|Äf�ã‚¢åä®ßêÇ®4Y³ä^¶Bü®’Ië±'xœURAnÜ0 ¼ûDöФðz_ôP9îÒ6e+KªD5ñïK*Î=ёّOày±ðLxq„R3ÁˆÓÝÇ¥ëN'øõ—2zßu?áwò,b8OQ»\ “p ¨è± D÷Íw9ê°†2ý©¬#ô©KÀaòu¦2ÀûÊž`Ž(±‡‘´n˜ï0Å-±G[ MÑX®˜—&¨¦³À}JÆ'P¸n¤˜’AöD=Ð2€‹nçóJ>Ýàë[}%¾=H�Ê0ÖWÊ ÁGf¡ëX�£|xÀùûüÁ²ÚȼE BEŒá}¥åÎ)™'CK¯Ã5-¢ ²’E–4µFfgW½ÿ£¹M æQ,ëv�ìíuR,Ü"1SݺãUkC ÔMƒÜ�çïŠ –$B®Š Ôð'}ªÞry …Úö7ý5cWkºr`y~éÿ�¢Ï/MOó¥Ñúˆ³êJ™fºhчºÃغ½íxœµXÛnãÈ}÷W8ƒµXôØ‹äa¡)ÚÃŒD*µ¶�¸Ù’ó¢ðâK&þ÷œê&uõì1òd“]¬®:uêtµ>�Y¬^Êdy_Óù§ó_éº(–© ÑÈ<úpô�FI$òJÄÔä±(©¾d¬ÂÚ•úM”URät®¢hí’Öÿ <¼ eá åEMM%à"©h‘`ñ‰UMINQ‘­Ò$Ì#AOI}/·i�èpq׺(æuëö+<-¶í(¬eÀ÷u½ª>Ÿž>==題T/Êåiª¬ªÓ‘mZÎÔ Zi?ËSQUTŠ5I‰<ç/®LÎb>QQR¸,Öꂃ}*“:É—'T‹ú),¼ÄIU—ɼ©w�êBCÂÛÀ*ÌI3¦dO5º4¦öô>nlÿ‹;óéÆð<ÃñmkJ®G¦ë mßv<]‘áÜÑWÛž�NØF<¯JŽA&Œ¡ˆ°©;, PµQ²H"ä•/›p)hY<Š2G:´e–T\É áÅð’&YR‡µ|s�”.Ñ›6Ë¥¨8©¨Èɲ)¥ýg,EYø h04ÇÆW+°�©oŒFÁij®ìÛÏÃÿrññûwl½žêi…)L•—àÆò<×»p�Í«¡u9»Voì›»Y0v‡ÖÅ•a�hpIó&Iã#¹_�%y’5YÐU´÷›åM�ýªŸÿ©´*‹Ѝî¥É|šnõ | ZSÓ³'Œ7 DU¦)U5ðË€Ì˰|‘pšÌG†s=3®Q)³„œ=µ'ÊÑÔƒb1¨Š¦§ex•~Ĭïùn aHûø}â¹·L?¸´û †¶÷zjŽ‘Êœ©ôú¹Öˆña`ƒUXß÷�’EϺµ§þ”¿ßY{Õúˆ+%PÝþ%º2€{ •Ï·SæÜ�22�Ì©�6FQ] ϰçŽcz„´“lá¾þ-×”ÓI*NYò°õ[5󵳉šxÁRñyýYË‘¶„ë×½(n}üò‹ôÙG^"�‘¸z(a“Ö·äiS•§’@èò¹Ü&ÉQ³4•°èGI¥M,z¦ªö×�×ÎÌVvä¬6﯒<6›b�­°¡ãúdμ©5 ®Ü™3ü9ÀW!°‘r±€/Ф3`Iyû¿Ô?°ª£”¾›ž¹ÝL:h•¡MUL)i\¸/EÙ亮+¶ìm÷ i¶Jˆb1G›r“èdÒ«F®ÓßûÎñǤ¹x™%ÿV�»««}CÓEÛ€÷¤]Ê")Ѻ�è �yó3ÕÖ¤�%IÐY¬û@¢$È(³¬RTeY”•Þú8ªDÝ[÷<¿#Ó0¿X4õ=ÛAž7÷Bj!Òäô{òôÒü®³á ‹©ÃâÈJÁ ÃW €ØÕ/=åfã�8±<ÿ®õŒî‚ À þ°MÖÇ/@âG±N€Tž•"¹Éê*«{mšúQÇA[æNu‚.ñ`‘†ÈÚv|Ë»2LK1Néƒ ˜Ç{dy�=¤±á#X„$½ÿä•�ëí¢÷%ë°\"Ïv“  î§›Ó`¹Œçص«5%·7’,yçFî¹tŸV¢÷^OŸ¶}‡£&äàF<×e¸—±âí;½‘¬ÞCõƒjj%“ÂΫ"Å”@,æRÞÀ¡º<ññW D@£’çÙã¯I‹iðÇ:ùÚKÊF( ðŽ‚Wí±Øžú{€\ÈȘKÉó W›3nêÎ<ÓRg\UFz‹‚/7£ê³D¦+ðG¶3»}'èà Ù—±¬`ín~‚ÁzAO€˜ÂG(7~m[²I°þ13F­ôy6{sY©„Š›åŸåfY†Y�· ijˆšZΖŽ1F?·<݇õ³ò€gd;´=k(#•áüx;iAÄJ©¶a®‘vÖ®l5ڡѧÎhƒ‹Üm/ރϙCT®+G»NôñûÞ§¯ë¥Á­Lm0�{_t–üðºYDæUÂÔ :~Ež¾l,Àª΋€ÏÑЋ-;»JÇpüF–Ý1ŽÉÔŸ±¶¶ç.o¹Qï0•~wÌà&�8]¿a[æõ›¦uÊöƒ‚ñ‘¢Bù@ž¹kŸôÁ]ê §u‘׌2½‰²¾«›]û3ññ§IëZLëp>D­Ó¸Áñ=‡Ç¦¶Ý4Ý_fAwL=ghxC:;®ŠóaôÀaM– Ó-6¯–f˜è!~ n_8‡mÏ][�=ž¸žo ßð®-³jR½8ÓÏþ¬RóQ)BÖȇå@I´¨e‘I‘„ˆ¬ mhãîZ"$U:ÅÐW‘Žú}ü®RØž¼7ô7×Ýa?>À·GÙ”«ÿæÛSÜÀxĈõc–()si~øqoÝhÃ$#þ·ö$}¾•ñècó¯­K¦ cŽ~7ç“®,âOð{Ò£lxûB“‹Ù7~rÙ‡GÛ‡¦þ%MænÃß8l˜GŸÏÖÙƒ‹.%x×E+Ÿ&8(c#p6Z9J<‡Q66ÊÞŶ—”ˆÛãÕ.´g“ü/xl}r;ßÃæÆJ>Œð��{[‰Ö·°¯çÁœ8üÛØö{Ø3—c±hªÿ›BãÃ�àÓØ”]žÓÆŽúÙ•ã°1ñ‡uÓ$ˆ†6Lt�úÜû½B(I>ÍXuãKà™Y­m×ü=e|B¬É˜ÇOnÆòNîèa(\¶kZðð7Q`…yµSH>û£\'Óø±éçV¾5æµµöÇÄ¥—´Ó]‘¸¯wã~Àºô/ÂlB»™`ì®+,:z9M|H ´Ť›¿' ØÔ#Ú¦‘I=„ç)G–g5Ñ“/iÐõ ͬ®�;'FéÂu; [.ÌÑåO‡pRÖàÛá N„Í,½fÙ ÃIº…s‹Kn×ËÛÝùBaFu‹Û3\˜“]É&¸×®x1É~ÀC,É�Ç‚°,©w93÷.DsÖµG·‚õCß#œ MsO¬îAÞ°w(½³†ƒÝ`m¯¤Ã™Sè b9À5/Å܇y׃²üýÑe¹*µË‚tP¸ Xúj]éG>,507™u8'=‡…Ùƒ`—oj$-‹ ¢áó!Ì™TèY„ôtðZnzêûš•,$ÙÃxæ¾ÇCÚ�,;Q– T¢�™¨¬à\ñÍ:=¯ºRÇð(I&`k }aiA5Áaó¡ŠÐµ|¹(Jm]ÙÅiz}w&,d\ZŠQUgeŒYÃlйßd¸‹jÌîEìµÓ\Ô9ÏÀc”B}×T�4ÕÕQ7ž™ÿ¬¼ø"O·¿>Šzýþ z{ OÅ‘'DÂQeÎg¦(4d.ã“/­0¥/^Wã 29KåàRfUˆL*Ú8Ìðê7VD(åß4,=ßvèE5"�GöIY�Æ�ð†Ê1ž¯›�&©½ Q�Å»³mE†[Ælð�ê(#‹Ëh×H�ÓãQªˆïSƒ–hÌóïøÊlöàzN>(ÉÁÃ¥¯¥É1YEEíT ¯¼4§ƒ6Y|hýÑ·Ú g¢Tš-¦(!��U»¶öJ³ÌÍ6w½é"vU±lR¶ù®C!âÜe¹Ü�3½YK #1w7ëÙüðo@ê©æ±äŒÌ¥m†L`ؤ½xç ¿³Z¡i*�°«�h¹cÞ¢B/«©ˆÝU@ƒ£˜nŽšçV:OàRž¿(ÕŸ×ÀöKùëSX“�¢¸Ü _“¨ùâûZ_æ) » ˜B \Eе[]eüu‚¿J—~ÂH´*�1À(0íŠYÿæ•’p¨á3-ôZdÆü$¶ –¦>hÍÓ‰¾g5–õ윮ý“=ˆëÙ[Ù-BÄYi�YK;AèPöì ¼NH PÚÚ¿…'0Ô½ZùƧB•†©ëÑ ¼–qÏfˆJ¸�XðÔ;W.Qÿï?ÿMª2LUm ümgÏa†¨CÑ?1ÿÕÝK¹üfËF—« � iÄùª:•â†[VgZO†°°Ša-$:{ ýQÚ­}§¬*gLj†~´ÿž}óÒŸ�Ž:.A«Šª¸íb (V;„(E`!ht´Óq �5â£|&¸­­3‚¹°K…ZÏÕ­g‡¢}t°ÐP‹Ñ«±K?òéZïªÆ¡ïëd� ©Dý�D<®EucÚ-x!+ 0û("¿YX{3u‚ÉypˆBr� wõˆdÓA»º’$®�ñõ??||ÿ÷Ç7Ïöéùñ§‡�ßÙÞ=þ!ýÑ>üð�}|÷ðöøñóë�µô¸Zƒ\F.F¸ùç×À/—¸×²]¶(ÁÚY;ªU&¶ìB�Šœ«c+ùùŒ—ÿÈ”>�æâK!‘«-3K\Š bï­&µ5þž—¯'pµÑ0÷Ñ É¾§ôJ唨ïCØ÷7KVD²öGÜ ¼¹¥id �ྕ¹›2éa4ÓvüR^æjA´;ðr˜ôÌ?ïÕ�-ìmU{T s¿§¨è%�L=…3œB>Sý(S+ð• ËT¾,öt¬žY� w™åöØJæø¨L®�JNŒ¡Ã¸Œ§y¢’.iäEGoXEu¸¤¸”A‘{º“Ë]µ`äý2npŠ›{ΔºÚ\ ´ŽÝåùN½NKÛÐrÒ8`Ó£Œ–‰¨ÚJ'Sk¯š9Öþ3”›8?•þ wˆÁ nz |G)a‚—y1éx‰û"o#ëm²r¶â‹3(¾ ´l/ĤÈoºÝ`Ò¬­±]ÈQ}Ã0н¶8^ãê¼dÑ(æR-— B÷�Úq¹g耧b”êl )G{¤Tðê|MO—KxËý�86RšàC^'Ì/tw¼r­›n}s53¾��ŽÔU5½_o¿ÙwZ .›CÎSúöþþt:m¯æÍ»¦î݆¸¿¯Û￾ÿæžúpº»ª÷æWŠ¡\³¼Bxœ…SÁj1½ë+|¨ ÉúÞ–BêCRšÒRBZiv=E«Ùj$›ýûŽvíØ¥�%Í{óæÍÓ ø™aÇ1'jKFc¾#¹�X­s8f˜¸$mv{°Ñƒ;ƒˆ£Ô¼'�1ñ/t¹1fµ‚�Øq …{ŠõrOÔG¨l/m9Á#9Œ‚p×'Äc6f÷ZŠde­òxm$ôÐN`Íó«´?×ûœGy»Ýº`� <ÙkÁ¹ï?Þ?XÓ9m�EÓ°€ya]×ñÍ"c Içø#ÜIu‹ð¶�âëX07JF)ž:ã,áœÝhfá>œ¡ç¼E"á% °^§Óiz×¢G×ábJøt9x½ÿ}p7‰Gü“Žoå„0 !Û0‚"zî:Îr@ÓÑh‘eðNÄË{hÑÕÄëƒä€+ÃÖwìÂhð.ŒÆ1.&�[ ÏŒønáàE&9¯£ô#|cRóüÅÁÞÇ›d�wñ O¯‹»0‹t†3â'.ižæyŒwQ÷•+¶+,/é$€OG½wï_wO:W�³ÃÞQ÷ìM@ÿ^ò yÿü¢÷—Îáàê¬}Ú¡ƒC(ê5¼K³1m>‰~Y(€ðcÀlYº K2\­Aj±ÏxÝ É4Êó`™.Øß#è]ük˜�÷øXåzÀýNŠøz)p²D`à³­Ò8X'M”!r·ï’p† à3gŒÂ#�‹)-{–0Í;Á<¼�S%Ò÷>¸Žî‚ÞÈø¢®ðéR/@ÙƒŸý÷2xb{Röåé«Î�‰ŽÒr@ Ø^äæ�E·qŽ;N³a”ñ^aÀ‘– ìaÉ8ž�&ñ-áe·êCŒ¯ƒ<`ÝÂÝ㛆d+K§Â:ŠîyÍfƒ¸§Ë\çÒ§^]t;¯iciÑæ`'·p¼h!:ᔾ›Æ‰ò·¹Þv—'1~ �>„ߨtŽ'…€x~t|‚C7ñ-l(Žî€0Ú/‹xô1+ Âaºàæ‹ îR¤ŒIù_ð×(š3’ÙáÀ™{Ƽ9?ÅÅăÀIïMÏSÂ%ìlšÞ¤}øï9ï0Fâ7š.à!EIª¨ÎDoÞdzÅ,˜DñÍÄpVR�¤EÝ�"8ðï¾Ã[ßGSÆðÂà.Ã’«ï<úT^±„�Xì(�ãý6ÓLb¤s€ê8–^¤ÙÒÁ (ÜCïrp~9¸:ê^ÀS½‹÷JpûT8ü.1.A÷ÎÁŒŒƒt™èÞEcÌKæ1Ùð4ɨ@%"#[Ý…@ ‹0#:Ô½†Á¦Ñ50™i˜|d¦F¼Å�”™A®X,JCþl0¯{Ö¾x�Oüùvˆðì2^tÚƒÎUÿò|ÙW€‚¤ˆ«~ßéãC‰]+�g¨‚oŸþð âCtiȯwŸÃnö$ðYíy›‹(_ˆÜèÒDcà^ ½Â¹• Ùz¤·<2\GgþVÐIÂá” � “ ‡R^/¦x Ðl’%ÍöÂ`²@¢0¾DW$OÙ(âùš|nx1$G“ R^”\ÀY‰s`tYˆ=ÜÅ9Þk p¨(£}#gb IåÞ$½0A]ÍMÎz€¥S´\ä¬çœuûä¤÷ÓÕåYøy‡8NÍ�7ýÃQl‚kžì·û‡Ý.Rz+uÁL`uÛÈAs\ ,tqd޳^sÕxŠ×@CÃ9Þ&„DtÎæˆ«ûÎ7W÷ß?ƒÿ¾õn=u™Iãé\þþ›§ß~»`u p曓öÙ›Ëö›Î:ZWõf�ì$N„¨°´è PºÈ0¢!.Õ„y�ã„ï3ë8,ƒ^„ˆ­|‹ä —µÌÂç*Þ²¶RÕ‹ }�ÅC3›A; A|ÄŸ°ä8LšÁ«,ü5žÒ¯‡aNé— ðÝKa¡ôä¯ÑhÒ ŽÂ$Îñç¢À?; ƨœÁ�!(úœ(ßÃñ:R�x‹´¼ž»ç(^„É|'<Ìk³áç›o üÌ¢èc38^$7a†ó"º'c`ž9­¢ ë¦_þÎCZ¹ùm[ý€´\WXœ£xGëú+û8ÿ¤·ø×Uï'aqK“žÀ‹0‘u�¢—2lÏÒì.º¡_e—Á.í^?O§´×s7 ZôE:ãq‚‹EžË€ý(ÒgòËþá2ÜÁSìOÓÛð#ÿ|�_ærý; zøË`‘}Ä_ðÎ|Ì@çÄE ·GÝähõ-’][îc=òˆC€Hº:í $xuÔén$?"ÚC í �G(ÁâŲRV.º?‰(ô½(Ò¨5 þcdy¦§¸ÁÑ4ÌóÊýÜÍãY<b+ùKx·{¨w ÕÂÿàÂá”/æjØÀ6.ÕÍ \àk&�s ÝŽà»ó,B»½H ña�”7( Øs½HFô¹£±Ž£"8Œ}ÙÆ9½ë®j|*8î]¼<;êÊú¹ê¦Ìeg�‰�Ë;%¥Ø£©yM— <ÌaCynm/«`éÉÕ’–Ælãàx¤¨…þ²óxD½îÄ ŠP•u�d8D),ˆç’< ã)‘GºËŠêý€F·@dÒENˆEjtÐAåÏbþ¶‰JÒ5*¸0^ÈfŒi’¼A£‰^YRÓ€ Gö:Fc˜]'(Àh5åͽӨTw“téR„oÉ:ÂäväDbÑh=Mšõ‹rFØ�™�[)UO�m,8¿e1üÖîïT·EЬlÌk «Ö�ªAô\z-újõÅrïâAW·Ø@¹‰ ÷¼æ¸å¦êŽ9’8cPjª‰ ~ƒù†ˆ.Äf ôfFáÕ«‹ÎÛ.ÊU.Þ�f^ZÍ“àC#XõïIy¥Û>�»Xû,sÍ·ºõuÏXÛš‡Xëž±`\÷T¸öËdÝ·ˆ_®ûSû}ÈÏ Ý=é]õ;‡tneRÊH䬤Ð8¦mºlV¤²ô1�˜v‚"“à].Hk¨§Éµñú»Q謮;¨Héݳ“îYçª{vܹèà1¤¶9èãw"uÆ ¬”„+å‡ÄWk LŠÐöL�‘ í¦9^jãwŸf@¿BPhøIù®¢š-PÙ�›Ñ?è\àÑø&ÁéHCH§¼*¢5a.).�F$°¥M ­V¤¯/ON®ÎÛƒã-•—Í‚²8U«�°QÖô˜¢9’[ňN€ÚY®Q€„^’}`Åv:�YøQLÞ4$ͱHâ_‘§òl`—åýÊ?‡]FApÑ=¿z}Ñ;¥'댉ÄsàïlæqÍž™á]¸n< >1*â€r=PG,Âò°R›±pK@ŸT‚HYãu±?aå¿2-¸n�ˆôž5&¯œH‹Ñ dS±ª9ñVVÚ�,[$e¦É‡”â¹áv[Fhâ“CCµg®““, ‰G3‚†F>ÑM^€Ú=\Û‘aýŽ1Á3ÅÖzä BYôËÞY¸ø•û€8e¤PÄ©AšîÙá'#�q�*S‚ôÇJè²*Ã[Hr¹ˆ>t¹nÎ]èڣµš P))Ô2\I~a”­Jî ÂU¸búG[‹]ùÈ‘¤÷ÙLjÄ�¥ç•ÑU2†¥%Ae¥£9ÒıšQp†•d$žê:ö»Áõ4¼ñ�Î���Áþqïb°% 4q¶M„ReÁ. 0(aóx¨ íYR•‹#Å7µÅäÅÉ\s€.�òä+ µ=˜¦°'6 Mã�¾à¨Ë­¹‰ÊÌáÑ>ln?p·èü+ñƒ¿´ß¶Q³i_z+¯»'*°†BÅ]òý‘bwˆ�Gqü™…}|™;Qê¶·ôU ®ý«öÙ™¸°¶‡{HÐ&œAjj_ûÿXÙFzA†Ñ šŽà¯5§ñdQ”=©�=Œ÷舓P¸ò {x'ž pJ— ¶¥ÿ6ølf¬Å›½›»V#r�ñ |D6¯oÄá:†ñêQøÃJ~{ ¬Ò|óôòdÐ%‰óðüüªÛwÌ6Bó\˜§$ΰè7!×â f�÷ Ö‡¿ÿ=ó†pj`:œ¦£�ÁnÜŠZ¸ú  pðoH \öøÔm–)±rä¡xçù8ÍX²J¢;'&™_ª‹-/Õ_b˜» “7;™ÍØ|�Ð�€Dˆr Ýµz…ÈÍ«œæŽË¢•Ä-™À �$©Y&Â<9왡‹Ö^ErÚà% *½Iâ_Q¨K–Á „ù —oÅ�+N¼ZšÏ— 1á©x‹¼|çói¸d)·M†|¼ÿ¶”Œ=¾ÊUJL€ïˆ“Š6ÄÒL¨Ð8?8î�]i@ÖöÆS-Ì µCYM I(ݬDê6»¨Iéàs¢.¨žá¶¿}§ó¥²ú’Šb”aRìè¹Ud ö³Htr£õªœˆ¸Û ±q ú[Yö­5qÓv¼uº´ÃßN¿sÞ¾@‹’XPÏÛoêd±r=ÏÒñb„,ÜV’Ÿo"v�¡ý‘—^':ÔHÔ²OU5P.G½üÚèM$Ý�,†Qh�˜g_lÂjwéÒAûÕU¿û�ZíAxÇ«�–cá(Φ‡ÆSÆ–Úœ /¢HÕ¨ˆOæÈfíÛè!„£oÌ™ž‚üŽA ôò‹àYÓ„9è'húÛ|| [4ë÷ÿ½ ¾õÌÑå�™P»7tO媧…#â¨æ®R(då[A;á÷0Б� è F♽¤u68BQ½§áxÌ×ù CF×× L¾ü3œ}ÐÇèš}�¿ø� ¥’ñ@µÍP!EZ!Ì„¸W9Ç*XC:I‰äFÝó‡ÿïÿ^582_x¾¡Ä0Ù�óÉ2Gƒ·ùC>dÀ4‹A@…ïðâ!Fþ„W¡žD±:�ANƒïÿ޼ÿø¿æ†�âË$X‘±‚}éøü /³K¢Ã(ù”"e?ì5ùáŽÑK�ûÁ`J�¯ŠÜXçÑ[¯¨ùáïÄ=>üCÀårŒn8¾�áÙ‰mâÄ‚?óK¦},Ⱦh„e¡»täÿØC›i·Ý÷•9£Öö…PôÎÝS¸ŒWâŠ|Ý»¸:¬Š™ £�àœÙÝ¡yP¤H2]–ÜðD‰�2,Nv ¼š‘S ¤Œ&8¶ðÖ‘¨�nùQÔä@9¡®¬ÞMŸè‚Òáq ¸��>,”©‘€mºÆ +§3Œ7ƒ¨m ÆõÐ1ÔxQíØ†¨!´m‘‚¤ŸÐÆŽ |gˆ¨YàeA#®¹ÇÑ)� _p?Ø9JçúÆ4M?ZØ @ RkЇÿí³m ›Äà_”’¢‡�2Ò¶[r׺GÞ�l»%|ööƒÃ<ð|h•›7Ó?év¶ÝMcd·ó”¿Aü}â!©˹}†4pd©�)N|“¥‹yþ)¸Î@2°Ô çÑÙ²ØÉ³ÜZ\ÇäI!ÎÊL Ôž(É�×I½)qÁ†ÿqC‚bEˆRË:»‘DŠðga�#xñΘ)ì‰LHU¥Hû}àž³�Ül�×ñ1ž0C!ƒÄ¦"" ŽcmÕ¨þÁÄIÁ'_ê¡Iˆ`@Á8!™<ÍršÜhÃÅr×AÂ[ÊÕ êÙˆ^Ý£“&ZþŸTm ÃÊ'a6vw€‰6QMoGÍàüø¤Ùq°{fÇ «Á×½!b2€idZÝd\nÒÍj²Ôˆ#ؽŽï7q§òé ýå5~Ùa:R`à‚õ);Ìkxƒù?&¨-VŸ4*è2~`¶Iç!’¥` .Åð·ZŸÐhh¡tL°äcÜ‹¿*ü^䛨¨ƒ‚ï�¤«Ãè¸ç_¿¦œVœŒøy¶:ê8ŒÑ»Nš œÅ�~к–¯áùCïy›BΧ_ {à¥òåaˉÒÁÅñ0(šapº½VŠÎx/’ôÊ~AW€T£I:ƒºX2³ (F‹¼Íؼ$ hh!yTÔ3Ê 9o�‹³¾�keE6H®Œ‚ü®µ“%\Q…ðfy‹Äé$¶Tðkõxs:kÏ£Èæí¨ÔdŽ×»€zïAõktÞ :gýnïìê´}~nÓ[mý´}ñ×£ÞOgWýËós`Šª¨G`�}-¨ñ¾Dààæ§ÆœGÈ1‘ëüF7£wR°´iÃFÓ;&B­>–².Eé@&h¢©æ:΢! ÐJ¢â@xV~0“‰8'ˆmuÆš!L s[t=NÞ¨EF·L7æ$J¶Šl(¡Æ÷ö›ãÁé ãû»Ó«3�QÚzAÙMGXGž8Òz‰ÀÊPK¹ ³1ù¿`óÃ4"$qž/Ö¥Ù°a¦r†%à !#žÈ wˆŽ³“Ë£ÎÕq§� OeËLHqË¿FYHþ³¨¨y°˜‹é”VŒ§;àG‡u¡Rèâ %$%©ujÂ> Aü-‰`p<*}›,ðk\™(>ºhÐE5ž4dX˜;r±s GÁ°±tŽ��(–Àƒ”qš¾à¯3ÌÓqn³1çQV,É#h½x>JüðYÏ•gÈŸ`òW¡“˜†Kg‚úÔGåR4qAäG ç4á®aO(àÞÈ*añJ�1�/$’(ÐZ,²2�ô�T�C§á%¼?H Mxš7O²¼ —tÉ;k®ÙfZ'vu~Ñ;ï\ Þ;'íÒzqó�®/✓Ø\r ©#ð¨‹î¸W—ƒÎÕ›‹Þå9:´„4oòPe‘Z{k‚Km„�ñÎñ±âꀡùzi3ß9‘Ú _5϶Ÿ+ß§~”+8Ã�‚aäz÷ô^Áí^wê@àSRÔÙA9¡Dw tŃçˆ0’y R{®Vq.ˆ¼·�,A·¢<Q�‡%cú؆€QVa î,hyÎ5M…ý»¸Ú {(`03DLNÈÀ·q|\¡ÖC‰AsÖécÐíaïô¼wyvÔ¯ÒòËWô$ù§--ǵ�{É�#³”‰ÅÍâ…ƒáv¯]m\!Œ””©«‘ö$Ø�hÐbh©ùÌömvê9ôÚˆþ-@±“$ÒÞ`œT˜ö¨_BQ›Ó¥ãN!4„3À|<Þ® ߃!>$©3ŠªJ›®¿ Ö:w³Ñj$>šƃ:i÷ûõÑFJfÜã¦EÂU>8ˇþ1USq.¯Éy$m…I©è8dÚ®âÕž8GÐ*�â]¢âl£d¯66j�´„SôÁQ^ÓdæˆôÌI8ˆÞ58æbðz¯lÐ(\¥Y|%€ÎàÙ0N4Ï›â»k×ÛE —!¬¡|ýîéù ü\\jBàw¤C'܇£é˜Iñ*BrX±~aRŠä É Yï]6÷�$5§«žZÖ(ÙFϤXdÌð†Ý3é8BeüÜ!ÒÀÀÚ†0¬†ƒ”›°µÜ xäbÑç]:Gÿ¼ï:¯¯0‰®/Ï”ŒGM e�3"ÆÅ;mòÞš¬FÉbæC( b¥'ù¹r ëá<±T(N(ìeöA™¡_’wæ't—¤Ëòi2z_ {†÷8Ó{#Ó š£x-Fò* I v[Y²â1>;æ 81Ç0�˜YÈõ!ñÇä—#qb-ËRà æp:,}DÙÈâ eÞ\RøØ¯‚('›úÊðW ×Íbú@DîÄžsI�Â\ø «Å?|&�µyËÙ0e?&ܹQ8šDn0"Sß“^ï¯À¯Û‡Ç \±e`è'T RKyÜœ½*b*¡Ó&1//`ULÖqËꈣH�×ÛH ‡ ¨ë"rª#ȺµB åŠg¦Ž ñ^%Í�Q4GüáU“*íˊǺä–pÍÞŠ¤ñ™&å;Bë0¡{>YDâ§*ïMà cÀXL¾@]"Ì¥fPdàG+°¨¨ZL*l+|<ÿïÝgø}åöØ™ç�ƒ tðˆå·§­Ö¾eýO›%KÉšÎô�çÿýì/ÿðÝwßü¥…F›e±ˆÎë—,*¢;EàÛ­/rSgq4³F�BæŒ ]fVޤC{>Fï¤1Û¸°ŽÎv¶ÅŠiñ)\� ¤T¾yª7àìò Ã«ÁñE§ ’§_0L⫊ ZÐmˆ"Ylð¬5:ñaAñÄÖð­ æÉOKæÊÀÄ¡iB¹YÑ Þ†ñ”ì#Z „kx˜˜¢œ%JGò¦³d:@øF¯ <ú”+>jâ*KZú–3%‘X«Âž_%Ǫ¼Ë–;|@‚ 䀦8^C¼È<çZÿ]Ñ•iˆÞPZÁÕ#”ˆDtž @㘠¬„ÏßgHúYĈ®¯(>+É)l:�¦ÞùœO#¤â‚¶¨@±3@k,r¶ÀðöVð†ýïlØ-Š,#âbÆå �Õ©)‡ˆží•¬²èÅÆ€­pú›ç¤~H]AYEêg�^¼ïÕ"žŽãÒ}B;;ïíÃÁUûädsqk2£Lorë$egK�F2´–•Ê5…Îå4a3›ºŒ¡€è¾ÂÞÛJpÕ$¥;Ñ tŒº­ó‹î[Ð�KÎÙ•ت_öíA÷Ц»ÞÏS#–¹[Qó„#Ç©ÞXMP³�CyÉä‰qlð˜mˆ?µ/ÎÈ“Rr·¬•9ÜS«1–ÎV�Ps¾!Å*³y³&ý7Î>”²#lËèqoµºŽÍ«½zÛ½\¶W ¤�²®ü6ΊEÈ�wlº+íá1w`Ö¶zíÿ:%ˆÊð¶ø[L4â�ªô%d‘Àm°²õ¨+—5­‡½½u —Kèá ÝH¾ôK•ElXêIï°}²……$ØeúAŠéžªÃ0Öwc¯W7lK¨û±û F5sªÒ'^Z¹gnA¯…Ø8ÄY¼ 8$»Á‘”¦©x,LõP)U\ÁÔS“òíh[N˜Éïîæ”*½[VpÍ $“‚!•‰EoG¯ nTŸBL¿F_“Ƈ€šW¯÷|À[bCí´38îõKçÀÍ û ìEHÒd9ÃR3ÕÈ¿…¸QÑ4ô6êì6•Q”°õ+3ÕùöïˆTÿøJÃØÜ«XSª… 7øÓ¶Þ®ÉÁ�k6ã°k¾›#³âm™×Yïìꬔ©Ó/ßó5PW{ å±a¥CÀöS„®›¹asôÇøÀŒ�Uªh—" Œ'ÚÛ¬ÌÑØ±³ Á`†Ži�_“(>gsq¤~ïä-Vk¼ç£+´ažö+¹G5�6Ég*¼J»V[°ó�Ò«teÝ ¿¨È Ò¨e–PÏAU2oÊ�÷‘›„šr¾âwAßúU9YÄFWÖ±¹ 8V½^â<µ†‘lc…dµ,–óu”KJ&1ì=M–U*ݯ°² È=(¢_ý±áh˜Ê 8¾¾èvÎ\çÒ¶  ®³8’`R“GñÉNÉ Ø”’m¶XY|í»gW¯zGïW¤7Ön/YVô#Éɤbc®f˜Ž—"T‰[«(�óºS½^üŽúúW¹“í[]Â6pñvìý€œ$Ý ¬ÓÄYÊ¥ò'5 hxÕèSSÁפ«9E’�‘Ï7§êüè^‡°.Añfj$”‘pö‡ �¿óЬjÊD‹Ùœht¡1�;l÷;WýÎY_ë™– �œÁÚáTBš ©< ç¡ ¬»2ùF§¡K¤\lX]kBoì8ß�!¿ÈÑôKµkØCp’ð |]�\ÓÜÔ�¥*B”–#V{Œ¼j+$óŠ“ßO¹çx�)Ý’�Īa&×õ/K‚ÙÀZ6¸ZJ•ˆÛXã™K›td{5 #¯ì¢ì’PÀ—ºÇ¬$pXŸÕòNø] ˆ¦VÀàˆ‰»´á†9&ê! kb;8¨B¾áª:!‡K’%B©±c½4àÇ™c¦>۞݊6Ñ=f®>±í�ít½„PØÚåZâÁï—7wq²'þÝVyYpêCÐúqJÁ1–©‘„’ö$N÷(qð/“øe½[¬1Õ1ÍáÀhk-#‚ØœÂBAW�Ê%«ÆXâÞ?ì�×—YVbS­àæªò,SÇ— “Úl1+ÌKÛ&%G%ÝŠ`+麲—Z-šžRnwuÑyݹ蜖M°qMyØ«‚/qÊV–ZT'�Ìãè½Dýì L.e�}óñ Õd?žEíO€Auw%ô�{?™€fLWØ2µÁ•m(s¡á¦PÙÏ$òFZÖÚB~šL¼6†¤ºÚ:T¦Ç4ºE_|gÕ¾¼8Œ6Ò 0ÅØ2 Ê.LÍ@¤ã«¼Qʧú­Rχë~‹Á¦‚_V«Þ\8ªf›•B‚½‹ÃŽh÷ºíΘNW©l“Y“d?K;ŸÚSñ”S�´ä4g| ³pô1ZW›‚vV»æÕU'_÷VÖÙ /þ“ƒGþ‹e*Ìx_)'µE±&Z*rRºË¨ lR¤Ä*_Z…xMiŸÀŠ�†ÙU1v¯z�ü*ÑÖi0�OB�¢”‰,m5jòé×ÔÞð´\+j8*#ëÆ›ZÞí p�æä®àW®Ðp²OcÀØ`.¢‹žç$N®§ÜRˆ„ºõ"ºª®ëiÈZ r(A°žÕ9Çѿ†ý«gýÁƒQ�K6Q ÕÈèƒ:O�qÍ«°Êçf?±•Ï)ê¶j„ñG¨Æý�¨.†­µAà —…»(c©uyÁìåJ²�%+ýØX€!éWôæQѬü‰Ü»òÀ™”¢â®šlUq£ƒ$‚¶¾㺫jL= J&àX;Öš(ÅÛ½�z�*à¾åÈ^âÌœ÷e÷íîhõÍyõÞ‘&ëŒþ•µ¡‹š�ü$âå¾-è@ûhVðFó:úS�’2>é[C­pŒã4ÅÓ c«Í˜ "–_)yw-‚á‹·Qfœ#ñuU5¨¸i«#•Óøü=ðúäc÷ðÑ'µ-Ñó¥îè]òWö½«Óöàðc�+¡ínE³àsM9O4à†D_SÄÖ”¤â µ…{+SD1‹(ë'«…¼ˆ¸Œ¹2)�¬ÊnÉoä¾2C—+{«ÓM:o�ã1UnȤ®M:¼% )Kžö,•3IÑtAɱ”2‚ªâJkZ5ûÈã�ŸÕÃÔ¿ô…ÜÍÑ(šWv_ÚwiÏè‡\ÐJóuþ>F‚Ú¥x¶¹7�³GzG½“®p¼Jÿ92.í*Sœ­†7ìžõ8+°HÇ©â%ã8‹Æ¹´•»é“ði·ÖC\À,;UWíËNþÎ:ýÁ§ï,’ šÛí ë~?ÒÎtÕkvöêòÍ'ol¸¸Ù~_ðð#mK—¼n[G�ó‹Î!üvô)»â1Æî#ÚÉ–›tÞØTªmÛÝú;ñvÛ9k¿’vƒn﬿f›#t=Šád…§,oR²?ïèP¿?ÉWSàÓ±'mðk¶°púÇ,?Ãécô5ûÐ[^«œž) Ú~Ê]wÐmŸtÿD4Ôö*†~R$¤ÎÊ›Êw¡JÐä\…�2L‰Â£‹ê3a”¥Á�ˆ10 ‹¸f‚^¤‘·j@c«0MÿŠù;NÎ)dšE%N}{iç´#SpI5ÈÍ‚)¸“Ü:Î$¹Ó"GƒŒy¥¡i0Ù1œôsÝ=› iÿ¹€¡íÄ;JnÚÝ}ˆ>àºü½‹/¥^×Ï¢›0SÄœöÀ}HÌä³§ð¯6ùÍS*áP‡3@¾y꥚¡ç²ÈgMoÆï†Ùi�6ÂØ®l¡F9Ó‹ ØZ¨5:Y¤a„´ÂÚjåW©t_¶íQS ½Ã¦VÔƒ©Hyëešé�htdN"Ðë˜q±(•$ ˆD6KÅÂ�Àrµ¡~ø7j ÚMÆÑ=ÁÊ|ñšÊϬÒó6¦r¦˜j.Кg¿ÞV×ì—¤lŽ yÀ¦ÏŒÑØqö^Ù¹}nÛí7v*ø”í;Ûªc‘T-ç-h¡XáþÔ÷ ¶IA”ÄÚ”E 5»¨<ª­–“Ûôc¤Q餉HR-òhªÒ’%m×:^rÅôY¿mð^©„šÎÅ>-˜Vt�(—?ؽ�QÒžƒj‘ìîí©©Ƶ”\ÛÞg3‡59Na�’ÊÙÔ®øƒéÛNð‡ÐÓ—šé†š¥Wù®·†îëŽÐóÇž6Loc©íåè7 Á‘; :öSR¬=WxÅ:H5”£Q·!—%Ÿ´ß÷°,$<¶C¦á2Õ :¥\‡R¤«¼YÛ}EŽš÷ÂUO„|br€æx+5.ûGcªóì·ŠÅ”}qÍw!2ËT›Óþä„uX!Ü6ª²²åša;0/1F<îûSQsMþFcÇ´¢F¥�mÙc[¦ÒÓ {ápÍNEñZ^ë~6uB Üóvóøœ½”³\¥¦º»vÉɱ-cœî%²LXYM¹•¾YƯcäèÎ.5L­j¯Íæ6vvqÌ%ØŠƒ‡]ø»¯æ´ ©^žTÇÆCÁ»?¡/ÖÏçÔƒ®qÆç´ÌHŒ¶øwé#HyCÞöM„Œ=WÝ©…D+,€üi:e×5Á=Gàs/²ek͸7wñÇx�ã°•f7ø×Á«xˆ ª¦*öƒm5¸Œç®â\Ü1A›jÅÃiœRºËRAYû`„P8Þàûƒ÷'ºAÚxÖäí˜bIJJ-вi%ω” ´LJ×&³½# qâ º†ÝbY sUm´ú×}Dpå1ÓUv°cY5'ÆdÌÀê$C©n°Q¥éñúèÉ3ˆu»ìvjuX8Lq>H¯µl ¯Âú‡­+ ª|Fâ’eTy¢²½íá3¶3:J–™+$"}½ÁŽÄCWþ¹v$“Ÿ²ÅNõ*;F²³g»ã(ËͰ\ü¶WÞy}nŒú–ÊÛwrpó SÞA<�½sû5õt'¶¼\MÄ’àÍÂÊpó]|øØU÷5P^žvÎ�£­œ ,d5ºj¼�ua½©RpgypP­¼Ë'�TÎ:ßjw^ÙÒê�càhçâ¢WÓ:hU+wÃ$-b�=4®Ô[©l| ÚÖõ+*u…ùÂÖZMv*bnÀ¨öeJcw_ulÜ„bã4ù ³@‰ e†(£y3¹MÕÄ,MnÖ•­ñi!TÖ ZôÐY�cı°�XðåšÊ–,Ý{¨b«®9qnZÞp!‰'•¼<)ëgž™?r‹eZÜKÙSã�&×n‚õHr…“œÔ\Òp"ê˧©ù"ü29J†y$ö”ŠÝ§±³5þ2ö¹¬G­ÛŒžj÷Wá}Íu�g3!¸Ër‘Î)3�ÌPTrÙHš¬»nª×íîÉèJ«XÔ*ÍŒBFœÀAeþI�»ëÆÅ³æ¾r¡—¨Î—R««r�RÚ:ñ ’Ô�ÚœN´ÈW¶¼Çé¨{ÌRÞÒ6ÇevQ¥ÒÊÞ°„úi{Pg×Õ%]Ç߯¦ø5 àìN–LÅ›$º¸Ö3Ä¡~Ç*îïÐ"ÉÊêï8t.¼©í/)Ù<’¹b‚^(XGŒ®NkLw±Ò²‚•4õ·é—ÒNÚªGΦg¡ÑD‚߉ëva?lyij*pÊ…“½$Fÿ‰ÄÛ¦CÞaƒ@�Ê»º÷îÁíÃLOZNÑãÇOª�?ñŽþ¤÷f³*­ak²wGÔ$žäÊ]6¨XB„ËÍ+IË4�#vd"Ôd<©H÷§Kõ²Ç”l·�³ÙÄný/"YwÏÎ/M€Nù4¬IÀu@."V›�((8똼±Öˆ“âÉ–Doü.sbr¹ÈÙ’÷Ñ|Îu}Üè�ƒEžäÙè`¶”ò–¨=I9#»ÀÒâ$HX㌺åWçF¬ª»vCfL¡}ä ¨°{V=kD Š�ê²µôFÒÿóùEï/L»í]bäáQ÷âϸ·uý½W½s0'ý¡oJ åÁ4R’âg¾Ï«Ø¦±nRjfF v,I¤¡;(£)Ãà¤õAW’G¦FqÑWQp9x½ÿ½ÕÚJñò6 c¬fD ™èZÑ_Ôô€#ƒà‰‘-²ˆ…ås«©s„©øwêÊ«Þ$ ²R˜zªúö��Em,såźþ5! ´Qű«ÎÙaïÈ­t÷’p$$׆ëÜv“„êÜš¦W ß±ùš #^‚:‹p©ñÕÌ9vÈ`ä]º¿_ÓÝæH¼¯[“=É›@$Py“ßË€q–µ]1~¬`�ê�DÙÎ´Š¨©ØßØñjöW Þë9-9 m^ìPEÎ{iìT*ù»K®–Þ:1úH!ˆT(Xà5‹o&…æ³_‰ ~ßX¸§Q×Ïû:Ų>¬dÉLTª‹Š]¾€ãiâñÿïï›|`M:­öËøºõ3µÖøºÇô~(†‡ð?'Sú1¦cþ‘ò� 6™Ðüö„ßžðÛ£œÞÂÿÍ'sùñ­üüŽ3ž!ñp3üKkóÓïôþŒ>‡cv¥Ü¥VÖsÚ1j‚=k¾¤9–wøãú‡§üƒ&¾~ú ÿøž~<ãô?¬þuëv2–Sl1¢¯É9H– ªøÌEr.,lƳÙðýýý†à®à÷¿_ÿbÀú'âxÃ÷›oZd¼i‘€ƒox`¼é�tÓ“ _oú~”&› 4Ù¥Q¾뿆 ¹ñ�o7>ñݦ'à¶o:ì ·b¶ákm÷°þ© Иm˜éÐú�.7}·þ V›Øl p›ø~ÃÏ6=°ákŒ©Y÷×�lÀ Êë@z½ãF‘*M�ÃKР߮Ueµ “&ùbè*(¬»²Z¯ªƒ¸�¬gXÖ]4�n0½ØÕxÿ\ÇBç%7­[îz=ϨÚäû“Œ;#£°Xé«}zŸÊ”Q˜Çèå·9ØÂdYõ̉ÏYšra<Hf|ÐF¤-‹‰¤HS¡Í°˜°Ìc>Òd:r69Ø‚ˆ(×-ª¢Âà+ý{YïUÿý)v©÷ÒR¸2V¨òZÒ|�÷Šó�ãuYÁúJ’Ë" äàŠ/r�ž×³�ñÌépîözªìÀÇ¡ÇÑ%t’OS'0”ó^³GQF¶2†Ÿl1e�Þ@¶8–ñ�"ÆQ4¼ÁåÖˆÆvÓ!Eâ3QK'î� 1TÙ=¸k§¯ðÂôi£_àã_[h—ÄÁ:|zÕ;ÙÚé/ecɺ‚�c6ÓÃ)m|ھƿßÕÛmÂMÄÌj'Ñ5…œtøI'XÆ[s�¯'Ö"s®_«Á©É¥èÙ|Û : =@̶ kmÎʇø·/¼oÍï/^|= `óßüä7Ÿ¶žfõ´ÛT0°áxÛ£®3z©ÝDíÈŠ‡~kï š9½«ñQl®¸Øöˆ68ë+Q¿:ÂPÝÏÖôÁ¼ø`úðxÖ†’ Íõ‡jJÊy€ñ®qðµWC�òe†/UI@pÛ”®XÁ•NÉÂõ)NyL'þ[}¦è•ÍÜ ÆšôÖº‘s”÷W‘>º§í7��ÞñÌôТ5åí ¦7°¾ïâ¹³ÌZ.Ï&·‡ŒzfL#ñÙ0QÁLjwu´§¼#Sp`¡öYôù‚HâŸøíƒ?ÙpÍéŽÿ4ß×€VöÏ®4w$y­±SûY×XɦÛØnýÖ›bAéÂòªìMteŦ1ã;^>Éo­“ GŽññ˜=òÙLi ÿ®ýÜ)ûùæuš)fiÏÕð#Qï?µ×Ð*J²Y}ЩÎÖÕdj�8fIÎLÎíq¨£‰INÆ�ÑÌÅŽA©�D!ÿòFSSú§®‡¦±qÛ¸Üò…,úzí†#+0 e®~(áx‰@9)wÞ¦®š™]:#5QZº­Êy¤ÌäTëc þ8Ù“šœ( ’e-tÄÆ6¬Îà:îKsÖör¶¼‚WüÝ;'<8q< ÷&Å�Bt/ÓQ$\h–ÇÓ#˜L€C8�ß‹e¸Äöd¦�µ\›jYNMUö®üo@Ì2¤j$"yD\g+*Þ4}/b ¥;ho Î�âžSâÁÁqy³ä-¶Œ‘ž“/ ˜Ð0<¯/²Ï,½Ëµ6+Yþ«‹ÞO}>E»øMÝ`ê P üG¶¹ÇvÉ„Ãz“-2¶‚¶¹˜¥­ËiÇRŽZ[{‡h­±ð(ôAmÜsNfŒ·1Tž32KÐcüº…ÍÐAþHM¹0¸%&èœî²ëD­o[ÒRŽ|cL]ܼìw®N�È!ÑîÃ%ÓÚq¬ `-·M�MÛµãÀ¥^ÒÆ4–n¢¤l˜4fO’æE]1s‰¦*Ü·Ðâ»çô«á³—Âxѹn³iqiJèHã³7qq¼JÔ�v´}÷¨“% >‘Bj‚DG«„Ae4­*ÛX¢Bˆ`ÝM¥ǜ¿áÔŸðnúÊ:GÊ®êÊx7v\QæÈi0¨ 0ý2ß·­€Œþ’GNn‰1à˜K¸ÆZA¦×ÎE¸ô̧Øäæ³9÷×à°L<ó·�‹WíA÷”ú*c±Z7ÜncAJeS«íãi›šÑF•5-=ݪ˜ZŒÔšf;^*%6±Ê-3¤€…Šr³es.YϺ¥cŃó«ÃvøãºõKÛ)§)©S…•ah½›:¬hS²垢1P`e#n!Û¤.²L�_÷ÏÂÄtŽWVx.M‰h©þ»1G°f{A5¬Ú”²; �“6¦m¯ºF‡óúkâ/ ck¢ m3, ;µ9Ñ\¡h£Ù¿v}%“­yªÿ)[0Kmì”7aî4§K,r�KD¡Nëˆ|ÚŽœµV�ÐÎSÜÒ›0ºfS\yiYk씂–£`²D¡�ìì„§ö˜·êÖÆ˜¹â¦” /î0#ue..ýßs¥Ð¥zº�?(Sbd\Äx z½“A÷¼ï–§Ù-ìÐR�r‚ )8NB“s{m™Î�RºŠx.¦T[®ÒÕF4…ÀTiA’Çe¿œ2kvûÑH$6eMÊéøéMË%ÕôMÿqjwH 7‘â¡ÓPƒÐèSU‚!G¥ÆgÚ‘e’t^øÙÑ6lÃÑl”ìÖ Ú–ï˜c3ÄÛ'Y(µÚoVV͇ü¶åCDIš:• À n–bKàñC§wŸíÑY»=K•PdÝ>†º ÖÛèûò²"ßœ]~•›üW~ ¼¡°nrv¡¼ˆ&¼µ‘v<€üPÁ£PÙ^‚š¢NÃpß¶¾oýÕÍI|3¡Z^xXœê#wóc¨0Ìo?èrž"Çg…ä pB±ybz°ÃµGJ§‹¨g�J+ñy‚ (5ûÁ©#èÏ�È\ ò�úa”è9¥·/œÌWJØAÆJ[¬3/ò©ìrÜzXèwœz&ÏÜà3{ì‘ éxŒNê¨É|4HÍ*§z©|-u®mN¤´¨†æp‡^¸xålNÉGJÙ$+ÔJ1êhT$ÊCp±ÅÁ´ýõ,êsï±=õZW½\㊄êÞfø~}yÖ‡€I3Øù|©tÚãXfÏÅÒðOi4c‹Ï5fT.í^S#"& iˆõ ø�yÿîSº�iU6[OÚOÚgo@ëw©­öyû¢¯�³7eüX,ˆsÍܨ] %ô]k:½�5Ù³9Åáh´�æ¹Õ„‘ó¥¬ eµCÊeÄâ’Q䫲Háâј ˆï“h:GÅ–užh6ŸR®/å]–†S[Èצã+UÕ-€RýQâ#´S…5±œG.«uËæ™Û‰õì—¦�$ºqÅIÙô¢ 8ÃÜÕñœBGä+Œ¡F(¾ñGƤ³HÃ6ˆ~+Ž×¹|v€P\½SÏ¢T_ÏA•£#¬éKþœ?lì¬À´ô»î@‘ã™19|?(•{z×-¦„Þ›w¦®R‰eU¢:‡‹œñ!‰eé12v¢Møö¹÷MŸ¼dà#ª®zŸY®-ëïØ¨^ólJÆ[lŒ*&¢¨äÅ+ñ0OÃÇ´â6Bi Z»gÊ4—ë�»E·cí'Bߥ$z Ø 9©F­ª1¡¿Rçlëç½jTx.ÌQì`mñ÷îPˆï Œ&r¡ãJ0¬›Èù"@θîù`(£� OÓÃÑ=(‘“Éø¬7¥P–’¿Ü&"†ÝЂÈɵëÄ£¾�ç7N±j…ù|�·õx@(í�€Ðª@�µ¿õaUØi_K¯Ó„%�-î(­×€¢ÜÛ̉ð[­E›H#žÇ‡Ð°Šeü²n͵ÇXdΩ×,[4µ.&£Ê'QTTR]Ñ8¥% p•¶ˆ[Zg%ò„µÝ¨E±z®ŸÛB¾ètÞv;?íȳ�æ.PïÉ¡26µ‚ “iØŒCÙœ:³uµûw„Ͱê»+��ùû:M ÷oÚ5mº5ÊqßïA¤d’ùšm]¢¾% t�=l/ ïdsk‡Ë'6ÝŽúIcÇÔò£_ª¥Dý úg!ÊŒ¶�*áV>TFšcØ&ñg*uB ˤ2ØâŽäº&w´;y¾$n/æ7[¥IT†ÍÃ7ŽuÑÔl¢‡®µ¼±£†±@匥­;¹à$õ~EAÄǸîr‘ëⵘôzã'«�Ñi¹{øw AÆv¹;ñjË­Á§P�ӌű$9îP*\änÝiKé®A"›VàøC1œG®ÐÇ °�¼µ ¹çó]yþTM¯Ü©¯ç¶@4ñ¥ :‚`ARh›Ku>õwñ¡(O€ãí‹D³Óô£í,æˆ@å(ðUø£àuˆºk ýÜÓ«ÙíÊ£“¢�YGsŸJ7¹Gà™ØùèA�  qˆ?K‡hÜg-ø±[:Ì=ë-@<^PyÄ!.ƒ¬ ˜PÑã ˜³þZ«ìr¢Yÿ¤‹q¤ñx8';¨¦OØŠ¦F^(ñx§P¾!ß—9+=Ö® ½58bm�F"IrâÑB Ù··gäËCYß‘Šåeåä%_VÄ5Û¤Œ�ºqkØ"À‡6{ˆ I3rÊ‹9öú …•âgçK‹Å2° 6t|/Füöy¬×·Šºº×ŒSMõx†Ñ¡Fb�xæ¨ë„ûŽ '«©æÜ“˜”ƒfw4I“%Òàªå{Û–~ T¯ r-ª?¨4,CR²'Hfææ ¾��$P«·ÃYƵZ¬sxttÊaÈ9臇•! 抑.�Ëæw þ¡â÷ß"ËèºZ’V]öcÐ ]üÆS©}×�!Ðr•°…,� vh#9OÜD�ÆNýþÂ|?Îÿ�ïŽ[ÆOÝD(SOéc¢Š[ÖG•ÃÞIï‚¶{u|Ù1‰Y¦îô(�¦æºy¬ðÈ]/ÉÏ‹¼°ïØ®Å%2 G±™õßáJ©£ÜjnRZ”ÆhÇ‹ÈÏð s²ò%7Xº7!£<2r„Vcrp¯¯ Œ#Ö o|Mä¨1#À€OÝ2Øæ[üá)®j¡ÿ¿<{NÞdúšŸ}O�–!üñü[úc5ƒož>EgÀ"›O¥âÜ7Í{‰ƒ¯‚ô›öéi» ë›Hˆ&øØÖgJO‘;-fqB8Möï„ì×5Õ;ˆ·\r†Ó²°mˆ]S@¸4ñSr2S¬Q„QQÚdZËG17 Þ.¿�Z.Ñ;6-óÙQ[–‡ðqq…£[µð>eshò½{-ÑR Ï Ú´¾o:þº7Øyðyë9_DœP%!ÍXg7®Á–okÐåÛjC–ï[[¾êö“ƧÝÓNÐ>=_ê%j¨:±}•¹ÁÍ;lmR-—I�ÛHØ�g‘õòá—®f£†Éa)óS?!`Û$� ZsçyÚaͨ,?ë‹Ü&ˆû ›¨ÛÑ8¸¨ÏŒÍÙöª+Bsè©£÷gíÓîáÕiçìrm¨]àþæ<ȇœÁ²À�|Ê®™$¼�oøxyáW^b Û™TÍÀп€tÑ'Ë©²Zt—¤œu>q›9=œĉñ’Ñm4åÎEáÐ-MQØŽ³a4–¤Ûˆš::òƒ0iýÀ’Ó¥]¦Â+é(d3³›h’ð-”åo£—†®÷W<^ø'^Ÿä=æµ#{ÜÐþd¼n‘ñ¹q›”'˜šcôP½Ù¼i ÿn ¼~LÙîfzs~B\¦gÈAwuvyzÕ9\tªòj¯Q½D©µ ΰ��ÐV…RaW/TÒ¨RŒi˜2¦Æ_#ÒÛ¨G×tIÒ8)ø\Šæ~ÎÁ2Ü g: çy¤“xW �wÖ·÷U[€áƒssÌE2ð}‘kR°8B±l+Û¢ë$É Áî"ÁNaÄæ¸¬†.pÌsiàMt?Boߪp†õª145ª,Ýt�«±cf{æ MÍnZjB?—rà�÷W—gG uu� Ük¦S­]Þ9¿|uÒíw.ðµO9…FMkÜ`¾Nã|ÂZó¶‡@¤ÇêCØ2¥ÌÛºYΣÁÚƒ[Ö:áJhÓý nËpŒ…ܬ‚ãÊ}~ѽñ-~xû©‹Ó:ÔäÇ¥»�‹âG´`’må‹Ð7Á`ýu¤­ùÏ�ÚC×<Ê‚ž†Ú`¡ŒZåú4Æîôºi„&>Fuì§4ûˆ:oìÔVDŸéKÄû¢d‘`IÔP€=‚¢3Í1*éþÿÆã—ÏŸ=ûæû=´ûþ‹5ÏfU'tÃ6MX¼Y7]Mš‚$ý ·º "N®6,D†Ó(j°ÑM°ç¡ûUÃ-†9Fn·BÙBðÃ÷¬·2¿nºR±qÈ)ÕˆÒWœçaâÉtN0Á­è˜=‚ã{åx}/�ñÌò¥¦…±:ˆ‹ðÜúÚToàjø»äjIfJš­jbnéNÏÇ�é6¦¨jdÖé¬ò·‘dÌÍ+I2Qpx|º±ígÉKÄ·š¤úˆ&‚¦=&e˜„ÉhƒíP±;Š’}.ñ„P·Lð)’ë|¨�í¿’3¡Û÷ÿY‡ÒñáÕIïЦ“¯Ñ4Iñ5¯¬"‘ô Sõãžç€"ì77zX+º�öÈ¢…=�¨4Q³±ãëØj£­ɪ‘Ò² Æ@Å;ZÚO¸ª\)¡€4W›ýó™Àö€Yl3Âáq—k¿Ö¬�Á¤ `\ìð¯´e,Ú¤Bc/,»T5�L‘ˆÀBòÎzªý<¼mº³þ­d�Ü4¾p‹©{œ7Ü b Àš� Ú M#$ÚË�Ççä�G»>¥uz'úª{Ö¾x5è–ÎSóI0=¡l¾2M^y¨¡DÕ¼‚ǧ'kµ¼æ*a¬D²Ý_ežu®¨3ì–5\vŠýÒxà'X�ìã÷î¼}v$ðcêã N)‘Z‰u(¢§¸’j§ÍÕÕ€¬B…|¹’Œ©µÿ!Cûâ€qv]˜²Xù·ã:‰’üò)Ð0øÚ6Jã ~ö¶{1¸lc Å ÅVd›u˜yU-YÕ uO¬4dý åÆ_¬òÒ$/’òo…#¾�°‡¨Þúe41 ¢ÿù€R8�%= €WÃßŽßøÒ�m`/Ìß�7É‚Ÿë¤Œ_¤›<Ë]$YXnçnÍóF%Ì Œ¦úà®Ë 1;Y!.ø¸ækɪóSùf V$‡¡“ø Hp®õ¿œ0‚Jtî|apÆeɛ潃ÀÔŠ®ÑS‹Z¿­8U›\~ðK±ÿmë{ø�øªÈPòÚ1«^Ã?Ó^Q l˜þ­1VÔ\ìO:Ô$\h~ú)¼�3ôö7°²¢`þygqËÃí3>çkN‡}„(A“OÆöHÀùÕáe_«7:&‹œIj\Ôœ×Û㊘ SL”ùÙ§úPGz-5? μ®}©¿¸MÍÂ��¬€¨D8ÜïÛƒ�”ÃðçQ ‹"‹AX‰”‹#+â£ÿÿ&Ë&ºg?˜ã!;.ÐÔw.©Ÿ·jÀÆÐ Úv°Ï‚O¼o×ö( «‚ÆÝ›/¨ïÖÈC¾Š[§ß²¯«,FU4ÛÖ/“9ËŸ ¢7+µÔ²èÙ9<éž÷;õF͇xDAPJoX%29ôÖHDbÔ’Ìc(žvFÓýø$¯Ï§‹›8w¥uýaÐ<}C‚0; §f‘Í—‹�èwÃîB™…E¸²¾P[òÁ,§|gìp”ëר ƒÄ›ÒŽ¼Ê¼TŠ„çfQÏs¬éSh$@‰²WĪü“€Cˆ‘RÎŒz¤w�¤í8 צ¨Œåǽ(Œ=HÙ*LO¯ :ƒ=!¼82ÌÞªœã„·Ù³ïžV�öü»Ï!Õý^ýÔ=rzJ½ÄÁ½a*×܇K�žõ~RÞ\â(Æb�'·Ò)Ö„ÉP¥â}¹äÄJ:®Öt"t)—pp±a!ç/#�á_œÅÇŽÂX™³! º¾6úü—Êwª;¼ TÝMŸ9m^ôÈñ§I<¨˜ò…ü�DwZøÿûd?qºQâÕætø^^6éò…ít'ÙŤdKåÖ"m¯B±]éZƒ«¼É•W4ÚÐîÖîÄZ5M�Hé˜Ôÿ¥ýÎÑ›³ˆ¬…%ÄêÍiXLþÞ—Y BÂW?‡÷ÌUXrÁ.ÁhGDwÆnæ›±Vñl*"FÃÓ�h¹Ï ž³pž·, íÞÍ­£U[Ûh4–oÆdÃdˆ÷D\ æµÁ/Á`x’QBÂAƒ³pÉö½j†Uب2]J"×6ŽÎ6s|­°*c §€}6α0êo ˜¸‡_ãð'¢[Š«eƒUšÕµÏ;u:n¥M�Û—#p˜eU*þK)jDÉÁíóÖ÷¹AÁ�ŒÅeMòˆ¹ +¤ È>švMM:í‹�Ôs"êæ�z.sjEsÙܽfpF‘§S6àúOOX2±iõ©é´[œ�{:å“Sd±Ò…œœŽ¿éüL[Á:ÿN¹›ü*~¾y9èäRàÙ1狅ʵúÉb[?çRXЯhÑÔ(RÁo¸8ç  €TX'-ÊiÖ2%2‘à/ošÐZ­EÀšsõ»VÈT7•"óŽ¢iL¹ÃgQA+©£‡w~ô•h¹«­ô¬¹×'Ð ŽOoS¹”U^dirS­y¨T)$ÀLµc‚‰%¶\yõ8šOÓåÌDÖaµ1'pvcÜoÖJ¢â ™Ïr~þH8¯Çcp~»Ù•Åë0¦pgÛúBödm™R®:Xî4/m¼È\Žiøcµ˜ØÄs–ô2&vÐ>íãúõwQ€ŒÎd^H“~�mÄ6ЇNkùŸ�±ó¥ æ1z3؃@‘«¶éŠ‚Î Î&T“‡B‰HÕ1†ÆÎ㲩vEçùyõ®¶�·�§#ˆý¤9óýNûâð¸söFí~¹^Yá‘Ó†éýuœý¾SrqÊ J÷KfΡ!ë:R'Ÿ%ÏD°(0´lo•[¥� ßx\•ØOà�Zä`×õÊÃç(¾RÂ\î|ʼnG{¼;i9,iâ¦'坯TÚL—ok•#S+OÏž[cIs XÆk\šÆãRͤ‘ qçýÎÅÛÎÅÖ¶?ºâ%QQÛ+„ê+P›‡–¬uú°"àyFË\?FËafãG¤úy1›+«q0ÍE I•Çç ~`C€Ý;ESïËX ðæWÑ“Z>Z–�˜»Âp:\œüØ þÔ>üxð'æò?ÒÑaè-æ/Ø’Ÿ¥ÕÑ"F‹,§s¾CÈÑZtO&@@ÞcN˜‹!¯épGSdTœ�d]µàÒI`ôüÇÆ{öÿÔI¨ý;a§¼ƒ>Œ"šáÚÿ‘mˆ¾¡ 15ú.Åmnm*›ÈlÌí(!³Ç¹SþÔž*Bcˆ(‹Âÿ©?‰¯‹ïBæG©ÉF_íJ­É¼]5­—}–v¹E²‘f²C6i#îMÊÆcèß�sµ Ñ¿bX½G%šèà‰ÜLc#)—^ŽŒap áùì6Ê\ “?í”u«Ë ð\�7ŒÈ¿ÅZSð¦3žK&p¾XTq1Q3xçâ¬}"[!\ /•˜èl›U”þüø\äabbv.­õáF­Ûv•‘¢ižÒ:Êà¤!XÐ$ûª™Ä�ˆRÆ%iÕ.OmÒ,Ë#Œó¤£�tepV�tè’Íâóú“¹ØUE½*F•Ý2xÖqáË/&7N¯6vrŸãéÖœFB» ‡øå$ÀÁqjˆMC;e•ÎÀ¶n'W­™Nùr] <‘sÔ•âÝR`X²Äq¡yuyqâØ†Ò¡ªk–ªÉNZ^¾|Ïs)¹nʲÈ`*ì9'kBÞ…OÝÄjQBhñ�“ûEþ"©ñéïKÊPÀXïÂy ‹(gÞÓÇÜ�_Bâ.£gMIåÐD2]Ã|&i.Þ—B¤“ ^ ªcÌë‰Êÿldø4D@Š‹ ŸC±JgYÖêH.¨| !L‹riý"a`�íÆÖ[¸ —Ù†ŠÊÊ$I‹ a1AàY±Å“Í¢€ÓçÚ(,ñPË—G=_Èq}­û­ZwlX»@s�üiÎhj÷ÓZ¥ ²0©‹âÔÐð@ªb“”aƒÅ˜Ú…ÕªF×Jb‡xšÓöùy÷ìMŸÅD:àb’¥‹¦±c5/d‹˜'hc¡[Õ62ŒV4‹z\r¦a—Æ ±b7USƒÛWtŸcSÛ×l“ËAÃ"…V$ÒvY¦8¯iV˺�lp»pn°¦ âv™µJ0Áj:®áâä§À>$&„— ƒñ UÀ°ú!ÃÎÂù\LL˜KðåXjý§TFE¬·Æžkùá¸Êk,d,JÛîáÚ1îÖœÁK<¼ÎÏ^ÂÏô¯çø×ó Õz̨™õŸÕÖŠ]`¿Q_«ø_mS¸zÅÀ]ÛÕ­Qš£¢|á»ôÕÖ½­xö�æVxl÷QReôó€àj©4¾ªÃÓ#›û¶&qÆsý2+$Qžœ”(��kñèÑ4Æ%41ž½q €~\š܈A kÞ˜�¯ù%nä™Â“\’öñÄvF )j`Ú×@JT9�2T…¡�Laå0¹YP|Háµ¢ZºH‡QľÄq|M Ü‹)ùdªF*£ÙË•Õö&¥™ Ìñz)Al#ÿk‡Ë l‹åÃ×KÜаhdú„'n«ÙƒÞCã%“zƒ”çÓ†^k–‚�Þ"­P�}°*! êºe�s·§Y ’2NÊìí³]H¾mfÿÆù{zÌ6ü¦sÓ¥ÒX™AÅGá'Ÿ†ù$Øý°‡bÚuœå®dáˆíf±uk„‹"�™ÂÁáØ):¢÷m>ÜÚ3qNÄžr ìêy¶Ô3‹G<Úº«­6Aï�ž·›9 -F®ò˃d¥1*ßä"4£ù�±Åf2É`o!_òŇLUo¤H9÷öÁˆ[ +• ÆÇŠÅ¨‡—‚Ú‘ó6)ÞŸ¯� Ÿ‡s”r±[z¹‹SàyFÝ]W†F„߻ϟ= îƒç?ü1˜ÍöšÁ”»ß·¾ƒ�Ÿ=Ã`ÄI”Ó77 ôò0à³oõ+-蘣‰»l=§w±êª<² áƒb»ëê@Ë�n ¿õµ û_Ûo¶ï3Ã×ðî#¦@�¸±Sö— yšT´àÊŠp€i10 œæÇ±æì¤‰J£„ _#¼L)'nl­Oé�ø°À`$™‰ bK;= ct`S\èµÛ<Äù¬B àzɯÂK¥ÏÿN_üaœÿ…†<w�gq‘Û•†³œœÜºÊÒÌ5ÿ§å¿þ.oÿƒi‰ÓØ 8É‘q­˜ªGóù(VÂŸŠœÀ4p› pesÊf<Ѧ›©–=·lÁÈÝDè•äñRD ¬/H)^$E¬.Ÿ,—ösêÈPcGßš jÛP M|x/¸iäôÏâU}#Åf)!ý’ìmXh/á&qåžzÚÂ’Cþ>&Ô§•ÒÑÿŸHþM²&¯Sl€ƒ02¡™¬�›ªà³(¤d6§›!Ïð"ø]Óë´ýÛ8 Þ7ùWüÁû“—ð�0¼ÐÎ_”ÉCCÈ'Ã,Ž®íÓô&-™ô5”§·7‰Rê¶Xª °¤ç©l¶z º`‹ˆ J�H-yJŒãbõlVpºR=–hPé§Yº6ÛôÓ,]§™æ6×F_}mlÔ~$̤?¿ña¹ÙÿÚþ‚�„)L¨Å”ߺw g9²=¢ü.‚v·ŸÒaÆû¬{›�êtÙs  œ½¾:Iú‚òÝê4+ãVs¸¥¼òqÉ|áÒŹ¸”aô`WÂýóçóñ5•°«ÒÓj|ÆTåkìj͉‚¥Tr' nÏ ø æ1ÄØ"ÀxQ ¨›åbaÝ|ò+\SéÊ›¥Ø(¦$¨—Ä-pf…&ãC¿b¼•Cò ~¬È*—‹w”ò®KÅ`ËFÎR _s"Œâ˜‚¤ `ª|H[Ê~me†&�ˆLس.Û/‹ãëiäš|äJïáÒ9âc¢4Œ7÷…OÇ{M\“œ/öU{px|Ú;êl< ¬ò‰ï}bù) ½$¬g“–�œ²x#ð¦ý˜Ö XnA¯f|Œ¢¹öAÖuÄœ¢šF£†”;áQùG$ …_´“È6‰•E²œË¥õœôEº–_ÖˆRrɈâÅq÷ˆÊt;5�=l¥¨ŸBEájR·„…ÂÜöâx�×¢ËU °Ü'u •¿£bÔ2Ek79o&Þnˆ%[÷3ÕM†U Vî­°=Œ‡Ó8½ÉÂùd)�@K£*�$aÄßõ-N_ÅCDXʯÅE©ßéšüÝ©´ }<21xDãWPé³z6¾H»Â³žíVÈi�h*ÍôB ŸsÛáQx´‚Vë ZiEXÅÄÎiï/]Ç+¸Â‰ÓîG#óK]º=‚)Ö3GŽdk(D³ôçØËO—Þì¶\é´lú¦þr~”'ù9Ÿo�âKRÿ¬—ñh] Šÿ,úÅàõoåAÇ©¶öŸÛui -ûI nÿ*"ëO U?üÑx �Pöh‘¦œAºd±‰мàh(Ê€¸é¦zq oõ.øfk=Žò¯ãŸÏŠë‡ 6.¾ŒÖÎæ}‹>Œ]ãLÚ€ +\I|pÿ#Iu0r7_ƒC"„äf}ŽXÑæJ Ød‰õ@ é3šŽó–â©SD}£²ÖØÙZ]ÛJYCiÄW×èúâ±¥35{ÒGšÙ;�ô²n1ø#ÍÉNÌ›sŒføú9,t— Øìï¢�sMG×1hÛ_òÐKÇY>ô“4sÈ3­&ÉeÖûìPÂÁ�îMC²¿Ëmù*ç#¯ÇqÍ4 ŽÙ¬æy|“ÈÝÁî0Øß\ÊC¦š@@È¥óf0‹9Æ]šTè6g>èVè„Ûû \eÖq=¸V/éÛËr 6”>¨'Þª³,ŠJ6T—™•ROÒ,æ*‹5Qƒá£BëŸmN•úš³¬9I¬íXD3lºÙüV/V׳§ó­RØìGHƒí{¯X—wˆl’ª¢Ç”"¶b@ífzôI2§í³í%]œf�Â�KwÚ-ºN5¼YëøfkÁÃ.ç·’<°ç¦‘6`úo¬aUZ‡;þ§h°1x Ñof÷¸^”� ‚ÜÔjàÓY½(c» 7€†,u µièÊ¥½¸dWÃ4Gâ…:Ia'C<Ç)–oFÎ…¹Ù’®!Å¥Á:ý~Ca„èE=–wjäK eÈû¾)| DìÓÎé+’³¯ú‡½óÍ.*q†4vœKÚ‚.Bž�Ò9µb¸‹¦Ó&÷RñZ <® djv÷Ï#œG½ÃW½Þ_+â©ÓmM@�ÒÑ-›Ä=ÚZò™‡Ï�^oKu!Uú(ßlM#u…U:Éû_mÊ�D»�v²ÝD'Ǽ¤‡ «Â£Œ°%hXz)süFX«áƒíE‘¾$9rl3¿ê¶/=øãê¨ó»>•:¨[´)žüâàÓ50j‹ÅBØ*oX3lÏkÎRƒp�‡Ë ^Ȭ]Z4¨Z°)x!ºŸGYL¯L¥‡¹ÖVW=j–®¯¾æ_2dÿLéð<ʨÞ,†½ýFØ‚ÐÓÞÑ0Å]¥Ä«øGÿÉccgÅáÁ£×ýךüåˇ&™’î«ÍiP#Õt"Šl2¹ä Û›l²“ä%¨Âär ®6ö¶kìþ6v¨Š7�%(x*áUdV¹Lô©ó‹Î`ð¾€õ«7,Ë”S-–0ñªäTV8ë<Ì´¨K0Y ö™I”½äÿ›ì¯–mc‡k¼Q¤*‡ç¢»&å D­ '‘&äò˜À3REÓ¦ŸÉÎzM1…kò ša´ ¹Á$™ž¥oi/¬ï“³‡nv…z,awÓYÊ)Ù³tòœ¹fcª¥D?FŽ‘½’…‡·‚P½EOês€9è ñ½›sÃq÷êíbS….áÞ¶/pa¯»ïÊgŠ@OmlݬNx­î„&)sÇiòÂ[“s#¶ŸÐ¹•Û->&”ýMý³Lóh…äšHhü~ÌØÛß9£n2°QP§;ýþ6Úi„èM|†<¦‡ûîJE|ÄŽ�\�Aq�%’\9…›m0âk7“š5V•=ªÎý¶ûnç•»¸ŸS©s,‚Æl2Þ¼õJÝN˜Ã!'dxÃ6cj�CþOµj‰Vl6ˆ4-¢§4M™;%*Æ)7® µÕVX¹CŸüRИ&†=įzg'ï $4>Fo¯ZÀVÍs>XË”Ç4E©¬¡[s(¹­)S 'ytV‚f£`Vœë´B G9®—È:MÝ=ëI9YW»¥Ÿã~ ÀÕ@¤pZåìðäò¨.b³)¦qçÆØüyíŠ*+ãš*H§QcÜÑWc¹�ŸË¬¶Ú}y[u Ë[ü– ªœÆ¶/”Faø 2Ù/Þª)ç9™Åm®ƒWu®�Kæxkæ0J¬ùxfl‰¹Ì¨RÊ×­ Ý@ø9Ÿï±eŸ+}.xíœì·_B(¾¥@?ÆC3Kéòù+¶4pËÀ¿­pª:6Kƹü[b“Cø ÂpÌWO²m=<GªwPÝuâð¹ý#“$uM±Â]0I¶G˜Ý�6eZÊñeM g¡lÚ. ¸Ç Í”säKJì&¸­p„9çJOœ¯µkå“—Oè&¤3¬m‚ò“—Ïž°²æù‚Ô€‚ýnqm¡€Å‰´˜aÄ)· ,ë´CòêqYŒºæJñ ¿/±†÷<.õгß<¢8ˆàæ�zïÞ£¡Ü»<9ºêÿµ{~58îöƒ�`Õ¿««Ë³K¬’võrýcí‹7°®îÙàõÕÕî>¼Z8áRkU¶®ì±hÍÐë’F=ZQÁ]ŽEC+­÷ZÉÍ æ5̓,°e�¥ž$œiu€ú9½ä;½‚‚|Õµ˜¢#�ȨÁheÒˆ(¯/ϱ™6÷žÙ”[ñUîÓ /ÔNS”D�¥Ú77>:`˜X8%²•Ð1'Q“ÏT#‡ZÌáQôga³ãZ*-�5à!�fñ(�¢öÜ_¸Æy¶†”6¥ÅµÉ kÀ$²}¢BÐó ymšº¡zLyµÜÎÉóâi�£îˆ¬Àñ˜êÓú›)›gùÑU8é ýæAù ø˜dS½ÖP„‚ZâŠ�Ð)¯gmy+¥ž×†¨PpŽWg�°¶‡¡iûAÖ3>± ¡Â|f3/éµgô­½×“MÒ¼­V|³#�ô©õƒru¿'<ôKïù™‚ý0ô=¢7þ˜Öec� † …\^œØBÑ §pÄIœp®YºŒOê3äáJi‹\½«æä©Û‘Óc ÏÐ %5¤þ"I»Ö½DS0ÔYoà¸9E°›ìI¾ˆ3Î%`:!*ŽƒHc�T³&.•-îbKKâ0V'µ…9Ñzšµ cHæ YMÞ�B�¤;+U=k¬ 8Ò¿ýA­H ñ ëH=™ô½…¯4I›Z¡o.z—ç›wH½€Ë[´†¶ònÚ$È®$žHæ°oØ-¶X*+«_i0‘ÇÎM�¦u{ô=ÂåSTÎ3—ö™Û¨†3£o½Ïs¿HÐ?‡á]¶”i:}tN«jºZÆqˆQ9 8–:5ðâ›’›’¿Ô-sÃGÆÍHñ-Î/FÅKc‹™Ä)nX"”×–…ø£nÛšPBb|D}K…NRž­› óc¹å ›la q.�¤öSÝÞ™ÑÅùåÉä&Å€kù"}Go¿.«†–[Ô±Yi UìàÞ‚Ó)J9pJ¾ôŸÄcïñ7v`ZÊ|q#V ;Ã\|ôH$N°%+ñÈÛB¦e›°�òŒ/ÏŽz‡Ø¨«=�Sî­F˜k±ºãö[Œƒ¨í¦>óšaêá.sPSÃú ª")ós\¸Ç„Îmük¹�v>»‘ï¸þ;†%(ï9V%ø5”Êéôc,ÞÊöàÿ°Ëp2A9þU4ÅRDCI‚bŸ›^ÂX ¨*g$«”QïSã”3iõ$ YÚt…b …¼›Áv;_tÚG}#Ht³é'?&+à­H�¹#°ð•Ñ‚Px+±oyˆÝ�£©ô‘?õ�oj»NÌtdEÍÏ©ib”Ø%ØYËРÚù4ÅbW¤Úœùâ°ø~ªÕ%Äa@Š3 : §t{†Qq‡¥gÏ/Ap ¹™«¬†ý £Ú®ÄÕVëß<¯t%Þ¦)±9·j�Zæ@Ÿ ϘB·p.¥¤rÝ…Ä¡†…k쵕Ñm¬v¹Ü[Õ`’ 9Ù… ’±ØŸ‰íý‘ÄùEÆ“!²”äq´¦HÁ®W /8{�é7J�¦ì’U²2Ç×CÂrp5D:£ä6ÎÒD*œ±§“_i˜v;fýÌÒ±븎ŽË]M뎣ém„µ^?÷�žÞ­5£»—×´¦®Ó`…š’ã~¯%µ¤Ü³öQ�öÅÓa&Qƒ×ßVðúù·ŸÖm{ãž�&Û¼gn´ýji£ë\ RDbcìd.7¥Í…eC;ò{_•ñ3 µ´ÑUwÝ:ÊŸˆ¸š¥+ ƒ·�|.�£:Gˆ¦8Ÿ´û}œÛçÇÌk•ˆ'fkœ'Ff ‡Û³’“O¸ ¢½"dÉ®bä®Ml«P¸ �–`:¿ËâAìÁ°dãµQ‡â§›¿VÔ…–‡t¾¦qØ;9i¿ê]�ÐòIÀE’ªàýTàz5ÎÉÀK)VÉyõ®û¦’,|š �<…c/5QJ ;FÏ5¥ ÊkÜið'Qâš“ ��¿Þ®4Ï üfÍ´YtQZ]è~±]{ÛY�—§'W' Ô.)äãuÕ£Qè„d6û~¸ÞéÐl/¦œ§)¶Ï%áçÄ«¦ÿÉ!+�bv[/³®€‰t×a+çäsé…¨˜¤cñ Ái'~÷EÆùDCT S_o†}Me´Aé0TÍ!YÒ+,"3^ÍÂ%éþ£”jʃŽ߈ªÇ‘\,ùÓ²»§]–â^w;'Gˆ¶�I:R™×feoìG™[ÚÁ%¼Yº²ì]ü±  •ÿ³ÕÕPðnPx!5¨Ã9ÊEí¤ Et?޽úß=ý?®'—`ïg”õÆBC)Wru8;(¬àÇ ª€ñì;,ÎÎç õb!j-zí꼈Þàs-RŠˆ��îLz› #çêF¹Àp,�l”П=}úi¢ŒÁÀZ.Ÿ±e�‘™ã“G�A»[±B¢ÁÉ»Ú¸Ç ,Š,0qº-�0ƒ‘Tå&ræRoÅÔû¸©öbŸ?bÖʵm=gÖüÜØ±¥Ö •“ã'ŽÅ™“Œ´fÊâŽ?“ª®DÊ2¬WX~^DÊeÿ¸wR"—[¯‘¥R¦ÿ…TÇù|.ÙéŠ�šFä{5çc’9ÑœFÉ 6I“ÏtKéêj~�ßeH>)aÏ–,Ã!Ñ߀Äx-2Lý1€(Ø ·i<7¨s!Ï(üX§ÿÔR�²ñÇÏ•îKà è�ÙtNÏO¨2†1¢­�ƒjŒ‚9À5 …ñ×XB´þG®11õ8‘–Ÿ ¸"5{­‘4¨‰Ä¨f½¯›:2VãÝP´Ro-BVdxNi´Òdcg…†$öoWvW]\½é»±³Uœï§Úáj9T;ºzõþ“¡m�—�mQÀÿçAÛ‚q…ÖÙ>9ù4M~ÄaåfÛK”XÄ>¸™¦C¬‚ ñ+XyY(2írš’ŸÐ,†èMyÍTœ’šig Ê”˜ñ,ÅVIaÎŽaõÇ ¹�Laú6â”<Ùú½|:²j©ß`¼À—x«¦£É5ÑæºÌvL[¸(QjUœOXÖŦ¹Mçâ*ÐebÿRäÎ9ð5Š>Õ¹øtĈ²ÿ ¨�¶?9¢lzè¸ŸŠ 2Á—Dsø+Q„¾î³WÇÝÎÒáš´´:LÁ¥cèzÀb@šl4YÒ9MmÀ‚[ƒ{áÒÏ/¦½ÆHRÝM ¹4©Ú¸*ò”­i¡cþŸ„ä�.áÏ“éÄ€ “NT³pn—•¥¨-†–2©Z&· Fèšhæ©Ñ¡ÜØò/î2˲€j ÝÓö›ÎÕëÞÅi{°NO ¢º6®Í~–7ܲqÔ/£à :j$ gâ…ѹášÅôÁNӆ͕êþ¦N“úŠw·÷üîÙ°1ñŒ&i -¿½¡?Õç…§®8eJƒûI1£&ÍÔˆƒ•cÄé¿}#§}³žv;Á¿vû¨r¥SF’k'r`³œ2nuo¥²9Çæ?ð¿£0ÎRç×ê7cþë†jóoôáÏóúŸ¾b~Õo�ŸôëM|Mÿ“ðÔõýÞùI¿HÍìîÜvé²&­+·:á"בèцœOôçšâÉg{o¼K£ Æ:VyÄŽA )4’]­¶ˆsˆEÓ1£ô‹ARo¿� פÚ5GH£?ø5MgZgR…¶Ú„u³ñCâ,QÌtiwqºÖ�«œf”®I\WÎÊÈ�tñ5Œv�bMüI–bøu?¼³˜}=Ìm°— ߊOºtŽWºöÒµ‚Þ“.$Äéa:÷Æá;RÁõKñùîÙXÝá  ßãt+¬5›ò¾JžI�ôGº|—R•¸‰•‰Yª¾éG0qÈÒç]ŽU±N²[:YàN[‡j¯Ét+>x=žjÇ®ò›ðªtÃáo›îEëöm÷U³ïÓþácî{–�>ußðjeß oyuÇÖm?ê±Åá'[V—ï-¯´|Š¥‘ÞCp%ŠÅl*•NÚg4¥þÜõÚhþšKØØq¯áÏámh#èöÈ­ŸÃLŠî®»ŽÈÞñ�K®i…„‰&EU-¤ èñ"c�ÆfÐP¹³"šWb)ýð.Ì(Î…£r7BW µ• >PƬÒh=¸Fy3ÌP«Fz+–‰KösÌ1«Àñ!À?|ý†+ÿ®ËèjÔ•¦¦¥ˆ;ë0Cn±SÂ9 lG”¬aœ€Ñ´TGßü7•øœ·^$MBLJOGÝuùù¯.Idýê´ýî꬧¹Ï[ÛëÉ…Ù�*^&õY|¤�qms}éMG¿'æ†ÚrK,¹‘þ¿Ú®¦µm ˆÞó+½4`»!Pz(ˆD´…Ô[!Ç"Ûr"ªJ )�üû]ÉJqcr Ž-igß®vÞ¼™éœÛ¿Õ"2t€­ +µ�œ·(÷IˆÆ½¡êA%/gÖú9 eš(qøÄ¼©�E¹krsšÚ�µâ²=6Ò4"gá%ÏÙ‡q›+Ùοµ`ÆäóÕ,Š‘ÇQé³ ´¤JÖã¢ýSCG)7ò®)¶â!á¦þ®“ÛôûÑÑŠÃpÅåÉ‚­¡)èˆr9÷…s÷„:÷´ìÞuÜUŒå›V´íBÈ·ýÃ@mÀ÷ÄÀp˜0ñîR¾ÏkÆ›4²rÝºˆò]˜6@ ÀíÔN%ÅýuºLlìa¥?•Üá­m(‹ò-Ðpf›è…Ç�qSb˜À|Ô“¼àˆI«8R�m‡àÊE– ®muž ÂýÿƒºqØkWËÌO×\Ì_Õò™^F*¥¾-¨vHÇ™\v-ÀÃ7ÒU¼\߯«dÙ—œ婬=å‚v�+’À{“mC9DM´¥‚ÏŽ¸E,j‚�2ƒP'P7÷î€t_`;Ïÿ01àMTVOW¶<òÊîùmý%º&¾‰ë:éy¢BKl2ÎÑ\%ˆ<ò7ÙÎ�̽�Š©Û{)“«Ì6yiÊl^d>Rƒ÷¯U½�FÀጠq;ܾ8­ÉK�¦¦ç‹é) ¡åóAGIÜ×ÚgîHÖp¥i·œ¶¹l[èÙsA©‚ú×Åìól1?WhÐ+¤h'ѰŒÜòÞs6úfe¤­-Uâþ)Ä÷jÈ¢ÌòŠó²@l¬¯ qw}{wsõá-~]ÐSWËõýÝò‡ø øj.®T­;=€rnþÂk3ó'š ·“M#Z%;1ÀIe['dW‰Òt¯µ±btªVõÖTc‰_^>[i7X½ñ{!�¨pKU‰ÍA¬UÉB¾ùÖŒÛ�øN˜>hxΔc«ºáX/cO+M°z»„ÙwÊ P êá ä8ìŒÕÿ¦ý¼œs+†�lºµv[zÈÛ!S@me#®Iô‰c‡$í•�%I Z€àY/ÆÀ^A­o ¬i !­ RºÀÓà·cWÁ²Ò´­é¼$ÿ ØëaÇrxùxg,éÑ�¶71ɪÑáÁG3/eFGqâB_òR³W¶÷Yð*¡;þw!#J NÇç¼þ‰,`E+;¹Uè<Ü×�åÎ+VˆýNÑñÁû´¯$Ù¹eö£ ¤\hЄÜãvºGIµ®Áš½²%оøöõï/i;æaÃAãà°:úÜd• AäFu`„Rƒ+'Ò3=“Ë6ãL\ÀZü—�]æ^‡ÿÐ&ºQ–y|xê´Õ½[í<Å'¹å$ÔÖ°[ )éÕGZoU­¬…åôkMÿ„[´¦Òp4IY¬»²É�„¢3ƒht«qwð£3õ°Çðr´!8¥ë‡Ü#A^ ?P„ü¯õv´ô;¸¥Q|Ülþ¡pªºìü¸cl(?jkZø±ÜÉ´ QÑ9|R†€¢oÿ±R°yH\1= —qtLH›^cBRÎs ‘g€¯'ÎÑ NúÀèíPçn«*-Åpèóc4öÓ (ìáKÒ˜p#-¥€îÂ1b°éü±ZY�”dÄ…€n`x8Â[ ÖdV9 X[ÈBA[/â eÛÃΰ œâ“‹¾W°ó#$Scö—É WÊê°âƒh7;ŽÜã¼ üé½$¶AP|#:¯£T¬pŒ~ˆÆ*ÜŠÜ…¹°ßér—�8k€™iÕƒ&Wbƒi|ž66|ÞÍy6yaX唃H!ëKØÌ4”°Lou»œúü�NÕ“ô/ıù¼õ0š½ïH¼¯VµRÇüT½´)h:F«¬j�Ý'2Ü¢㤓­º N×D¶–%‰"«‘Ѩ'J¡u”©“×ß"”ûÖãÇ9S6Û/Ð'\¨¥Q6ñ Åpå™H�dØ6´ ~Jù"KŠQßÀÖM€m7n;h#ÂñZ¼LåîÙj‘DeÚã}£À˜5˜âiòòeÕ^Ìâ™f^×û˰H5�€Öè…�l(Žö×uD>ÆÎ[_`äFWÉPh§Á¥d!û»âÙR±+ßþK:"ê7@)AZV²"r7¨Öå5wTXBJª‘þ v?V>f+‘kåF/2™DAfm´pÜrtTåiÇ–ðÒÓÈ�„x©4©Ç`„éYC<ÂQ\¯ËÑŒ’·•öBŸMì(P.åô¶#ì‡PD‘aÏF"‚Õlö–"ÏÕùì4…�øu »Ê¾o°Ý48�¬ŒØåU+©ÁÞülv8°" É­q³ƒìuNZMÙY[@ŸÐÑ(j_žøîÚ`Ó)_þ€‘DVOËŽ„q‡ë«-¨Ï$oªœßb�®µn.–5ú?öB� c::eÐ[VAn%þL ç÷‹T°"·¶Æ¹—d0”õ˜)èÔY³"(Iœófb<¨2ðAèÅ—Ø5sÞÂgíJÁ2%¶Sù”Þ/IÙ Ä6}Ô”AÇdëd¡IT¹guø>óI�åy=@ ]'œÁ’¹MðT¾±Å©•eàzÙ”Ë÷g¬Te èÙY ÐV/ñ�‡è›çsÐ0#±PšÐûwaˆ_§fÎüMä�[é8äƒ"5¯ÈP¦êøÜ"Ä:Lfó±lȪÂ[ìwòˆÌ¤Õ½…¾$ ¶¾Gäg¢~ ÇU¥ºjlm�DLîÿ‚;�1� †`†³ÉDÓ*虘Øñ8þØ0OÝ[œ5Qê*ˆ¶Ò°ž ÀÑà+s ñçÈUÆ‘œFÖ:a¹g|í�¹2b1Ù]‘©ÏhS¤´©©Y<<ÑŠäÓ¹˜J$·Î¦yI�“ÛªIެgÉD¥1Ž&c™Ø©u‡|KÍŽ¿ à^5±@7:¨¢Žœ¦a£RcûK³ ’8ß8³Èl˜•�±ž]%¦�;r˜êmòéóÓšyšEjfÃ"˜ºVáö‘ׯ̀‹âí Õ—�ᦠÓvKí–RÍ�Pœª_ad.ñ1»à)X1¶D[èé(ð>C¨#S�ªÌ ž€7Ī­´|¯tÜ{ø»€?â3]BÎ�)wv#„†÷jL_Â5†lqn N½”}À™¾ÿ:ùæ‡CÐ�C¤¤6ÕªßFío�° ;ð –tr)~Óâõ4jVÞQ½+bÓ�“Ú“ùlȦà7_ Δ¶ÔŸçâJ;j�ðÒ¶�‚]1 ¢ª›7°Ôyc‹•`€¼HÍKš‚Éa>÷]RõuÅ¡Áq‹š?�ãˉs/q®�?[¬År=?,ÖËu0îÇåý�7îÅÇÅÝÝbu¿¼^‹›»üZþæ�X¬~_®®€îh¾~Äé¨K'Ñ„+U6&MDsRpêM.™Š"{ ±`Ìûåýû묾z¹\½»[®þzýÓõê¾?]ß½ý´\ü°|¿¼ÿ™BèÝò~u½æ×^ÆíâöáýâNÜ~¸»½Y_sµåÛÂo@ÿ6Õtë@73ÜNÃî�ºOÓõø²ðÙë¶ÀBË�1<¥Içä²�f®ÀÛjExPGÊ®T|ˆžÇ ýwªíðÕ’4c³6Awa6�ŸBoy…°ƒÌ—¯Zà<˜/¾¿ÒAcƒñ£Ùc'Ä­d4Ù3œÎGo´tMv9·¿¡!®ÿ�4Á(éKL'Ý¢$DO“¢, üL{&]3>cÂs¾“mêh›JÕЮð `ÆÕ™Ñ¹´-!Q ×ÑŠ)�GkÓm™Ÿ&CWŽÍ*Q‹Ó¹ñæàÉF:Ð-�lÉü>‹ÆŒ6F]8€¯WWXWϽG¿/noá‘å?ß  iZˆzð¯/ä¯îáo¤Ê>Þ%Áßý.(ükÓiB Õ²ÆB>„©F‘:ùZ«¦r $;ƒþo)Dæì—_gøh2á«Ý!¡ªïú²Nz..®L÷‡ø¾@–£Aøï.uëÔ¦:  @ñ£¾;ÈÊvv7‹¹â€ç�ñ"”šzVp6/¨øi?' (NÏrÜ@”!cå¶‹hfŠq¸ZݨôÊ Ý�M.œ�r4¸F ža­˜Þ|ú—_PM<ïã½å½kϤ!‡´åo¬9Òeâ/øûUüBzƒžG·¬¿Òã>Hª¬gš†O‘¿*.ð�øÎåå÷("ô#\¾üø<ÐxÝù6” 1FT¤8"uýfCÓ29Ù…@–C÷Ͻrú¸ûj}ýT¦%_ÂПâþ�3“�ÔNßpÂKƒü�§øÿH¿ñ&³­•š¨‚œh Ä ­ÛŽp@  ,tÇoöùiIâëîô\óÿM¨ìf´.xœ}R]oÛ0 |ׯ ’¡h�Äö¸a‰ã¶FÓ¤h\`@$´¤$\dK“d A±ÿ>Úùº‡½ÑÔïxtÜn�:h†Êƒb¡Bc "Ö ½jŸ<úC6ÖC:€&hÕÕÕœöÁÖhÀyûCË!úý>¤­7´m<Ï\¯×<|'d…{ ¯ÃIú8zÈVùlQŒ¦ÓÕÓsv›ÿò4*î¿õ>¼Ýϳßc%šÞ† H`8†²!£ÚQ…cç2ýZªc ®® Uºy‡¦š72æ?ø3äƒÔÖÑSÙDª·B,4›_§óYñœ�_Š|v—Tj½¼þ§sÓ…£tD2aœ"¼J«4ØMŠjdl9“l5¿]1wò’Ze½N UŠ;V#¥J”{c·Ëëñ(}˜Îÿj°_G#ٺ˦$uÝ]Ñ¡dþçäÓW�íižf³EÆvOÕ;›Gú„‚4H•öB; ç«—µ�¼ ï°!I|ð;k·FŸ äThœ³>òORÄ Ö†p*ƒÓ’x}uR Ðþs¿Ð{¬#iþ ùf1ÀÏ ÅÃ@°-¹ãg,©kÛßP¬uÝ*}$ÙôàïlЉø,kñi¿³xœ}VÛrÛ6}÷WìÄ�©­‘䯽<ôM–äTSÇNu©›'"Wj`P¶ÒÉ¿w)%iú‡Âe÷ìÁÙœÃø­xBXøƒBxÓÈ•ÔèÎΖ;é 4ES¡ö`ÆÁï\X\½§)i´ƒÆa R‡Ù�f7rÛXÁ `#íÝK+(lmÍßXøáÙÙù9¼ÂØT•Ð%ð¿ÛFaÏX8©·g=èõîÌ3ÚB8Vº_{=)ëF*? ÌmÖ8"mR$úež­ô5£T]¸ n‡}È‹Švg•Ô²jªÌâ‡FZ,sšqèù?Q–¾`Ñx±VÈ#^Ø-úŒXy¢?k+¬DÇR;/”Ê/cy×C˜é’ˆ Lô©Ví­QpK(úç �VÔu[ïÉ.ôzàjQ Aï†A&Ò™Ó®jJMÈ܇keŠ'J5¹Üäp9ê’¾':ÙÆÊ0éLj-Aa×Åe?))Šb×ý®DaÍq6ñN—P5΃6,Ö(|Pá+e¨‚À�AbÌ&Ìšõ †U{„Á•z%ïIƒQpÙdz³zsÙÖ7JÁ;z©È3€,vÆú>8YÕêT6$”'¬Iï04ËÂïˆ =\ŒßŽ~Ÿfãl±ÝOFó ¼~2rà;sJ_¼!äÏÒïˆ å%çëj¾zÂó±e+Q‰e?®f†»•P+R@qi|Ž ˜ž( šZà‚P‘­H&{oãŸqùm9K]¨¦Ä¬$ýÞM§Ê–'é£&¸š;’aÎÇá 3ÚœÁ)D•øÑ:” 9tE=,e>ûg�Ö)¹Õ Pp ]s¨‹Æ5Ô[îâ îAëE‰ð$õÔ‚œ€O,KÕ‘ïÔh}*Š»óÇ!Ü‹ŠKí,yN!T`¸¶r/<žZœé æóx´“†-Ú { nEqhŽ€•Gí¼[ÝÜÍÆÙoÓÑd:ÏngwÓS¿xXÍÇñsòð×û7ÓûlvŸ_öÉ܈¢ÍF’�lÉ›…& ƒ¡j‘«mÂaôz�¡0*¹×sšý,�´éëf­d‘í¨oÐfÁ’ƒÃ™ÆÌ‹¥”ÎSÌFºÀ~kªh®ß»£ÛvTÄ6{¨oWc’ì<Ý[ ÏúŒ`“LÄÑ„¯*ù‚å X[ÜÈ—£-Ÿôt~üý8�Ïæ˜jÝ4t O Ý9{|¿ÊÞ>L¦íAÿ4„?ã[‡ý3a|©¹&÷µð»{bžý@[06gç¶L\\؇-j¤+ŽF)ŽEÇ�\tuç-%sÁŒ… ޶ÿ:i‹÷Õwÿ$WYÍçÓûev3»Íßg“ÙüÓ•ÐB>¢¯ò´v5_L$“ñÝj2åU‹O¯ZÓ›±!È�DïÁÖc¸®•%ÅJbW„Î"ÐÝ%Öïz®Ý+¢J¨­¨m•ôTµ:Þ´uMì1/GËÙ8ïÔÎ_·£åè.K‡�ãç!Ü2�¿1†ðã j»/ŽÈÓ=ÚCºß÷kÜRƒG‡ “&H¥°¥üHU�ê Àëá²@í0!¯Vnw¢øÛË/&ä$‹òóŠ4Î=šX¶Ó÷ŸÂ=Ó ˆ5ÈÆÅ·|0°Ï6G}°aurl¤uÁú QK²)ÞgÃØË’Uâøj‚]aeì«HÙ/CX†ÜÖâøô… zG݃í±ÖaÕgï° lê°³f2MLÑïJž—i0]prD-ð³ç¨vqWcA¢+Rú֜ڋ¥—ý¨øŸ;ç8›pe èÌœ þöÃìˆv¿*CO†ãÛ*³êªÍ}<ó Šî º÷ôÂü Iù_{Äèr¢xœ31…œÌ¤¤ÄâT†òƒòVâ©»¶lôÛTg¿éfwïá™Êh É­Äñ\ìí¼Û÷ÏW2µá…)*M*(ÊON-£Ç¾•iŸ6<ÚßÁÒ{oc¿£�•T]I*8£&KÆ…‡,94iuo[QyÑ–,OY˜ŠŒ¢ÔÄ š3Ç^ì±Úº["hÚÚ’-BgÕγ8‡ÂÔdæ‚b^ù�ïûÎt —µåºVÿe²YSã *àac£óÔ@=ÓzÁ¢ªË%—þ-5Úsíq Þ‡`·ÿxœåYmoÛ6þî_qp?,5;H¿-ë07q[cŽSÄv·bZ¢mb²¤‘”£ÍßIÉ’%§qÖ ¦¢IDïåáÃ#�ì¶àõ·z Õmt[­´à§¹90¹è,éõ—+¡èî‡LrðãÕŠEA("ó�-T?Íé ±4Diü #¥%‘VmÒE­‰Œ×"à &•ˆ0O#_ ”ì ˆqÀ�“­‹¥¦·ËìÎÏÎ_Á»8^ {Ãᥓ ŸGŠ�F— —z óñ—ûÒ†�-ÅœwÎà„šîSó婨Æ)¬Ø¢XCª8ê üÎç‰QÌI(Xäsؽ4vœò>9ñLc¼&ößæEA`Ú9½Ô:Qßw»›Í¦ÃŒ·�X.º¡•SÝáà²?÷OÑc×c…\)�ü¯THŒv¶– G>›¡Ÿ!Û@,�-$Ço8TèÂF �ø¶AÅs½Áq#5�À³T—Ëüð‹‹ ÙÃ`Ü„7½ñ`l†ñ—ÁäýÍt¿ôno{£É ?†›[¸¼] &ƒ›¾½…Þèü<]µ�#\h‡ß%’"@7AɃۘó’ óØº¤ð1´h‘²‡E¼æ2"Æ$DIECjˆFjB�$e†FÕ¸ÈP·ñBÌñÃVp`æq’ž ì‘ÄpQüG¼¨…äQÄp~~•uœ�ZbX½�«:Yÿ#ØRDâÕ¹W"ÁÔ®E$V˜@íZEû ‡æQ8+¬b´ä$1®ïÄe»t<ÂêJD‹ìî°E›³¼ëޯŗ‘­YŸc‘Ý=ÌŒ¼ÓW¹Q§Þq£Ž™øã¨ñõòïΞjb)¦¹ýü\ÍÐ �¼}?6e¢¯â|8]ÕmÝ(Á\…8Žø#§›Ùi¸´\êA¥¥³ä5íÂ!œ••߯¸ò¥H(�¶)^fFÓáðqƒ›÷µJs­ã @ä)Ű£{DŸ,.ô¬cÔ�ˆ—í¬.rÑÒª™‹®MCEÊ_AŠ*RY¢Í¥lCEno’®]ƒ•»‡u�7g®ÌžZÚçd•‡ÛLѶïÄÌ—6x„°ùefþ•ô%|ÞƒêwãC‡@Æmfe'YÅöàã4edAmΟ£§i�(¤ÐFv´¦5öýŒ¿ÌX›¿Ÿè“ó«°JY¯ÐÇjÊÇ…´ì^ŽóéÿýÓç÷Æýëlò�sT)æ\1óîQð0çLñq”ßFÓ³æœÁñ[q®é´+h*Ž i)�Ó1šž1{kK€=úÚºõ IÓAPÇ_W ‰Áóæ¯ƒò þÿåÌýZ —y¾àòAÚrâ0±<¡ŸìnŸ“‡Ó¨;U9”çMC»ez64Ì‹FÒb†è©>åÅ ÑDÃüTMÏxjä'�Å#R3% ª>?÷G“Á¨?<©nZ‹þÖò?;9m×ö(𜊖‡?Èçƒ+é3 Üö©A7CËÜ™1©¸ªÔÌÙýVÂ$3›2ã…«—œ<“’m݉»AVµ�Û8s­”Ÿ54Ñi6V§S KÎ VE§lÖ7vMùl¯È0îoføV´¼×m]×�ƽØ)?°hºNbŠnS™“6z2U¿�ýaêtx"ãúN ¬éo*y¬IÕŸ"IÜY»»5̱”\§2°qæ [g‹*õ}Ž…x`ïLÕàê3á'¥ -7"íòQ„½+°w.�ʰ›#…Vj»X2·ZY¤//rj\R˜&æâ‚…aŒe2F&¹ŠSé#‚Ô¬ÓY ³-|)úü囓¨Û0Õ·3â[÷AS䃴§/…$ öœSDÆõ«zl,vYt¤ÎK[ð“·ƒaZ»Þ{Rq¦®ÒtAGW#& ¹+µ¼}ÄI¬mõž)r�µ·r÷ænw7wðfεw[Õ D{­¯_C? Ü€t–O¿¶'}þ޳=±ÛxœÍ[msÛ6þî_�:Ó«¤a¥NÒû_nª:r£;G¾Xrz�¹¢ ‰ E²iÇ—ñ¿ÝÅ‹H ¢äÄÎU3‰L»Ø—g»Õë°W�õa�ÞQ¯Ó9böÓ<ŒÆƒïAbCq•&B)&Åe,AÛéã9Hñ)È™ð[Få )àY‘¡Ä·2.ÀÀSÙ¼¸åR ›Y¬ OË¢f0+¨]&ã);î�Ùp|Ì~î�‡ã™ü:œ¼¹¸š°_û——ýÑd8³‹Kvz1z=œ /FpuÆú£ßØ?‡£×`.˜G|Ì%jbÆhJ1#»�…¨‰€ØÀk•‹(žÇ¨–.J¾l‘Ý™"dr!W±R„%žÎ�M¯âÂÀkK/œ¨wô,žÃƒ9 ÃóáÏ Ì œ Æ“ðM=ƒûq*|�€,�’r&Øß¤Xˆ�Ýåß+·T1›fY²u3‰§›÷$HŽ÷Ö7�aÖîòï̵`Qž” ÿ‰�(ËŽO�Ù§£g"�ÅsÆz½ÚŒ5vFáIÂh/os¨oq— d S—QÁ© ñYX0ü¸«b3¶ä.yD\ `T¿ÔœXŸÈ™Í,ày…]�h•&H>/ÓÝYãq“Å3ÖêX¢yíÖZ¯}ç…lN¯…Šdœ€)Ä×’¡´Äv‡VŸŽÐ@HÿëRvKD "RŒ¸1 C‚ÿ™ÿc†ž8~#¾6' ¿€ÄŠSøEàå ËK™gF@¤‹Ò ó’ËMö�Ø¡ºë j¦3±2ã\Nëª÷Ñ‚u¦æöÉѽ±áX¤�FD`Zá³$1’0‰ªp=˜þúI‚‡ê¦¤ùlpý<ÖQuŠñ5@Þ Î[mö‰u�¡Ø+6ç fì®U n�®ÎÏÙ}Y~Dù½ÏÛyNŽ{©/%¿³±JpTÆWÜÇužš+Qi¶ŽïE®1ô’½Í3Z€]™ƒ27±ÌÒø²ËEDù}*`Vð$¬?¬гN«CÄí^¶O§)h>ËnÓSñy�k€¦VÉ‘ZÏÅôÄ!¨_@"4}ï‚ᙟå .µÖ¬ßZÝžeÇV$ òâp<˜´B‹¼ D7ŒŒ¬”ÿ9²¬ÀÓŽ<€©TÐkøú>çM¤Ð€ØI`Ba�fuAbT]ûü/Ì™Á’@V÷�Ç(!,ä“Á¿w¾iøvnûÿÛkÏ Ú$¯œèþO…ÀÙîÕZÍ=¡ä”C• ®€ŠÓ·ú­“Ók¨â"X˜îÐa3^p*8Õ! …’’„³Xêpï’Ío—R¬Œ.EžÉBé䂹kß5eAÁˆþ�:ŠåP£è1zAbºj±5—‹Ú0f³àwX“â ]v uwùXsǼ›™”°xެRSºuˆÇ V†›¤PµVB�Ë»QV¢\!'§B·Î¬à ˆ äŒà!Tí®¹ 1[)T «,„º•‘óÕÖ@vV3F•¶¡RP«Vé»Ýîlú³Yl–ûšQkf×U+åà ³øJž Š˜Ž:ÀÙÛ„§0ì_þþër8šœ…aëyÀ^´} ©áƒÃºÆGز\?„…äÿ|£ºˆ¯&ÂÛhcN‚"PJ‚d•C‚YkãC÷Òr5òÏêAÔ&äE«V…Uݸüg*ý#õô Üçóö׊ÏO—"úàÌ‹.*•k ¬c%¤KèC¶ ᥃†V’ ©ª’~”Ö ‡œ5äìú¤gÓÝm­œ¤ÒîåK“¼Á[Xî@߆è“5G Yh7v ºáV@.³›xf5Ô…‰«Å4Ç}:–©rË´òÉ‹�]îû$Þ÷6¸)§ÂÉÖŠy®‡eEW[––à0ª_ÞT.õºPƒ —•‰9±æuØåÄ. Ó°cÒuK K$pµ2ÜpÒ¸×@ÙMï �dTÝä šÖ½#Œ„ŠÿTËS2ë– Ó©ÙzHh²í@ê é7kŒŽ3fÅטè»­ö”6L×G_Ó*{=5céýGÉkŒ7"É�TríJ©eV&3³)µ®ÒÞ.‡g¿…ãÉåà[ñHâ†�‚2e¶;&¶RïF2­Bgã1û2ܼ9õ ²77²Þ��ñü.ƒˆ?¾(ûéG»s ÷sY+°ýlÚ@2=ÐÇUCú�–Bç5)eÂÝÞWeÅü_¿íONß4ºûßzq�»iókŸwI÷?›ƒ�ä�Ž\‰ãÓr>ÇÄõd1ûvðöO³ö¦Šÿ+v:Ìôd!¬WØyØ,ÊûB¸ú Õ õWÿ€P±k-«©¤®YKŠ„PòºE—:µ™íÝÚ¨¬™S•%e!`å+–‡z³^•ÖêQÚj£MîB �½#¦ÄSm³|ömu`;3È6Yrk&ŽvU¬±BJ¨£©(2ˆ¿�“Ä.íR¬pG¨…Dêb)³rxFgÍEë°k«Ôµî‘›B‰Ž§–/‹ 75±}»ëÚ»W¹"¨°qV\~À¢§^\ÖÒµ�õ¿¨Ýƒ‘hëdzŒµòžÞH»†NØïÝ«“m¶ÜŽø˾4,ìàbÝvj}ªhKzkœÞÐák›O¡ã¥m¶ŠÉÀ…ú@Ç¡|Ô?A¿á‘ä£XtÙ¼””åÐTâÕ <œžìˆÌp^ÜQÃ… 9ÕGj/Iñ½Mm4¦¦6 iµS_å T‚ $1ä¼À jòV@ÛjÓð5Â&Rk�€d!Š-`àF‚c€·` ±èâÝ;4ÔòÈ §½{„è´{À„Û°"0\{±§R¦þ–âö÷Rkkmá¤ÚÂP ÛÙ¨g’‚Gxâ|H¢ZyÖÚ`4�™¶boc^£–Y”Ž­4L³’ìBJؽ²Ý‘ë÷ä‘;¶ÕƃÝpÓ‘©Á94yØtX“Oð¸„4�ÇK} É॔šCRºƒkÚªJ‘†ÂFµ^´Ñs Nº€É‹®N…5›é=®LÆ‹7ª€½j(öAäÔPá.ŽÆ„?Ô ¢µÃþÏSñ�X³ÑT”t·^%¨KJlI²?~<¥ )b½çÙÕÒ: ¾‚B?©Íx1ò"`?jŒ°W¯ðÕÀÀGæ¯÷}4Ü8 Û'UNf³[N˜}å;Ä",kBÒìï´Q¹ÞQmÌÏ=ïу)�'—Wƒðâ2¼L®.GVcš ~Ìà¡«�>ñ›DhmìTiC´�̺{çÞ½w¸Ò]ãX¯kwj°ÇÇu�¾ii¢ƒ‘á償¥‘Óæçø[½µFæ:Ø3�§¶9Ý?X§fàšƒg�ܲpÈ=¸gýóqrwC—(°ûÙà=¾¤ý—â×£F3ëjù½œðóôøÕöjð×Á/ÇU,œ~rï<°åðojܱ¹ƒwã7 ñdÈ5zï�,¯^L›À»¥É> n&ßVÈÛ(S h–ƒ¿6xi‹}«4~9þ·å¸¯Þo¾¼£fôî„OT/� ¶ÛZ<¨f¨�ö�©÷kÂöÈÖ9=!l«g;6uÝy澇 —Žp> ¿DùÞ‡a¯ú�hŸZ�ÀÜîo6O›ærûiÁ!&¯m,4ž=, õ»Å_€":Ú$b¾LÄÀCöH˜òX¦ ZUãøOҬ͂<¢Øƒ¾]vðƒ° }î4ìéÐçqÑ^ômœt5e°ƒ`:ÑÚ•ÂðeA hD|¡f{÷ B‹`NPº¾Q±àøxŽv´QAÃÊ=É´´BC½‹ÜúvÇ3-xáä�¼ãs0A§c½ªƒU…­@„ÌK¢½‡JR¹T�Ñ^�°px?fÜJ0Ç!Æbæ ¡T>¿9Ã4!fw«éuÜ�Óf‘Oƒ³fUJ:AiaI¦È”¯¤¸RÁd6ð©°Çój@ÞCäè€íMdDšsþ˜Ö6S–~,~¬ÝßóËâÏÚ²øZxnÈÕ)UO]ÏmÙ g|YKÎÏ«‰nCAŒV¯‹—æ’È‹K3×U16îŠC?íæÞöә߱N-¤úà}Ì!í¯DF1‡önK¶Â‘Œð‹‡À!â ãžïµ·=•=ñ3wÄ'cEj¹tÁ£âýÔ¢T¶‚w@â¡IÊ´ÔÀLm}ËçñÚlVªäk€Òøgã–Š_ÎÒªâ·ý@|q®lžH›ÙÆ \’E[[õºÛm ÙQd¨Vƒ†´*�MþŒ®VUô“·…°Óxœí[ÿsÚÆÿ�¿b™¸à`pœL&êÌ#˜&L»cHóúæÍhéµBR¤Ømý¿¿ÝÓI:!�…¿$ž¶Ì$¶Nw·ß?»·‡;ûp|_ØïÔ:ûû5؇Om‡ÞÂ6ÛsùÈÅܳB˜z�fš< AxÀ`ÊW0a!ŽŠ+Ÿ‡m\"÷1=ÿ*°gsAOýäŽ�^À{Ï›!•ÓÓ¾š}j›Ü ¹‘kñÄœCÏg&þPoZð3BÛsá¨} šPW¯êÍ.mqåE°`Wàz¢�ã6òLÂðK“ûlLoá;6sM+[Ì%µ ñ ¿¨=¼‰`8� ÞTŸˆ:PLÏ…ðÃ7�Îjµj3ÉmÛ f'žvN‡ýÁÙhp€«Ÿ\‡ôð/‘ ´“+`>rd² òé°�ŽgÇw¨\daØÂvg-½©X±€Ó6–ŠÀžD"§°„?[Ÿ€*c.Ô{#Žêð®7ŽZ´ÉçáøÃù§1|î]\ôÎÆÃÁÎ/ ~v2ÏÏðéè�ý?ÏNZÀQ]H‡_úI€lÚ¤JnI½�8ϱ@®BÏ¡ÏM{Š®á0w±‡™·ä�‹�σ…’ICdТm{a &äPA."Ô©=±§øb †q:|‡Â ŒÞøüã°o|0ŒÚ|c»¼ü%.uM'²ÐqE{^׆¾…e»8ø–§od5 Ãô�(¤ÍZ­Ó�þ³gИ™f“ÔL®F.…,“ý¤KÑNIô¼mÃç9wÑI`\Úßá,ä´êÑ ™9} £ñ)|¯~ó`ÇÚ°ÝPp†ÊîthímÁQɤ!�K _£P�ÝùÂW0q<ó·6ŠÆ´à$L�1Å1ÙN4nƒ)R—�ŒÖû³O}ÃhŸê �sàhööÒaûÅëWMÍ ïF‰-ß÷ûFoôÑ $Ðm)ÆåëWÆ«—8¾•N‰ŸòšºUÞGÕÂÓÓó³÷ÆéyÿG㇋Á ³ŠL™Ì•€H\ë>Eܽz¹;SÆðlŒ )úÒ&Fˆ�)sLæo±Ã#ŠÂáér.$]ãã§ñà?HݵPUÈëswsé…ô^ó•ö:@”ò×Ô,ç‹y@±‹v ÝLP#†Ô€!@}ŒIhÄ:ˆßÀ1ü4þp1è�Ä›"„æÞé𿃋n™62“$÷!Ùž9öïß�‘Cè{qdH0Z2'â°Th«¬�Ûg#�eà ×U 7 àÌRÐs•� 3]©"D"¯Š-¤Âg<�DÅ% L„†ä­–óíf� A´b‘‚ˆ˜s,7¥—™D† l©8ùYv‹Aw¢•Ø¥øY–9ÀVb×E{woP«ŠË»«5¶êf�a£v¥×ÜF»ÕI&¾yw%'4¯‹žßM’kÞUo[ÂËäŠÜ×G�°’KpX©‰"VªJa—í:°K϶ rÑ(‚Ê>3|´Rg^6k›ÝFÏîkм)5#¼I#w —&Ytà™»à® ZHɃ…°ÄóÄÓˆ«ƒ·KDY´³ª$*„À¬±—lÕBÙºI¥ð„æ¶ú'W•è:T,â‰$�e~`Îm�åBðv]9ZíúaÌúÍÛ3ªŽ(5±Ðº g[ìú@ÆÜÏŒI©r³Ž tSkîlÉd},›ã1+³ç]�ùPÖìYV�;[“YÖ7‰R=¡yŽe¨È“•�ÁÂ…a ª8(fžF-Áà:]ºð?·ž ]¢<=lÁÓçùWo ~Ô©fЇ8’Ú´õÃâ ¾ð‚«lŸfWwŽ%<‹9îÞɹ¦\˜s©þbàæËG‰qã�·?è¹ÖÁhÅ|åwò¨«ðýn^–î`Å;˜kž^þ&ÿ,ç7h�ÝÔc#£Hø'M‘T’´…¤Y é—ú^Ö:u LPIéõ$¡»6éW÷wx>ÉK®}�¥Ð·]ˆ\´‚¼›‰‹ è Úm]ŸY8ë�}‚�¼§GvOV¨D�~Å"ªDh\‡:®·ÒêCjõjP°+®¡8_0�£,‡ÅT¾5sßXiWÄ«¤»Ré^¥E+7Ê÷bÉI{ë�X•xóƒââÈݸü1fìÊ=‰Xë˜Vè h8·¸%kRì¡Vkâÿ÷x+¼o G7íômºN°–®J–<˜Ú¦¼#Ý {ïýd[&¿�°!ƒ. ÿM—ÇÆ�Ð�~¨‚:Øzvßp*wËÛdK»±˜»כѱBS«2@fz(ü­€)U—u披· æš#€VnÕh¡ w…⺱¶9¬…s§ {Òh_ЗªU� LwAS:“S‡^áÇ©ÂN;ÏVüÕ"i"ÉÉn•¬ì?6$À5K("=œß-¿ËíÊ,S ’,K6~Xœ#G„àùkh½1£²¢ú¤²â�`›™¤Lå6ò=ß¹!l-‰G4°-Ü;òß½IýåQ7©·¤Œ[Õ_#}¤õö³ãåß!¡üÓo/öÛ«çÓBÇ]ϧ»uÜåJ½ãžÁÁ÷Í yÛÁ¦4?ä§è3Ôé§ÐlÜX{WË07íR´{Zk>Áð¤7þ‘|׋ŒÒ›Œ/ßè&ãFäßùr#ü�÷Šãkä)}Ú×9Nƒ\ãWkû(™»™,4zƒ¸ÅœuýWË\w½ýAëon…ßéºÝnäþ�N&‡úNvoÔ.’ˆÇf±<ÍÒÙK´xY¿Œ©[”ØOVs‰=Úª—QVSGÂX¤–F¦©öJ¯ptöî¯õ»áJK·OƒÇs¥•¶�/¶¢gœ~ñuá`@–˜�5k⚯Üf.ýNqþè�Xl”‡‡B.dxóKÁ)yn(ë2zIañ—*36“ñnJ ±¨ì�5bX!<¸–VÎoyWHûýËô°ÌxœµXYo7~ׯ(µY Ò—"nŠ*ŽÒdÀ–äI¡v¹áÝå–äZVãü÷ÎðØÃ’ì8Ç"A"3óÍñqÈa^ý¬zÃΰ×ë@þNDÊ�ݤFq>XÓÐ$+RžñÜh`9Œ>L�æú°f ¹Œ¹†BÉót­Á¬ù"\»äÀ³%�iFä$ §€;i¿iÐF•‘à„UÉb«Äjmè×Yø/ž¿øþ‘r…†M§g~õTD<×(¸Ìc®¬àQÁ"üÇÏôáWZÈ^ žÃ-èú©îñ)‰ØÊ2¶E JÍQ†Ð`ñóÛˆm†H"zÁòˆ;¼¦V@vÃ'/C. Ãå Ȥ¹˜ñF¯�)ôËáp³Ù ˜µv Õj˜ºuz8�œ�g—ã´Øï¸ÊS®5(þo)¢]n�hQÄ–hgÊ6 °$#Éâ�Fä«>h™˜ SœÄÄ}-–¥i9,؇°› Ðe¼îè&—]x=ºœ\öIÈÇÉüÝùÕ>Ž..F³ùd| çpv>{3™OÎgøë-ŒfŸàýdö¦Ý…zøm¡š)È•<¶~»ä¼eB"�Iºà‘HD„ÐòUÉVVò†«AÁU&4…”21&1©È„aÆíà"EÃÎ3‘àD‹ÅtòÁŒ˜Âó‹ñxñn±è<Ã)‘ó³¸9�Ò2ÆÔ1\›ÁºÛúS›x)e:XÿÕ¹¡1ÒìGEZjúÛá·Á@÷¬ _:Ïx‹`8l­¡R„9°%H̶à$ÉÕ ,–zá+ta ùãÔîÙ�¶4ÛNkÚ2ÜÈ©³b™’¥a£Ïê¶ðÃB¿t?óšE×'©È¯)EID�i™/dv5�VñWRšZ8 ؾž³(Œ:­tMQ�zRž8ÒKS®Ž›úDâÊÿØ´³Kô·«&É�*ƲdæaÅnÉ[NlkžáX²”Xª�IøGc_ãó+O;_]”©ŽÞ–yd(˜*„�h� f#Ãaਾ"kœf™#cC×|ëGì â¦T–ûO�s¾Âj½ápÃÒ’“3‚rˆ¥:³Fò©…à¶ça ²TÓlXH- ô¸/³. ôõî»;ÊŠ…9>Š�UöÔô‚Š~gŸw>'çFŠzÞˆãîG'%·DÁMÏv|�ƪ؅ã%¤À4L:sI‰îµÔÖŒÎiü ÖžDÂXDÛ>nK¦ŽË˜$ÕƇ*Â-ôUܲijåÚ~?lÖ<:ü)GÇJ‚¤¹ÆÿÚ)‡4XHŒ/W¡(]Û‚;l!â)Ç•’Ê�-Jmz0²¾ØqœMòÅã9p0_Ãóo܈®ìÀ(Õ•1$èÅ3ì5½o¥îW¥Õ’Ã&EÃ/ê¨�8lñ¶À…õ¡³Ã–^ÆLD^†;ŸÂwÔl˜æ1µ'¹C©”×eq@íO©¤ žùŠºTg1ƾŽûaÝã-{òâÿý‰„IJÂúuFãÂjì©G»=ÔkU$rr–ÃÇ5·�WR†³S-Þžð[ìÅÈÓ3„!Ôˆo�ÐŒ* «2ˆ2XvŸ-"lÀ}¸yÜ'rdAP(­ �‘1l˜®eß/¤Êâ5÷È©Ï�°åĵ01¶×O˜HIÃ]ìy‚”]À¾-đչäÁƒ¯}MbsÕÌ\§û©´'g®ý®·V6A·Ê9åŽH-âDÉì‡3Ò×#iÆ6ÒE™Å4"İËex F ¢06#ˆƒ‰}õ „o& Yå¸K²Ùù¼•h;„º‡ œæ_E-öÊ�*³ÒUh³…ýÊ­O ÉŒÝ~·"vûEZü·«€Û�<�ìö0�q=ÞPNR¦V˜%»9‹—JŽcÇîBeÜ}@;D²,ä—èñé~8¾ÿÿéxðb{ó‹ñ"yÇS¼ý¾„3Û—3ß[5»t_ÚU+0·¼Pjb!$;ºlƒýZb<»;�Bcod[8léì-�n¼_v�e¢àÄÏÁÖÏ€9SR3²¯Q Læ¿@hîÑÄ[X1›»Çצ öPbói¼OjØï0Òû|Ho¯vè‚Ðìqõ5¹§‰ð)§P(¿«é–î€xsc“Ï?TÅK³ œtºfÊù‰Ó½¯ _íÛBýæpðÅÁ�{ûßAÜû ¼zc<¾‘yª÷Áï{ƒ$yÿŦ°™xœÝVßo£F~÷_1M^À"pJ_ªûՒؾ º¸2NïNª„ðª˜å–%ŽUõï à\GõÝéžÊ‹YvæÛo¾ù±öÆðæ{=0öFÞx<‚1ü’Ë!ÃÜ]Ó²û’ªz§e±6¼ºÞ/àòÅå�ðN©‚ìçóëÁz.S¬Ì ­2Ô`Ö~-Rúvøu#U—î °ØàlØ:³_1ÄNµ°;¨”�¶AÂ� t´ð>ÅÚ€¬ U›º”¢J¶Ò¬»s—1>*1‚Ìp òdž Ì@zmLݼô¼ív늎­«tᕽ]ã̓ëiM/ˆñàq[•Ø4 ñS+5E›ì@ÔÄ( ñ,Å”Qh¤=£˜ñVK#«Â�Fåf+42L&£eÒšÁöü(ìÇ$™¨àÌ� ˆÎàÊ�‚Èa�÷Áêfq»‚÷þr釫`Áb ׋p¬‚EH«øáGø5' ÉEçà}­9¢)YJÌ:Ý"Ä ¹ê)55¦2—)…V­( u‡º¢ˆ F½‘ §´!‚Ôr#�0ݧ£¸ø ot.sÚÈ!ŽçÁ3�'ÓY|Ç£sú,+|fgäy0#F*Ï4*w ¥JË6Cxݘ¬«Ú·dÅ­F‘=Cè¢Ý`e@ãŠÒý²öaªÌ~èYeV߆·ÑtÇ67 uĦ¦âÔ°–•q(ÕU§š"o¹°Äî\· û!¸= ½ 3$™V–Õ»Ùöè +?:Þ_¾‹_ájvšÉÎ…®jM{ùEcve—Ö�0,ÉSV�Á­8ß™Ý;×BÓ‹ Ž€ž§Œ{8«?‚¬Ÿº=Š¥Ïͬ­RÎÌE)ÿê¦ÍC¦¾vluñ/Ñ´šœ¹È¸/‘K,á@bµöYÀ«(þ-퇘ûõ`|âé÷þ>e÷œW/œÙÕ¨rÒVØ,#¼!šö«/öJØ+a¯ä´—€×�õÏÝûK~ÖgïõÏ>MÔhG‚IMãM˜“Zûµö?ü?µ~û½´fÁ:­o°¬yt*ºg¨Ýð®ŸÍ‰ –NUÅ7j y‡|ÅÕŠÚ¯·§{b˜:íMèt-¬ü œ.ãÅÌb›¸6„ñ×…ïtÞq.±ÌìÏ©84ÛÖ·¤Åj‰åO±!÷ýñ6\| Â~ú[ÿAšä¥�st‘ôÿwxM«ŒË·»9¾ýÿãý +ä¡°»ÂxœµVmoâFþί˜’/`|J¿•^TBhƒŠÈ5†žî“µØ¬êìºÞuºä¿wÆ/æò¢«%„¼3óÌ3óÌz×uàã�zÀq[®ã´À�ßV2BñßSìoxéS¢dˆ„:]FxŽ*ÄròÈâï¹ÞX~•/pñáâgøCë5¡N§£Â{*T†0RBJÀn†±诰ôàoLŒÔ .ú ÃíÂÔîb§S¸;PÚBj�0¤�Œ<>[� }GR¨a+í&ËS 0oøR`è¥ä.€‹½:ta Òkcó‹ën·Û¾ÈØöu²v£ÜϸÓÉh<óÆçĸˆX¨��„º)ªv¹£@P![Ð ˆu‚d³šoi¥Z÷Àè•ÝŠ&”Æ&r™ÚZÃJ~Tö¡µL(h=˜xm¸z¯Ç Ÿ'ó›ÛÅ>ïùdìÁíŒngדùävFo¿ÃpöþœÌ®{€Ô.ʃ�qÂMÉ­Ä0뛇X£°Ò9%c W2 ÒÔ:k„µ~ÀDQEcr/ KJƒ¤B†‰ä½´ÂfKGuq"·u&WdX�ïO'WTÌØ¿ÿµ/Æþ�ï·ÎÈ$ž°R° ¢4DøÕØ�\û›Ëj­-ŒÁÄö7íƒ5‹&[á¼yÚ ŽRÿ>Z*Ú£6|m�Ñ�+×­ùð.‚aÓ6ábì.FF%±ÒÀ‚¿4~±Ñ| ‡/ƒGi"• æÛ9¶›¯ r’×H#™rØ+ÈFr_[@ÇÝ �ƒØzœ{ÿ8Šòc› ö8sÊþvæœã<7×Á§Ê(ЪZf$iñRnEÞõÜŸR³¡ÏâSæJ–'ÞÄÌb•heKJ5éžrpv.hÒü¤qÄsü exÈ!¦~Öù–žgÕkà픤º­¼àòýü²,>Ââü².Qƒ�ÜK ªŸ�htf‹é”¢Ã€R¤nÑj~®<èyã»ù>à˜Ow°wo@«¥gŸçÖó 2,EðÏ‘€�þ'¸ƒz7zG7^ÛØ�oZ Ñçý•‰ËAiN×8F¹Ž¯Q¼ÔZÇ&“´ÃxÝü Ù´Ñ2<:»_�»A¶CñuÜ9¡x)êwtÿÞþ:±uö-|«ØÝ‚5 аZMA¥“lÔ¥æ�è{ä´ôÕÆ¨†ä»(wLЦtV�ÏBI |È0h³y(Žõ€.!ÙiÉVŸŒù·ÿPýÂ0h¼„2�Åì4æâ Á.Áv[ “µâó…¡o—ùxóúÍ[øMÊ9 �.³Õ#0¡QD*B¦À, ?À?ÙLþdJs)àÍÙkhÓ‚“lê¤sA"Ö2…¥¿! ¤š¡ ®Ábg�K p�\&1÷EÀ`ÅÍÂêɤnøœÉ�3ããrÈ�Qu!ø&½0&Ñçýþjµ:ó-Ú3©æýØ­ÓýÑðòúf|}Šˆ³÷"fZƒb¥\¡µ³5ø " |dbR�?W çŒ$Ä+Å óh™•¯‰ ‘gÅg©©–ãC³« �2_ÀÉ` Ãñ |Œ‡ã ù4œ|¼½ŸÀ§ÁÝÝàf2¼Ãí\ÞÞ\ 'ÃÛüö+ n>ÃïÛ«0¤ õ°ÇD‘“•,´¼�«Aˆ¤ƒ¤ðˆhš˜§þœÁ\>0%Ð"H˜ZrM.Å@!‰‰ù’ߨ¡-»HQ¿õŠG8�ç�†Иkïj4Ž'ÞGÏk½Â.Xó$nAœ†8†Q(ŸT†~Ò&äßÓ`ä4Iœjúm±Gƒ¨áäò¾µ^1ò ß¯­¡|��ˈu-#·Y'Œ¤¢_ÒÀ€7ÓžK)Ï@åÿ‹LŠ�ÊFà?–G[kBÝÀ…xÅ0²c�Çt!¯ ×·à‡v}d~˜oiHy¸¹�9aËĬ­<Ú¹…©øt(ÐKŒº(TLÖQU��NÅ÷mÛµÏCKòã3i%„…¡ÿÀeªíÆå@ÊÖü!ðRÄëRô3 Iä(B`¼Q ‰«3qÇLª„%Òå 3 Ia1[2a4Þ“…›žCük=¨h `¢Öºù¶ÎEMÑjÁlýÈcŽì(=9“2®ˆ±Ïwy†ž¬½„+³U0óƒ/dφ }^ÊKR½ðhi»QGo›Ñn®)3n‡úHIaÔo×¾@N¯âì�ež¤¢�—;X¨ES…ý-5�2ÙÃQéä�(È8Ôqìáªäc)›q…nà Êîx¹†x2)CaÀV^ x–ÈB/Êax"2xR,bŠaûQ.mÅ­P·Æmmoäxesúw·uíZY…^¤V’à1§aêÛø#MN©)™æŠ§MÐÝòçùÄáªäÎFaÊëE5ØŠ jÊ�ê0®³·Q>ò…޳=ed¥ü õ&µ’iC £xµàÁž¢TíΓ-Î*eE œøÊ_B®®2D*_Ó0›·cÊjÂ& ‘FT4](Raï•Hä Û'\mcÏ2ËõJûû¤ðP‚ˆÞv—0wÚûØê� †®3§ãv׆*t¦äÉœ8â’a�7$qóÊè™'RãjD5|¡þ^û£>ghÌö—ƸNÚ¥¹£Ñ�cžRU#yÛeâî5‹ÒØ¡šŸb1J®‰uÜë´çgåË}½•:¨Î#t‹ÝýߺÛrPéÈ¥0·ü!{¼•g ^BìiÀUÈ'Wà8ž›N ú*òc}¤ú”×�\Ýʯ·¯Mv‰;’Z •÷üræçˆuq“8‚oŽWÛö»&ƒ~æ‚=t¸ƒÄúG:h,©(zÜTSÉ0öæ`_LB˜Ò Þâ½HLŸ§²\\vóc¼#Ǿ=°*ˤý¶sÖTN4‚y¶mÀY(õt¶ÚÞ§n¦Û©peïßtŸ/Ësþ`o+u“K–7â£v rÕÙɳO!µ;ö8€Øq%,±QrTä·ãm3ÚÝ�±§g:›lííÁ)9dàÊWaañy‘YÓrçéûü¶é÷�$4žðçç¹KlEô ‰¡O9Ku¡ì 4½²èý˜žKöõ uŽ2S *»:g¦Tpü M¤Àyeï© 4Ó�}Ïù•úßòœËù<ç¶�èš&¢á^àÉA¯b ™»&e/^EÎÓ¤‡“îa¡RQ²ñ‹Æw³ïö•¬|=Ûùv–�÷»�owî5Þ½ƒkaß¡ò×è>x“¼¿ËQ¹¸¿Íxœ­XßSÛ8~Ï_±—>2‰Óë̽@{Ó4Ð6S ×ëSPd9ÖàX>I&ä®üï·+ÉŽB Üe†a,Kß~û{å~Þþ_?èô[ýN§x—ÈL@¼Ì§e¥¸â¹*–ZÎRKOÃê^¿zý|Tj†GNN†a÷‰ä"7"†2�…› Œã¿ð¦ m¤Êáuô öhC;¼jïÄR•0gKÈ•…ÒÄ�3qËEaAæÀÕ¼È$˹€…´©“P"Âø0ÔÔ2Ü΀”•47³�tjmaúýÅb1Ç6RzÖÏü>Ó? �OÇÇ=dN\æ™0´ø«”µ�.�Ȉ³)òÌØ”6ÓßYEŒZZ™Ïº`TbL ‚‰¥±ZNK»f°ŠªÝÜ€&c9´c�Ûð~0�»òmtñéìò¾ Îϧ£ã1œ�Ãðìôht1:;ŧ08ýŸG§G]h.”#n M MI¦±³ÛXˆ5 ‰ò”L!¸L$GÕòYÉffêFè5‚Bè¹4äRƒc‚Éä\ZfÝÒ=½¢õ8Úãû>–І_S™É¢€ÏL<ôæÚý7+§Sñô÷ë ™ d“ÉÑ÷Ó÷—&Ÿ&“Ö \‘¹X_Ä­9ÏÊXÀcãLN#„X[›*•Ñâjµm…±QÚ¦¥Ä‹áEVúk‰[‹JC{؆Z/DË ß_ÛCÉÊ"6ÇxȲ%Ì´Z�¥0­¡#¸4")3g\-XL¯d^”6"õì²$_r‹r„ùUÉÜ '2fÌ, #æÑž%cØúëÐæIaõa 6,µ¹…Lä3ÌÌ ÆmÉ2‡[Cù·˜Øm�þ\�òFÚ:Ç*Uâáà ê »õ1e)êùs�çìvrü[*\üK fÐ옕,C�­æ”ae>g9†xìm¼!�"f»•ës‡­;˜š‰¯£{تê먖fšºT…¶`šÍCù%o5ÏZ¡Æ«¦ž’¶Ä8ÅT6LÉ9&{äÒÇ)°¢GÈ{^³eŒ˜šE·içMk/›LöÓºanµÈÑØX:0æ±h¸œéç�;ا ð¦?¾”Æ‚*m&oDãTÔ8Ö°[¿åògÝ0“Ú§;˜È�¯SmÍ2[¬r™÷äöp€Ø6 91i <8¨ f�{j`U”{ðnÐd™â k¬"~ˆŽ ÁгA†˜o°x^„ºšFíA¡ÜM�#ï©À*™ f|Ÿ¥ðÀŠI(ôÛ8cõÖjB½©öê�cÕ´bï¿Æõ‘å}Êý)}ÏC�òNúˆ}d«88R%x(ëÝŸyj¥ .öÉoÊl#ßiˆ+³,Ú‚Lë;¨?Ä ÐÍTy‰ +Œl‹pŽ£¶GߘžäN¸;È?Ç^m|Ý"~X†kYXš£òª5o�…•ó‚A°ýä8âä�ËÜÊŒ&žÒH@[qĨ‰ëâ¼FX{4cB¸¾�ƒrÞ›bú^Ó)ƒÿ…mðÚ¸¹‚¹Òap�†`B´à„Ân˜ÌhvÝ¥Ø&ñFô¼œƒRvCò}šz·±ÿuÛ25bB£y¿:½¶ë:BïW K¡µÒ>(ÑÊ ×Ñøô�çºä$Ûðà˜%µ¥Y\lK¤•1fá*Á÷‚à®*Ó|�Ú ;_E¨^Z1/”fzN!톄€\*­iÁ(!,›¢ —t"6ˆ™ ~mÈštÒ2=CWŠ[¼:AS‹rB0£H¹Í]VŒ¦Œ_—…§ã¯&4«¢w´#•Åm I6§™Ý›Y‹9Îóë 7ËȰ˞æ&׈….R�Ç$y«Èz¶Ìªj쥛™WRlØÈõàQè(Žc¼Eq Öå†Í!VO‘á¨ø¸ ÙMfn%T‹Íc¥½ƒÄ0¥"˜J�yê™Ô�‚Læ×(rR8坿*WðƓџ—ãóàèGÓ bÙluˆôŒÒêÂÅÛaÎÀHBKð”ièÔü+ x¢ÐlìÎ÷Ƨ}îÝçÎjäh,a�}††>wÖî'³Êߢóy²‚üÙºLHà. /Þlc s‘îÆ.IØšx Û‘wòT]üˆ�‘’ÑuyC³9[NÅõs5�5�Ãå5ÐÙû¥Þ?~<`Ïfùæûhµ»Ö.”ecÆ?“m=FEkpƒð>4lêo“‹”ªëž?ÕëUçê³�ë÷^î×À ËL@¾kZÍ ¼óãÕ%Ž©@(g7I…/>kP$½œàK'n˜"¬ný®qç¾b¬¾n<øm#¬÷;kZü'DxûŽ}k¯>!>ã+%aý ¹ ¹'²•xœÝYëoÛÈÿî¿b …$(TšûP iŠÓÙr¢V‘ ËnpŸ¨¹¤¶¦¸¼Ý¥µiÿöÎ샢d9²¯¨?ö5;Ïß̬†}øð[} ?¼öûЇ3Qp _њƗ²|à¥àeÂaÃÍZ¦2©@q–Š2«:˸‚LÉ ŽèäWuÅ#x±ƒ­·µf·‘~o„ÓöÞDV;%òµq·ú¼}óöø(eŽ\M§—~÷T$¼Ô<…ºL‰ÚšÃ¨b þñ+øWZÈÞFo K:~©Ó{O$v²† ÛA) Ôš# ¡-GÀ¿&¼2 JHä¦*#ٷ¬í=ž ñ ?{reng@B€ÌÚ�ÏôÚ˜J¿·ÛmÄ,·‘Tù°pûôp:¹Ïã×ȱ?qW\kÔæ/µP(íj¬B޶B> ¶´ËÇ5Ô(²àu=-3³eŠ™Th£Äª6 ü¡Øí ¨2VBg´€É¢?�“Å€ˆ|™Ü~šßÝ—ÑÍÍhv;/`~—óÙÕäv2ŸáèF³Ÿá/“ÙÕ8ª ïá_+E ›‚TÉS«·ç,�CјœFd"AÑʼf9‡\>pU’£U\m„&“jd0%2…ØÃŒ�z$]4¼x%2\È Ž§“ŸP˜q|=™ŽãOq|ñ çEÉO-á±2)ê”Ã3££õŸZz§‡fWñÃéŽáÚDëÍÜ…IUÔš~.øWƒB@ç²ÿ¼xÅËTdÃáÁž7WÚÊ‘ÈÒðÒhYß²’mx\õ íŒÆþ†‘d‡¨+/¿½þ=: òcU=1èºE,MÑ?�b¢ mÎî¦�¬™b‰qÑè© ¬-’}Ð7ñYáæ 4\´æüÑ£ä$UÜÔ(ÿJž3x`EÍÉÕ¸R/õö/ëÍ ÙÁ²X�¡]ºNt#+}FåÎ�t²­0dž;çB$ÂíüjÞ½g\sõc^¯VP@Åð%,颰8Ö×rÃ1æ7©vq¬$X:2Q8­Ï_=Í¿K„j2}d§èVf@¦Œ»£óïa'hû‰OP½½?Eà¶sކzèv>Í?�;½¨Å�r¦±eÌk&Y]¤À4áÂtàr¡Ëö=ŠB&Ìð3<5é]G׈Í6Mñ27ë�µµwATòJjÈ#“0Ü"âKô`N()‘`Á™>Gͪ׳†9”(f˜8»?ô¢3§ç”ʶ‚Š çN‘ÎCPQý‡¸i�–^лj�^¨�¶XD*Pp»1€Ð�™Æ^ä��3m ›½|ŃÒïÉF‡ÚW�pUJƽ¯¦d Ýpe2Ì<µÈ ÖèLãvö0±e¤D(óˆ°¡”�ƒjn¨jQ²R] Btþ•4ë@.AÞ5t©(#çMQ€7\½‡ñlôy|;ŸOç³�ÍbÁ™TÏ!�øÉ­­¿?FŒ Ñ6’Ÿ ‘Hü­§½{¸Ø£ƒ�_6@tŒ¿ 'j_^ÒìI´8㾄%/F‹FŽ¡kÔ*M7ÖøŽêûHj¾åÉeTŸÊë Ö;HýŒ@\œ²ò £�d ßntÒ²¬óëAàñÌPH•‘çIF¬ÑRŽMA¡O¹&Ýü2ÿfθ” &‚Í&û>Ž„¸”ëû$žž!‡8|à�ìÝ–o#±8úÌ+bV Ìc·@™×Š$KDOòá›ê[©9ÃOKã|f[OWé,µ‰éþ%®ñ"µy;ªÿÏhFéÚ, cÊEƒÄö1¼`Ø­¿s î¹Y‘—yìvÚ×Àà#ÇeÀÇ™�xÚ§.mZøBS”Ì6• aø_¢LØ‹—ÆYu“ˆö¬~Q Ý?ˆA·ŽÊ·®ûºÄün¼ëí¤‹ Ò“QR+í^Œ\bQÔýöö//9.–öÎ#¤AR'ú!ÚxäBF¹v'ô“Tþò¿ �-7+¨Ü ¼!±nÏæãÙ­SóJÊ¢±ÈæOÇU·­¯CƒFQm=¬y‚pìK0Sô Ó46%ô^7®l‹oªÌ�t¢SÜ?£+lö¶BîŠkûœd‘é5Qâ8 ãrdW»è‡¡Õ¡ã\ÙÇ£ðæ×2(úeE=©UyK"/Áí— wËšm _¡»§µÑ­¥Ñî¶Yqìf+bãGE›Ðº¦RŽJçKî—6°pðŒJíFJM06iäðæ-rs”ÑèAè€V@üwÔŠ%Óf(`?vdÚáø9È·‰5+7ö¡P£K#‡WÔWå¥tË÷|å¹|�kÁö]¦Ëû”µ Ë±¦]Ä“ë«É µôïÍøãÙŽlÁ)±ÀúẼ9²m‘´T§Ÿ/kn'©FÀe{Ъ´}0ÈØšªS[߃”ÇnLÖ�Éù^Räµ\än­}Ü”½Å Øšèö뽃¨xú×· 2?H‘B¿çκ�ÑÜ•ØdÒÃ&e²ˆó/™Ž H‹1.ÆÆÞï%³ïO¾†þ˾}îßDŸ|õóÃþ‰'Z÷�|øãÒ–-î‹_ý•Ñûò“þпÅxœµYmoã¸þž_1È­ãêìmîË¡¹=œ“x£©³��Ý.®™–i›¨,éH*¶q¸ÿÞR”(YN6¹V@‹ä ç�Ï Gý.¼ÿ_=ÐíŸô»ÝèÂOKsX-wó|Ù[㈌Òl/Åj­éíʽÀù»óïà&MWHrwwU¬¾O_@ž,¸½æ0ÈX„ÿŠ™>q©DšÀyïthÁi1uzvA,öi¶‡$Õ�+Ž<„#ßE<Ó ˆÒM –D¶B¯Í>—ñøRðHçšár¤¤K!0]½Ö:Së÷·Ûm�i{©\õc»NõïFWÃñdø-J\P<&1W $ÿ5µ�ï�e(QÄæ(g̶�J`+ÉqN§$ñV -’U*]ê-“œØ,„ÒRÌs]3˜“Õö ÉX§ƒ Œ&§p9˜Œ&1ù<šÞÞ?Náóàáa0žŽ†¸€«ûñõh:ºãÛŒ¿ÀßGãë8š ÷á»L’(¦ Sò…±Û„óšËÔŠ¤2‰¥ˆPµd•³FIúÄe‚AÆåF(r©BÄ&¡™6CzÑFý“oÄ'–†w£KTfÞ|ø×åã‡ð6 O¾Á‘ðöI$M¢8_pøA$Zï3®zë«ÑS¦—º·>õÆ4Wf„ö¥moŸ†áÕ`ôpﱋ˜�©áÅ“…XôûþBG†QçŠþNøN£àôê~óÉü5t¼`+ɲµˆàéZrÙ#+�ôĽœGY>´ü³Xèu@�“‰�•YM“y¢Ä*Áp8x¶DrQr¸åtL_Çbmh* !y]šð¯à`· ‘"$ŠŠÕÀN–úß23»å ƒfÖ ��ù†'Ú“þÝy¨÷í.˜fa¦åÅÉï0W¡±uqàv%9Ã(@,h8~’è�¦ç�/c’m¬]½w+±[#¹Î1�¥(EzzÈþ‘+ s_NÂÁd2|˜vvðƒ§é·?Ÿ]4Ví«J“Ó²Bý?ù \Þýy�ò'9þ»_°õàs‹ú?yë¹´ye³¥°©’2%Ýì6L·¹©q�×ÖK-éë�Ôëw›e ‡µ…ƒŸ¡w9÷½%ªÛs“åºÈÎx €nÜIó8·hÏâlÍL· MLêÆ‚a§ÌV³Ñø.Ó q«<1cr5'+Íà‰!«†;Üìq CÃÄŽe¬Àºxîs�JØ�‚2•E1Ûd6‘ýü.€¿þâ³!õš|ÞÀÆ«Áç lŒ�ßÎæ¹�yC�†ÆWʇ2„Ü2Yf!t ׯJ5k£Î†µÁRÜÃNÁÁmꊰ�lKñdêÃ�.›&Çî0Sjh eºR Æ›àÔÇZbíbÒ½^£(¦©‚Áéx£Öi[�MürE+hÒÊs~]­ŠÅ\2¹‡Žëq™‡›6m®³£hSùó¦Ù»0Z¼ÄÅx`ŠãG@Éîb{ä™™ýC]Ýʵ3 ÕøñîŽÚ_\Ê÷žzÛ•Fß @no µâþb[s‹cÃÓìݸcA×þ(®~ aé»Î³‰ÉËή-)W!»€°ÒµÙîåßz»‰?„7Å™²ÌÀ­8åÑ*T]ó Å›GÌSpŠÆÃ‹Ìš›.b- 1¢ù¯9‹]…à_—èýãøÆ´qSg®¦pËo:�±Æ-¾5%fÉ*,i C�YÕ\(%0!D’ÆXZ�ÄŠ¨KQP.µ´[Ç£0oøâ1ÔÚ rÀI€J»Ž ¬'½µ¶4¿A]×#áØŽ”ÞYãg‚Y8™Î—Ó_ˆ±ßq[dh hü«’š²]ï@”Ä(k♉Xá�F²‘²D¡ÖÒÊb3£R[ � “Hcµ\Wö@°–¥Ýu ÉDýñÂeÞ�—árÈ wáêúævwãÅb<_…Ó%Ü,`r3¿ WáÍœÞ>ÀxþþçWC@’‹âàc©9¢É§@bât["PàƒÂï¦ÄX¦2¦ÔŠM%6õ€º Œ D�KÃ%5D0a˜LæÒ ë–Žòâ@AïL¦dH!Šfá{Jf}Z-¢åt]GQïŒL²ÀVÚ\ÄY• ¼16Y+•�¶¿wVû û>/¥M�¸Ì*Ã=|¤ÓM:Núðwï ‹D¦ApàÃ}×”L¶ïê#¦mw%2"•¥Š-Dkù6‹,t_.{m3N4 "ÔèH0m_i´ùÔôï1äªÌog3.9j­toV†w¦è¼Å"ËH~†â‡àÒ.�„$Ðj׈}ÀrÐy‹·ó%“WOŒ¯š½¦Éè¤!�m Üc†9Ûî©£H¯{ïz2f»ÒnüF{*"µYÚŒé¢@­ôWL†�ŠŒ{ÐÙh,ŠŒ²KvÔ8Ô’¾˜"ÇVþjm!]wµµàö œLÅ®5šÒúÑî*Ópy^˜!8mÄŽPS-h^uɤZå/Óé¸\Èèÿ™ÓÂIm Þ¢›<{24“ žÎ]¾/-äOƒKÝ:¾81Qìo�E ²¿EaBE§¾­øRäY’—ÿdªÝ¼ÆßÃ>ž£§ºu»œþ°ÇN—‰®!ÌK»û)Âw~{ÛÝÍÇÚ;Ów©ÑM+-ð<ß�`?ÀIãF.ýfzº`Þpùì=ð�›úO·ÁÉ»À¯ƒço£æ³‹>ã`Z$\¬ýg×Ë>íï_DXIúì†Nxœ]‘½NÃ0…%˜ÄÄÏ|Õ.(‚°1!Q@*Ej��J@®ß4F±Ý8¢�ú •2 ñl<Ï›÷G-õv|ì{¾c~íÍ^;£d• ç*L}à^å*Ô¶…t O=È H�‚ب\f(Τž[Rñ~¾í\Þµ7ý6«7Y‡1¿)0‘�±-ÃoJg¥@h,òôQuw§Žà–�[tñ-¢‚--$dØtA$¸åÀ- kvæË- Ãz„›ÒÊ9q›ÞÊ!´%i¸�ïÿ3¦uÈ)ÔúÝ. Hd(„^YXˆ–ÌD7Ê­˜g™Ô£mØåQ‡sîovÖÂÁÅó¶Ì5câ &=‰SN+òêØ»òתïÙ_?gõãíoþ!T¿ÞÅ~ê¡q¼¡xœ­VQoÚ0~çWœèÃ6DIÕ½uš4JÙ �TèPŸ"'¹k‰�Ú)ÿ~g�„N²¦ñ‚üù»óÝù»‹½|ý_?x=o0èÁ¾%4|‹°0ÀD2/2ÎD„Pq³ÙŸS{Y/µFt6 �É9˜©ƒÞSè;Ï«ªjÄöÑŽ¤J½ìÀÓÞÌŸLçËé5E\[<‹ µ…¯%W”m¸VPD m YRKÒž‘6âJqÃE:-S1…ÖM̵Q<,M«`Çø(ís•Œ è�—à/ûp?^úË¡u²öW�‹ç¬ÇOOãùÊŸ.añ“ÅüÁ_ù‹9­¾Ãxþ?ýùÃ�ÊEçà[¡l&·¥Äx_·%b+„DBÒF<á¥&Ò’¥©Ü¢”¨r®í•j 0¶n2žsÃ̺ÈËäõ®xB ÁÌ¿§d¦Ácô®â;hÏóÀ_¿¨Ù˜ 3£‹“„ NU? G¯šµ‘ »§û$[z-h7j#u'zÓNì¦=\ØÇÆqážZÊ�}l6‹º ©­u±9k]gú±©] þÞRïY(wÉG„÷4<œè­±âbq8NÜf9±ë!åÂ=ÕºwE‹'à Úß¶Ãó‹žs0±} 4ϯxâYgªUI÷Áxœ­VmoÚHþίQ©ä.ͽ5—ê‰uÔœ ´×S%w1 ¬dvÝÝu×ä¿ßÌÚ&Ò—«ÎRv<;ó<ÏÎ̺݂³ÿë�V»ÖnµjЂßç"å�ª…¿¤å@-B. SÍôÆ«ÀØ×”FŽ@;|˜>ƒ9o¸ž*Ã}p¥y¦´Å>†s •m´X,-­ºÕŽ;Ç/àR©¦ º¥÷@$\Rä\bR°KAÆüW¾ñà ×F( Ç~äP/_Õ›§b£rX± He!G\v)ŒÃ ü6á™!!Q«,L&ÖÂ.]ž2 á†we 5µ Ý Pó]G`¶½´63/Ûíõzí3‡ÖWzÑN ?Ó„Ý^4î!ârÇD¦Üëc.4²�n€eˆ(aS: ¶vj/4Çwxa­©ê�Qs»fšS˜™0V‹in÷«ð!í]”ŒI¨cÇu8ÆáØ£ oÃë«áäÞ£Q]‡½1 GÐFáu8ŒpÕ‡ z„Ñ…åÂ<ü6ÓÄa ’’ÏœncÎ÷ P}ÐÚd<s‘ 5¹ÈÙ‚ÃBaÝH*µŒë•0t¤Î(L*VÂ2ëL¼(Q»öLÌñÅâxž#™^<^ÆWq\{†f!ùop“LÒ|†%ƒ>þ²¾c°ÜX²<˜~úgûâÕ¾mªTJF‚P H²47ôWã·YA½[‡Oµg\ÎÄ ÝÞó¡Îƒ×ìV¬òñ§ºRÒ5!¬PT”Ç£ZÅŒ¤�%}$£Ó‡h2ðû’ãùØ‘{üŸOúñ8ü»'�_*’Œ9j,ìÆCZt¶Ûn7'è\"„O5À#]ôÎ'—p¯²„Qˆ†¶¬‘(ŒÈéxkë�FX/gð¢°Pâ ]³�qS"…�dmy†I®5—L ͇@Î^›âÐh4¸2l?¸öÄ«mãv±€§Ëó¨è ÜvÃᆥ97nTÓ'–.¦ÑsšJRíÆE¸‘êÜþÒ©ÝÃÔÄ3®ðÄö´Ð®û辆C™n›—¥GJ-q !¿­«pÔØ”·¬‰ƒDàžGæ2{�XŒ�9¨íy9wËÚðኧé©ÊÝ.Í�³*šÛÕ8ÌXý,NjÿI°Ù,$K¦¡EÓ2–lÅãÌê§œ*“bÆX@_ ¶². ø¾ßt¾qŒ.ã?GatÝ�ãÆ‰?6KâcL Ììiz€#¬˜Ô7,Nq¨uæcÚ7ßÃû¿ÿ*ó§©oQgŸS S)Jô,¥¹P–˜�±|Eç]”þ¸#†„¶{‡×ÜUDïœ64©*m†u»Gy’Ña•îHZÁq›k×ë%w¬Ê­;¶4…%^o\º&úPmýà@ô êJªB™6QÊ/f³VL.í24Û|ŽÝýó¼ ¯Î#‡»»}Ї›ðó¨?Hî¾Vð{{؈I^Þl8�FÚ΀.Ò7¶ù‹.€OOË|ø¼ß¢="¶«ŒÖ8Ì4O¿ ï¤Æ®ÄUœæ7âz¿§à^§V¡<¬ð~8èűçîá¨üõ& ÂÇqó!Òý· òEL÷ÅYM°Ö�.tÃíî.*Ó;„i‹!\2¨Œ§OÞì÷î¸ß?{»—övëð¤øì¦ëÉÝúî³ûû?ë)Þ¿û\ �¸¹xœÕVaoÛ6ýî_qsÐÀö4«K¿µK1Çua†]ØÉ‚Y:ËÄ$R%©(n—ÿ¾#E;rjn7`Ë"yw|ï߃ó«AÏoù½^ zðë’e™HÃJFE�RõWfü–z b‘ç‚CŽz%ZпCÎ�ëlmÜ¥äD>6\,еdéJ›ÞpÓ�³—g¯à½)-6�õ˜ÅÈ&Pò%è ˆbús3üNˆA8ë¿„Ž1h»©v÷� ±%äѸÐP*¤L�å„÷17,ŠŒEºb¡#2�À�±lB¤è•Ö…zíûUUõ#‹¶/dêgµ�òÇÁp4™�~"ÄÎã†g¨HüT2Ilk T“C´0¹�*¢T"ÍQŠ B%™f<õ@‰¥®"‰&L”–lQê�„mðí¦¥,âÐÌ!˜·áb0æž­kp}5½¹†ÛÁl6˜\£9Lg0œNÞ×ÁtB½KL>ÂoÁä�Hé¢uð¾�†Ád&•˜Ø¼Íw ,E I³%‹‰OË(EHÅJNŒ€–3eJª`bÂd,g:Òvè+^f!¿u–4±„0DfާïÃÛÙàÇÑl^…aë„æÇçL( �³2!‘rû«vc䥓Œ-ú«·fpY/Y©Ì¯…÷š@{؆/­ä [øþŽ�ÙRp…Q| Ã(ËhÿÐSÄ�W]Ï�…ÊnªzÇl³•2ÚPµdO)û†±2‰I fîK ê“ÐEm5Ú�JC¼Š$ôL åZzäªm¸� ÒûŒ¡žc¾ØöÌ_·õÅÆ©W G8ß n›½F�Hw:“›ñ˜Ž" Û.œžBçbŽf3RÑÛsX(2THõfzÝíBÞ47g´��ÎËPwýõëdJ‰Ûaµ ¶Ó¶4Û—Y'/>—УÛöàk6ö)Q—TY þ¦õP×ðIílé««\}†Ô@7èmÉ6 Ü­Rsݽàá‹ãIqÃð2�Âг‚žØ·f¨}jË ®µöí:Ë¿[g‡”å"þ%•o%eÔt¼ŒåX5Sv„|òÃòÙ£›A¬Qý“Îl-“²ø'Ú©µ±Ááâ}‹všfä¾Ñ�“¹‘€hœ^:f~„Ÿë Ô.+³ê£ÊȰ!2«±ÎÁ™5UDÛ*.Ö7ã�}¯ËÿF–.Ï/”U%ñÚ#JGì‰0UüXa:›Ë#„éÌ÷ Ó{ïµû`/ÙÇË÷àÕëÆýÞ3÷ýÕkΙ¦Ï»Ý¯Þïø¼6ÿd¢“©çÝ8xœm�?O[1ÅÅŸdFYXŽÂQxž:4UZ@E¢RD@¨Cõä<ßäY8¶uíÞÒlÌTù H|0ØÙð{JÚJìs|~÷è>f/­ûŸ otˆYÙB7|‘Aø £Ç,¹Btð’aÈÎÇ gé;&ÚPè!DǤ -"Ý&ÕñLÆl…¡ŒÑ‡¾d³}­=)-3ÇSQ¿DÍ\ÔÐÜ4PÇP¥6!ÃE©ÃŸ5-̽wÆMÃXy ØEÖvÚÃ!³¬z8ÒEÔÎÚ”Ù‡³¦ÂÁbI8’‘ ­ªIé.WyÉ´�½bÎL6š”™éð÷ÝîÚǧ‡ïëkÃåëúÎÖðìtt‘ËóåÉÆç…ÀéÕ�Kx–Ó™ìcLSmsºmêµvµ-Ì\:Íb…¢Â)ÊÊÎ?Î,éæF³ðÚM"«ÒœÍ å¯öùÎ{/tEKt1àØ*¸ üò¹ýi³–ßZ¤ªxœ340031QHIMÎOIÕË`¨Í^ëo`!¯8¡?ä|ð³„mŽ‹fXBÔäUä•´xo˜ú-þSõMçýªÏÌæ}_ºç;TIAbQ1È”ŒE—NÞ©Xø^uµ{ÀÃk"âµå“�lÄ+Õ¹Þ xœí\]oÛ:}ϯ Ò‡Ú†c_¤E’Íâ:‰Û›&EìÜ¢ÀŠ,ÑŽveÉKIq³EÿûÎðC¢$Ê’»1ĦÄÃ3ÃÎ�"Õï�³]}H§ÐïtH‡ü9ó|J\ê„.í=@ /tÂåóæ1þºP?ÈñÇïɧ0œC•«« y÷•çÐ ¢.I—2?P2XÚü“Wºä/Ê"/ ÈqïÒÂå¥Ãö)B<… YØO$c’D0¼ˆpfô‡C—1ñâ„‹¥ïÙ�CÉÊ‹x;¥‡ß%F8�m¸Ý&( gú�ÄŽ%é‡8^F'ýþjµêÙœm/dó¾/î‹úW£‹áõxxŒe�»À§QDýOâ1�vúDì%0rì)ðôí ±çŒÂµ8DÆ+æÅ^0ï’(œÅ+›Q„q½(fÞ4‰s Sü@lýP™�ÃÁ˜ŒÆ‡ä|0�»òm4ù|s7!ß··ƒëÉh8&7·äâæúr4Ý\ï�dpý�üct}Ù%ÔíÐK†MUI]®·1¥9 ³PPŠ–Ôñfž¢óÄžS2) @"²¤láEØ¥tÆ÷^lǼ¨$6Ô?xãÍàÂŒXÖùøë•u9¼¸¹ZŸ-ëà {5\�J�ã'.%ó‚8~ZÒ¨÷ðw­4ŠÝiú¥B@IJ¬ðp&÷bÙLðp–~áßýƒläðâ�ü·10lš—œDà[œ7¥l‚[Äðúî‹5‚©_¯Zmò“ô”äŒ\ß]]‘_¢�ÏÔ_¢ñ…DÖ/·c†oYˆØ%—¢Ø„¸Úî’¿ÌKä�²áq²\† ½–[©`€Ã*™±pã¸t†ë9q®‹99ÑÁœËäûסu7ºž|xß-” /oîί†ÅÒÁí§ówÇÅÒó››«bÊ^,OnG×ŸŠ¥Ÿ·çwKF“Ò­wãÉM G«ï]eB;ê!µ�‰¼y:üðþhêá¸Ó9màpÁNüX˜`—€÷b,�A}"Õ¹4ßï-“ K +oÆÜ‚e%Å2€!nïD“­˜&ªdæ‡vÌí=7óåÑtÃàf,E%�¥ú!ƒP M¸ÄŽÔ(MÂh±°còÖfóé»ãÛfl>ŸNßîZÙïŽ7U¶`¤‰�¡ˆÂˆ³s-#°IÇkéa%ÝhÓØ°;W2s*±ÒñM£1^Áª:8Z‡çÑJ—�ߦ Übzä›çûdJI‹Ñ¶Ã¨�c2d)d‘ >ŽÇ”¾uw©ˆ5q6§ˆ²‚³î2Mf3L>z?€{äýšBÈÜRµ^€1TR9�† °žÝaŽ9›E43ºñipúÛõƒu€¹nÅàþöSWLlcO)(äñÄ‹i�‘_b[4~Ý.¯‹ÕP9Ë0òx>uïÝ罯Ց om·8Çpú/êÄ ¯|Cö]¥@¯KCÏ%�T°v¦½QmØ>ÜÉ$‰2ðýÐ�nÅü»%ÌÈ}Z¸|�É X/&;Ebàµ[Õí]à ”,Ó¶>‚DÕè)jÅK`Œ�B¶b MKsDÓ‚–£Àxª†£Akó+E壸¿é®¹¹éÂ��r %b¤‘½@-ë#cXÔ|ß|{ | ¡ ïð]\ëR‹i Ò\uk†xÃ¥�¹‚&(jº�QƒfªÓ`…³(má@ˆbl¨%3ýv±ÉU*'_ÂÄ{lb O·F3ò'ƒ`¨uíɉXÐq(Ò gg†;R–]…¦ê@Bi/f`·ô=sù ¿«Nx½a°P¥â/<@jz/ ’8ê‡ù¨iJ—L™’„�2ä?¨ÌA¯“}¯¬_ö3iÄ4SçÎ'ŒÆ pò©Å@.ÿLçw=\lJ¥ÇúƉ_ù“!q´LFN­Ý¦Œ”îa:ÓÅÛ)]Ž(ñ )œµrzª¤Y@Ê/}”ÁÔu^I.Uœæ¦uB�ä2Á)g"=öÔÂw/÷À {@=Fk„TiÙ ìzof½‘U¿šõͺ‘-62ë­�Ь·³êff]½y¥lÝbÔ>Ì[î°ªÓ�YE¯æý ó³�]ŒÚ[!¡yË'‘{µåÔg�]‹[öa×r�`�N*tój×ϰk1AÝ…]o…„v-*îÇ®µÙq…QãFÔ}˜4ßàZ£ŒJµ¼šô3L»¼^Ä&&½š4߃±q"Òt¨Î–Q*lZl"ßܦù7±•¡-Éü¬¿þ³ÎMøžï-�8Ú«›<ÃMЊvã&[!¡›ðgSUnR�X�$lW€ÈïpªŠ!r1P\çwâ°Â>¢‰<Q«d£‚^Ýän": ÒVHhÜò‰iir�³43îÜ.o£UËÃ6™µOƒæÆ­NóÔhƬ Wã~†q˧5R61î­�иՓw°T<7œŸ5HúI37áÛ¡£dz´vágt1Ùpô�œÚ5<ÔÖD%&½¼zÇsÖ|ŠûŒ´­ùäžl5”ޝùèÛ‹U^SQ½©r軑åÓWý|„!ðc”›7éZøßÃ˵1@Ó¬ͤ©W+ßÐÊ­¦BU!¥VnÕ®dÖ ñ‘›b] !½í4®!á¹Dª‹Gf¤Ê‰wî¼WÀj­w~ÎxÃ��žìê’æþ#47“öÕv»‚Ê�b3évë?b— f9"Rh†´Ò¦ŽX�´©#=_ê¡ï?û\îyl4nÿÇ�ÖéÙ‘žz•ÌÒfö‚¨Zz‘Œ¯ZQŠ¥*Ë ù1K(¾ %J‡F‘x›ˆqƒ£¶§‘7“ŽjZߨ¢ï3IϯۈlÔ¾ÓŽmܾr1G ïà‰ ¾3L÷æãþÓ6¡‚rÊš�°= H@WùÝ[é«pÔÉydÖÅC“|# ´O ™T™®—b¦Ôj¦}§�dÙXG\xÉöµÝ§#úDá�w³ÃEèÆ`]˜Möà;|¦ºÕV§é"[ÌÂ'<õ… s¸š@?+5¾ñé¿SöÞ§Ê·>Éò~§üz*ñ"5<©7ûÕ‹Ô¶|Wâýaîù²˜xœ­Z[oÛ6~÷¯ R`s ׊=µëÐ4Í:cY2ÄîŠ>Ù´DÙÜdÒ#©ØÙÐÿ¾sHJ¢¬‹�4‚˜yî—�”Çòö¹>d0î�ƒ�w OÙȘ¥£5LعHn_­ Ž.óyõëÉG)W°ãúúÒ¯¾æšÅ$1SĬ¹ØÒþù'Cò'SšKA^�~ }\pæ��¿A2#ú@„4$Ó hpM¬`l±­!\�Hn¶)§"bdÇÍÚòñTFHã‹§!—†ÂrJP "“p!¡Æ ½6f«_�Ç»ÝnD­´#©VãÔ­ÓãëÉåÕÍôê%Hìw|)Óš(öOÆh»| t Et r¦tG¤"t¥<3%Þ)n¸X ‰–‰ÙQÅ�L̵Q|™™ŠÁrù@íp˜Œ rv1%“éy1�L‡Häódöëí§ù|qwwq3›\MÉí¹¼½ù0™Mno`ô ¹¸ùB~›Ü|æ>l¿U¨ˆÉÑ”,¶v›2V!‘N$½eOxª‰UFWŒ¬ä=S4"[¦6\£K5#™”o¸¡ÆNÕôBFãÞ žÀƒ„Ìçï§\Ï¿ýpu=ÿu>ï½€Y.XýlQšÅŒü”òå’j6öÿGëŸñiâèEÛ4Óø×c{2’³Ë3ò_ï1O�+k0ôÉ/R�Kb³(¥Ê þš¼Ê@±Èó°e#¿ pKóó%P™»EsC*£7í¤/p?Ø®�¤{ž“ÌG�$c»û³‡ñ=M3ð™‘ÊyÐI¤ÏÛÙáÞœ™ûÞÅ Q)È®¾†øgÂpš>Š›Ý�³óƒ7Î3XŸç©#T!ÃD¶_øØè˜Îî&7‡åćÉå,bJ|é}uœ�HΣô#3±�Ä S K djÈw""Å ´�-—E2fä+Á¬òH¯e–B9À¹÷ÚÒÑ4ÅLy +1’ ˜É)½Û‚…7žû|kT>¯˜É Œ§t5K“³rř˥jÂ!Ðï<.·Ÿ—¶¸cHhÝj /Ë->Z„ Ô+Âp‹ †nÇÓÔêLð� Oéž+“A¤Äƒ$Ar8�å‡m ŠltucÜ»—<®ðDшΕÆÙ¢\¹8Õ 3¿?°¼‹«jòÃÔIb}†¶àÌ—šÂ^Ë,ûvÉ•OÆ7 Iǰoè,/i/³”iEXlMÒºtjø,õÜs†õ¥ �ÑnèÚ·o/�-š‚òIÙ= ‘ÇZ Ÿt´‚ Kîé&š;‚,î÷œú�k±Ž{¨ù¿2Ž`eÊʘðJÚ�ú–q��^3[ñ[¢µ²oˆÿæÓõ5êÌ”ÂT¹P�X�T\ÄxÑ�á0²Âõ#èó†DkªÈ  ãupUÖbˆ �´@µBhH¢äÆ-+;cG]Ãå^Šþ�€þ›¡"AɶX� «RœqÀ ?iMšf;Xi•G#ר}»kôï¥÷Lp[‘w 0&`)”9än©ôÛä�Fçç§V·?$$(Â5YéûB^AÜT�d£%Œ…š�O6”íuc¨Uzþµr}N�ÛGxW ÕóÕⱃl Ò2x\OÊ ó<4”ŸuØ ó$+ªÙÑ<óöCB'å.ìW±ÿËì Y—üüâcéU£®êcãå"ŽñRÀvY*[ •\‰` �óÁLÉ¢lz³²—uÇ€�ƒ<€úâÌ�Å}H©ãÓŠ CLØÒ¥PQÜtÖÔN(OѸ8 êþ‚P‰ AØŽ“zè+þ Ÿz@4 ˆݱb›´ò6Bk0P7è}‡áúX˜HÞ¤Ãî~ÙÄ+e/W­è �QkÅž"aBW{e˜Á‹D,l3­�9Eœ‹¦ÑÑÈJD0È4S15´ ®�g¨ÇÏCÌŠáàe,Ù @âdyðå^¦K©ü}Š˜pÚbdÀœEåžh£‡A_T÷¨ í¶þ çýV‡W â©àÒBüAh¤s·¦áACÕÍ�¦íu7|ÞYxóSê =Géµ”Nª½ve¿*Œûr¬üæË�Ô_q(L“ÎÏSƒæNh€jÖj�‚RžP �×c 1šç©²¦÷x·E\anã›s+%w‡éá%Ìôz¾¤ÑßA†Ô}óÈ‚7è_1Î�pÈrŠl³dx\ƪ…w™,/AÚzÍŸ9p¦#fš�•·5v´•šÛº²ÀãÉ~ñX·Ù]ݬʰ<ý¬¹¨²´ÞÊEÎCØ2Â[R™ÙäZÊLĺýJÄ KÍ ® Îg{g4òò%™âÁ4‚s)©õĆ™µŒõë&˜®aGãÅ+Ï%÷O‹T.œƒOÇñ�†Ãâ"¤ öÜ¥™wGÛY¡(Æ]WHáÑ+àpvúêû$‚«¡ðL÷Lc¬å&¬®pyããY7>]Šv_µ©PhÐèš¼¶:¦Ž_ Æ5ðLê©!ÞÓÝaQzAÛ)Òè ‡ ¾Å–K`Ò6ÍNðÀ�Ô…õÞf�IÕ£_Q.Z›øÉþ¸è¤x f<Å…UdZÐÌ-J»nõLZW{z¡¶Ϥw R8¦x ?¹û†oW¼vš/Ô®ëŸIû:Í‚nÛQ—eŽ”¡¢UµÖ¡&W$`*hhu‡ž^Œ\d•ä;Ê‘‡ŸßR�Ÿ Â4(xzIªÉÞ˜Ÿí÷vˆi<Þ/ž¦Â…WfÇðJ�Uâý3Åf�“&H½Òá9`7ÂkfŠ·Ä NÂWÏgþG¨² ›_¡ÍqÛ¶å°Õ®,fß4¾ÉýjßÛ–ïs[ßæúyÀk‡ï�ÝÏÈÛ·äÊEüÏžðC $õ?¨9>ï5�bxœ�SËJ1u Š_àB®µJ´Ý "ø@pá¢øXJšÎÜq"ÓLÉdÝ‚{%áV¿@ðü¿À_0™ñQÅ*šåIîyqs±öÐu`5©ˆñ®uŒUW¶¶×É!ö„‡>ㄬnW7Iz¡ž;¦ŠÜ ¡Ðˆ< ËAÁž`o�‡®5¦ò�K{68P5Ì1È¡²XB,ã‡P«'>iJQÊ=(Á³gÎÆ( ÙzÕ�ЕeMeØ–µQÚ•Ï «û^«§õ©Z(ª©‘¾sW •H²I⋨ARi’I—܈kn@8¯.¦m»â8ßö¨¤pÂd‡ì9ÄZì'Ósí\"#ö2„ê¶wð�iÌlÉ}²@ ãr�HpÞtfR«D~¨üÓÐgyN9m0ê݉ï£0ITg®Ø¶ôÑolj]oI]æ ;-r> j¾æÇ/@:­ž¬�¶†öÛöfô?íÀ»•×z`—3 Ͳ¢ô#ÑÒRúQ„º�I+Ô#ìYÙרŒåÒër×ùù.p*ö ­µ&>³Ãxœ­V]oG}çW\‘—ˆÜ·º­B°Û PlÜ(O˰{FYv¶3³Á´Êï¹Ë,°ÇVÔ•,3_÷žsîÇL¿C¿þ_uú­~§Ó¢½MuÆTx9¯âϽ&e~”{¶©Š™RcI‘Óë«Mä ~‹sFË>× §ÞƦØZ½\y ë]¼¹ø‰þ0f ãñ0ìë˜sÇ •y–üŠiP¨ÿÂJ—þ‚qmrºè½¡—²¡–Ú¯.ÅÄÖ”´V[Ê�§Ò1lhG%~ˆ¹ð¤sŠ  k•ƒËFûUå'XÜô)Ø0 ¯°]‘� “o$åè•÷…û¹ßßl6=U¡í»ìg»}®? ¯'Óë×@NÜç;G–ÿ.µÛÅ–TD±Zg¦6$ /-c ÊÂÆj¯óe—œIýFY3‰vÞêEé‚Õø@ûx$S9µSMÛôn0M»bäãhöþæ~FwwƒÉlt=¥›;ÞL®F³ÑÍ£ßi0ùDF“«.1ä‚~(¬0LÉÍI¥Û”¹AEÆ®àX§:µ|Yª%ÓÒ|a›ƒl×ÚIH&b&Ók핯¦¾á%Žú­:ÅBJQ4½™ëèvvMgƒá‡è}µ^`Qçüè: äqV&L¿8Ÿ`soõ[snaL&“‡Ù¶gç{«¶L¥;çq‘•NþZü€¬‡¾Ã6ýÛzÁy¢S¢~¿±G*Œ¦`Æu.…jÙQOˆùmÁb�+c[„OÎMÊõ;q�3^sî²2—ü„øtW§’HnùµÓÿ@ÜÊ¢À�#Og>Y¹Ü;–ÖÂ4p9-úWå�ô«‚¸Çø}‹8{0x+�$q¢¼"e­Úvie²ÄÑ[+] J¾iFý³u Z'`d�Ðê@rÞ8:ÿ�Ø Ígº-Ý æR^•ÃFv?὞¯WÞI±„ò A+Äk*Ená×…ñÈ'%vZ�Ï �8û¾Ý]éÔ<Žõ1Å.£½)Öhk"©5ëƒPr-Ñ–#íŸè…)ÐÁkS‡¢Ó#‹åˆ×…a‡°IÌÏ�3óg48äà‹ÊJ)KL¢2ç—åÃàyí�ÌljÞÏÌ‚ÊÀ ñYí tCOhÐÝ{•þop÷[\üõ‹ B©]�Oˆ‚OD¹õXr…‹ªÂ4ÃûKC,´Arì«›&\߸Q¥½»H#,¦ë°työðµºõ¯�Gßa¾ßyì�²{’ã‰O×È?4’£'ù�<üÅÞÈ´åý² xœ­VQoÚH~÷¯‘—Ö"vÝSè�J)½Z‡à¤UŸœÅêÆëÛ]‡p§þ÷›±×\¢´i-!ðÎø›ùf¾úðû¯ºÀ½Ð÷=ðáM&s„Òêø«t°¡S6D…E�‰!S¹-ɳñ«ÀÐ7=©$;šÀ=ö&Qå^ËõÆòݨ½�«WW¿ÁŸJ­ c29ï‰L°0˜BU¤H¨„a)úr–>|$p© ¸ ^Á vè9Sïå€!öª‚­ØC¡,T C¨Iá}‚¥Y@¢(w) "³“vSÇq(œ7|vje¹ ` ²cGÖ%½±¶4×a¸ÛíQg(½óÆÏ„“h4ž.Æ—”±{â¦ÈÑÐøO%5±]íA””Q"V”g.vÀ%^k$U–RØiie±îƒQ™Ý � “Jcµ\Uö¤`m~DûØ�J& è -zðv¸ˆ}ù-?Ìn–ði8Ÿ§Ëh¼€ÙF³é»hͦt÷†ÓÏðW4}פrQ¼/53 4YÓºn Ä“X)|oJLd&¢V¬+±FX«;Ô1‚õVn©¡S†ÉåVZaë£oxq Ð»�2ˆãIô–Ȍ㿗óøãx´œÍãqì]�Uø¸AI^¥¯�MWJåÁæ�ÓC‚à³ÃaÏ¢±Á¦ÇGY>)óÊðÇÃ{Ò=UxÔƒÿ¼ ,R™„á‰O,ˆ²šÄÃôl„mæÇˆiÚ}‰‡ÚX%–p�.ÆUZca!aÁIû N7´0o¥Å-Ðxiä¿Tì“!èc g®pÐ •ïi Sm ´uå¢>�îèKzsÜRóp§dzîqßo�cÚQï+¬ê_n[Åv൛,*hdDN¹˜ãÒ´{©Zl�£´6�¶¢VRÑ‘çÆTIBjo”ÇJé•éE'ðO¡_»).å³r ½º0§‘Håò;ƒ?k†’¢ŸÊ¦vʺU½¾n;W÷È5øÔ‰  Ã45p› +ØpKë´Î„榣å'ŠÒZ¤gö°¬Ì&^‰äËSúÞyeº«î�ß&sÄw¬…qÝvfÊ¥2ßK�\Ïq£-CØ w¤¨ôQ†È ü;×t Ö•S‡Ó-¹ÜYØV†T‚Í ¼®!‚VÇ~'?ñä õÏäpC“¼–Á ­±ÝfÁÖecLÆf#uB8ÛàìFÿZïïÃ^t«»óÐôï¦yâ767?~ÃzÆ‹ãý¿&.í²Uxœ­TQkÛ0~ׯ8š—Ä”xôa5…BÖ²Q£cϪj�k�#éÒ5+ýï;)Ží´ -mÂÖùîô}Ÿï.Ïàì³d¹È³L@ç•iÜŽ—érÈ éênñiãûûñ|•N—°¸‡Ébþ>]¥‹9Ýýãùgø�ÎßI.ʃOµcT¦b)1�º-ÏJ(l[’¯QªBI¢fÊF”¥}Dgˆ�•*å}4’09ÃhU© Zo}Ë%½+UÐFY6Ko‰Ì4[.&²»,ë]Ѻ2øÒ=f¤nr„w>äkkõhóëéâÎ'aW£çeÎѦ�µn<ÿõð)PÙПôáŸÞš\Irsè�%uA,|­©%"Ug‰m8:)º2G/�ªIµ/òý±ojáDE~rsº�‹l¾˜ßΈH�‚}DÕ×P“��à1ðB!t+Zr"ð°ÁxŽ\…Zƒo¤$·µç˜ôXXûŒ›9# ì�ú�2�ŠBÑ­RçX?E´›š}»¯<Ê=aPú»„a#Ȩ"~¡È_S€/¨¨­§æ E…n�9G8ò¿xJsO á:Šs TÅ-Ñ„V*Á€KqÃÑ„lL@ç¬ÁŠ â¿Œ¯mYbÞbТ±¼Hrµ"²T k«uÆìŽò/Ó9Ñîžöa/- ¶§™�—³U+Å“¢ë¦ÈêàöC’лЇB¤ÅŒˆ”ÄÀ0Í”VÿWÕÂ�¬ÄìŠÃŵêrÑ\ª”ÖŠB¬ÉýÆ"2Î+W70ê« u­ G&—–�7ÕšK­Ó*­�£ h�mK�Ð[±ó°¦YÚM)š´B3\ØP®Nã)Î�RþËÓ·ÇŸò�oq°;rª wŽöúßûÌ® î%JÓJ-Òéðîxæ™g^v–^›^~©µ{�^»Ý 6ý«D’±yaUÒ]ðÒ%¾)»¦¸HC«tj(Ö9­tþ›Jç´RvÁòønH¥tÒÅ;NS¨³u®æ ËO'Õ?9~Fo´žÃÎpxRJU(S##*ÒHæd’ú™ñ¯ÜéÐÏ270OÇÝ'Ôb�f¹Õ} Äå—i"�¡\~,Togk…bœ‰X¨ó\bÏjF¼Ê•·2:¶+‘KV)f|VØÂ*|p».ÊDJÍþ˜ã&½ê�ã+y?˜¼=¿œÐûþÅE4œŽéü‚NÎG¯“ÁùOgÔ} £×’  väu–³€©˜J9ÞÆRî@à ág“ÉPÅ*„ké¼sIs}%ó”3'“ùRã2J¤«IÔRYá“ìÀ/6Ôk®›pµ7üÖ�ýÛ£üò¿”OŒÉç…$Òß#²…ÿnîáü.Ê=+s^²·¥¼jÉ•½÷ éF�éöÍ) &ßåü—œX]»�îšÀ ²\ë;趪ê±Î¥…|8ÈU­jp°›‰¹Úá°ûQÂìñ\žLwÖêcÒBJò„ ª4‹a çM®1â­;KÜà\�2»cq5†ÔZïOh˜ñÈ�~küHb.â7b\r[GhÏ"/ó\ƒ7h"5+MUûõEXw.Q‘Å­Ož0žvèøž ƒ•upDÚjĘ–GütwÈØ¸ºÁ=óÁÓÚ|0Á•ŠŒôªË« × |m °‰Î��‡É¿Èl6^Üz£üÓÝ·÷Ê;o•åz¯}ûMÙÿ D/_Ò)g^¼ýAè3~tbeC•ø©¼ÇxœÍX[sÚ8~ϯ8“>lÂÈtß6Û�R’m)òÒÐß´¨öáËÁ+.S1èv4”ŒpéSˆŒÃêFzÙeÁ ýV&ⱉ×)[Øø}æϹI´(,":T \Î…Vr†Ù s¦Vgƒ/€a Ùlí°g�5 c!ɘ†�§%$.¬>[Á~dyÉCêü(ìœ@<î× Ô ãìâ*Ž+d¦iéœ+\£a,ÓXìª^L³™+ÄЛ%ˆÿȓҺ¥ýÜfõŠ 3•–9•”<'(z„œ«r^;{Œî6dœj�+Qt r¥ÊbƒYÀšh5ƒëÞí%jc|^e-T™ûü«„gz:wÂ_+!-¥�Bý‡wƒÁ‰¥l“Œ BŽÁLš!5d jb¶Kb2 yÎ�Ûüo2â q!ž:ç(¢Q‰ NlUj]5sÊ~¯%oˆ�.%Wšµ ®?aT3­™«Ão5†ð>�x§.CÆÄ’¥© ë³|g<:^†ÛuñªÅM€#Ç{af%gÌ)z]•H½šnŠ(–kÃ+'‡¾tŠ ÀÈ1ø\Wû³k?K{¾¤Ø°#K外 ©˜¬zžE%VQ­¹-±8õBÿ®¼µ¶_»:”äœIß’–çh°€üÔ´7ZÔjµ|òõx³Nµš ~œ8ÚÌóVH³öÖŽÿÞ ±V'ØÇïЪ›óø§ëA2KѾ$ôÕlFV¤ö‚F«B¸÷”ÁcZ{À9ê j'hдfF·k‚wF ?çóΑ“¯Pî åò:n°h`扭¦„”"n²tå¿Y©Îœ³±cÛPʼëÌô’ûàH©¼ áK’Êü¿B(®±J5ÕÜ}îYlùÛùÊXUÀцP´øtÜA=’¨t)iPÙˆÚ äè˜+‘ÂN �¶tk¯ <¢ø3» íö||¢qësC‚§|GÉ‹†Ý7 îtƒ£jÌBr}Öò)¼¨=Ö>„yÁ_/™¢.÷w#)DR!u’`�ò%½ŽSlJ‰²|—ÉT±m1ïâQôþ¯ˆJ(î®Â]K©¾î…„Âr °tùCn&»~‡ÀýŒ'†ú�3ÍS“ð ÌÔ„k»{Ì1Ã�"Bboš¹™àðzðÔâ�Â’åléAÚ”îTÄœã}î©eÄ[^,ËÙ˜kGò-UWý¸ ѤnΚ¤hU+Ï—íÚH“©|5ø¹F¸*‹h¡!Òu ²«R oãÑÐ9«Æm…´fÊÆ�m}ˆØR÷±×5Á'’ßôPAf��_ VÒ�•–Ô ž®ÂÐ `ó0 Ÿ®:º�'^H¢ 3HžM¦5É·Ô·Øçò6Ãl¯3[®¨¥Â Ç *ŸsŸ¿è:l9¿T÷õtu«1nû—À™?Ti]RàW®õ¾qìÎÇt“æñ$u1ÖcV§IŠŸöÞsÌ¥v²â•–¢’×"§¿~e Pdh4\N^Ã+vöKï¾gˆ½i`'ö �‡Æ±2t4Êû\ÖP“„]­Pè\B‹~xzŽÞõ&ó‚ܰ0åcG¾zë}íÞDQÛ¶¢�[EªósÑ<™ÎëÙ×qÿÆ­VÒ9°òï-©Íö@)§DFq*ÑeRTVÒeœBh-zÊêœ)}+¬d˜�·˜5þ añ‘ìÇ”2¡a¯!Yá*^'ëq¨v²¹YÞnà.^­âÅ&™­a¹‚érq�l’傞~�xñ~K×c�”.â‘÷µe&r*eò¶–ò „Òt!¹ZæXbNÒtÕˆJBeÞK«¹u¨Óv踤ŽÛˆaîÐ LÏt1Q4¸À’JHÓyrEbféæf5‹¯Ó›4\Ð jy|‘^Õ¹j ?Ô]ÿÓ#£óEfŒ:4¢ö~_KÇVæî¨óZ5ŽyïI §Cøwp!u�%@øð†ƒDS…Â$©uð{ØÛÛÍìÏt±\½�ç°£zÝ�»Î|²©X:G™Kƒ[ŠøªÒ›<Œº›Ú[ÚL{-©ÌÞ…D¾„gÆzÇÍVá{ƒÅg¢ƒz™3�ÎMþ×9Æ7 Î2*Â9/‘öÔâkôyÆgÅdZšŒrÛöÔÔ%¢Qž&E¿ ÝîÎT•1‹,Dÿ ýT]ŸÒS~BxàxZÙSÄ�ùÊQäÂù'ÜÏa³Ïÿ¢(L$�„ʳãeEXIßXÞ6t2`y cµ´ÉVZ(Å3Чh$�f|l•Bñ°,=xܱWã»!sP“°ÖŠs…¡™zü:ÝN'_ihì|÷mαœdò|sæóü99}z{4::»úÀ€å$œtÝhüŸßŒ÷€ÏÜÁí�©xœe�ÁJÃ@†¡ ¹yðœ�^4”öRDèAÛPh°i@{T—º™šÅf·îÎzê+(yŸ¡�áÍ·ñj¶š“‡¹ÌüóýÿÌ÷Õ§ÿö0%Q`/÷!„)W™�•Ò ‘´ÀRÈg0[CX€Óuë¾Ù 'Qâz |­ø pe%¡Þ½¶‚EœLØ”1¿“áJHÆfñx<º›07ªÛ�#C™�TíÛ£Ý-’ÕÒåÜj�’þ|„„Bp­ r%ëLFHŽ€Åó„}ßÖˆ‹#x2ÌÖ¢³R‰ì|èûý0„»„BIEuYsà6Y^ð., D³4ºaI:Oé<Žþñ„ÉÆ¤jCÏÁªëàÞ;\õœÿ¾ÞO.=·ÿñ=wZá<�›ixœ}SÝŠÓ@Wö"^,Ê‚ ˆG¬ÐÄš’€zQ*ê²¢*î®â 2L“ÉvØéLM¦iã²¼õFÍ“ìø>’g&i³Öµs5ß™ó÷}çÌÙñ¯­ïŸÞå,Ò*õ‡xðŠé¡Š3 2†˜%\rÍ•Ì@+˜ªô( ¸†‘JDJæLr&µ(`ÊõªDp ;gß>_Ú|¿»sðfïçÆu§º‘„”_7‡Su?æ#&3Ì^ÇU9¡¨æòÆŠK K§òðÊ­ìƒ×uO¬&ÁÊàÚöÝYÏZºž‡k=çEÏ9…AFªÊ$Lˆî9Ž |iSÁ¿°lJ„M¯T¬é6³¥îXÝ<Û'5ëðy›Ì:@ ÚmSuÞ”uOÀŸAŒƒ_˜K§®íÅÌãiŒÃÐSµÐÖG£±?Ó”Ž ΃pñ˜2=IQ×î¡ÕÄt�Lc§ÎFpùˆ­�¯!¡qŒ¸�£ÍôªKtÊ·­›åïÛ[ó‹B×9±J×õÿR �§m™"(*P¸8Š%Ýýˆ šÂh"4 Q³} Ü»zK-Åù’s†ì¼+ØwÄù¿"üO,ÞhPíL•¦c¹™S~lm^_¬Ã:š†P†XÊÍ{­æ§˜†»ôû°‹¿EX|Tøqõáeóú-�06½xœmŽ=OÃ0†÷üŠWΆšf/ #C[YNì6'üÙgUñßq  n:½§{Ÿ§EwÓÁm6X ÏÁß^’f× N‹CQƒÎ%c n±Š<ƒ”Ö-ùÉfm0ŒÄ©ç²˜Ô'ŽybÉn=ß}�cˆ`'®…喝1X±ÁNÔt±äÑÒtÙ†Tj 9S_\pøX}ë<ólâ™’ùR¢•^ÉŸ®�“¥qê‚·ÃÑ(ÎѤÚôXÊíÓãýƒ”¿ù{¡¼²åÍÄõ¼ Í'ª\¡¿›xœ�UÛr£F}×WtÉ©Z©J—�7I¥œ—`„×d1(€ìøiv#41bÈÌ`YIüïéá"É—d×yˆžîÓ§OŸ9[”;ɳµ†Ó÷§ßÁG!²œ�çÙ½“Þ x@®h}+7êù¹Ñsܸñe°ˆáÆ CË�]'‚ ;ðgnì>¾]€åßÂ'ן�€!OX†=”ÒàG�ÜpÈRCXÄØ+ÑR%KøŠ'ØW‘U4c�‰{& lJ&7\™I*„—b–œo¸¦ºþçES“^oÅ‹” Åw˜hpá9¿¡~€Ðùuá†ÎlØ[åì�h*3¦=À¤4ßýÉäÑã$Ç—þ7ÙWÖ'‡Ø‹0tü˜œ»¾Þ’™>N÷‘ICgÎ…ë;¹p=ç+Ï­ûÃgHÏÝ(ðáªõuÉ•(ްf’n6Tž&»/!íß´;Vã¤iJjÖ<)+ò^$»A×O‡ƒB-«%J•¬Åñ³UªÁ9ƒ_;&Q°m§)Å‹$¯R†»°\RŦ(¥§)KDʰþ[nðXþ?ΕT*vè?J*wƒ6ˆÔAÅVìÚÃ^3¢D%¦žÍC÷ÚŠÐ6‘àcË<5…’·Iì+g¼GÖöHRt�D É%¦yßP_8b̘/ÎÑ£¾„İwq0 w”)&Ϊå»]ÏB¶Á­¡Ï ŸVeJu½ß«u…Úÿqr «ÊŒçŸ[Ë8Û›g†æR-'hËÓrÝäŸnó ½Ã_ôˆ }ôûƼQxD”µU¼>#ßb\x¤c´Á1ªw|O%76;ìñÕ í•ØÁÕ·&$î ®¬Ø¾DìÛÆ°þþè/°oµWD#yRJ�ö¥[îß4ï·lµc0wÂÚ›[œ$˜7îܯ;dR ‰ qo±§½Jr^ܵJçÿ&ãN3{í´Äâ^ç4SC³ð­E=kø¥Øö(¯�0Bxð-^w�¶È¥cÍœÐ4þŠ<öëqðí®BËÔk‚—A7ÛŽüWQ<Ãq„¢þh×·½ÅÌ!7—VLnƒYDµ9¾ZêñˆD”§•¦ðŽJÓ<Mý �Påùø >·Õ?›ëÖ\TZ”9»g9 Ìßq7'•ufå[WZ�¹,¯ƒošl+5mÅVøÑ‰£4xîyˆRA»�b�ÂAN®�&æyƒºÝ~>�ÿ<×ÒUËð‰ƒ1óÉ)^ï‡OxœÛ/ü‰M@YÁ9¿ ²(3=£DÁÈÀÈx£€S}AQjZf…­ƒ³¯£·k¼§_pˆ£�O|@�«›g„WjEjr<~%9™I)™E¶*ÕHjkõÑûx:¹x9peæ%甦¤B4€Ô²‚Oî唨wàòKÌMµRpò÷ru‰÷sôuuàrI-N.Ê,(ÉÌÏC’sq vò ñô÷sà K-*F•s K¥–f¥[qùd&[)èú¨TC\\« ›ƒl•4Ð.ç´œÄt�*O•j„Sk¹ ¸fÕ¬1xœ340031QpöMÌNõÉ,.)Ö+©(a¸Ñ3©¥­Â裙¶áû{ÁY ®' !*‹Òõ’6îå5�'»«»+jûÑÃlïTT&Û”äçf&ÕLpËÞ 5!�‰±…û~ı;š›žÇ†ÀԔ唥¦Mt“`K›È™Ðœ²ñàý›·| b¡ŠRR KSKAŠXOùG¿�Rt6I2,²–>¯R# S”“t5P ßò» ù%® ² #cd_ÝühhSS™—TšT³*oZ”v6ÛÒº­ÿŽÄÎÙ2¥÷FšTMZfȦ.Ÿûo Žl‹ox}òÚ¬Iì¾U´Å¡*ÒÓ* ¦dj°}‹šjZúCD} GÜ㥇.Þ@Q_Q� T'yµƒEq¯êÌóߪOkýÈ3yV1ª.'ˆ;…¾^÷ˆ9nܹ§Zö}â´Û|çO3›�BÈS ›ÌŽI<ŽOüãXüŒ¹¥SpJo¸Ô€‚’¢øâT�¿O³®ÛzÐ0§ç¿§LÇÉ óãÏDVT’˜œ TÆì7eyã]…Ú)ùœÿÃŽtM1ç<‚¤¬,5¹$¿¨î‡ÞЏ©×y¦žæúz­'×üŒ�R¡êŠRA¾wrö½aŸ™½éjØ¢3S·>+v‰9UPœ¶J½x†)ŽÒ¥²Súkt5,u͇©()*-ÉÌ*úÞ˜ô.À,fm`ÚS»ÖC›„Y–|‚)*M*(ÊON-.ªÛÁSýb]†D¬Q‡£ÃÓ :§Îýƒª+IG|¦aˆÜÑîtoa¹I7ö×-sœÖ2%E©‰)@5×D¢ªöLZoŸüóÁÑ׎|Yÿ9b`j2sAA`ó¦¥ÛÎ]ânþ4µ!ß)©Yû†ý6à>ŽPxœmS]kÓPgi�“!ˆn.ÔÂZ�VW†’¦Fc[Òf‚fé›Óö°4‰''ÛÊ(½öú\xá¯ðÚ ÿ…·þON’¥¯ò¼Ÿçyž—üÚùúpû¹¬á‚àéŒÊ‡/�~l¿¸Ã~n=Y9dÚœI²ìD"4…4˜cHá¥G B»h’}¿Ä(Îrž‡£tÊ]øã8í˜`/-O'×y.…öu8¡‡Çc'BÍ•‚”ÿÚWÄ CD"‘)±#D L¸¸�.Ѐˆ�dÜ¢ «G”Ä{)ŽÇ! EéRŠ2ÆtFÐÊqSŒç)å|iC’øËõ(ˆ ŸL¬àFAᬹk®@á ®@î ®¸*l�Ì XÂaS8dÂ!kÂaS8äÂ!Îu'pW®Öq]›_…8dQϮþÝÝß‘¦~¦Œ4¹v“™±d¯J.XCW9¨�è½áH1 [ï�4óTQµ6öÁ‹]ôZ”;–nt׊µõƒò^³UË4µÞÈö-SÕì®n.[Íf+ŸåíådnyTù°eµ!ß§VWŒ —Uö¹¼ÿì?]†Þ1S×’V>~À[úÍñŒ$³?å£-©!±G•7ìcÅx‡'uUÑ;}Ú·zÝFâ¿<¢6ó�_Ðæ¿ö1Å�Ô„ò[åŒKJ¦“9Ö.=ý·ƒ“Ë7˜‹æóòÞã¼kp1U‚§í¶hgß+­Ò: ˆý®|bÇ÷âûÕ†„|—“mHéˆ/â³½xœÝ=iWÛȲßù¿Ø` &óCîó$f†s‰™‹MæÎËäø[�ÉO’YfÈ¿U½¨µoLæéœ[R×ÞÕUÕ‹Éî9YÖE¶v×v·¶ÖÈùŸoì'5ð•Þ“§ÐÝÆøí�øBš{ÍòSŒàýóówüísoàú‘;$Sè†$¾uIkâ àR'Ý0òŸ4{¤Š/Tø£JíA<Srç<?ˆÉ4r†J–û8p'1ñ|2î&cÏñ.yðâ[ЇCi Œß8Œà:vàu‡ $¸Q_$N̉¾�ãIt´»ûððÐp(µ� íŽÙ{ÑîùÙ»v§ÛÞŠy‹+ìF Ýÿ›z!p{ýDœ P4p®�αó@‚�8£Ð…gq€?„^ìù£:‰‚›øÁ ]3ô¢8ô®§±&0A°­¾"s|RiuÉY·B~luϺuòëYïç‹«ùµuyÙêôÎÚ]rqIÞ]tÞŸõÎ.:ðí”´:¿‘žuÞ׉ â<îã$D€LEé©Üº®«‘p0’¢‰;ðn¼°æ�¦ÎÈ%£àÞ }àˆLÜð΋P¥8D0cï΋�˜ÞJñ…ˆv×ÖþËóãéÐ%? ⧉۸}«ÜòüoFúݱw}íDî.š§ýA¹aœñì~ƒ>ì‡î�ýÁ8È@»Q"ÐØ4öÆúÃ(^Aú¦¤nó^rÆ{k´×“÷î`ì„\¾ õzìöäçà�êÄ �&óE’ë¨7úqßCe€ŒˆëOïÈŸk.lÙ XèJÄ�髸¬ÿc·ßºü©ß¹èhõÞýLNÈ^=iÕ\÷ÎxJ-¼â;wî ýZ©,Òsc›à(¬>Ú{¿ý¯«Öy¿{öS`ïçÁ®`—£üùîcL™„G±x/Iï× ¼ÕM‰â,Þ¤òuÖ±Gº�Î frh�÷�ù›1‰�/ Ð\?^\œäƒ ÈÔ¯9dÓ6 ôÕïqfèý‹�íË˳÷mÆNçЮ}ª¥$÷ããµµ;ëÀ¸OFn,>ƒú«°¶81 ²…&qÈÈ/¼XëÁ­Ò–÷uíª¦OoÏ�BE�äÀ"¸1¸á`Pˆa^6–D¿‰}K¥mV:r9Ÿ8aäær¬Áʃu8xG£Ÿóh/�ŒŽïDôór1££b÷2þ-_'åNaL;hêÙ=��›¡ÀxxCAÏÜ—íLÇq”e¡àù_òLñ £"Ý8]t~èNÑËeá[§ÏÐõcïæ‰ §4*‚ÑRGpŒ§)G’´„ˆo(Ý"gCvïâ¯ÇIËV8šÞ6JFÒš Èzmá{ÈÌ1xBä‹~W¼ ‡°ãàn¢B§e‹þUz•ûÖ÷I—«DÀ› B¤QUÕȼ{ºK B%4Š„‚BøOÕÊ åz#ŒÆV3ðh-ÆAðe:Éì’ê">þýžm¸6„Ùo£tóæ‘�;‹ç=4òáœ*tiV¢º9F˜¬ hÿVŽgoãŒ# €~¨$<µ'Aˆþ�ßÃ9#:Í‘�e]�šç„z±¢±DDÌñ�MÇT»ðÞ mótl¥:_cÎÁ»!ÕWÒ1 GT㾯Ð�§¡O¨X™_øº¶&ðØ/È-ûˆ<ÇHÖÌ`NU§9L« l¾FFŒG}¨>fô¥Ü´âfñùî.8Z'Œ1¼ÞÍïÓÞgÄNÂ`:w8RÿGÛaÖVEæ=ÊüùAÊû˜lo{ªø4J¡�G¶ÛÛÙ„üƒt®ÎÏÉ‘$ÀÛÞÿÌ©L´”޵ë�í>×u„…ßF*B"zÜ£2ƒWà`ì:þt¢U})m4ÌpTŽ(ÓеL L„ wªYmoa*ŒëøáµØó§n]œ2€KK`…H>‚õ}€$kñC€(£:R¦¥R�±Aú33~ÂjͰ¡1¡Ñ‰¢Ôt_CSÔz,à|Ǩú k7T‚³VE]dአ%jU Åÿ¥™PIÔ‚°€ x�?kA‚™o�œ@6€üŸˆ4ìò.H€Ûÿî]¶Ž¤TiÖ R*R¬Ä¸pÿIè’b•÷`�cƒE®öq0ªþ_[—�³ÎOuR¹ò|‰óþˆ¬G­S¦mƒ±›Ù=®!ŒùÂ%”âš²ÜÿØ:¿jw5Î?¢)€Á@ìH«F1ÐU'×Ó0â å§³²³SIòÞE%ƒ¦¸‡æA“�a�æ�U�4SÝh™Š4O¬ºhg2–'4ÿên†X� À ZþSŒ~Ç è Áƒae*|>IX’óÓV¯u®ó�FOÖ‡ `™Ú¿ä%‹G$ýÇ‹Ë^µ¦ÑèéX›Z¾ßâþн ±ûò£]4d%B<:¡ÖxÌFÇWÔÛãÓ�·IŽÖh4„ïHÞǼH}AÕvï·_ÚýnïŒï(eÕóVê¾qÏS׆!,–}9Cµ YÚ„óhÞ5mϦÍYíNyI‡ðu:GO"\à%îO#gäVOÏÎÛd Øs�;K|›6„’ùÑq—â|@ìúýë'È�±>z¼ˆAéж¡÷Qnnª* àçÖ#‚žêw¿R¢Ð–B>KHP¡7ÁµÒâ5»õ·;Wt Q…J9¡U¢øi©—�ÕÐ QNøV+ÝÔ°£#—Ñ 5©Èt]©Ol½0“«¨5m²[ Ñêzå—˳éA³RËT¬�ºqŸò[[U÷Åÿ¹רÎÆçó`ƒÇB‰ðªra>AzI`¹›û@ë…�䃗µŒgážqÒ繚 ±†@žõtðKf“ÝBÈ© Ìøøt5´ªðb®Ñ Àç$¨~&U@(ç-(�5œäãp'4›¼‰äæÉ&†×œ:1Ù /8w¢;éw F£QÂ’€rq}HDŦÿV2Õ°Ô½µÄν}äý�®�v£±ëóþ ¿«�…,}¥ öÈ“÷s MÍX°1øŠ¦,й$Ýi‘›éxÌTi’¼Š ¥ŠÈÀºÃ-êUB¥êvÙ •â]MG­ÆÕ’«Ô¸‘Ь åŒUV&ž Þë””ÙØÐ`›ô�mÒ\ˆ¦Ô,š�È­ =]ÐÞL§¶�XŸ¯7s‹h¡'YÌ êê�Ë}…©Ê|¨¦9RM-%®ô`žÀÔDDNœÈOJÉT$x ¼O-¦Ž^%š8WÏøñ%f2÷Ÿ‚Ϧ¬S=…+*ár[”ärµcÌgÏæï{óДø‹²”·›ù„úO! Û‰oÃ`:º=bÓ½¸Ê…wÒÆš¾´¤D¿8rêùÃHŸÿGoaŒZºw׆©FR)^|²uQ>ÿôj±Ó7†,å’#— L´Ú`�3Nð¸RuÂW�÷íÓ³Nû½–Pdz.°–&`#«V‘UJ±fIQÚpRçJ€¤BõÜ^©‡†øf·Û¾ìé™kVÊi¤V�ï5¬ì[Ö‚ÑW�•‘P£Sü{7Œû¬¡Tr�lðÚ×þ^Éh”ñÿѹØy{çùl¸ÈwSº &tü‘{$ÜŸòy}XÀrú'E(´sÒ³ÉÈ·'ƒŸ·i@Îã2øy»(? %ø‘宥Â\ÈÛþ]SÊ5*ÕT…•Ö¸-½D·›m�·¶TAÊ3sŠ™ll”-@IhöJT^RF»m~b–H_KÄ´åé§\}E‰1“—%æ™­ÁCöLsÁÔRº± ¶ä{@ö*@Êq*A’y£[$UfBó¥ÂšU6¥êZfR%®ü9çÙÇœFÂ׬hfbÝ<+]ˆ‹K¼ç€ÅGx«¼‹©O 6‡d"·±N…®[Na\’g·óp€W–yPY/b0f/]LÌ­g»þ|gc7ð²W—[äÐ’Ñ7m‚_á"¬’›�Êe¾¬ì�1â¿-z3©ê"¾QǸ¶’G“då¾hË‹ÊßÛ#6Õµ­:bÔR¦x¬Þ-Qfæº`ƒupÇ–�G޽닉 ÊÄéëiD%ãe[Ñ®ÒûJß�T<#¿»KZcéð ÆªÑH][>-ÉGUŸtÍ·Ü–U©ä\"I÷|Üp^æP×`ϰpnÃh‡g_¶Åý†6ß‹=vˆ]î…ˬ,š2ÈtÚN3 ‰ ÇRãÄÌñ^óƒ$!Ñ_p-–K§¹Y^€}e÷ÞÐeŽË· Ïéå,×¾æ,]S„oUf‹× ¦kö<ækJ%WÒ3šq‘rðJ›3^†ÚæL7¬VlÄúG+ÎçM[ÐûüœBT®eö hÑ9Ë�Ö;>{�ÐÝ$~’¹Ðªå¤¹Šlçog93IÄÎgJÄŒìÿ?H¤ o�3"o 9ƒ¤º�¼<Ò²V/˜ñ·ðÀbÔÉÛ×JûÙOÜ�Í‘Fq@gèÖC 0’Ç ,_ÉRðeœÀ“%íØ2†yPÜ»‹N¯‰Áeÿâ´*áÉF,ÅÐ%)×¾Ú†t¢lýߪÉó V�Ö©¼Í}ö�¾âÚrÑõ׆(�Ë–Á–⊷jÜ,7¨î×)ÚàFyX³Lô¨ f�Ú‘¨Öÿ˜ZÊ(™( W7´®q‘ÈuÂRÂ5²-nJr“•¶v{!¸û™’ˆ …óy·¾e¤ÀQ0‰ ¯’Ñs�hû“§LÛWdq\ü¾—ÒŽ/›Nù/ÝÄVUô/}öÇ Ü‹:ÛZ¬v:ÓZ¢g]ʦSðVã³Ê•¢®-ð"g<¹uªò)N¡¼’ßÉÙ{übõ--û17|rvþhíüïç#kaÞ"ÚÌ9¯‡[š«§²v&fÚÅ–Z”·ì_LE®ú *©DM•ê:(^ù^Lð”ÇE�XTÏnEmÛ×Wæ³µrðœ>]п“ÔéO9üÀŸHzÓÆlí“u¾}0–y¸Ðð• ‹€ f68™žL¤Ï�{Æ6r®ÃÞŽÚ`b€4áeç§i‡N‰¶ø˜½ ló Ï;a½çOÂVEVt£¡=\.”^±ÀRÉ×z!X°Ç�AsNAO)&­¹Vœ‘¶SDú…ÌTs¡°W à ½æB¡5(†j²Fkõ‘· ]êÍÄJ�·“û²_ðŠÃû' ÁŽuCEºb“RŠöÚÝ^¿Ûî±)pN€5ZµrN]ö°È¾Ð®…w´žP¡ñ€ÞðûuèÜcý°Éoàp9öPLß³;Œ³=ò•|=æÃ=díÝxnd±ˆ�Zšj~h`ž<2ÓØÀY²IƒeÁt™1ªêDTÌpêD.4¨øÁ=?{·¢Ù_™¨.y@Tâ´3>*W¢÷ðCW¬6ÅVê:×µCbí »½K­-;IÌî^¤ýËåÏþrÙQ׫cŒõ†ø¥±¹Ÿfsùc÷:üîue%ä–Ñ Cž«“ÓÖyw1‹²l¹²Ý”K°Ö¾X¬¶Dˆô%�’…×5mô°,žVŠæ¹CŒQB0G¶5ŽäiVŠK±Árê ËhGÙÛ='*æ“ÊàR·qçö“lwîm®Â¶È«3wkJ=kþòæ˜SÆ× 2Ù幈A¦òÊü fCÝt’‹¢OÉŒþF@ITû{øoOA2—‘î•2Ð=iœsà8l¾9|óúûæ›ïJ!S%¸P—xc»æ6ó„‹×sƒØoÎߘ̤4Wü°{9]ï/ l [Yt{‹¨YÁ8§j5š÷ÿ"7–9•¨;1Ë�ÌîÄp5¸âW\áU’Ù cJCu1sô~µ”),äeØÜJ<‡Í…‰9›2¨¾_Ð… ÝqürCkáìt¹�ƈÇe_>|—±qÚGJú,Š Iï¼…Š •‘4ÚÙÊ÷1Êd©ºÇš.n}É´J;ó�ÉÍò;@QvÍ 3aÍÐÉ„hÊ÷&„tÃ[O׫øú$~HŽº”ñeúÇrL{)½ŒmU©V@ 9b®}CÓŒ%Ey@,©ðŸÁªˆ÷Pƒð|À~ÜkžZƒö³g¬Ì êîê/û°RÌ€¨:c»Ú>‹d•, �h*aMÓ¿É’x&Sû†'J �¥$)Ë`tzþ>±»®b Ìη1 %sË5ò÷û5²+¾«û²Šù-@ý•#Í ög­-”µ”ÖÌ–p0“%<ò1Úÿ´ÿ¹d“§¤I3£I†1óᤠ¹g1mšþ"©"ó‰Kf‘ÙÖ|q£Ózªy5KÅÒÀÎ̦ҜÉTxßebM¬eed)#Gþ³È¿°ˆ=ŸÊW‘ kµŠáÐ.VϦ³Ùâ8|IqX‡¼Ã2ÂÈ5¬åhÉâ‡'m5xºì½j�ø|+–ÿ^1¹üÙ”[V$95?B75Dgý½lŒžÝ”!´ŸÁ�ö“Â#pÒ øn!ùK/t·K¶šÅl½y9¶š*[g>݃EUê b7ŒÊ0tPÌ�³sÝx9žTžäyt+s†—•*-0˜âAèÊù?Ìá¿.ôF-í>Æ®y�_F6ß-W6gðõëãy%ÈóÎ*ÉJ˜ O¥ò•ž¾ËH}›„×ðÖõ&Cwâ¹ÃImFBµ¥Ç×£Qö·påÅIÃØ™ÿ§m u®¡¯ó�Ê'Ž?çºÂB-�7œ›JX$ÛV­+¡®¯±L�ÿÓa{G˜ê#x.˜ÔÜÁÇUMÅ#Ç[¯¯2#lfÐaÿƒeþRA+|™¤XáªfŒ›)ÇÕÆVMpß(Åuì}qèñ-®´¤p_¹uäþИ9“̳œ_KΠ秪ðÏ[øJVðíª×($ÓÖŸ\~pž3ž Õ;“šm©Æ�c3âaLQžºµù8¼rÞö†ÕŠü °­F)ûœÊÚKr–ãoZ…¶6ÀêþÜ-8<•AûíK²À4Ë$4�sdq<ʆV¬vËjÛÛ•ßú‹¹Nùrz@B…s Ùgù*bgô]œHeöø�xt¾$Ò_¨çMø�â�QOq±"qóH–x:´P»mµ� ŽCïS¼&CKØAÿtÔ•Ï!Ô3ïÝÔïÿ·F±£Õokû©F�VÝ(Ì[³eZfÛ´Í ³cvÍÞCãXCx^ã(Ã6Jìžh$h- "õ0¾ƒ86âYfë€+íKÿ4Z«guÛûâÂî´»V¯•=˜¶”f#GÑ…a½æ‘y÷qw‡BßïîPß[µS´«½O\Ù¢‘ Ê–eµÛ6ÊìtºÝ^ïðŽø¼]zâþøC¤ï@Ø6¶¬×a›ªTîÝ%»Ç÷þ(`³âE~êgñƒú¸wyÕ�ÿŸã²¸xœå=ksÛÈ‘ßõ+Æ¾Š—”IJâ&Wu–­¬dis®håœ%Û·•¤X 0ƒ€¦t¶þ{ºçÌEÉ»›c•-3ÓÝÓÓÝÓÝóÀÞ.yõP²»·³·»»Cvɳ8¡$øœ”9¥£±§a¶¼Íãëy‰¿^Ëd¼?þžü)Ë®¡ÍùùkQû<iZЈ¬Òˆæ¤œSr¼ Bø#JäÍ‹8KÉx´OzXá©(zÚ?D·ÙŠ,‚[’f%Y`Äa¤Ñ›�.K§$ÌË$Ò�’u\Îe„0~0²i@õ€`'H6S+’ DÏËrY¼ØÛ[¯×£€Q;Êò뽄×+öÎß¼>»¸<Å¢Åû4¡EArúÏUœCo§·$XEa0:“`M²œ×ÀLj”R¼Îã2N¯¤Èfå:È)‚‰â¢ÌãéªÔ&éƒn«€eAJž_’7—OÉÉñå›Ëùøæê¿ß¾¿"�ß½;¾¸zsvIÞ¾#¯ß^œ¾¹zóö~ýHŽ/~&~sq: ØxèÍ2Ç™1²’FŒo—”j$Ì2NR±¤a<‹CèZz½ ®)¹Î>Ó<…‘%Íq�CZ�‚IâE\%{dõ ííìüGœ†É*¢äeO§AA÷‚¢ y9š¹Ê„T: #:s”´0àe4ͲÄzh�€gŸ�z€º‘S&A.:µ•º¡¾‘–yü9()¹,ñ!šÇÎ ë'Öƒ¡_…%™L‹‰èþ¤$_v|Â_²8-‘µkšg /iAÛ‹÷çç sˆ#p€•bk(ºX–· :‚Q cK/?»n²,óà ßÅj1t@)Ö-HQf(ú ÛLC+²±~ÿŸ Mù°¶‡z' Ù‡-çYĤ5<ÈQ¾X /Éáb)Á·pV™g·8ìú Å”2®yшf€J|;ܹ;Ü�!�Éç,ŽÔ&³dUÌ{Ú¸í²¿ÀP0ržV QN—IROãÁŽ‹§æÇÖÝ,‰ø÷m€¤t]©»!ÆÚ¬��·é… WÜsŠSÔV<ì†ß9„0MÍÁLn7†3ˆÝGÑÙ“ˆ&´|ÄQ”ÈÑ$«Us°=ùvCØiø\x:»?Ú{ L¤ñì–5è™ÆÇÂÂôSr»½ÝOôv2˳kÜïñÂ&"»¿«Ë•Še§Äã. nd¡:ž6|ó@íu£°‚›Xb?»Yf9úR?®ÒðÞ3¹˜ÃÉð¡>PsåW˜Ó@×AeÞƒÿ7Jk.ëï|±åNûTB¬Äßò'yE I²°w0`Æ8›iºÓïó)9ž‘óJ€{ÕDîs¹ÊS6ñp‡GÐ3.çôº@Ð …ÕLŒ¬šÎ»‡#Sq¾‰Þæ*÷ O€U˜Áoõá#PÞ¬DI–}Z-·›®T£ ‚ÍÓË”**¤$uºŒ„ùv“ü³à—lD^)b¡û«ë9Æ‹\äž¼ªZô…û\ƒÔ`À£^eImÖd”â}aÞV…¦ˆ Þ'üÜš`<Çš¿ô´†!Ç–aå×,`‚`H‘ež…”ÇèËâüT¹©ò£°D~aM'!ÄzѲé¸^VÕ@¼ÃÿWU•«ýc»1«Ç)Æ•›Š·*Ðè2iS ãÓâiùÛ!µQ6Áx“tcµi¨!MËNUÓ,�Lƒs*ÑÆ*YãIàËÅC‡i6ª±åÈ:H>ñð‚¹` b ‘3†ÌÓ¬,³…ˆá„\zèh0Õ°ªO”.U~Â(7\åŒ+G�Fø, |�e¯x©¢¶)à6à‰ª.TßÅ­@w»¥kwÛ%�ýç*H „!+âÑ™®J°É-B¼Ì}4² ¢y¢I³T÷À¥‡Ze_ì[é&Ñ{¥8t*˜jê_Ñ{ÚÜkæc £[÷ýá-/'ç¨m ¨,±—–4Ç€5(Ë œWØPÓí’Xj�c)™çÏ…+#3<Òäß4?ªèk¦2…©?fyÞŠQ0 àp’YœR­û¶ !:Žº÷–¼XŒ€þ‡hX‚S±ì'Pã‡_¶êÞÔ²à$Çš�Õ–ÕÀ¹›êrµ¾d˜ÕîTÖ‰ƒ+F$ˆþ±ÂÐ�� „þEqz�ÔÉ@Ux,l‚*£Ël˜n°T sÜlp»cfçÜá°Å{q´‘‚©pÉèŸ)—: M{̪5Û¹ °§P#Î)€(çL`c0w«zT�©Äßâ�&– ›ø$®†ÈúÕpZ3ÑHEe¢h»…côpLŽì>(s™>HRS“ž^?®|Œ=#sÀ�ob©SI* ƒ÷–e4%ÍÛSUÖ°—còrc¾Ý‹/v§6gŒÅÛ6¾h<—Ôo­nŸ”tG«Äçv+î¶i(í ºæ¦îƒéºo«ž)¼ö¤d½ãn¡�0>vÖ�%û­ì£#b¤i„+ñ H‘‘lÎÛøã¯® ‰{ ¿òØ•ÊRÆ©–ãlAºµUô¡U9Ķ�©é±LJ„“xq¸È =w†ŸŽM° ¦½Ôßÿrwèd…µjl2ª z†6›�°³"*+b®]‚qÁÄm@Ö”°l Ó-ܪïÖÄ]”éLÕåU§–)Èÿ'k*j¼¨Õñ y¨(–(@Z¯‰‰c›�’Ù¬õŠ1¬-Q§f6�¦�òu¢ÖÉåäøòòìÝ_Ù0Fçq|{±Wú_ª(*sJ‚’|U7h}å;“¿ª¹Ô¯|·$%_%­_ùnØ‡ß ¸Ýö±¶mcb&ÐÂ8i(øPi�šµ·³æÚZ«"雉ÚÕ2ÂõB.Dߤ—SÜÏ\¼˜Ü2Mü®è“&}9oÓÕŠŽK–v0wÅ_×J­›¦MïÁ³ âÔW?¼æu Î{ç0ÏrJ5‡iÈ�!ƒ³†±þäZ5iFå‡ÝU: {Ö}Sn|0õ&ýºb5e¾ Ê~4I•é¹3w¬œ¥ï¶ ›BòÅV¨ª¬v´ÕXæ€<ßñ�fäÚPUPîѸúÇš8´qã{”·9±ÓyëÀ¾Ãè Ød4ˆü>ßôÇ×"ð·mÈ›µXæéZã8{•ºY¡5¢7À«ŠH�XHlÝ—#Á•‚ç Ö/ÔÂç~>ž—r&v¢s‰ƒh%ÆM¯(sLº¸O‚{[Øï±æ£ FòXº`QÍpÊ£ Žñl,ž±Ç)5¹ÂS"kŒNq-sÄ);›¥{ÁíFô:�ËÛ·hËíõI?Ø¢í¸[t\.–ìQÛÎ*§ƒþ?®×�/Y”•Ó$@·EÊ:ä€�þ#®ä2ƒÊÓº4å<(Gî%hÑmŸCƒ„ÔZm$W¬–vqà8ôå\j‡ÞƒAq=T‡&†Í4;=néôØÛi«¥Ýé±`ÇN;0X�Û�[�–Ò†nyÅ)á“W¿eáØ(‹ÂZd¥ ¾Ê�Ÿ· èÆzNA.e„+U3"QÌvϤhK§Y^èûcë•Omb�}çß*�κ�@{$Ñ [󘛀[£ï•*Omb µ|«È:ëví‘U7l“-ðN±C7Wø`k˨w境wÍ,oÃáD2nîÆ¸µNYs`ðvÃ,oÃá »ë»ÐwÐaŠ ,X{(m0LßRi‚h�£<ðZ!´K­Ú­ƒNÝ7uë Uï­—«[ͳŒ=¦ßà0 s–¶ØÛÓaAÒ™Õº)'üÈ…š¥RŽ$á@4F3Ïž5ÇÚ†Ö=r�s2¿´`�Ufm$3Šõ}‹¸rï°!L­Ñ÷£ªàDà8R’Î5m!±·*¿Û½=ÛÆj$âöÈ Åc×è1¼PÂ[2 y-Ašá�˜§fŒégú#ò†q!g؃ôV‚cU ]K—’qnS§#7h:PÑ`c’Ð`ÆqR�Š(îûe— ¸åÛ)쬘Գ%åå¡¡Ÿ£ô…ÉEµÌBC„ŒSÆ0y Û~¿AÔq6ºÐMtËÊ›^…ÍêöÒ –zxàmJÙE¶F‡4woe�ãÖÙðžì�¼£ÜqEŠÙÉé"ûL#™½h[H9Té{AŽÃr$˜~f`ªe„QÅm~‡½åÊæ§®{ÊÍýR8ÏêE™.ŽÇž½åÈýõ<†p�m¢gÒ‰� Pùå’¦RßÚºlvµϻãÞpm?ùކÝQp¿añÔµT©#n]ٔإ޻Uª–ø …Nm¬on\×màǽ,6îç4`À°s1ì:¤uR( ¤ÍWõû‘=øiÞÔìÜgsØÄÜ]­ ‹ÁT—àµó •’#Ýér±†õŒ/›�ï<²¡øŸ °À˜sü½¯­O7Pb‰ÿe'ð³ÉíëxÀxEi;!PºÕÆɕ΄=¬è°Ð…ÃÎ; ˆžG6›¡Af«Ñåt(fZ6«FÞAHÛ‚¿Ûjn``»×{Vy`ÄØO¤‰ «âpy ï�¶?!c×Jñ5%¶ÔäÙ*�j&÷Nû/D~?ûã^Å ø.“S…I'òéù[-ø]‚9�g8uDÈ1¶æÏÕúF¬õZ)ÂïgrÝ@¬¬|œS°{¦d†QY5û®«i|Õ`ãÛdî;ÊÏ®±QÕÅu!RHѱބ)g”0kÖ3öÇYÞ�LX ]{Ik;:t¢R4Ò*Õ¶æ²'ïçŽ3Ïfʈ‚³>Mâbb÷rxÂ`œMÔ\ÞGCåŠØ]|iÈ ©‰›ÒtsOë´¤ ¸žm«ôàÏnl®tÕ鶇«­%Ñ…•uÜ5¸á¨†kc¿œ3dÔ[×¼?! §6£Í VÝÖÅu �06ºZ>ê2ÅF³yÇ2Ñ'ª‰V-´f£OÔA‘&×°¹ð‹?gV™Ííueëë&Uua�Ù7fªÞ47]¸µ¥e6�¬¸$IJ ÆÙöG}ÖYÆ™ÒXÚ-[ͳ ™¢v±Ž³åT©ÃµÞÌBŸø,´Êd‹®lªµsTòPzo#}ŠFúÄ2ÒMkíVÚÙÚ4ÓÎJÝìtkSbH.ë½ËR{ 9Gê~¶Úh^�Á)ëÃnR£±60|skýoé‹•ëTqI+K‹¸(iz6¬ü»\í'Lm]¼+OÔ(«$uü&à2AkU1—¸yõZÆê¸Ù%mŠÊxRžuûMN_‘¯_±óf½‹¹ ¯Ûzªßç£5Ęo1W°Í0¨ _*,ºP*HäI+£ŠyœÂ¨º�uò7HqµV¶Šʆà =~}·MÓ´bI™¡^ªnw>?©q§µ¶MbkK¥[ˆÑ»§óûîÈt ‹gvþU�KEcWf· v£ª|jÈýV{VÙÕ¯†o¨Êæ³o–Uv�"MF´qÅ—ÅãÅJÜL%ÖÇÙGe)¼Û‘è¶+hÍc̯Úl‹çä°RÔ¼ ¯(Ñ¿ ‡[.æmä°ÓN܃ÅÊu®Mþ€ó}—ß°Ýq^v­?¸H¹:»¼â/9ˆ(ÞTÆ´íÃùÕ»³³ –M>Ÿ¿?»¬•þ÷ûÿõŸÍ•'?ÿ¯¨üýö÷ÅKØÒ Adåí’"^ÐãÏÃj¢u> »âGs[�OXÇ-ÍJM"êçâJcÄË0ø¯Æ®Û8îڵĩcœOW0ƒßÓêIw»Áuv½"@-t-:¸ôþ7 0Hy�FÙYÂ~b@�EK�J²ÙnÝB¼VB!Q¶Åb&&Ö9‡ ÔvíÓ€<åÈŸ¸Ù½t¡ûêÑs|+†ð/¥ç8�sHÌ.ºÂ·”†e¬ÌÒ¬ÌV¾'"E§¬@§GðÒh¶g­vAÞÍ(£o�b^Јqƒ”ép™ÒÇë÷ËwLÁ]ƒÝýÒGVÎ.”uNʆ‘Óº`í[USO[1µCuJZ…U0ÑOA-²ì!»Aê3ž¯cmæA¢Ü�8àǧp {Y(»¢í·m�­ Ÿ™VP[«b/¾· D«™5(Úl Þ½úÿîGá×zr;æq Š5— öÂÛtHo⢔/Ö™¶¹%µSŠŒ;ˆžÔLëžQÅœ·ª‹;ßÔt¿ƒ¯¦uƒK©I]•ª^…ø*ÁÓ·µßÀeþ.j¸Ä™÷§îQt‘þãØn•Æñ–HoâØœàßYCq½/²öí÷E6æw¾Å«¶[°e/€TfÞÇ¿±ùaWÄ®q¶ß ¡‹ (Ö—x+®Ÿµ‚≾ M�ƒ3gîݾŽQV7…‡»}{Ÿ›‚FÊÝK�œ¾y/µ§Õ‘»Õ�&¾û�"´@yïQÞv¼sžÙ\ìÚ%?Ð4bùE¶4Ï^ÿË_â'À�Âû­ö#¼á ›¹ñxœí[ësÚHÿî¿b–+'ÂÁ`;�Ú²cב„ä¨up-àuö’=JHVEHZi›<þ÷랉iøAövoù`è§§ûׯéhí�ãûz‘�ÖVkgg‹ì�N<ŸwŒÓIÓ�>è„Ñ<ö¦— ?½Ì>�ƒ½ƒ§äMNaÊééKI}ê94H¨KÒÀ¥1a—”´#Û�òIƒüBãÄ rÐÜ#Ôä£ZýYÌÔÌì9 BFÒ„/!\2zíЈ/ N8‹|ÏJ®MÓßS/mÇsbG ‘c�ANß¾"aLìiLá Qâ«Øc^0m�$œ°+;¦ÈÆõ{ã”i€eò�Ú*@f¤Ö�î F^´ÝA™\t‡ÿ:;’‹v¿ßî »�9ë“—g½WÝa÷¬Ÿ^“vïWòS·÷ªA(ÀëÐë(F @L¡¤.Çm@©&Â$"%u¼‰ç€jÁ4µ§”LÃO4@#Ñxæ%hÒt‘�ïÍTÒ jmmýà ?uÁü¾7Û mIÇ»¬)ŸÓ8Âæå‰24qæëCÀbJ},a®°òXX‚Ùű4+ŒÍ“VJ•GÙ<¢‰>œ`8Çt!¹žv’И•àÏÐÏÍOüpZù`tƒûA\™)@�”yeÈøCF. $H*¤s…1ºÛŒ²ËÐMn—h¸…�áî}½�á8 }2NFÂOF^à1k‹Àk1ÆÈŽ|±¸Á&Þg:¤ñgû#Ücsíá̾ÎÔ·¾ðG #rL,e�ú—oG[œÆ›k�œ—¸“¯_9'*>;ÑÖSI9/Mò%S–Æ™Ø~B�øè·­•’æ³›®ÍlI3¥.,âû¡cí7J"Ö‹y¹œe-*M#]…* fvÙî‡Pã(³w~zŠ^¸Ðh÷$¼n@@Ž€ÃÀ·ûw¹O¡ç\n”+±†óñù;™šÏÝÄߌVÔ¹ò§> ¦P �ÉžÙ€« ·Âhtf³ÍÃ=¼­ |3Ør¯ù¡Êkè!í*Q®Z�L V[FGù|Óc—ϲHVòx™9Tþ½�ÀªA¸øàÄÔfÔºS–¬°Î"ÉÌD’AáDóêú²°Ïƒ]€›SÒ¿oeÙu� ©t1[˧‹5\¨�q8W–©kyG·ù@ÈäX …È)I]ðñúfœM¯ÒÓ8¼Z%·ô¯€^©©H Õ’ ·1Ñóê™ää„ì«~°dÅ¿çd_�îÊ…ÁU~År¥Ô$Sò„!�Sziè<Ë!£òRUÈ!­ƒQ§ß‡~à+o{g R{mÃ6ÓÍÞŽdûsZ¯-²þ’ŒgI mƒ²$mª’-KŠêrß§ì|x’úþ*.¥”ÝY�OÌŽõ=ò†ãS;^%¹AäM"èÇéÀýÜ*á0�32qÕ2_0ˆ¢I©Èò�F*-µ«E5G{0èô‡æØ>1 T—1�Èô…êŒÆsè§DLËý!O¼î"²¬4ûqÄvêÆ¸xdXi1Ù(Ü®Y¸ h*ÙŠPuÚoÚÝfÞiC7B:g积^œž½ü)ÏaÛ?ºiŠq­mòKeŽQÁ¹Œn •Ĺkêã9 ñø¸E9iOÛC‚ê‘Ê@Ëc…�P£"à{d8<�¢#<|X£©q �þåÒŽÉÎ$°gtñƒvº4¯ì�¢FðÜ„$ÀD†L«E^òÍ'± £³(Œíx.Îø¼€Ÿ1;žRöOÀbê°0ž7ÅÚʪx¦§o6a97�¬\(C½Ô§šzJ±¬‹;/Fz«0Kkÿ¡ñ@ý, m�¨e)uzg�Þ)e0}Ѽu�@âK|¨m'jLGDYðHã'öy^\.«*V•3'Úa4û F‘Ð M R FÝwçƒ~½(ÿ-Ö“a"°f³h”Û P„%'(,Ïwßñ—Ð[ÝЖlS4¹Æ~Éi¶"À‡ÚsXïDâC­Î�F·C:+8Ÿ ©? !úØ(%A÷ÿ[@Ý¿xw.@‡woòwguòcGyÈM°½œ}L0–,]]A$¸fËŠä˜n³¥¶ôCÓ‚¹uªVB¹á³¦F–4µ*‹™ †A_ž³,¨»Ô¥E1cóœìaɳ÷z6\8L�Óoˆ’P-~‡ð×5VÀµuhd(X:~˜ ÊÐ4d!&›0om „Ö݉’o ½ö–ÛÇB4?Ä»o2'fb\¯7šYîÛ�4j*¾t,Sá¢*T$D G› ñÌò]û3 Ÿ¿_š$f¬"%Š µf—Fñn&mm-HD•¤�ÀŠ *^ÊÑäÉkl;Óeù« aQb 5Kù˜J¾ÒÀ÷‚�V‰\á !þ„U U—ÌᱺŒgVêâO·©^\’¬z}� @]i“,Õ¦Q ®}j'ì�´Y8óð¨{€F¾íäΞßfQ�9�ïBšyfå8Æ”o´TV­¢õvÉ|�f³Ù�]¯ð˜r\/ ¡)Õj�òŸx¸¥¸‘iÎ*fÒ!7¿+Å;­`�cv±#]rعSí$ác¡¥3ÝÆ •©Õ¨˜j<ð™Ñ™Í­»64å³~!_½|p²è6àÝr\"Ì6Âô¸Ží0‹:ªuLÚ�ý5QG&–…LÍX×ß—Ù?zô^}˜Á’÷¢CŠ…ñ>¾l±…%Òs„»âík~ZmÎÈ…?xÒÁ\ü¿ÁªyÙ9Ê §à™ZDo:Sljn6GƱy~ù„œãSˆc'4i"N’›ˆíl.>šOeãÉ{4£ð£/Ü€�asPq£j›|kè3›1é$†rù¤4‹ãW çc%J�Z�T .h¹×7øE„ø‹�ðzZ‡-¡Œ£¦%=x ÈA]vÃÑ 3q+” ˆ saÁ œb®+¯¢JW^û 2èþ»3zÛ~'3h¦û/�~÷õ¯£^ççÑYÔï Ïû=+[)ùt±²âÆÈ´BççÛ’˜{Š˜f9×áÊîÝaúAƒì×7sµ~&3˜Ø5j4ìŸwŒ–+\>€–k_‡Wï¦Cˆ³\Q^K,œn�ÉOù‘2ï p´ÒêÆåÊË뙤1ÿ‘Fn�ÎuD!ZlqEÎÏ­¹5`gd®<׿›eÄqGõ¡ýðÏc˜§·0Ìúv¹³[d†}­›ô�ôC § ¿@Ü’û3ßøOd¾g¨ùV:Of¾¡´þ& ëþnøóCr–Æâ–—Ì0eN`c4cQbçrº0¼Ôçß©¸íÞ¾l§Êþ¯Û§ƒÍ8@Y!ñ}… ©Z†#¯,cJøÏäXHð ŠI£< šêå”(ŒÚ@V§ã™ë)§�µ¹¶¡ƒ+<²}Üj÷AKN¾7Òß=–ýÝ�¢?/Íxè<†º|°ŽÕVíZßvÞjou·zP!iµŸª¢:îÿ€¨k�Š»ôýu]Ù -¶�瀞‰*î¿(:¸YEѳ$¥ëª¤tg® ÜBEÖùÃ{@`fÏÇ´Ä[Þ×<ü¼+÷†«‹ �5¿�->W9cu#Š¿9…=l8Éx~Ë[VLÀÿ/#ÖÆäc—Gxœ­SMOÛ@!pRT¥âcHLH,×DÚ4B¡6í©åˆåd7JËvâu[ ¥pìØ?ƒcoåΑ+¿€?�½›€“Ɖ+º—ÏÇ›yã·—?o_ŸM⦋]|^ÇrW¿ö^ÐûñéIÄœôn|�–&¦2©ºY1\„á½CPÙ² ¥VzœW=m¬}¢¿×≲£q¬U[–I4‚BO“©¹@¤¬WÝÀ½Iæ;£Ð™Ôªf»N X-¤Á¶ì¤ A¡- à�c -Ë�d…>ô ºÇ–âò,€ª‚ƒMR7±¡Ðq£;Àqý%7{Ê驨¨’$€_<HM'Ð?*|·Z Gñ²Tá›UGƒ÷H'èìz¬Ô‰è9Ì 8n翆i¡`ºÿ¹)³k‹^K¯.¶c�lo Lš™Y>-•²y¡ÑgÀGë¤Ê�f°T—šg¥†u¤Ù¤ í0K1ñÂCÿˆ¿ŽÏCÌ à?µžÔšþ‘=ú+aôG5}&2+_¶{ÚLî‡(èI³Ã|4L?'ÿS?üÊ…üA¿wŸ@²ùLÄT¶öû‹ðóN¢ÈÛ{Û»nÝ@ sjð¦È©v�ß‘�j7óãH]gÿZzj."åg/Ž5z;bqmAP%(`ÏD`U¡“«Tà<¾=áËñLÛë¶Þxœí]sÛ6òÝ¿ÉÍä$E–,Ú}‰ëLÝÔi=ç‘ï,§�N§£P$dqJ‘*IYqÿ÷Û@$À/YTÜ™êÁ–ÈÝÅb±_X,9쑳]}Hox0ìõH�|7w\Jl×uÂh`ÁvÑòW�s·ˆð×»ø1ŽŒcò£ïßÊÕÕ;}åXÔ ©MÖžM-(9_™üwúäg„ŽïcpD:ðRÜzÙ=Eþš,ÍâùY‡h8!aœÑO]EÄñˆå/W®cz%'Z°q•ÒøUÐðg‘ à&ÁI.3L/¢h¾7›ÍÀdÜüànèr¸pxuùîb<¹8ŽÆÏ¥aHúçÚ `¶³b®€#ËœŸ®¹!~@Ì»€Â½ÈGŽ7�9Þ]Ÿ„þ<Ú˜E26:pfë(#°˜?˜¶ "3=òò|B.'/É÷ç“ËI‰üryûÓõ‡[òËùÍÍùøöòbB®oÈ»ëñ—·—×cøõžœ�%ÿ¹ÿÐ'ÄãÐO«gl:(Jj3¹M(Ͱ0÷9KáŠZÎܱ`jÞÝÚ¼£äο§�3"+,�—4m$ã:K'2#vI™4<8ø—ãYîÚ¦ä[×™ÍÌ�Í0¤A4X¼Õܳé¼à×Ví½ˆæï„‘=ó}W¹xí D®-‚0 rž=uÂÎ,œòA¦žoÓiDz¶Ë¾­¢ Oî}Ç&½µ�¿@v•Ïgj�›zÖC¿G$¥ÄØÿ 2��à«…éQ[ÁS9ôƒ6ì4 }?„å�¦°2Ó% îhç€ÀGËBŸÝ �¿�6'ßùUÇ‹H§‡vº;�{ÝBžúD§×•¦( Ÿç·�K:—¸ÌÜb)½·#^Á-‚›%ŸVÀàûµgq�ÞÒÍ"ÁÃ]}� X*I€p¡TÉ>Ëk¬ùXþÚ‹Ns"âJ>½Xë@Hø;þIÎH<ÒáÛ5m“Ð…ûGüçf�½3þpuE^œ%º„ó—#=|ëÑOQJ4!üú5¿ðÈþ4Z^<•ÇÝË�Yh*º\Eå¶-Ä.ãÓ>Ó «Û ¿Ì/¤œ­Öáb:3­?:z7 1”d�ÄL¾ŸLÏ'“‹›Ûd.éAd¹s©�—³+É™§:‘ b¸›×ŽÕÂ,?Ó‘WÙ‘Õ0�Ñ!§C•–tYBÅPá u!   H›4ƒÄw3ƒÔä…ÙžTtø^ô7ÑÑDä5u4Ñéz:“Ïp®ÑQIÑŠ<±^2ê]ª£Zõy–:º{¿¯èœ¤±þªÄ§Æ:Y?¦&j¦õU%jV$å\!„•ƒfM‡Zﳞ°t�Re;mèö+œ«4©b]Ôi™”‚”YIvˆŒ,2” i§‹ÛF:¢¨%Éêe‰#Ý•bÖðzE~’bJTê&¢¥>&Ÿ`Ö÷õµ¡b¦6S¡˜’qÉrËi¦d!{VÍ\XèÒ¿§»é),äÝXä ;)¡­{ª¨¬&hÁK)”²ú*;ÚlQ¡£Ú¢&ciAËŠ\xÉÄbv´)†B'¯»Z#Òª6¿Ý¾ƒ®a]‡ñ0£s?¨®©¨šÐ9 ¨gÑiRK*€ôèfº¥¨ƒt•„7[hÊ ¦d¼ '*I½!iSyÉBåqò! £ efME")´Dº’Q¤2š§ÕÃÖLâK¥´%5W+êÙÕž.sP瓬`¸äùG®›Y+x’aÈj‘ð‚É œê@æ2€Ésè~~,U–ŠôP¦œÏ ô÷jíúµŽ2K/‹P9ÅökV‰·Õ¹ÂÚU‘[’JÏÙêV ŠtÉæEA½O$Tt)éÏ=eÇ9kJå×éÍמÕmr&À±3—„¼ðp¥”RÅ&^æ§itÌß”E¬ÏÐ~qfy‚ÛØÒ3ëÄmNZõ}_Ú”šÖ¢æª1ì�®Ú– –+xË&  š1mk@숦«™®wår§ l¡UÂÉ‚·XåË9!Óuÿ±>n}/*ÍonB‚­±¼(XÓ}D Ó{øg±jºJ¾&ÊR‰%lßKâAjå¦d'Ǥb�j:ãG—ƒ²SË$]쉥XX�&\ù–é’%�¾½Õl{ç°Û÷ Á‰4Öw!À\JìÅ׺Ž`=~rAòÌ#.|×f%—�·šm×OUв!§iÝëÕ+=„ä6Z²‚ËåÊ¥Kˆ!a‘–`øeîéclœy�äž¿ñXçÏ ‰YϸWL*D�ÈÛ³tŒ´òGˆ r�(èáÄÁÎPÑŒ>ÙP²0ïá·ïþEÜ›�˜!ö’0 ›”¹bÃøåÏ�}Â[^ñûifÞØ’†ßÓÆ&­N•HN%Jߦ3%Cbœ’ׯ“›]‰ÁßüÆN0›­$%®x M¨œôS8©€Å‰Uœ»rqn$„Qc, ¡'®yœÌ â€—q1(9l«•—¾Â¡ú©øsÉg-Œ“¾´l‡…ôxõOª�ê›W1eM>¼s?2á=ŽÜ‡3ú·\~dù×ǤÍò#öS+âÝÍ3îtÀugËm϶µS©äD|Þ¶dá¹zMÌ¡”4ƒÏ�ï&Lêφч¡3Jùî¨ûZ2ùÌN×Å÷Šs/o©ÒSÿx nU†×˜rÂnW“ôå$,Ž#z Wy¯™LÉZóÛÒîð–‚Ê>õ©¶9”[¸%úIû:Æþ¯”ÆïVÒ}Ñ -=+o†Ç�'›ã¥'F §çl…×q·ÆœÚ‘l1M×ÝÉ{hŽÄŠ0[aM!A·ýåkÑÀ, óô†;]ÒØŽ;‰È,‹[MÉÆþ›ÜFáJ“e?xq$eCÅÝ�ø{$vIøÝ�¾ 9RzÁÆ�D—QQr�@£.àqœqÇ+ûóÅÍåû_§ÿëÄ¢àŒŽñî�ëPZ¬@‹w&gµ0 iÀÌä«sCÖžãq~H£æ�ÇÒ�B8•xœµ|Ú(­P·Æàܦ³¼7D“ê/µX6vƲ±ËFc–G;cy´Ë£A¾8^†Çø¤{žšæÆÑâ’@‚Ö^ù5ï|y{_þ«ÂýçýÏÀÿræwY²Àk Gÿ¸à¯é‚ç /Ñþ½Ãv>x{ž÷æ„•P·=Ï»ˆu/ܰ RË¡îÈŸòï'²oÍÉìöæÃE΂ӣ'ákºÒñýs�ÇUo˜+h?(ÓIþ4"—áæ\–¦õ€�U%Ê97ôD2Ç»!sRM¦RQO”öŽª×ñ÷çWyÁRTÞÉÜ·t±SÂB—uâ�û›X„&TÈtÏ®¥t£ÜJÎ¥ªí#Öyרg&‰�ú½s|<é⊺uä®óMµ¶:D£ÂØ \ÕΚs=j‡klcå‡(æ�éx}²\ƒÃ60 \ÊŶháxwùü÷ÙÌ DîjU¤9×F;\—o`�;7Ý ÖÚó Ejúl©-%~çö™àË�õy±ãKl�ú. óì3 òy&ëüËR?óÞÏFçë;Ìôn¯:UKI2.EÈKØæÞ¨é¦+y41‰ » atóÔ¤)uŠ€Ì`ž0ó±jK5¨M³Ù¯)Æ:®î©b|nRl@Âhi{±…ÇŒûd·É¼P¦»€Ÿ‡O§Æ&?L§¥­ÓÅ�qõ:Ç‹z]q•¼>##!ˆøÄ¥Œ½Æ}DmzH¼ŠÝˆú�¿Ž,)ú‚s´–3 *.‚zÜzA?®ïÜ2̵ÅÛ¨oÆ>y3dÞZ²ÃŸ¨»¢ÁÂúrâ÷èš®ë[&öÍæôœë[¹& o “¥Ø³{ÇÌÛcrPÚÉ\¶y³öBÍJð L6Ç>¡^¸ð‰ßx|oºøPœeÑ0¬´Þâ^>� KF˹Æz4E‡i üû–|ƒ}óNù³ÕiN—>Ý4_ßbj“>°ˆ‹­[T3k®,BWn�-Ñóœ¼+5õ›âÊVë˜ò*©1|Å¢*Ø;�0kiÒ%õ®«O걕³nLÒZ*MÏ:°–žÊy| b# ®¿8#FyÜŠynÒ�Z®êZ·%ørœ‹O¨»¡‡ê®;r)«übÔjîG9l“À´'ÞFMx‹“$G ýâú‰¼Mx=~6¼~ 7�{ÿ›ôù{cx6ã©À�x_Ç�œU;QÉhÒ�þUH‰6yYmòöíAZc®RìížÆj d¬¶ä@ÐÖHîÁÑA`:Ö›É "Îg!w]Sá2T~��q˺×Ñ„D‚‰¤¯Vʶ9¢ÖÚ’÷+§ îÏÉ€±Õé?NŠò_?tpŸŸ`¿Ó凧ØÏ<({Ídé Û•Ð�Ï?õ�8¼vÂÖr¥Lì.ÙS]Ù2@(®+ɇ…+ÅÝs½Ï×ß]�oÏ/Ç7Óë÷ÒëE2TÒ‹÷@B7«‡ʇÏΪñøø  Lðð-naöEÞˆr(BŠxŠOBz[ŒãÈÁè¨<!—…1¨ðá¦ê T)e”È÷s#’'~ÁˆŸýÀVüÆ1ƒÂOƒÑ›60®Ã®Âˆ8@˜.‰’9\nSÆ4ó@@ŠCf»s×FÖüEj-,‚KùÈ„,Z¹1©4F€‘ñÜ•›X oªðE u÷ö¾s}ÛKÿbQä�å‰Ò4·ýÄ+5ù++±—¥Æ$Ö<÷üÜŠY—›pm�,ê;PÒiâzõ� ‹“JÏ‚ùå6仄(Nœó ð*�nPiB’å¶%\j»Žª•T[“ë�•O}4 ‡ÚŠÆ†" s¡Ý¢Cv–�= Tà ³=+Ê´÷MAלƒÃæ®�ækšžë§W¦¹GèÑ”\?ó<6IUæÚò.Lòjô cð±qùNgÓþh­–Õ®k=Ç�ñóbv/¢Ùk=mÙ)–ðw.Ô¿ ƒˆ£¿àðgŸŸiJzzr ¶Ä 5Ú¼»žÄÔ}4PÚ2‰7ÒÇ·oUêæñÃ%)lçx¡t}SÔ Uæ?Làø0yøÀ‘Ñll²Å:jL!R.Ú]¢…6±6"Å ¯šr¸l-^`ú¹k´¨‹Ü¥Ôp¡3öä–ˆÁ¡"¾�ÏSv–°Œ/y¼ïWÃR¯�²Jjúû~É-Áý3ßÍs…8ð.¢•,«ÎO­å\!›áð)f¦éï|3ðH��øôsoöÎ|ßûõKañ¸8&¹øÏŠ’˜ŽrF¾4þì64u¨x–JÓq#Šj] eù—FãÝø}¿¡H˜Ö� ‡”·©ST²µš.Ô›‘þÌin-ÊZC¥G² à¨ìMôdÑEüB”‹ûŽ¡I­ Rlm8lëLiäv§ã"wÝÆ·L—,ÂsxQ¦ƒ"wi÷—ʲ¦‡ŸùpÁþ¨÷¾?�‡ãÑۣʨÌH�zç�ö–ƒ9†§ºÅ~QTP¿¿zÜÞ ««7ê¿Àÿ~ \ÿnNx[.»Ö¹ef•a— ¢ÆK¦¾Ã$¢Zß®3ÊᓬŒæžÔšÙv󪘕nµQ€ú¿‡Ä‚¶��*¸HC“G|§T�QL}E?¥[«Àa;…@ãˆK�6O�æ<®5h1¼†¸pÝ[ü@>%ü d›´£F&¹T4IJ‰ë§,K0éévá4H}â€ûTîB ðҤϘ#ª{ö’Ù@՗ქu‰SÒÑ–=¤5IÄÍ"ô”èËŽ©L·v©`zÉ"w~Í(ˆRµÊN€Êü?b÷¨º$u•¥ÚðTÎi–£³Èõ²ÞNœ˜„žÂÔœ¾Ÿ5‰¨¡7ÕÅ÷ÍLª£nöª¿mV’T(�«2%SVÕ•…‹?‚wRI/·ØÕß� ÕͰ¥0Lé_Ò} ‹ ™†6–�Ë­µ,í‘&õGc*N¡Ëñw‡ãOeÄrŸûñgLR÷ƒšZHK%nÎ-Le´À‰öÚª²!ŸN€y69»rã¤=LbWa·ÛD˜Åà6ýNL^ƒµ°\¿£Ä1ÂTð`Œ@¨r4~' U¹)»‚4ž*�f \‘Ñ¥¤6³‰F M2ÜkˆžD—å¥U#Ýj �o½22ÝØ¨ßCrû5)i DÉ犚ò´J¡Aj©ÑRu¾jÔ·Ø|�Ýk¥3BÚ;G°ás»‘oޏ… Õ]EJÔ-$AÆQ°$Èì€ñ,^è¤r\ß=Rã¸óèÖD‡íLiâÒƒtYær¸||ûæ· ¡¹C>�= ‹üjŠÄÅÝ.Ôó«¥ÁhÙ–ç�[öESs§vé~÷õÀu ÕsÅKêTRô–˜Æ³žO/¨€ù êi5‹íœ{ÓMVpôÞ$Μ‘~ãŬ\™œV¡[oþüîãtðº7,Zcðcýu ¼ÜäÐÙÂ;nMe“¦˜ƒþªzïM[+]ºL¥æ¾Ñ­¥>pEŠàBfÅÑ ðm±SÛzI·Ñ†Ì~ m*!7™ x~¤¦vF÷z6ßI°1:7~f'AäR­|¼r¾K³o~¦PrZ1ƒ¬SK•Ø:H=ÜØ­ ¦Ò€x¬! !Y»6ÓSÑíÁ atýy@lðÅàéSM˜[÷O{gÃÙ]¦L‡w}º)'W%HwðøJÙ �`ˆÅ…{Éürþýä¸ðß2ˆŠL(¤…'Õàû¤.ø–v¤³l¹ÑSû�îV-² âb»š?9%•As–²[qC�W™RìÅÓz³Vœ›dÅ.^¢gÙ¶8+!@^¾ÚºooºýªÝÂëŠn@–ŠSÜ“• �£Á¥.jÓ�Kxz 2¤×¥Š…Û�Åï‚ÐÆ£Š&4ž *äŠldué,–lØíª¤ÛŠi›¥ PAIèYÎ¥nåR4o‹?ñì‹›ÀWž`¤RŇª¢ ¸ië*B—³*•©Bìr¼x« ʤ-Ge¯•aŠ” ØySex.a98o(†ò°*b´ÌHù~GáÝßëìQ†ªË:fIYÔØ„bÆÍ`ÖŸÎÌi&ü6BãCU9BÍ{ޏƒ{i){˜Å÷P)+h]–ØÝN‡ÿ Ìní4´íT²ÿK28ýhNg“þ ¹Õϵ‰¡VŠ�»ËÆ ÿa)Ûèý`•fiSB<@fW5Í[¥ögw+œ‘FYØaø8”»Ѳy”ù¬RQu�\7v°’Mük¦ñ¨Âز1H ”ŽßYPU¢FqšÝjÄ…§)†ašÃÁ«W½iŽÎ~mÊòä OSϺaØÝ˜yógxðÝ áù)ºˆ!ð½kúÀvHéI'3CŽî�à5º–�iNĪo¥Ú›íPÍžŲ]vEÁ+Ÿww5 Pߨ¼>¹Ío¶ÜÑ#¿åêÊ|+æ{ßhe ˆŽ_¯†?mƒµÕ½�PÛôƒÄäuìe³ ZáPlÙ˜¡€FqéÒ ÒDø°¢EçáÕxw+{ÍMúoïƒDäÜ\p%·®:(î )Š8÷aGgáÍ`ò·Ûy¶åÕ5ÑV¿IÉ'±U(6e·ºç��Œšã‰9éÏÎ&£ Ëúõ‡{iꆵÃo)�â—AÎK8u#|µ²k¢õÅ©m3þ' U³ÉY_Yxí�è<üçáaSA2e('_Ëò¨Pw ¸u81/¸)%´¶€2·\¯Çio8½�hUºê‘ÇzÞ¾]1�æc[Õmǯº}ü{Ú/7(SÅæج§±I5D0åZ�—\ù×'NÎ�Æ~ܵ6§®ÛÍîßáÜ>%•Qª‡ð�e¹Çñ!‡ÿ@sÌÒ(ùÝã}õS·dê{®aäòài{Ó¥³}/Z¤+º}Æ�õß›ƒYRíóì¨Ï¯ï”Üq³Ó<‘¥«s̃9/ö‰òõeŸCÙAê'Gù輘��«ñ•#~à¥áïÝ( ¢X^ªPÑÆ´.¡çR¨áw®Ï#/TÈpt]-Vð ìw…'NC©S4¬(²®;|:E8®�oÙ߉w�`_gÅNþ•—få*­“ß²i‚66_®•½Ð¦”Pë×Ý�Õ«³…ÿeœ®|;‘uœpUÒŠü“æ`n”åׄ.d]â¢MûÄ¥î&Iú//guÁŠVt_êëô/†¥gžö—Ü7ëä†êï_…hAt¡FWV²Œ>çß4‰7Ü é#ìú¢zq•¶éVAgJ¿~þ\ïÕª×ü¦”äçõC9v,�»‹}cÓj�W÷u‘Fk§Õhì�y3u\FfÓëIìÇ Æ¥ç²0$û=v�v²"t Yt|ºôŠø¡³€Á½ÈGޝ'r¼™IB]Ñ€!Û £À™Ä‘¢°”?[•Q�ÔºCÒÖÈÛî°?4‘È�ýÑ÷—#òcw0èž�ú½!¹�“‹ówýQÿâ¾�’îùÏä?ýów&a .˜‡]/”ØtP•Ìæz2¦°0õKá’YÎÔ±@4oÓx‰ÿ…HD–,X8!š4m$ã: '¢*É…µvvþåx–ÛŒ¼t¼(Z-YØœ¿–F]g2¡!kÑ0dA¤¿g³©þFâÃÚ{®?«¼1¾ ÀŽà zˆˆ…œD΂©w4š«#adO|ß- �âXŠÅ1œ‚�äûîÇÞø¤Û\H`uŸc2Ïv¦„´Z2 D5d òŽY. cÜ)K`š }/»S—ÌÁÖàëS”Ì@ksÇ (}Ê‚&B£I‘u&¶"òç� )|ˆ'(.DŒe� :1Ìe æE!ÇDÀI86GD½–ý8#÷ãœq�þdÓˆŽ—Qð Ç¿òDX¾ €™Ú'AàS>Ø„h¯õÀÕíã�¯'QÀ8:°(@·èâ…ÙrXI´Ä g"ÄP± I¸ŽÇôØ�ìó4ð €QÒT#ùZ¨ï�œ;òv8†Œ0êöÏ{ƒñÅ©‘šÚÍDÏ�’¿ê¼�»Â®Ž™ÚæBE4IP/ti$rá z-�y¡,½îîÒcA’[^‹q'Lî’W„s2>½üÐ�»ƒ÷o�:Çëc¡w½ôäc>ãÛ·Œ‡$ÈÁ}]HP1cþÅ (Έ½Ð™¡G_9v47Iö}Îp1N�ƒ�:H! P“‘Ž6ê�áÀŸA›®ë[Æ¡Iž@²æ$q¤Dd� �ó˳3ÔZJ«ž:Þç_ã}0mQ†qì%ñ$ü¯D1³™~Íæ+³"ùw’QðšÂ:k°¾BN�,ÒÇUqˆóžáðéu‘'ñqœAkh¼Nd…@d ordæ¤x¨=ª_I6Ùàa¦¢“uW†¸t®ÁcXMǘØn@!õ×ÜÿÖÛ°l@¢:z63x|èüÁü©¡£T×8\‘¶6JF¹³)"ë•PéñzX¡f6@t h…‘ øRzH?Vaä<¥QDzïîUMð ®üÅwlÉl(ÆelXo÷ÛÅ<úÉÚ˜ßÛË(rúæ•>GŒRJm[!•ò‡’ -<Dþ©"] ãY.£A•é̤È–3Ëwý ¼ÈqŸ¾›„ƒbä¶5ÂXžâV �ö1üy©è*‰<²¿¿’qñÊfÌ=�~YÁ *ß(ÄãoÇ =¨N. �[ØÏž@ª ±�Z°EÈ"¬]-JŽ®!Úˆ7NÐôA÷ë"LÈÜiS!$°ŒŒ?“´³—*ª^`�§#É_¾êÌ¿›ÎÔм*ˤÂ_ÍqÏÀàwê’»ìï6w”’Ãõýe!ia&Kî¯])EH(xa`�Ë JýîpØŒ õ~¢$^Ñ©ª.Q�Ÿ®QYñXQ$!9§UŠÛµŽ¦Ÿ¿älÊèlE¼ÌáÅ¢÷!«=Peg)é zšhyì±Èa†©OÖr… "^b#•åˆ<#CåÕTTò.M¶Ð:N›+H~) µh¹ K©òÉ£Ru˜ý}¥¡;|ÔS¨‘5=¶6Z3]ð»×ºÁ•ÜÇ\¼Y¤‡c«Â˜¨ÛÕ1µ; ©çD+bÍ™õ-ßû6[£Xè´�øq:¶ØüB]T ßÒ ›Ù «Ï*/…ù¯É_)�©É èJÕg$ ] ›ÓŠ–“ƒ¨TÓº'ÕÃkGÇŠÚƒIHIlgÁ<¾•)äM=râýs£‚ËÅê…+AÕkí 1¯˜(m@²^ jÚà@q¬-'Îp…¥>¢OÝiJK3·Ú,T&îŠD­�¸A  X‘}²ªo‘MNØCJß²0Û/†ðÓdÞ ‹€qcJ³Êù8W¥ªXMú•WãÇÈv4˜MžÃ5ŽüñÔõiRªÊ<"îÆI#€6;KZÉ>÷ðJ£7f¥Aê.çJ‹Ÿ̽ÿ° %­a¤³“=Ò¾žNÛpÕÉë×ðå>´HçéÓf;QZÎÁz2í6JȃVcHâdÔŽ`dØ{ç£þyïÌ€•ó+äXUe¸Yõ…ç~^eT†½‘ˆð„¯$šZ-×�hp /°g15‡�#“Õ‹»~É!0@1ùcœNû¨ý¤]d;:²OLò$]ôRý}ì ú§?�{ÿ•ö ºjh 0$kâ©ÐÃìù§î @fÁ­ø‘„ºõäp³]߆ÈO©~¶å~¨áÏVÔÖáJ*k7;½ @%‹K7æ8"æ¹�,p¦Ã77húêAaYZÎ06§_°å |èºÜ•'[¦yr§ ñöš�ð Ö ¶›m6Ù,Ûsß,Ùõ4¹n)"lº��p19à6‚r@ETXNQ.þnœ�»îäŠ}02Ŧ„¿¯H‰©ï€y~<›ãA›˜5·Ó“Õ~ΣVÖV²zVGƒËžÌ,;8"¯É”NB#¶ÚÍïžvþ]âkpg÷)¤�›ãN8nçé!Ç­x¹èÁ´Êªb1Ó-:<ÉÊO nÐ<ÁoÅzu®¸šÔ$Å\넟ovBÒ&tŠ„¨Öê:ðWüšwHÖùb$7&ÕO׸ë+ÿ(Z  �¦ÞÌe2N›ÿ”4ˆœ•*?——jáÜ�]›` æg¡¡�•GÒÕz�-¤–RÌMįjæ´kÕžœß¦¨¿� kÊ1¡.Ì3�¶>¾ßŸþôöòT|Ù=Ž?œ¿—TÍ Ôpsª¥<ÀäÏ<6ªomo÷–yÖ|AƒÏwjðJ½Ý$%›¿–°¡Ç*`Œ=ß›¸Ôú|3Ôä ¸åÅ®`’ ŒO¸h(^$í4Ÿ†„Qk^jÍpóíÙ“±„9¶cñö/øAç)?I”¼¸šió}^Θ®ÏËîÈ®uÀU„�Œª½R'V�ž*¹L!Óÿ¤@é*<þš Ý¡‘˲¡�Ëw¸L-úCtt7uöŠL’?ñ,d“v±ïž=¯x�®°·–jæ´Û?“òFºÇ´†ü†çú™óÇ!³£øÉH°²~ñÈU¼�Çðfr8�cîë¢çµD¯®}¯4ËŸÉ’#1›ÏˆGÚËœ¥£Pᇼh¾[“¶pe/³¾ »Í#†¯Ü´�âÙÅ'Íñ³'µ8¬Øž6l?ž¸¬.)£APÕðtMZ‰2X½=«›œ±ué;ߟ{D÷]ŸyÿñãûðãëTÏñŒë?ý ]UT¹ñámùð1\YËrç¶,w†e½á7?¶ùÇ(÷•T/&Ñqý�1 Ó«!z?A¹67õ_‰“¤!‹ÿá‡gãÿP#@›Ö-» ÙÿÄÓ¬m°Óxœå[{sÛ¸ÿߟb£Ll=YVÜ´Çž:>'ÕÔuRɹôæ.#S$qB‘, ÅÒÝù»wI¤(9¶/픓Øûøí ÞoÂñC=ÐÜßÙo6w  �¸ƒéd9ZL†ËpÞv°Ut8A¸ŠÜéŒÓ·³ä t;Ýð.¦8íââL�¾pæÇl Ì"à3§¡íà/ÕcÁ�,ŠÝÀ‡n»uPS]µÆ‘X ˜Û+ð‹˜! 7![:,äàúàóÐsmßapãò™à£¨´‰ÆOŠF0â6·�”€`¢›+¡gœ‡ñ«ýý›››¶-¤mÑtß“ãâý‹ÞÙùåàü9J¬f|ô=DZ/ܵ­ÀQ"Ç¡œž}Aö4bØÇ’ø&r¹ëO-ˆƒ ¿±#FdÆnÌ#w´à9ÀùPm}BfûP;@oPƒ7§ƒÞÀ""ŸzW{ÿñ >�öû§—W½ó¼ïÃÙûËzW½÷—øí-œ^þï]þ`C¸�[†i€bº% ÜŒåD˜R¤8dŽ;qTÍŸ.ì)zJð•E>j!‹ænL&�QÀ1‘ñܹËm.š z£ý��§®ïx‹1ƒ×_…¬=;Ñš\ŸScœoõÜÑÈŽÙ¾Ç,âkú¾za/ï”î]Õ'\¿´ß ¦åh¡w½òNÎbCИ�GAàݠЄD̶§6 [Lðs<;RHß+ P€Ë`,mó€8Á.xAð…>-BŒ!/ˆ0£`.ìù¯ÿhÓT²Ô˜M€€p8ü¶ø¹+4†ñ˜‰aÔ:ЇÊ@CjrHúz”ÍDòÎ 5s8f äfs@®¯Ò�%¥J#¡ôi†î’yCCe.óú%¦'Ïfna¤-i(f§l~ìN}ŒÄ£è³h(fd\΂Ã, ü1a(¤¶(œöß½ù >môø] �Œ6M:Ú¹%ì2ŠfâÑÎNLáæy Í‘áj8¶¹]OL :94Õ'DÆÊ�l6iÍÈ:RÝÇèÐÃeYãªQ-Gˆ±áŒÙ˜†šRÊã ërÅXë,aݼqÇ|VÖ1c´$•õHŸ-íÉÛŒ† å—¸£µ¾‰©ihŠßEþ{ËÀ¢)ížB¡c[Á¬`ú´Ó‰˜Í•¸ë„Èôÿ¸ã¢¥ÄÈÞQ°ª¯‰éTçŒ VAÏ™‡º3TPÔqB6¿°U�̉çË0ˆhÁœ3> Æß˜UN„çõÁ\ìhû )¹Ê`- ÅÆÎoyû‰€°@º¿¥´UžŽÜ ÔŸl’iöÌåØUìv~» Ã]Ó‘j 'b|ùpùñâBJr»S•ˆàXëQ^›Ó³‘é³6Óiy òJtð_™xÙŒDȼŒ‰Ëgã”9õn'¼ÛƒºôþÆ·ºâ£8¡*ø/{L-ãrMWe¦sD «c¸Ö¡»¦BV,ŠAìRÁAD®årpm©O«kŠô®3˜®ÛðÉõ-Vz�¨{\ò�ƒ˜q 38æÏβ£ž²XÏôŽ—b”kg{æ£jTÌJ–ù¬Êǖʺ6Ü[© Q¬"Xù]²Qyè}…¬-â4Ȳ¨ìk¯«¥ÿŽ…TNgåOìØ>Î~µ½‹U©•oÄ`�©e’9’©Ô0ã$ÿ´d“éšF é`ÆŒfxŠ‚%Äœ4Åüòãåû¬ŽR¢?ÃBs! ˜ýÿ¥Ö(fFC·-ª.ô�BskI3ñpÇ9·¹3ƒ]°c²$¢µÂ>eΚ)cm£VwTC�4¹Ð=N˜ÑžUEšÚ¿Ë¼Fbªýw>™\'[燙-ã!AÌàuvÌ~õ×£÷B˜RÅ;…ì&B'�ÖúÍð7ž‹UæýNGU„ââ0°}—¯ps¾¼‚�xaóNDe=fã‡Yà�³ýO¶á[[E̪]2¾[ Q( ç¾=µ¦×²B¥Ìœ'â%ià£-¥Š(ãé`pÞ¿*®Š†â¸Ë*_„çlî„«Â|³7t(¤§F¶½š1ß‚ø‹ŠÃ!p¹ØŽŠÏ↉7'73—3\ìµÍ3€VA`íÌÓ�ÅÄzóNfü”rñv…°'dW=`¶ÙM¢è“;ìîÂzÁò úæýŒüÌ0mÃ':¸òVˆYH§ß°çìɤ'HÕ2'§L²&ÏJ,>úŠlºÎó¯ðîoqæÓ©íÞß)µ‡ÇÿJĘȉmèMP´½jt<‰«Ê9gX”fï�óÌÌ  2L/>½ú0Ã%¡wØX”�Ô²´0QÍ6Y¨½‰ÊBvdm£—ˆ»{O÷„è(ª ýwo ãf2›¢8W1v¥Ün{þ‚ÒýÂçyù”-ÜXŒ0ôþùkcmêgÔ¾ÕÒò'!/É�µÞÍ›»ËEƒ§ýþ»woL-_Á³¸|g¦80jò¢+kÈÓCïv_".|ʪ&¯É]—7tp>”ÃëE¾ì¦,8xYØÏ®“Dg–RÀå­³œˆg3tòèBÎÃ=½6G¯ž²WHãYíC¿·|yXŠ^ÊnKÐò¨�=ÿw!¾ÜÃíl �m²ó�€2‹t†õ–Ï ÿ5Þ#¾âU•Ò]ÎÀ±=/p²� z"Ez0©8 h”Ý®?´Üf¢¤@æÏ~]”l9¶I3aá¥ä†Ò&ã ¬ª©V¸@í'%÷?",¨4.ÍÒ±¼SÔÌ·N�:[Þ\¨º´ð !’ÂVyd–)žs”Âq~¥£LPæM¸fÇÍmš¸„AãQLµùNÈ–×AªŸ²Ë"ðǘX¹=m›Ò2ïNï0êÚž·¡„/·!,ÚÍ„�¸?º–x� ƒêf‚~Yˆ(êG„’h£w…¢O¿dNA ·� .ʬŸ!"S-1iÆ,§�ŠTêT’v‹kwy«¦}ñÏŸ1F¤»ü&Ê ÌÇšž˜†MÕáÖ*Žd󃱡04Ó.›µdƒEåa‰È•?1toQó¼jtvÍËôÂÔ ×—«óÁÕpp~%—v%B6EX‡ŠvMžÑŠ®ä4_ìօ.¼€µ^×ÐWAnoä÷6~J5Üd’4µ’¦N'ÕÊ:k·G�´llåÛW'»Ë*°ÑâüçîgU«ŠAíb6ÞÕGw>iƒ‹kmWKLùãy¿÷ö§áUÿãyV­%’gyc«¼]ÁÔ‚.ºÄrE&9è¾8üÓËZRFLÏÿYO9‰Ò\·áµ·U3÷–{V98ˆÆ6óWkç|nü¡xÉÍÕÖxÉçÿ/{”d…mÀÒ²ôy[(k߬ÑXð?ܬ·§ƒ‡E‹oëá9Ú#ç;ðLôwaúòÏ<ÛåR-F�µ‹Ü¢>-t¯{ý=æ¼h˜—3�ÙXj$ו»“H²;ª)é\�fÑ1ÎAc ³\$"ìÚuuÍ6ÏËKÕ›SÂFBE¨«=ˆnÛ.᲎ݬã�v:›u}8;vT´ƒ- ncÏnªcñV~v%ÿQ“Á†½gE6H$/É ¥ý‘ Õr/sʽ`þOÄ-LÅñ‰<èÑO’ª ÒMi%w��îãüŒYÈð÷‰ã›ÐáŽ7Ó òm~c„Œ`,'â¡3]rIa)(vqªÌð ÑCoÜ€Ãθ7Öä¬7ùmx:�³ÎhÔLzÝ1 Gp4÷&½áŸN 38‡ß{ƒc ª ×a·AH ›©’YBocÆ$l?f) ˜éØŽ‰¢y³¥1c0ó¯Yè¡D°páDdÒ´Æu7¸ U䢅Zkkÿr<Ó]Z >²0ôüæüS�d›we’ëL§FÄZF±�׿#¿­}ùæÕŠ7<\rgÅZœE••P²H¦EÜ2ÂY…6õ}·Btü +ÓBÔk‰v!«¯R¹³`5Ô»€•ج\zè^Ñpûc8�cƒ?V„y§‘ŽÖÐ#†nâð;�ƒ¸Jd8€Ã±Žž;è >ﯭ‘ˆèe&:sn„bVã ]¼»Ä‰ßÖèUã¸{xú¹¡A£78Òß�n»£ÑpD7'�I§ßxÈWp¼„¡ä\ÙÂîÔ±:Y%Ðq×ØÎ­R||®<Ä蕨ñdé™±óÿ´pë¥.$§L�àx×)°JâE ‘4MbikP{U œÞªk±iHÇB¥~À<¥‚ CýhÔíLàïÎFÃAÿ\ƒ±Þ;;��†w#¼£œ@X6(mø„xjbøœ]HXgÅ·ƒa…ëÆ‰�‹[1/ëQ¶z÷‰å¤² ‡t…Œ/ClÃ�XL}X“½Ë¶bzu¤·ñû ƒ=¼¼\ûŽ•2A©‰)«íYçE�!eè�¹ 9‚ëx8h¹xlÁcïh6›©#]º‹‘ªÆ �`?�{6äŠh�@¨’¦¯c)3™@æ:ç2Œ@ÍЙg)âùØK/8#î"‘&‘[À ‡IVn\QŠ52`¢Y.˜Ç›e#^ÿˆŸgÆ'íXoÈŒõ µ§0]Ú¸[ûÃÏú—ÎÿôÃÓ}Üû6aû2V{äüÅP€ÀÇ8 mŒ®å%LßõCÝà<¤•pP£‘ÌĢМCfmx íÛ÷v:LÌí)DêØƒ*ÌWvñ¡½hìÃ4dÆÕ>@«…¹1¼‚YˆE%ElúXØ4%8J@+Ðvß—Ðú¢†~.Éc+·÷?ì–0Ï™ëú7(ëÔwKX"<®FÚ.!Æ%>U¶uh"›®F{¿ÿ¶ˆxgsôM*`QËñroØÀj”ñ’)è—�üêÞ|m¿)fi*²@–KðcoÀ(jr úçÚp�_S âͧw¾mwÊk~­Áà´ßWU‰ˆŠ<]ýö°/¡.`ƒ/’Õ]ß4\H0M~�ûÐLbHìŸ=q0žØJžÇ–¶5›A£iùNk¬·ßZ[ëí�ø? ÿö²ÿšëí] 0·Ñ?b^�öÖ-¡(d²˜á­Oø÷Ž¡öqÏ}h·µ"}�ÖB²LCÕ‚Ìýe(�t<éõ�OT0ލJ¬ <à™Ð‚ív»­æ#d›çôrXÚß—ZÕ'Š“‹œíÔ<þ«Iœ©³×ãfBóDXkʼ—­ýýpÕ¤ š0dÀþÙãñ/9' Ðߘ°01Ý^Ì0ŸîÙmÀLÎ,Òé=ÖBLŠjX`‡þB”î‹LÔ7÷C‚º·­û&œ9® äBöZÒ›¡± ωº'5ï©ËDzºo¦-×À DLè’�f¬�2WqˆUx(ò¼–bnDTð—F£¸K‰uÁ_K‘óÕÅõË}¹(OLa\#KÔ5O4J…ƒo^é´±ÊY¢v(âjX-–ºBÈ‹Ë�tŽÂ$ Åråk#ÞôëVQèH`Y� N<+±ðª,|Ñí~…CÏ¥Ó&4ÑhVl´œ{aH…kn�xÈL,h©&±\6Œ¨ìK#È~‚´YUzÿ¤;žètG½“s}<uÿ«äÒÊzµMeã]¦õ|vÚK,ð]7÷2t©×÷©—é–Ýb1 Ê'R²k>±„ìU�ª’uý˜â²îµõ‚W©¨‰íV™C}Õú¡¢49õ.Ц@$ ©kžÎ¥×¢ùŸt¦2bt‘7ñ¿‰F'è« éwU…sxÐäÁ—Ž£žYò:uš£Î¸«�»ƒIoÐí+x¸¤ "óFç .1†”ø$! ÆÝIlyiÁŠ IãÿE«‘g/É¢Olt­Ò ¸ÑHnOM%«T§ êÏA´^£ù<£Eei‹û­,•|/ncß=¹YÃGSÌþU­ÿG÷x±.«½²H—�OôqW| ¤7†yµ ôüÓÇZ 5. ŠiH5ft±sYjèNÀ0ªGRHOmpÒéõWÄóÚ´/ÀÖµÚ ÁžèˆTÃsÊç«4 Q šºx‡I€x¾h_jñ¤á¾v™u+]"–×|<¿_EYÞ °ä¶RrM”Ù~…®–z %íc'­nѧL;—â7(Yó2)RÎÛAüê•?Îéz£½®“óIŸ”¼ŸŽÛM€ˆÒííNú‰ç©#‘¤j\è¤×Ç…´lÉT‘Zys¤ÈKik{!šùOêj;ÑÕn£„#9ò²oàáÕs˜õ´fw4èvŽŽºãDuI·&!–µ^ù0+©÷SÎ}¦‘¹xûò\ˆ/ÒÒ¥ß</¸�vß/ñq¯ÀA±O¼û‘ýSÕÛ»ÌzòÏ3ˆbº~Ä)xå¤íKµ6õP*)§œ¬7ÕÅjÜ·ãß¾ýø7(Ãþ è�¸®xœ340031QpöMÌNõÉ,.)Ö+©(a8Ì´÷ õYÝÚOni¾MØóÇ…=²!Deb^bNeUj‘^ÇóÂê“=[Ï6J8?³ÛÏztkUû9¨ªäü¼4½Ü† å‚B+öO¤øð[£Ôã¨ÎÕþ^@•¤¤&秤ê%3ljxк›O·¿)àsä„B‹êøÕm‡ jÒ‹ss‹ô*]]z4ETkŸÓAçWßåûz®™nd‡*Ê“4GjÏþ•§{«›¤_åÎû£öõàåw’P%‰EÅ «>”8¬•ðóåÄ¥QV|>–¹ìʹ=ÈJŠâ�/I­(ÑË`�x£™³Sàܬßís60i¦ðoXgñCˆ{l³»xœ­VmsÚFþ®_±£Ô 9ž¶3µëL&©&^š¡Æeé7–îÔ»S°Býß»wˆ¿w’òEÜêÙ·gowå×àüÿúAÍwüZÍ�¼ž³„á$)¾Ré%(3⥰Ð:S§¾¿¤JSåÅL/ò™Ç„?Oè�Ÿž“Ä÷J…×¡È Éâ…6§Öæ'Ç'?Â;!bôÒé´Jt‡…”+AÎ#*A/(43â£|S‡ß©TLp8ñŽ¡bnùÊ­ž…È!%p¡!Wm06zÒLãŠ4Ká!…%Foý”VLÜ0.mˆ™&'`’1ßÑeÐ[>–K�Øh=!c?Yã”ß Zíî ÝÀˆK�O¨R é_9“˜í¬’aD!™aœ Y‚�@bIñ�&â¥dšñ¸JÌõ’HjÌDLiÉf¹¾CØ&>L{€”nsÁÀ…7ÍA0¨#Ÿƒáo½Ñ>7ûýfw´ÐëC«×½†A¯‹§·ÐìŽá}н¨EºÐ½É¤ÉÃd†Jy›Û±Â\¬CR Ùœ…˜�sSˆÅ*9f•)S¦¤ ŒŒ™„¥LmEò2Ž|Ç9Z9Ï“<¢ðkÂf3¢¨_>½Å«½—9G"”ø>ŸÇ#È$‰Sr ×”fÎêÆ’¤)‘ÞÂud.5Eœk5ÕgVû-&d,xŽéhF‘¡�*øB’X2nꀅ" ûŠ”§4²,—ÉTy&ðg�3Na<žŽíþ4èCˆ¬àðoâÆ’ *p/ÏðŠ$O9žŽÍ)¡È¦ýÿP#QTW”ˆ°Çx�0B1¯ÔÖ¢jõ¾ÖZó– Ó:•cl.›òGS.­•l…@K^Û«iª� ÅŒ³§R¢@ÒLHýxîÍ–¹aO$nãøb ƒm¼š3©ôÔ0ˆ,m„ ¹#³ôž=®¹e{K|Ôá Okþ¡â‹M¹îZXkn0ç%è±Ê<ôy·RG·¦Z¸àÂPÎÖ½¤hhKö ›Á¶œÈ¬úŒèp±;1%xc&YÓ{ÂÍQ[9§Øö Æ5Å[£Ù—�x9Hžè= ãY¾Îï�‹b)I¶à¸äxù²©¨ã,ÕCÒ./Og8�®O¯&r¯6ŒõsÎßÃÕv•9Îj©n_Üs¼ô„åÂí‰ë‚"°^®ï»^í$n«3óJÜÄ­\þ9q¯þ®L&·j“jµ6qŽã(j¼2sߎƒDÄ1�¦xŽò €¦7Ú,ÊC?Iu.9 ßO?�zÃöÅt0ìÝwgèý’4¾67~™z?\½(ƒü>¯;wÁE7ÐÛgÃÎ¥[q¨¬ö”:›ýv×"«‡‘ýrõ„Í7ýf«m‘·OØÜ!뇑­Þ‡M ]g?Q©J8óO¡ï½®Õ|b;UCÁwP* µA"�RIô¸XY�%UT¹ä¥@h¤]·‡t>»íÔÊrÂrBWT¥¯QÀ-ckk+3‚¦i|ÞJô•΂|1Á,šLçñÔ#™Œ]—9×R“»ÕxE"_‘´œ7 4ðL#õ¬r"-­,³•Ú†kd‰4VËUm�Ò9H"›¯”/¡ÆÅ=øÆQŽGQ’Ec´HÆÑåW:˜…#ü‹½ ÐÏÑ<‹Óu[mÔ„ öª±¹C|Lè:üˆ’4G‹,ÂcÄ"˜EFÑ,Gq‚Féõl‡É(BïãüŠÀa£´`Œ7lŒt˜‡¸yˆ`(�¨ Q˜3¤¯ò|–=ôèýû÷­�`ÛJç—�¦´]öèøè°wÒïmcŒY�‹dešGÿYÄs<ÛáGÎ0F£pˆñœ†ïQ:Gáå<Âïò0~?�ó8¹ P–Nò÷á<‚aÆq–Ïãá"ׯñÃÓV`’… jôÑQ¿�^ô�ú ò¯£óïO/ÎÑ¿ÎÎNÎ�z}tz†OO^��žàÿ½F'oÐG'¯ara8чÙf€ÑŒ�”јЭE “”¢”Í¢Q<‰GxjÉå"¼ŒÐezÍ<#4‹æ×q,Í0‚cf_Çy˜“GּУ��¯âd4]Œ#ômœäùÇY”µ®^(O§ñpfÑ#öÛýcžåú«,Ótj=ŒSëÅ|6Ç3ÒŸM¦iH@àU‚Wz�¦áœMîV«–:@³“¤“½» §‹h¿ °�¢wéd’Eù;4Œ®âdŒÞÁÔ³|þ®Ã|5Ž&q¡—ýÁÏǽÁÁys ÇЀ·ÇÒ¡6þy»ÿ6Õ¶[›Íæcól�ã?E¿Mô5jò®››0w„€Îxôl6ÄIœ0"ábšgM2ò(Gäí8ÊFƒm‘߀ip“Æc´E¡”=J‘^²¸FÐrÏr, ƒD'�°Ö¬ �÷ã�(RИ<ùõ7´�þ @_ö:ôN.~l6Î狨 ÿÚ Ì—¯Ãio'ðÛ~ý&ʼ]OR¿^bìíû*Îxϧ‰·óédâë7è÷°j8é777>í1Qî}˜¥sP-×Q~•Ž—g&Êhû®~`@"ZLv`;ŒãQNåŠ>Åÿ%¿…DÕ¹ *‹6$Ù�4@ã�Ôº¡ÐøHø¡ø“úÇÔ¼çZ:’0ÑŸ’NðsϜⶳt‘“qš¢�˜w:üw„[Mæé5% 'Àf€ŒoT8›Lâ%͘1L$Ÿ§�Èí‰.ó(_Ì*Môé§ å9HàÞÆ§p^e/[؆”²™6 ÌÎæ#•ÕNéÁ,•ÏGó(Ì£æ¦díÉÅñ1¬AkNx�µo§b‹µhÊ8$ÈÌP ä@b$þ„�&±Y$óh"¹¼§ò€‚_Äê³fSŸB¸°(ÄäT1AÇmØ_’8°!4(Äxu­R)ÓHk²ýâ÷è#i{oŸÑR4üúk­)¬%ñòÑ#tx�~G`BE`ƒNâh:¶Ó°•6ýÂpßDIKôñ0€óÞxcÍã5“¼{ð@Ó÷¶Ô�X¥,àÀF…¢©Æ&l”."9ñOÊ\ò�A©T˜£ ³Á4½lbìzgg§gjüv!Þµi{¶ÍþaÊ£·�ûÙÛF#Ð)d”¢žÜ*ʘ‚䙃®bmntÜTºƒlÑ]°oSAZ%©ÄmÓ˜ˆÖüž\/áxÜ´h¥­ ›”n:ÚílülÅÆÉÑÉw˜±¯Cì��‘ŽÛýYÀx û³M7cKð­�²Žu5É�s’*†jXk(&F«ÞtnÏ·²h>“Žä¯(VÄr«FÂɰI4º²Ô'ø+*ÈvV¨½{þæ§Þ ~†Eõ¹&bùṋQ®Â9ÚÚÂ(ŒH:}ØÏ;÷Mµá¦ÝÆÇ Ÿ&‘D*±åñ»²þ�)½::<n,M‡Ü˜:Ì´_d#[ùð¢~ú£Ÿ˜�£E–§×-ì™Æ.:÷hÚÖ¢DBÞÔB7œÏÃ�u°•j#˼?lTœ¤ù„\�õ‘?­ÚèÞt^Glõ‘Åj)#ÕÙÞ’ÿ1 @ºšÈHâ 6%úJÝy{s_„vD¯í!ÎI òö Þsë&í•ÊTÕ8x¹Œ®gMÏ�Äà ‰>²P ¯œ›ÝBLF* Ø4WÏZðI �k³HF°9-XmmE·gõÉ÷Ý·8¤"b3IýbùÄBŒ{²«Ä=4·Hòd0¸8¹è÷^ ŒîˆÆ–ü»Ô‚Pùšq™HÙ$Aj2lNsæ4oéÄ`ì/Ê'�…æçb ÖjšÜDóœÓ$ �ì N{s=,ù»rD0¤.?;>7Æéb8�þ†ì ÃÕà#fŘÁZ Âùåp§û7d¨—�.̃NÑÍ Øç)βQ˜L€ú ÚþùýÆOgGvº’(Ð;°Îoô8­BA"úçÉ©åÿrœ�åŽ÷[œC÷yã™-{—£»ßZ\aK²àÁg‘+-¹T”DÃØ-ŵ6rP”’Rfbú'™4‘uæ0—WØ3m5IÐokSI)ìóá�æ7ëáÁ³”C�åNÅ>v®öJv2�«ˆ¾ØJÕÅcýÎŒüµçzKWfC×`V´0OYm D)ˆ­`¨‰ð­l*BÃÌÍ2÷/%0¢…³¼.c<ÌîËh H�³¦ÁE÷Ðëa+Ì~¸˜ümôna«6ÉÖ4Jз ð˜ èkÔqrYÒ®(‹ÿ¡ûÿ]àþð/îI4“/=SˆC Q�+Ú^ÕRm€Þš}l*!aC‚Ögn��Þú¤…ô%0©‰ Ìd.š>¨&0¢s�-‰~FËÕ2–7¡åF$VJ~†µ°ÄÇ9Ãt‘ûÙÒ@ ðœbü¦½‡}«Î^6%°»ÅV©‚Bz�a.ûb�Óȭݳe“Æi¹ïžGجÂýô: 2_¡ÃªL)}šj²] úÜfËq&á…®-…í¯ÝÎo{$ü|%—ù/’¼8:9ÇÞü�¿°�z6]dL3`±k©K¡4–c8JÉ «Ä|ÒÄà¢}Rò7Ö â´,žìê`ŠÄ·Ñ äÏ?¹”mâÿ½ØWG1ª3VZ4lãWõ4«þ ­›ñëå{×â;cùщÊò,¾ÄZÝÏ}7ºÎ� çãÿ-¶«1¬Õó}‡ó½¿˜GÓ�ØO—W„³!‹:©ì-ˆCÕà-TRn�ÿ—˜ªFÅVÏÔÎ`*pµ'ÙÉ#A!Þ//‡Ã†©´‹ƒ[ÕÙk…»¶œñ®¿5¿e¬juܦ±°º¶†ngn•�+¹Âmš ƒ{ÌpÚ´™ÅCBD2‚µ^®ÈøÔ*XA"F±gå¨ÎàL).LÃòŸ’ð‘î“Qôgfw³–fë_Ij4fekIõž”8ª]×KHÇ*¤Ô'®Y;�µðÈŠ•£"…xWäS†û F©z̸o¾³þJõ¤W‘i��y‰1d�»Üq|ŒÖ�3葆Úåð˜Çé(œbSƒl‰›ËžqYÉévÞò(‰ó8œbó'ãEa”’û/júâ„6 P˜±cQCzZ�œÎœ…óðZ”q*�K6D°ËuB¥´ÀÔùSd�jñæ=åyÆÉHÑg¬â¿EE¾•IYº _�;)?˾ÒÀÍs?‚<\S­í[Rਆ¤c¶˜T ™£’ŠÍ2¤W€ómQ~uzñò¸W€3u�ïeæž/�óÁÙw/±§V,àÞ%ÖÔ¼Ö/OO� p.[quñ%gL—ÇŽ[Kò]"Ëü‚e‘u•ÉëV®Š»…(ö­ºý„$E9Ä*ófGµµ™‰ ÒS1Šôì¦ûˆH’~pöòⵓfUèµ ¹ Îb‘Òy–€³©aM†�V0 I%Z¨³¼^=èFRPØHMù �ÆËÅdÍÕ %OQÂbbà+�òœ;"ó.œ³·Û23_‡2Y†¿Ô~2åÒªL,-9«¶ö©ïih'MJ�«8{™‡Š4%ä·[Ý0•³z.i­Jó•¢*š§@áç^q7½ )÷y(³ìq�:t‘=¾\²8Ùiü‹$[ÌØÍ $… õS.7ÁY½å<¹¦Ãý®kw¬­þ¢0¾R­øBÆZj×^€î\ªô¢–óìâܵOm° Ê-lVa/#3ØÄ—ü6L!À\ˆ#‡_×TÅ$A�ÄurÕ$]$c¸É pÅ kU¶3ø fd„­ÔŸª*cÙût¹U?±Ï 5•û÷g¨)Oí{¬,•!x韹2+Yf䜓.HÚ›uœíÿâIP¯ä‘�{úa·Xź+½ªAþëØz°_ÀŸGY~‹»¶X—åFÉn#Æ)¦yŠõùMç`u"™»|&ٔ䚫õR/1ª×ƒFóš�XñA½N$ƒ]¯ I¯Öë"Ò9u:ñT•»—‹IµÉͺÔ#·^ëQ¯Sr«Ùìz] ÉM®[+ŒXå™ër5Ú^rÍzÏ3况?ÈÆšD[_pi™µèЧ@.ÞGju6ó¦ÊB³ÚQÞ-éC«-e©Ñ–>´Ú'�–dÏrMÎhGB˜í(ÏŒ–ô¡Õ–­ £1‰ØêHÚú ¦­ £ƒ‡nÚŠ0ºxȧ¬£ƒ‡Š‚rFs'1µu`tÐiŠ÷Àó^ÿ|pxÐï×åmè+!‹r×BÀ�iü� Óï±ø—RÒcà^@ŠSruô=q骹ã½"ëŽ\$¡P @6Vê5Jw”ÿÖµ™’V~o¬^’ƒù2žÂM ƒ„›’¿Œù-ÆÆäÜ„&n(í1v*X» ¹ÄÓÅ„BgQÌMjéñ:úù†ör‡á o:] ƒÜS«C?vÊÁE¾b \oF/)dÿëãõ9¦'¿IvBš„'[!øéx»‚€*n†·¸¯âI^�þòæ»ÞÉ ÿýéÅñ«Aÿ‡£Ÿçßõ)Ý áíüÙb9š¯¢dŒmr(ÙôŽô 1ŲבŒ­qQËoýð$¯rlÕùsfZK¥`« ¨4dÉÎñè»*ª-E)¬cDºgU±ZKš]¬2žle†¾Ñ`Ï«2šlW$šlG¬2 ÚÒ?¤È2üÚiÿVeXÖœ6uJ?iÊžUBTmJ€oòL—‹¸X ¦¤õž¡yLNm±ç%º­žrЕÂóÚÇôL‰‡¸Õ€„'àã§}´çÏ%Þ¤»>K¦_ÅÕ³{bL(]N†#~±5~ P�V~‘퉩)û½˜_BÓB(¸ºÊmÐÒ‹€'îÛ áu›gû^exÆl·äVe‚á+�L<$»0N0G@ =ØrDm›K¯åW½þ!ËÝ7�XšÎUÃ"Hü—;rÙ8%wÃ÷éxêd)<1Ù@Ìöv�Rfx§ç�­bj6Ov�‰É ±m&P¶̾îή…9,.L‹ý¹í‹–ý4cÿP¼ðGn4Úiuì±h9NSñ6ÝÒ÷�²Ïx8ØþÐî´»í�¶ ŠhšÂWuá±F¾ûØ×Œ“�äšðo±ñ8‘Ý Ìui \‘€+Ðèû@Ù—|~Žõ�O¼Y]GSõ©ÝðXƒ@ݱ|É€N›�´~‰G´…éèðÏ�®U@ö:P·3'8¯ú°çJrÑxªd+˜©ØeÓóÌÕç²VnNmúÊÍÁϨܘ\íh¯¿³ƒ7°üð>ã#‚ܹE»Ázœ¼Š�¥³V±ƒþN«TŸ¯¢Ì$”ò“E2‚‚æú>šGÏÑ¿d !¹†±º.£fyÉ8È*.쀊·-£FÎ8Êà‹(´â]׸/B¿¡¸ðÊhP碭­Èú® @ "ó^¡`׆“Ð(bQ'$µÚ£—ÝsA sΈÿFP¼*­NGð§håŒUßȳndH¨T¨‡tFp²¢ñ%¸ íh…ÉbÆ2ê-ôzE™)ÚÕ£‹•iê¿ÞG&‚‹®²ö^a½’;ˆ©e–®ìÒUS#ˆøª¢ð «žý–âÌ›¤Vâª×Êj`4Qá>5;âµÝmÓ¡áù@λ�T%Bõü¥”ݰ'ïÝ-)XT(Râm·_Œ\›[Só«ˆ zÏÙÕ`Ž~odH)™FÖtcý:ˆY†0É3Þ v!|�áAW7ùB“`Jø’M̹_Ù>Vé"ÿACåæ!û¤¡¶xØVi-!c·¤§y?˜¿É~�£Uœ!ðõ’øW9¢HoˆR./RÅ]¡=¥h¶[õW{î.]¦H›–2a2S¢ç^ö‡§'çØ·8œ¾–Z ÇÒ³©nVU�Z%}Us3 û¦õÚ§þ•Q]zßÈeç¯å%U’vKÐI¶Å;ý GW¶¾/”é€,(“p÷„3 ã½bóX(¯ÁìùVj �ØŸ‹­'M"Dt|s4JÆë½£Kô]�…¤§L µÀçYöÒ±Ê~í’¨°jCèáÛöC~…®×óç;W5W+s-¹’O3ù·LJ/%Ñ#/öeÍ„Q£Übg9ö*þž¯­ör:ÙϤGHΠƒ­£T—n‹]Ýå¬:Ód‰§ôðo:­klPEyó~ ÓBf­9Çð[j„R[¦¼ ççÞÙÑë7ƒó³‹žHŽŒ4 \§•ÜÙ l}bÀLïŸM16ÉD Kš�pwz}pÜï)ý ‡ÌhT´­BZPU82¹À�Ñ\D=hE}4p4KÁbªÆßC²Ã!@¢�þù™DM;pxT ÕƒXÔÇ‚És ËSÔƒXØÉ©¥é ,]Ñ"k·æliתÓU?�¥jΚô«+ÖXŸ«úÛü,W¡:Y±g¶ÅÌõi'â± ´›© _¡^¿¯ÌÌÆo�í;Æd¡Ë Ûd] \RÝ3—æý¬óƒ›JJsÐݬDòIæuÂ#ßy®°íhJrÄ—á¸b�¡`‹æ#YI�DšOU©£‰* Ûƃ„Ï+ég*2‰_UT‡O,źð¯�`à§±…ÂnW…¿RÍBêÆy�ÛÐe‡^|¦ þÌÊ×.Ñ#ΊÚl6þhx;úÔ‘àöo�vûmc¯b§˜uÚî´;Õ{�YŸÖng·z/V¿/?à‚M­Ýö³ö¨úÄåØGüòU»‘‚*úõ‡NÕ>¬t~ŸˆÝñ«vå‡h_l¢T†I â øÄæ�H`÷°2Á!$‹e(Äki³2òÔÌØGÔÞ8‡6Vìú©¡™ÆÁcþÙ§²sÈyM½XÍ×U—IÓ\3}tÒûçàôlpÖ;¿8;Qt UÖÆ±U‡fT:8Yæ¹ï¦<õ{k_«Ý®âjZ| O«Ý©àhyÀT‡ªƒÃ¡dµu1=:¦dªxth•:¶3–yt0ºN¹CçPÇŸú¦’?çXË�cº²š;縄7gÍÔåБo×"~M¦á%便ì"ñj®U¥U]7ß <·b%#Ȱ¯OМt%nK>oa Vs9IGrÙˆ«ûÅUŽ+¦]ŠÖÖÃða Fdyè´Ffƶ•Dò»@ièD©äÂÐeôUlÏ�?Âä#ùèó>YOØD(2þW··Ðk¥î;EZì@œÉV“ØV½‡*�óG1?@$Ž8-à ydÜQRʶÝnëc,Í¢×YTð+õoÿŠÓ€ÌsÆXW±ˆÀ\sÙQ`X-Eppº;Ø¿¹+B8ÝÛÊ”¸óù­…ÃîiÅÕÿ…¼*:°ìG R°„ã½% G9ŠèR`e,K!ö‰“Dã*tñŽZ�:+Uõ,ÀQ¬ê nÒ(TõU¹'öˆ ³°œÖ­9*6r–T=Z�Ïlþ&Ÿ™‹U3G§9fw­ $%ÿí bN&a{صßÊhÇ-Ö‡Ôl7’J0Ž�QpæáªQC¸žú€êÉ*ñ—?–c�­ÒÁ)Gáŧ¥òЖç¶29™ÄM¹j²ÁÙƒçkúúŸM/� e±Vð^•Ãu q#ï>ö™ƒKéû7Q¶VUO&k*êmWõ#…c~çV-Ùqäh+‘d¥rÎŽ-ɹ÷~'þÑš$¯"æðîn>%¾Ö‡ êJ=C½N±q}C½Hj4]Ù]ñBzK ²f3ò‹&ÉÑÉÏÇG¯îž E®e)E>›¾`®b�Qp½›vNí†qBÈÝéy~ñSÕ+wLk?(ˆ´ºê±dPÛŒ z˜ ïIݰÚA§ëR´÷õ/B6‚j=Š‘Ô@N· âÌö‚œ��0zH|H �á„5¹z4쵬rýÏ"…۱郵Ä0Ö·Íu*&°JR¸ç„}‹Ô­¨ŠnðÔ>fª\dh(¶¾–²lÔO[ÈŽjžZZkÅ>~òôÙ7±*�N†”í5Ê¢1d«Oë ±÷ÖŸLÛ3›¢ÝÓ;7j+¶¯™<†ò¢�òa]GVèJ’# ·IJ N6> ¢OŠ;Ê£¡8¨à,oRe“F·ñÓ'»2 Ô%à7¤¦MíÃ_lw�.Þ,è¹OÃ�z'ñÎê%ª’ÚXì£mô-¬¾¬ÉxÎSù®¬©c4g�­æ¢ˆíç £Ÿxoõ”%HФ&á—Ñ]62úk)pß_ §xQ®dÃW-°ü·Ù]T½e^¿{@ÿgñ„–6ÉžâÐŒ}º–�Äí––w-iÝiéwô²&®€Ô…W’a…AŠ­~Q‘´é®fj+KrÓ“Äè2UP²¨î–*Ö¿¸Ñv·| ¸l ñ“œ"i„6þ‰¾n·fÐ k¼$œå+¸:0v/U)‘XýlÉhäî©Ò±ø=l%ƒ‘û§ÊcŠ¡l4fr•ŽËÇâ×Gm:F&V†�æëQàXÍæk!—!ÍÕSâ™A…â¹Æ*%m§SBžœ³(AÏWSɃ+™j"ôÇ0X�ŽNŠtÂrn7ô^êÍ¥@s�êQ�¡P¥ÀŠ$Y߯âs 4ékÑŸ<%˜Ó?«]•à"#wÕÀŒ8¹µÜ’2/m ˆª6zm<á"ê4ÊȇÐFé|e³4!‰¢_©Ÿ� ŽÁ&Ï0ˆEŽ®áÚ8ùÉÜà·Ä¤wkê÷µNùÓªÓÀÅ’_–vKþÊ3¾�}Ë¢S�sd64±VbOHÆi>àRæOMñZÉî­�ܹs$ �•ërf+–é ‰Jå¨ç‹Aƒ†MÇܾ+ÕG0”©�UK/Ë×¥p#7Õâh-X·ÒmÐXÒÒ8¤ÔŒB#!þXN~§B2ËÚs(i�‚eÊumbÍSX�5Ä\A*)Ú`²Ðèu@,†€/’ˆ‘8 °bj–­c­h¿áÊÚàcEPœ€øÄ¢¬ÓnzHæm–¡ÈÈI¿Ý:ovúÍ#ôØj ãˆJ ‚~N™ÀhÇk z�1ú‘pd*(òׯS,žVa“wm&dR 6NÕNÂ6þaØ®¦ŒÄPhô¡Õ/ÀY£ßêWµ‘�­ÁoÝá>6z½FgÐjö¡Ûƒónç¢5hu;øõ�+x×ê\T�bºpz�ºÉt*iXÛ€ÅuaÂ�K2¡›°C‹§)™"Ìø’Š#‚„Š“º¤ µ™ˆ-˜"*#íÅ¥ò=-6\ ñiJ¿¯!´Áâ�çýÌâ JC„Ô¦ f߇ÖÇ«!$‚Läæ”&Ž(‰I´þB�Í…_Fl<&’úö·6{õ5û[̲T¨à°¤ ÇœG{DT¹K˜jMóŠ·›Ü�é^…�6Ùߘ;¯˜Æ¨ã>ÁŒ¨˜ë®VŒx`ì{Å�NXL!!ƒ§B >c.©·e’„Õ’T zÒ(òŠ™ìþ% ¸�%gad@âaq{—=–I4ÊHbðXÑk5RP Ôõ(Q™|D¯·Òëµ6„¹=¯p̘íL 7N¥wíb½=o«�¾da¦ “€|„76®ÂE²8K]?WWíÁÕe*˜íUuËÙ .'?T.‡<©²üW`!§šY6P|Nc¼µ/½f ¥‚CìYbÙ%¶Ïz�ó&nv$-ñÖ%žwß¿o@¡êÒš†�6N]Z¿ù¾uÞmwq¥ú–n¡ùJ ´.š8Þ´š½QÐkuÞBS+œX#xPñð;h^l•>§\¼�‚¾§�B´ÝÀ§jéÉ“rn·FP¦h»â­é|)Žèo+f[ÝÙ<5LjÓlëáþãÌØûÚ�©Zq1O®ý›3é_êNïãȬéïZÖaR¡N`ûðñ_4PuÏ3/9ÇDäÁ�çoÜ u€L„ ëº^@ëåvÖÀ;ã‘×8´oº¿Nwx� Da®�õÆŒ�Éüæk,µ Ê’`>JR9+ýb}ôÊexÕ=§�®°‚ÁvÕRîNY×ø@È{ˆ1aIö…b<‚ëT§_roÏú£F¿ßì J/àå©BãÙèVz£•i’pivë1ƒÀZ¦‰2ÃøˆbW'fs4¯¸écp2"r¦·=yª÷þLSÖœIªÁk“ûäØ&�\²î2¤×ñçeæù‘þxöŒUñÏF³lÔ›õûScýé§OOËZº¾åï.‡r†eÒè2Ÿ~zþ´þÿƒ"]7‚ÓIΰsà­…t4Ù¯Köm#¹×ó­‹_ aÏim¾´Y³\®ï{ óåHχ|cH–¶�j¾Ü¸– æËÄ"]$j�Ù=Ùa™dæÞ¸îæÍƒ™³o›Ü¹9âÉ=Å-DÔœ®G÷¢*K›±Qròú¸åìzùÆiÛ_eI¢”–vAåGâå>R:¿ðð|ã§>ì�XîØ&a¸L¬÷Uȳouuãÿ$–î,ÀÉ4è^tKsBñhñzšŽÇ¸5ÍÊ'ð� æâD$˜cZvk27<}—p6â塃“´ …7¯Zá‹IŠÿ%åÂ^?ÆûÛ=„¥± “Òž [4#²W´�lªR<¨›Ù�í†æó#“ƒñLHÛ¼8Õ6„oæ¶uè¦åm@¾)O³×ëö°8—Ú–-(QP «ÅðÇánE¬³G¯&LHÝ‚1­Þ%¦”Ì¢�v}(`e~4ˆƒÉmŠ¿När°ð-ù1Š&ÐâüüDÔ>|&tDáˆÆ$½¥äxîùð!Þ8ä''A’ƒî>ic…–xÕêbт̼F)Y$ú£Ÿ|:OI?šÍ§�ú”Üé-GôÒÅ>>ˆ>¢›ÔƒêA$H4V+/@ߦéÒ œ8$‰Æé½Sìf$iÜ,R�`>@[­$óBÒ:�³A‹üp<88ØÉÏgÃÿ¸|?$?_]_ ÏúryEN./Nφg—ðë9¾ø@þ~vqê ä‚qè§yŒ˜’’ŽÝ”j Œ#R2§~0|@-œ,¼ %“èŽÆ!`Dæ4ž ²4GØÍ4˜©—²¢^8ÐÞ³gÿ„þt1¢äûipsã%tO|vo� /Æ$Õ_¥·1õF‰^Ó ý¤%éè&Ц¥BèºXBX&9¥þÔ‹"Oš`8ÃÈŠóÜ¢$¢L†ã`2ú?ÔO»X Xø)qo’ùÔåånJ>?#ð`CÑ–M ¥¾gm°sh�‡Y«+:¦1…¹’�[:eiê ShY5o~|`yÖ]§ oJf4½�FoÈÏ ÒÐ'²–w…]DdôÞ,Æî<�sØ€ü¤½ƒs€ºã°Óf¥Ô¦;â´u”*®è*ä�ç’à7D:A ÓRñ”ÞÑi§:§^D*BwQ0F¼ CÁ 9ŒõxÈ~¢rø 9&BÂJ¼æå:¯qڱ◠I0Ë@“z �¬Ï@ÉÖJ¼È ð“7]P© €ð½ëÅÄøìÜa3į½Q`d,-¡†…kFì �� €²ô@‘ôo—ü@C:¾¿S:‡£1h>ÐKÊH®w7ÅÚ@ˆ8+Ôð@A�g¨ÖS�J—œÁ¸qòİaã4Ñ1ÔÈ pV¶æ@–Á¡Ž cÉ¡FÄi}\ÌÉGúP‡ƒP�ã¡vÁØê ­ñj“_ÈB.ò@’³i㦒VqÌì0ïø˜ÜÁ'¿y É®�ä”KÊòãa%b¥+K�urpðn£éˆÙ6F ŽA¹{DßåHiˆ1ùEtâÉE;t $cê±Ý©6w˜Ä.X¦vU�2uY€\y*“†iMfâ².„æ4<\Çæ­ÏQ¹"3ƒFÝ0�ln ¼xÂÄ'Áþ|n<4“„AÚ6°·d>Š6ÑùJ¬‘ÓܨtŒvCÔãT�0X0(ØR+ àÝ$)WG;ðvéØy·Œ.&ü¥*M¯6&__ˆD .*+bÂ5á—GEƒ£.#AvŠJПÞ$BÂx9ÕøÚ Ï:— æ�h°°O­—¢)zzE•©¿«Ûxä¥^�HèˆÇdÞ€?›«Ã–�…ö™�«ñ±tKÄÚ¨d¤s(×IýOsðªÀíâ¾ôŠk%±N"»ëz°Ã#ÔŸ°Ø©ÚgÜæcÒ¾x~Žh*oa¥”.â�à+n_½Ê_ïR¢ºæ2­YÆS¥E¸$�ÍÌÞï‘“[êT‰]ò³‡X…à(¥ >pGÑâfJwÇèÖ²v? ÜãÁ 5lï“Hb}†p`¥Ý]K5"µRnEÛúRlC"%¼ �´P´œÖ%IØ=âËõM@ŠêK…ˆ«®nNá±ÙlE~JøܤБÂG‰8äÀ!û�ŽpnÚ´Ÿ™ÇöæsŽ\T·mµŸ—×áËùýw­Y;o7‰£{¥A‡¼x¡#ZsˆŽÓG•‘cošl“{9Y7núwó ÌtÄó·šðfn´˜”¢‡χ¥©;�À(Lß=šÒp’ÞrBÞßbX·ýÜÔŸUj¸FU|ÊÃÀØ9 ‡YE”×çvùèx[â9,Ê6Är-´£þŽ‘êqd!“Ï_–mUañ 꽦ÑdBaxÓiä·{ch$ŒˆlÓQ¶0ï¥0bVóyy÷"oµ{¤.É­ªˆKä?ÏÝÁðêìâÇÊzÖERãV¼hâ†&®­`}XÔc9ÁÅÜP�Íø“Ó^�óvι*"+=¨@Õ]7�ãh&ª©8Ô@B”ç�62T9WQâH3è9&µ-n©M¤Õõe÷¶‘kKïÞ2GM“Ý6÷˜×öã‹G–\$Â%+±P¦Íé5³® âZpV1´ª—mkïFkvÇÁÁQ_Ñ£i'Øð“Ë‹áñÙEÿʽ|§W lG ŒnÐ�»;ùaáp#)¡ÕdŸ5Œ¯o2Q²‡&J¶ijžNÏN†5ÌL)8Õ°�Õ0e¸.×è9Šr¯ý¦|á¯Æd PhÁ §º†ÕΗ€²I[>c3 +å˜×7f²É†ýx6Œ7PŠp«‘›F‘¯ÒŒ)Fµ€§UÁ6|äPj Ì­,LVp“óàz|§ P&4NóqÊüv²^w�2(^äebûËác)@=�ï:Ö8ŸÐS˜ßmÍÆ1�&4]…á&k`Q`¼sk Ngß4¬fF-,B�ÿ *´RöŠ(?‹Y–Ú;r×Sç_.†}Èíû.LçYÛ�ƒÒg1[œmZâuz+h×ì«p€·â©èª{Ÿ ¼t6‹FÜѳjrå©�3ìX7{0…«][9Ì‚°m°Æ¹!%2$Û+¯Ì]²Ò�~J ;¹>Q-þjФ™2É È8ll�]É8D#³ ·"ÄJèŸÇê Lž(ñ¤ ”�î¡È,Œå—ÍcÏêò/5|vÑÂä´g}4ñÚ³FMÝvL¢üPÃ/oÅ6mdõÜs„—û¯2’9ϳPJÐRQ:eÏzén.3¬–[5pJqÖdÃZŒ³Hn]Œù¶«Ä±‰3�Wé½¢˜–Ów+Ýf½µÑyÎ"U*?sŒ¬LuŠ�o„Ô2²Ÿ“KÚ—¢o*N¬¡]F·æÇq¼´†°(›%ŸTׇ�£·Í0´{?z7^ÅTv0_Äëåê` ïë¨qu›ÖÞ‚&£¶Nç—Y“5{¿f‹û pœG`cI›çytVu)6âIˆs&g`¢o SDKÇD‘>+ŸÌ½Ø›)ÔS ‘ŒÊO¹o¨å9²;Åõ¬…dáû”�&,ÉÿçÖØôÆ­\EÙ 6WP`àU”¡ÁžêpurG¥ËNM¼UˆËJåx$Zy›FÔ‘•yf Ôèñ�ÚlÜØN¯œ 2g�¥Îb&-ÈèX�7o$r]õÌ…Hݯ1SrñQ ¹à4œÉ7uÝ÷ïýS×5 uù­–œPµ•�ÉŸ{q9D,ÌÀ¥[f!6ïÁê.œ!+ÖØLMmØŒ!‚¦Žç³s/'‡¡£‰ïÍ1çÝKÀ›¤#~"L‘Äk“«Äè)Ù‚ý"™Qm ìãlãÜKñPˆBëo÷·ãÝÿÞßýÎíþå×W­Cµ9?IæN™ád'ÈPó~X‘½�y³fAfÙF0ªêÛó`u±’ºc°0rµð€cûE‡”‘sÈUÿG·ÿ_ÃþÅiÿ´ÜØGyŹ£`„¢qì5ùkïà×Cí5ŽÇQÜŽï0_B´ÌVðâ·º$¬ƒ5&PÿêêòÊ!­w^0ÂHü¾™;äºõMÿ°xC¾IZÆuMšH�ŠˆÙc3ñ"¹àõØÇÞÌ=<̆|CŽQ#y ~8·!›GÐ’Ë8›JìøÈ8H»êBbJQ.¡|i ‘WV�øúHËhòñhj�>¨‰n†Œ§WÄÞÔ�k§¥8IüÎÆ$HÉÌKEv0('±8öÇtF´HÉ¿è™V0am\Ñðû FvæÒã?Ö£šçùö-'8õM’.µØeò‚u‡Ù‚jê+Äî“ûÚ‡wàõ2êv²w”½ƒ± ¸Óþ´Œ¿l|l[$Ð%¢½*É`|1q- *6|`‰g 8³·xÈ–gÅjÔRuO“C–^ûð2K`+$¯Y3[/�‰l,|ˆ}‚jxy½ÿò�¼zåkzÀ!ñõõK\ËÈ_ÐeA“�Ñà¸â“]üRJÒħ.ǪmwüRˆÖ® tu€çŒÓªWÍm7 B!ý´&/¿¼šûÐ]ý´ìrWT䦯vªg®ž¾š·zyöô?Ý”•l¥@n9™pSÁÒ/v(jINÏ2–v& 4�Ôáç ü‹öHYfàkÊÊò�co‹‹ì.CˆÙ4øT—�ŒÂaÒÙ,µ¶Æ"‚¦L—#²¯­9U¼—j†Ï/5}’·˜y7ÔÚN‚(“ç+ºàÈ[Z íëý·M³MRó‘*€ ,V<~ݪqd×tð[&Øk:EëŽ7“€ŸHyï•!/M{6;G¸…I§jÍêL.e6HÈ+7›�‰iÅm¦b¶ÛfD�œ¤8¿'lÇ/ÀÙ.q/Å ÄÚú#}àŽàhÁ®ƒÑ/Èèê9:Y'LߌÍnʤä­M;³¥D0uc¶¸¥^Js·ç•ÚZ²«r½}ZÞ4µîí7ÝÓ³÷´>�•bÙ`cB~Ê„¼¤À„œô�†�]âJÛ%ÏëÒNe?ÍXÑN ~T±¸H]3TÙcª¦\Ã4l.õF}¥°~+1¿Œ‹üŽž”ß"áÍðŒób<¦qÁüð—E–ÕLIƒâ�,äá)¦Òè”uÆ 7}Û¬9U/rÇŽªmô°¯ÉuÓ¼(o÷Hõ^a°»GüµæûB¥Yv’ÖM¥ÇT±…ÁP‡¼.»jêb pÈP>¶Ì4}Œâ«¢÷,й ëêN —¯€ÅËC›ÏZÕ¨¦§[Í#W ü��h† ¯¢‘W°¡<‚êÅ|³ ½Fì:7yPåÕ5o ¨xL Ôñ6Ö—û ÓQ1$ìåÊxS¶‘[9E•â­È.yÅ÷xÌ ÔSzi7¢ÚÕ U·.l‰à5œ–-,º’úû¡,/¢ñvh…ù)ZŸ/qûÊÒ¬Êe ¸rN‹)l‚H¦÷‘Åî ë¸N¾oš³%ƒãóÕÐMìù),yÀ»:êÉeîÌT›ÂÂ×!QCç-°J£w[ ?¯üP05\PêÆÑz(Ä@jm²°(¼d>�{_‰AÍIŸµ}õ*мgŽJÝ)nP�w‹övEÎ8£Ñ§dAhö›UîRºP…m‚çu�>eT–$ß`¨BÙäá“ûAÐëFó8‡Aš�òFÉšæ…±«'Ì |ª“å)ƒŽy»ë«Êã±F ™”­7LØðŽ®íqáGãÙn‹¤…²ÒüÕ¤Í>™‹ûqõ!#»0qŸ™ÛÒØK耡2)…Håv‰:Ê8ƒåf tD!Ë;})o,}Â]É"ïT½£ {Ì/pÙOà;ûœ—n4cÅâH^­º2‘¹Ven?šÃÛ4I쀱IFo⃘ɋS•|(Ù_ËÄ}v¹{^œüòk¶+öY,u[¿–£’ž<:z%DHVaK¢b¿¬Á~”ª¨ä”5Õ2Kuè¼ÄRYC)’ÕA¯ ûƒ¡{r<軃þÅÖLçí»±U'cBS¡”kGÖË ?äó]ÀÀ*¶3íÅzë¶Gdˆ2qK§sL×9&äÇdæt"rbkN•æ3ŠrW¶]jS’4WŽºá`gæÍK6鈵¶âoÁ·ýV‡åþ¨‚uòû[™°ñ¶oÓ·ôЫèáÀƒß¤'Á8Ç==ÙAäðçуeJqYÊ„PæRlê_�½ûàöÿ³-{pXÜZ¿DìKá %;¶Ah‹dk¾ÖUs8 Cµ<†iË<Ú…a¸Ò}L…6ƒá•ÖJŽàT§õV;…³1åk•lÄÊ$.(+É[;ºe¯—ÊÒh,ã}ZÊxµáòQ8q+ʨmD©|·S-Ī$®Ü¥*½K<%V§šÕ¥cPBpW›O‚v;*I䘒ÕÉɕԡ£¤+l) y¹¤½uÎî×\Ë&³I9»ÀþXX=¼z߯š 9Øx!Lží»‚e_ ¾•ûûÕ:ª·v|{V|ߟžŠpOEx�ôìº7Ò³§Ò³±rcòP¡Ú¬Î-ÛwQ�ÃËdŠ´ëØxR äÞöAî �3¿ÆÊƒ +3¾œgVtcò†ª£œ_;ÔêÖT¿þ«dKKÓ®psUYéqÝ|žSAßËX�YáÛרÍ4>Ŭò�Ñ(ƒÅýãhýâMùIó?œÖ_¾OÓÊšDIÚhRå¥ê€ûu&¿³¢Ÿ^CÏÔÇÁ¼õÂ=%ËLéþÃ$ã/÷6¤dü(Ž>-Òù"å-Éÿ Uƒ°›ù¾ŽÜ=V„˼æ¯ó�ZþEœÄÁx‡ñ—Þß~-VÆãb@¹™z:j¿àÅŽh‘%TòŸõW¾Õë^÷òʽêß_],_¯e±Ê&z>TiSM»§@ÐÀ:EŒÐÿUž…íò“læÆÿèÿC_¿ã×ë°•5–q#è°Á*/g§?¥^,¹Ø9¬Ï¬^÷/aß­Î0¤•™ißm�i*f½^�Y×øŸ‘Q¯·Ç(§÷§bÕŠŠðèdcÖUá§ëë‡ëÖo*ŽõöWeP éu ³ñ®·ÿÄÿL®Zž‘jt×–›ÄóKRáöë¢ch’&}.&ƒêû½>(ù~¹£È^ìí‘>;øÇN}‘ Lãqà˜“à�݆¾cÏŒ;�µUŠ´&É&àž ÚëU'Û²9Vš%ŸíHbv<á="K¦Ë·@YŠôMÿ¨œp0¸¾±@“” B®” â]ˆGz-̳Mé`�o g[äæuHž¢è^‡Îöʬåyî/CüCå^*î×�—×tñï£†äŽÆÁø�3´)–s�p2è>Ð9þY[.Aø·•ƒQY�tÖ6˜c›eKUd˜ñ麅ÿãFx‰kYÍ÷¾5Ŧ„Åú†J\Aj4RK˜ C�� Uñ²×:–ªÊ1Ø*CœÂf«xîá×g¬ÕîÔ0P,çˆY¤tW¦ÈÎÙ«ÍPÒP‰y­ªxmûŠðlb�Ë&͵ٗ1mÛžxAøV„á}¤&óÂÓ”¹7ÛanC:ûÝqi3èà¿7ס…Ó¹çR#¬Ž97¡�b™�]Õ¼Y´lÿ ¸—öåЪ^xœÕXÛOWWÈ…x✠’Ãä‚í:€Ñ®ÚâÒ–¤ ¥¡ é-ëdv­Z©Oݗݾ­öawÕǾô/¨”Vêséc¥ªíwΙ� ¶Ó­´~0ž3ßýû}—<êúü�GwWTÇ¥_W»^’—µ\V�ñY:gXšYÕ)‘UK5kïQ~9ælŽ¢Ù–Gxø¾ìº—ô>¾T'}ÑõtÆÂÁ·àq¡ûµ¢[1»¸J5OñHFáÏšCU�Ç%Ç.+Óp=ÅÕTË¢NJ"ø©ÕØ#r‰SxØû¯­ÖœžcXË)4Ðõˆ¶¢:$S¬–”Šç¤¥�½Ä9yÿÔ¤Ã2¼ÔEÿE:/ñWᆱ\~s~~tköÖAy˨õ V&Ÿ?’ÃmÌðUgI]¤P¦¸ë†§­(ž­ M ] ¥D‰aöRr Å>a¸š G5:5)²´ÓQ÷^§h¶]ksŒ8Ô«:A½0;r&=­ã¯«ž*Ò&RPE\<Ï2Î^ø±0Þ£xÂØï§HÌVËÌtJL±æQ7ÁG&]·,4©SÂÿï2öç–¦” “6ÔKÉRË4Z1óWæðéØ):W²+ÔJ…tY";2lœŒ4i²!Á_§“Ï’÷Éå%eîæÍë7�j^Å÷ºÏ~ÁÍ’1g,-g —�Ƕö£8Û@À‚ÜÀÚ¬/EYzñêí¹ÿ}±îOæÙ»ÑLÛ¥¡gA”vãáôöé5 µ¦ rê,\ŠŸi_äðk¾/Ö> Abêo[äU›ºD%bR§ja~‘•x+”°ª‰¦^�“Ì„ôtÓ¡, ¯‹<댽qT!•æ=ÀìllŠP%’-ºÌxgíž�/¤÷UòcøïÄ©­‰ rëÆ«7R÷TŠ2_Y®‹t\[IO“+&U-R­NLl‹PDZ�Ñ £QOAíŽÊ7§{#qÖ84¹µA6³„QûDè äÄ`ñøñ%$m0¹bWv[òEKèÜž”�é!øùv~]ŽÁ§kÒQâ wmv®vÃ?'àã‚rˆev 1ž 1X¼“O ìéXHì¹›¢Kª#ß™�ïNæ „—ïžMrB1&±`S)†Çw¶BÑ7ü€lf¹sÄsªXr¨½�”´ å¦5ó£yFÐû¦7Ùx W”ß©•ó*ÿ…”€¬œIS]ê²h³Š6´¦Ù(h˜ÍºÃJÆÖư7ªìËd_UöEÇÈf~œ¼ ½ÛÒ¸p"’Ix¥ˆ=^hÂ2^Wk¶¡Gb»S†Þû$–%™5Ž<¼11| _œ;}ÒïˆôÙ@äÖ“;mZòšjV)ì¬ýñˆ uý4Œ­^èÂÓÿèÃ`¬B�‡ïÊȯ9¢{‚ŠÕ»bWM�)¹_µ=ªs½©'Ð;�ŠeH¬öƒDOû”‘®Ç5醿q‰¯w’(È—7îÑZn†q*Ü»\¦"S›èðiˆi‰DƒZ^7–ûà§ËI(­÷ÁíT?g«¿_C~0FºrÌüç�E4!ëãdéôLSÍLSÓÁß–GX¨/„Èœ`+Ä¥eC³M †�õcj‰h_ßÏØäÃàÀ—ÏÃ÷åAfÌ•Ða‹Jô*ªÐØlF ãämÃ4ƒAuýÍ……,±lbQœžMª–CKûbj®!�!|Îí lçåŠW«£è\G\n„îñõvÒï®=q!TuµÆå]{rwR¸”'³ì;—–}`pIþZoä’pwèX€<øj»Þ¿;5:S§c«‡©jÈðY¹?ÁesѲ耰¸º“GÁŽqþ]>Y;q4ân¶\k¶gâ¹ta =ŸŽD–›€A³ñÚ Ë…§ NöI£3¤åD4×õätNv*${dW#è˜s½¡w|`Ò‚�Ý?Rü'|Se#2'—XÓáiš~0¨ûXç—ÂÎ/1ÿÛ_ Âèâ*Ž(Ü«¼•«¿¯Y Áчðw÷LtüE—%¾�ŠA±ÇH€{ôDë²þ¦ð*­‚qŽG³ÂæÄ–�G»s"Ò�E§…³Ï "† œØÇæØÁ*� ¬î­wåâAIŒ®g±ÁpÕ�ó¯ÛgƒO>‚WÜsøÆ~8, ÚÆcœïð‹ù rñØÑ5ß]*¿´ëü‚LÄe/¨ù[`ü£ÔoØ$f/�YàËk»½¾þ¿ö©»-þe`—Rõ#Ø.&›�Örç]|­­‹¸ª8;:,·¬¢bðöýÞwØÒÚ=܉ð4Åx3éˆ+u2“ZËØú�j—w¢KⶇaË´ ›a»(þ¦a¶Øÿû‚’¨ûV_ÔÙŽ˜ÜÊ7¯ƒ¢Sûd¤ˆ××T]ìÝâ®MçŒvÇl-´ ‡² øÑ¹�ÂÑBAÆ1P�ÙfyÀfO„Χb$�o]6[|††°67%iã6CæÐ6»D¸Âq�|”xîËèoqó>å €Žpxœ}Q=KA;ׯÎvÈuGÈ�mŒ !`!’6:ÜÇlnõ²{ìÎ�Bð6þ�àò¯x{—ˆq`š}3ï½yûyòuøv\ÆÖ‘ÅÔh¦gîå›�‡ƒu ¤ÎHâåt|�ã‹Ét4ÁáíÍlt7Ã+DÔ°ÒôÏ„”N‹*#8+T’ÄŽ¢²P®–8÷˜lùÓ²¨œoQ«“ÕÐv`½éLI€(ÚQ°õ ÒXhíw� $•£ ”Î æ6^,b RÔƒ0üR’ul«”a) .Ï7å8}#Á$�”²ë®["έ©æ¹©¸‘QzÞðøµÄaÉ�ßD†Õr´H_¬êɲÀÝ�‘û&°jÎõ9lƒØ›Âö= ÷çï�Öø`#�ùã~4¼¿�~¸.©•¹�xœ½YmoÛ6þî_qõ€Á6œ¸õ¾ÍkQ7UVcžƒEN‹bZ¢m¢²¨‰t¯Èß�DÉ”%'qÒD_‘ÇãsϽ�çô:ðöG=Ðé5z�N:ð~.B±N<Åõ©�C4:Š4OæÌç0— 0Pb£˜ÒÌÿZâ2ÁUR�œ:5«Þû2Þ&b±Ôôu–@ÿuÿø]ʪ�ÏŒôXø¾ÌL>£r!#蟾† 4ÍT³= [¹†ÛB$5¬GBAj¿ñy¬ADàK„.X„¶l„^¦û-„¾r¦Š3 #@ÎmA`Ú€^j«_{½ÍfsÊR´§2YôÂLNõÆ£3gâ:'ˆØ¬¸ŠB®$üßµHÐÚÙXŒˆ|6Cœ!Û1¼H8Î!³a“-¢E”œë K8© „Ò‰˜­u‰°šm e,‚æÐ…‘Û„CwävIÉ—ÑôÓÅÕ¾ //‡“éÈqáâÎ.&GÓÑÅ¿Îa8ù Œ&»À‘.܇ßÄ Y€0) RÞ\ÎK(Pè[ÅÜsá£iÑbÍòš'Z1OVB‘K HM(VB3�Uì¢�z�ÆO"òÃuÀá·PÌfLñ»52vº|W3ðyýD(Þ&Aö)fk%ò<¨�Ô\íÍ(̤ +ƒ¸‚Æ0Ç0gá#÷C–Ÿ”³”´0BHŸèðµ¯Á›)Ï÷4|o>$ø'‹…JlÁR†¹!u×ÀC¾â‘V©2Z…Š ³¨¨ôtÒAÜeиd8>¡6tù]Fy—ÃE»–â&Û“ër6û©[LÕm²›ýÙž6x7›‚µìz•\·3w[– ìÌH®_$)x‚gôÑþ?TÏËܸRó»¸-<´ïr̾\ç�’sWè}p=¼�M‡£‰sé]œïÐSœ=‡ÕuóÔJàzgݾD’âùH—luœ›¬ÊKtZ~ ¥ü¶Ž«åªäŽçŠ=ûœdÑ=Õ¸&ÔŒgªÑ¶ÑÁ l—KAÝéùÔ³Ôw`F]…y‘c€¯b}�+ Ä×D¡Å:ÕòZÚoó ÜXâ­ì¢Ø~ì5îY.p¦™?ËnAxëßÈüB^íÒQ„­Šêa ™+k.gˆª¼§ËºWŸWâÙ_ÅžÎÚµÊ úþËsíÅÙ¸«Xý¤`ÞßêÈxnQtà�n°ÁI1rW¤£³§x£‚óuä?©ûKcÆ4(ûçu¥ðô&>ÒwÑxÐïB ‚�¤2�cªª "]CÓiëµ§<�PÿƒôfùNWŸ&M5»ûÂp›]ÐSÇ�zgC×ñ\g2E�Œ[í´y,o›%éþžÔž¾-´¸Î4s%ö“xÇoÑêöV2ýÁ4ÝÓÖT›!9¶ÏÎåèü«7qþjå:»PœhV;’EwÀ4{ct§}U§}òf_ _蛊¾·åôòʱö¬TÁ¼îÕ>ŽÝË‹cº0£@}@YÉx²¹¤�NG Ìq¶˜{m=”# {/‡‘<‘–BøXnî0èhrŽã¹ÿB<«ë.\“ãc±ÒEÔ<@²Ÿ×ÝçN·£•=Α•tÙç úkIqð9Q@?¢Çù?ÿk×ÿšY´"¿˜xœÅXmsÚFþίØ80oöôS�dB%eÊà)Âɤsˆn*tªîâ¤þïÝ=IHHSl§úÒí­žÝ}voWí:¼~¬ êíJ»^¯@Þ΄ËÁ×Áø+w´ Z>¥…ž§y0c‡™ €�K%#)ÐþâN)HPµâmoéßb¾Ðtw‘ÜÀYçìø(åuôû±t_8ÜS| ¡7å¨uÁ¡ë3â•|BåBzpÖê@•Žâ¥£Ú9©¸•!,Ù-xRC¨8ê ŒQü›Ã} ÂG"vÁ<4f%ô¼'ÖB¸áK¬CN4CqdÈYV˜ŽA/´öÕ¯íöjµj1ƒ¶%ƒyÛ�äT»ß»°¶ÕDÄñŽ+ÏåJAÀÿE€ÖNn�ùˆÈaÄé²�‹çÇ5ô,BXB oÞ%gzÅNj¦Bé@LB½á°š�@—1Žº6ôì#x×µ{vƒ”|î�~»¼ÁçîpØŒz– —C¸¸¼ï�z—¼ûÝÁø½7xߎîÂ÷ðo~@ L¢�àSã7›ó ĺW>wÄL8hš7ÙœÃ\~å�‡�σ¥PR…§¤ÆK¡™6� vыڕÊsá9n8åðÊ“ S¼Í”â�n-Þ”¬¹r^¾�!zéºæ*§RééDJ·ðwäŸh=ÃÜÂ\…÷ÜqYÛõ \¥dÅ„DJ07É@%¾cvœRZq—/¹§U‹dŸOùLxzŒg·?¶{ZF¬RQädÈOÔ8uÆxÈUuó‘†zæÿb¾Å–Yß|É–\/äô@ëbË ùX)4Æm"Ðq÷ÚVùQ¼6Ÿ6ß8”àBßÂk蜗K`tÃ%úb«DºC©ÁU¿ –Å àr ¸tòóÊÝã;í«ÓœÓ�Cb_§‰TÉ*xöz§ñ5ˆä“=xµÕ�YYº &uõ�=ÆÂ5è >6àèÊkŠ()Äw*-žôš|éc´âa^øG� EñUêgºîÖÿfX�«;íIwíó»G¡Ùp€ê †x3äôp_Ä$ÝüI@—d»ªÅxœ¿îÃ]J‹ô29QŸ2ͲF®ÔYX±ŽÎ›b$±Í³ÿÙé_K¼:c®â»”%Üõ6x''7ȟĦŸS[JBÄ5Cê{�„LS‘hÝ«AÍ»`k�@w]aÿ44=`‰³%žN¢^”)áŒI/1�~‰cæ7껲Â8dÍù.a3>­‘‚ƒ§O4+ÅZ¨h˜ÎÇì¡eT¹zµ^S×tˆF¬øaü�Ñ7€�àpר10Q$…›ˆ ‹F–=_tmkl[8”¬~µV¹Cè›Ø×…-Y@P‰"ÛE©¿:•&ˆ[̊ǻǮX02ì0Ž�Yè94cÇ®o[éè±;Üq1Éלô&b¨³`P�yÉ_Æç{â�OÖ°÷áËx4¼²ª‰Ú¼'̘xœ>HR.§Ãú#£¡“Wb朒ÿŒ$ma3špÌ÷�tº¤û”ó¼¦7Át²,êüŠŠÏÏ%SlVìIÚ›”åæë"÷d8_¬aú¢§1ŸóèìÔ‹”ø÷”®-ÄÇ?YÆ_ŸÑçÚÌáys0ûñfíIú˜W�paÆNüyù—�ÃɉȞ’2Ÿï®ÅMÚ»>&Ž¿ñ=e7(ÄÿŽ “=ïH°¢’ä{KY™¨ç¾·±Æ’5†ÂÊq‘“4¤Ü%·Ø¿ ¥u kî¶§>mÊ´+–7¥Ïú©XË9 o¡ôý2�³’êÈ‹7xœ­VKoW�kð(Ø&ÎÃ2N,;²�ñ‹¨/,G Â�‚Ò´µI%Ž;_ÇCÍÌtæº`*ãE¥n�Þ}Õm骿€J,+õ/Tl*vÐ5î�ñc¶^X÷qæœï|ç~çÞk¿~ôP8©cAü.#>ú-zbíϧ?½7ÛRkw4AU‘¦“ÜLä}Â|5³Iþ˜ÙïÏžÏb‡šr§fLzMÅš9©aH 'ä¾çqϱi35$`”øA‘êIîGèo¢3¶ E Q½& ­–"&ò<èÒ=¤4 ’É‚áMjô ±³»½ Å"Ø\%AC¸­ÉÀv \ßË6G’,á„ý»$˜@]`ëHÇšÒq˜XZÛ²….gY¶}Fþ:µKnzg[v×SÉË`ƒfà@dÆnh96hP¡gÉûXÑl‹éuUÑi‘r…q{¬R7ëè(Ãj޼çŽ�ä‰÷œßŽŸ¼ðÆâWLÆ%ùdEN£Û*îŒ ƒ€!®FÉ×¾ßÈýõ“Ñž;£á“s¶§ÜåÞ˜»!é]òôÔþC[oN/!Lx�B-tÛZÒË•ÚF¥R*_3•³\„¡Ea¨“1V!ëEød t«h •�«(yé;û锳¡í¹ƒ®®VÁÄGv¸[=3![Öª¢ï$3\9–€;JÒ­ÝqªM_AM§Ýnªì¸æÇaDhZýÂw¾ÇX¤«’\Gw­ˆ�…·C=�_H›!=t�'7O?Â&-�¸‰ÀDwÖIC*Ë(J Žu-üî�„üë¿�µè�^ FƒŸ&ò,'{� Ç˜è�.q? ”ÖÝ"’…H‡Óú�Ö·#W¹eB‚ÿõo¾ÞÒ…çóÆ(ÊÛ:.ùfN,]tI„F[±¤ÈBKÂ�KÀz*ýN¢¨<0…òæÐ²ÊzX†ñIžcäÕœÀà0† œ1�b×0…m\§©äÃPô�²Y¸ÚÖ1r7iSãAq›RÜÉ�lhuÁÒõWF©<œ“oçO/³ócÏÒP•ÅòûÐù'´xÞÒzF»ü 5O�:2 X¿ÉQ§±ø$kgøØæ‘ Ù›`Âi1b½’M¥àmkHþ^ø&”Nl šÙ´"‹ëÈo.ïƒ{FNfW˜—=©JZK×Opäçpt×\º)ØÚÙº¶µ±]«lÝ(1ý21Wù¡¤\\¸j ÞQ]’ Í“ûáÙÏÙ�2 b>þ)äräDC+© ¿}7>ÙýCYÛ¨šŠ0Å0í<ô�à“Á»ÙËì…7’)•Ë_–yˆÆïµ/AÝU‘ˆQ=IßÌŠþ'Œt‰v‰=òGUì/….Gþ_ Sƃä—ð"z£šž˜ä'ÖÙ¥ ³ÆÙÐÓ;zGR¢ïŽË¦ØÓ»$×AiŒd�áÑ™5ks¯2}5O¾�xœ½U]oÓ0}ϯ¸JÒ,íúÀK·‰R2iuÐ$„PÔ&µ”Æ‘í MUÿ;×ÎG½µP ~hšëûqîñ¹Nß…«S-pûVßu-páuFsœd~¢^'¬|àôûR‚�80 _Á‡%ÍiY»9„[*ÖzA‹$¯R—B¦ ÆrymsºXÌé7Oµ‰õ?÷%ã’¤°"rÉRq\O&¶›!9{ˆ³ÂÙõrkm®ÖWÐy9kðV�øÛLh0^6#kóo[äDÎi±~ƒ¾×k ç×ð”ËaD4{—ˆà12å++^Œ´×›0‡a0‹Ð•òĵöéŒ[Æí¶XÛ'Æ~B€D—#ejˆUȹ¤ ˜ýªÄªYõTÝêg�@ 'Dø 2"|•¢É�°Ím˜ÚÆX0sj£øú Uó¶É+âÁzœy¦+l¼–³(£x2ƒ8 ¦Ñí4xo;ªÐz\P©ëµË¬‹{X³Í‘­“*^㬻=­ª4}!òE x×Т>ņG^%b“†š'5­V Â�eÈ|ž � RɸŽÔÄÉU†Xo�º¸É'EB@�éü;1ãïHó©¼ßb†½Ð¶"1ñŪ¢9Nƒx_/ns“»i4ÆsœÅw76^]{|=…ª‰^Oâ8�méªÄDíž!l �y‘BU¨�ÆÓ!>(jë0bx­¨1ÓðKéão— §qnµö9˜ÝÞ|‰ƒ�v[ÏS·«÷ÍÁ|÷Õù�Ñ/s ŸÍU_[¿�l/°ç*`{4Y~ð§å/vÊwd€:ÀQÑÙŽ¿"•†~å‹ý»ƒxœµWmoÛ6þî_qË�@vÛI‡a˜ãbnêtB]9ˆ“vÅ0´DÙÂdQ#©8îšýö)ÊÖ[“lË–ŽÇç^žãQìw`ô\:ýV¿ÓiA~ ˆ‚`Þï=ßµÈcÉ–‡Ë•Toçù œN_Â[Æ–¸`:=7ÚÓУ± >¤±O9È…qB<ü33]ø@¹Y §½XJáÀL´‡ bËRX“-ÄLB*(b„´_ôΣ‰„0�­“($±GaÊ•¶cPz ã“Á` IP�€ XPT"�Ó+)ñc¿¿ÙlzD{Ûc|Ù�2=ÑŸÚçg>9F�ÍŠ›8¢B§¤!Çh[ zä‘ú‘ 0dÉ)ÎI¦<ÞðP†ñ²‹É ä†pª`üPH.RYJXî†]TÀ”‘Æs°çðz<·ç]òѾþyvs ÇWWcçÚžÌavç3ç�}mÏ|»€±ó ÞÙΛ.PLÚ¡w W ›¡J%õuÞæ”–\Xæ’H¨¡‡¡ÅË”,),Ù-å1F åëP(J:è+˜(\‡’H-ªÅ¥ õ[­oÃØ‹RŸÂå!õr�ß…LÃHÛ­?[€COFd)ºÀo‡--Óï0ͯ¥–^¸o'×SµõÕš¬ã•­Ù† J t¹´^ÏÝÉÕøÈ�Y.ößÀ`ºè WƒS™ò šIï[;Ë» f3��Fð×ÌufÎëéìü�YAjª_FPÓÌLðÛJäsåbׄºOÀ¾¡òƒßé˜Ñ” chpwx—e¤lôÁĘ ÉS”ß?u©2É‹Kí3—Sâï*Ký¯õò¢Âv˜z”bà—âÖk¸«½æ±+E£ßCdŬ˅ô–ÆRêål:µ�áž?¥a½.œt3÷Šì½ª�§×é&7üGÕ¬MfFýœ¸‚954.Œt\.�Or" B¥÷?°ˆýÿ3u÷L–H¼e¡�E¸‰äxfªKcÙÀ°™®�“tM}w±•Tu´‘=?…tõŸ¢Ê�Ø…ã÷Êßפ-$áÒUN?fX%,•¦ä0é‘Þ û%žŠ„oUH~˜�“,Î2¢²©A±–lçúå©û~ü œe“ŲʙŸØÎ‡ñôöMeŽê0F}P놪�51å}äh¹Æï3ÏjCNƒÁWÚbiÍ è”Éг\ja™åý’"A(µ —�v‰´ÍJ}(ZVVDí XC³HÏFµPK%³ÍÃQá ¿Ð°˜È j¡œìïmÇÚY;nk×:~}X¹ãí]µË~ÖXª;S‰ì¾µ*·¶z÷V›Þì6wG S.Ù‚ƒÙšQõ‘$ß7Ìr ƒØõÕGsÿ�Š÷kæ·‡8ÈvñqÅr-£QÁpCl¸<¦žê °!¼ˆáµª›‡¥¾Þ=¼à1�}€ñ^5ü]w¸´/'ÃG³]r.c©àÜÑQ ÀÊÀñ+e2~;¶œ‡½èãìfúFý”åXŠWíó g£O95Uê|aj ½m à å°N;9]¦9�oö ÝËûf¯Ý+2%ÛýÿÜÞõác³ÛmvúíVn»p}Ûyßî·o;øí4;Ÿà‡vç}º í�{Ïg3@˜s%1¸ßz„¤ ˜®€D=¢[¦¥ãÔœQ¨�ŒÜ)ñœxÄŸX”…”"@ƒ©±­‰hoZš3TÎåþe9º¾Õƒ�”ÆoMÄ÷7ÝdÚ®¤›lk8Ô()Ä\Ýa»£µƒ™�!C.®¥ÁÊ΀Ð(404´Ô6t]{©Ñr–…-w© �-¶ùèï…¶Zfîã³ÀDÆ…Z÷žë3ÆLH0v ºÝbÁƒ„ O^êa ©õ0¤œ s¿c˜JðÑÇšGÃÐ,ò¯ÑÀа�K�žKE›Ž ¤´9 ^à¡T*ròþ©6°1‰0=/£ï4Ðü@Ѽ"H�ÂeR½?…+†pš€ÈpÍÁÄ ¶¨y…X?J(ñwŸ¡ï ÒËÜã>ý9ݧC<ùÖ2A‘²ðö*Ö]ˆ&-û¤w“î�RÇC2¦rˆƒ~•¿Ì} 'O99­Žá]oðcóE-¢úž=;úåüŒ*97ð+kHÑÖ&:5á%Rx¹¤ï˜<†JZ³�^ö>~ª%2™àšÏ¡xi©È4Hs‰ˆ s_Põ›Ïê›5Á[ļ—ø±•XF=�{W0‘€gueU4p��_ŒCM5;$ól¶Dq j�\xm÷EþG>ÁÄpáË9gV>G–ƒã,ƒ¹ÌÅÑ{sò†­ì h6¹Ãé74“ ÕíÞv‹�¿spƒ':Û2Â�xJ€Û‡Ïùú9Ÿç�‹T]ÆšdlLͦê£1‡…ÁìÜÝ܈n¾Qc‹*¾ÆóT Üж•„?á0¡¨(œ7ç¨ ß\ �OÌþñ±s‹ý áÁЈmñKz¢±ùdžwì{d.– Â7xèÁÃ"Q’…§ãò\<“f^L†#KôæÄŸ,pQ áq‡¼VâeÌ»¬i·”mñ³SÚmÉøW$ü?‹ïÿ·t7Ü«Âçù.Æ%ÐGÃ3RjY⥱––/GHCÑ,Â�Wx:¤+ü]ÄL¡ÎJÉt^ÿ3 **!ÊnP”5ÄLÖó‰iÝ'™)Áªìp‚RŽ>ñR4›K£c^BÉ~Nêò@Îgh„ž©æ–ÌKŠcûÃd†Ž..w¸¬ÎQv³¨ÃÔµ `q‰‚ƒÀ[ðÌÈÿ §%–Ê?.°™à¶rbgÎ*(§/Й%DÞ¯ήúáα>t<ŽÒ¿ÞzÄjɲnTr»`÷¡óú++ëEüÉ·+d| äv:6ðX\šŽ„¨IWDç)Ñ’ÙE0Ò’‰­�"1ŸZÔ²Ê B ìk4 ׈~«×\7{­A¯Õé·;­¥�{Äø¤@Iß9ͽ�­èùHK¯ÕG˹m6m¯Œš î:Ðq ÜhI†¦‰+úæ»NÖTLî·ì’¨¢~I]aYìJ-‚¢?ˆ vñ*– ¸üs�¾pÚ5̼\¨"×üÜê¶?|´~R"\wê«öúÝÔXn*Ö,«B´ ¾ßgÄÏŒ¯±)¾HûnË ðb€¿Å7ÁôßÌ *ê+¢²³Ãªìkëp†–ë½.JuƯ•pp-Žów[—p›S£w‘á¥GÞ-¬ša¿{×JNpÕë‘|½Šg‰C®…0ƒ[åàzuõØlfk°µÝÚ.vëŸ�­ × ;8ZÝÖ욤âFãÊ-–JÞŠ­@QiÔëgçõºz^;W/NO+g•Óm�­Ò>å¢�Ѫ÷æÂ“Äz¶'¬š7½—qíÙ’k×Û{>Qï³*›²“i¿‡ « këèùºµnëÅî«HA â¢Z­ÕΫjí¬qZ???m¨çÛÂjwú訛¿¼ ¼“e|�ñµ;/“h›,a¯@æç.|$›å›d³|ã” ó†TÞ�,„j©Z=Å@4Nø¯ªVê䤖 ÌÀÞ¿»‘Ty1t•ÒùÅùÙE­R«7ΪµÊiƒ'Úæ‹ng¨\Ûsö³i«�㺚¦À¾HŸå²R²= �“ªD%'Þù�ü±N‹Iòë6Úìhv€’�ËÓýGdýepêå�«*>>`P„YaH×ÔQ�ÖOƒÛî Ûêßu;‰ ³›,Ȳ•TùŠ–Ÿïb§°Ÿ²:ûÕvü›ôíÊú?SB»fºõxœÕ=ý[Û8Ò¿óWh¹§4�>ºÝ»-Û}.…ÐæYú&¡Ý^éësb%xëØYÛ�²Kÿ÷›I¶$ÛI(нËîS‚­ùÐh4š�ÄÎ&{~_¶¹³¶³¹¹Æ6Ù?Ç~ÀY2ÎâhÄ“¤9‚§ôbÍ®cr‘âo‡ê¶¿»ÿ„½Œ¢ €�œÊÖ'þˆ‡ ÷Ø<ôxÌÒ ÎZ3w?ä›{ËãÄ�B¶ßÜe5l°._­×Åu4gS÷š…QÊæ ~ˆ;þyÄg)óC6Ц³ÀwÃgW~zAt$–&âx/qDÃÔ…æ.ÃN°h¬7dn*™¾HÓYòlgçêêªé·Í(žì¢]²sÒ9lwûímàXBœ…ȈÅü÷¹Co‡×Ì�G#w|î‹bæNbïÒ9¾ŠýÔ' –DãôÊ�9¢ñü$�ýá<5¦øƒnë @dnÈÖ[}Ö鯳­~§ß@$ï:ƒW§gö®Õ뵺ƒN»ÏN{ìð´{ÔtN»ðÛ1kuß³_:Ý£ã . Ã?Ïbì°é£(¹Grësn°0ŽKÉŒ�ü±?‚®…“¹;ál]ò8„±�§~‚Cšƒ¢ ü©Ÿº)=*ô í¬­íìì°O~ÔöëØS—½9íw~ÆRh� DÆ®8¹áã”yë¼9¤ÑŽæ isíoû!gA:‡Nÿô¬wØÍÜýÇî�'kkóÃQ0÷8û‰Çq5/~Öùaš^Ïxb> üáÐMøŽ›$.D“ÊÎU JÚ_Þ"‰FŸ*Þä³²ô}Ê“«0VëY擘OøgóQâOB×j–¤Þ0ŠŠ�¼ý,…°ž]';%rÆÇW®o±=Aß=|¶ö7Ô›±=¶ 6hþØ޼/ìnæí;‰FnÀ�Íóx2‚)ÇQ™/ý8 §š§dÌÀ|‰ß8š12¹:|•~6Ñr]\ÏY‹uÏNN¶S4¡‹Î�c—¬´P¤¤Á.¢ÀCƒdÝx2Ç�Hš C7Jù3|~‰>ì~d³¦u‚<+²ªñR>7ž|,Ú%£Ÿ÷9|™|•|Âw`ïØ�ÃÒnÿÁãˆ]]pZ�.üÀcjÔÀÆómkFkæ{%dä› MŽ¢Ÿ‚,Ýt¢ôÅÈ*"šØÃ(žºAp�Q– ‰0:ãAQaAX8Ÿy\ {å&dõåš)[¯@Y4tZ­ÇÇèˆ9;KaµÂ l¨j —\^_56w¨õÁ2¼¸öÔbîz«á…æ¶^ЍÛà…æï9ë�¸Q0 °L£u×kžFSZ‡Y»°¬â×%F `Ò0…jõw *™ûG…B†€€TDæé<� æjËóÄ„šFŸ@ùk{uP’ è gµÝ::$™j²Úö^}š æÙ—@ŸÜ†^è9ÑØ>À~ Ø@4²Oi(1d6Xˆš‚¹XE¤Î4ò8ydÉpÆ8RRxA˜Ô”‘öqM³=æ³Bóæè�Ù,iNütgDCg@¨¿óªÝ:z¶“ãÙ9<}ó¾Ó}ùLâ˵Q�íýøã�¬À¤„øò—&û%úÍŸº²é›,°EŽ!jæà²L cà4�6ÁDF_…1s+š^c4œ€H<`gEîñ‘o ˜T€ds(ùä‡ÇäàŠW�D&Húb½Žt(sÀÁÇ’þ¸z›ÇÙÃ`ŽRŠ¢Ep…œ¨×ytŽàˆ�Bnó34Èp‡ÈãONý›�‚ùÉECËI40T^z³ň4@$>t@ŠGÕ!ʤR\DúêFÚè�Ÿ ¶ñ<¯æBdR`˜’ˆèþÆÁoA7âÓA]aAe=ª 3Œ.9ËY˜Z®+8"y C½J.\áªËØÔƒ/ˆÍÕz#0UÂÔ‡Á˜E±ˆG­+E¼j³þéñà]«×f�>{Ó;}Û9j©ŒNÃNä¼77í_ßôÚ}Ìí ¶Îë7'�6<îtOÎŽ@ÁÙ ížØIçugx§DSO ³×íÞá+øµõ¢sÒ¼§ÒqgÐEÌǧ=ÊÞ´zƒÎáÙI«ÇÞœõ ünG€¹Ûé÷€Pûu»;h2  Yû-üÆú¯Z''HñµÎ =ö¢ ¬´^œ´bèÈáI«óºÁŽZ¯[/E–ê@zÔL°ÃÞ½jÓ£N—0uYë“XÈúáiwЃ_Ð³Þ ƒ~×é·¬ÕëôQǽS €RˆS…@»m�…lŽ´ÂßÏú휣£vëÐõ±“zc‘»2Ì£íq çÙx„ðÃ�?üý#ØHåj3±ž�CE«~¤íg̨2Œ’…ÙM&ò™tˆ%A@ù¤Ã.\ê ˆ±0y c´+×lgG"”‘8ú±íϸ÷S+®÷•)JéȲíûú ÂBìf>1ãÒX�Á軋‡&j-•š»ÿv¸¼ö§cÓBë9ybN�]è)~.9¾ Tô"ûîyžÌš~¬³­­ì7™ŸØaGî0@x0áäRÀ"�X2sGÚðí‡< ¤eSöÒ`ø àos³D“ ¼¢ZÎéÚ ü�EÓz]0á�%Û0ìÚ:¸s)¸@DXŠ™ªI9øÔøñS.’è¦_—KM «|\‰$—àÍyóYM{'™ZÂ6ÔHà§ []90ZýR¿¬‰E·À{”q#nè’Æ›•BajM‰æèx>èP›*ŽFMÒßä¸LFMU08е!ïi½Búzjà!Äž}­P.£³¶~)f¿«HSØ]F[Ìo,j”Hekš) ûjð°VT_�Fž÷°»— ÚÈÞ˜›r€UG?¥£ƒŸ/åSD¶¯Jff†Ûž½ frþå/ZkœÑÔ À°–f÷ð…¾.¬b¬ü¼³ÒˆÞÛ(ŽŸ>ér#œ+ô&uíEßi÷z§½[?v}LeXt%uêô^äZåÕFSÛêj°o8’”º4GÏÃCt½,‡­F…º‹¦Õ|½ýóÌ÷,áj-’4šÕ,„†¼tãoc®Z J&ñBÐ|Ô53tpîÊ4˶•0+SQÓ_§‚D ¨³†ê’¦d*0·l�=J+«—±Ûc±�K«¶Ý�?ÊöèÑÁÚmÕÓšû­)âV¦Ü³ÕP4pw–=òÖ kO™‚uC¸Â C]Ø×ÙзY6lÌÚæf�mläºVDS„TÛ—ˆWæí©ýJ�(ßòAÙVN/ÞstG>Õ,Ÿhmó’¡"Þ Xe[UÕ]²ææ•½q¥CADA[â!¿ä±Ì»àX­†yÀ7º‰G\š°;„ÐQﯱÚênĦnŠÉò« 7mP…TÀÝ�ÍgÚ¾r’.¢¶ ñ=Iåá.êzÖ»º¶£¨©“¶ fš˜0ù‹¨\VTAˆÐÃÈî F–T°ˆÇ.€™¦6QZe‰FŸœ„§ÎïO01 6ÏPpšlšØWÅ�k¶�AÎâ4ž›“z8Àͪ…pÌæ1o²Ã²Q9zLaJé(.šðÚÚ¢‰}„ât) %¹Õid+ÓP²]�F¡Ó0Íé·q�Ès¹gßH(N±XÈ� õ®aª¡á™Ïš¬Ïqs‰ýÒÇ@ÙX&Rýe Õëw^"h! ²@vÃÄ×îiîÖG’âú‡ŸÅk �’Ñ»jP!„eÖ²j¦(¯�¾_DóÀÜNàB8DáDë<Ì£øÃ«Ã.¾À‰[¶îÄ—K„’x#ùzä1ÏÇÚ^þyÆG܃!Â-ˆQ —¥Y_%ƬžÊVÍïVãòL°…™J9G(üE®QÌR>4”Íf³bãËÆÒ¡\Щ<“›ÇW7 õ²1ÐòIK%;æÉ…*ÄÚ�KKCì"34Ø\ÕÞÊàÝÊr­l‚¾�£œWò--(ZâîP=�Vâ·2ŒQœ÷5¦M´ûyù¤‹¬­Úè4„ $Z ÿ‡QVWØJ’ù&Jú8É�cY:Ls¶o¯³FÒ”ŸÎª-[V´Ú–_ ŠÂÖIÞ–±j)s2†ËòmÊ øW5ÜEu…æ“Z¸­£æöm`ÔWUsš²gƒTàÆ¥Âë²a²ëRKt¡Ð›ŒõºœŒY¦ZJÆî³EÆz]N&¯Zd@ÃD¡´©L¸†­èŠÉ P2çÕ|@œ¸ÓÞÞCmïÉÃ\‡’'Vµ>c-Õ�Ê"ݨʔ‚ûèN³Šy]ã)޾vˆJµÔ€³t«ö¸Ÿ°Læ*äqráÏ�íä@FþlÁÉñ˜ýÇÐò“ÂF´4ãµÒ$ô7)û•ê´DëÔ†¦ñ·-8{C1›yùþ‘Q¶¯hi|1]•,lZÉÂj"+' §ÆË˜ÓòûæÁ�%IÚ’“ ì®ÌV´Rfsq+eõô|c©��w`¥IèÍÃ$óèëTú6Ÿyd%n4§ã†ÞÜ LBDqc²{Ódï_žiú->ΖPÙä˜Ý Ítà.&?Ù”˜o§ñµÆ�„áá(š‡€ó¨íNwÐõ‚ ~ •Þ‘‹—X†ËdK{�Ø-Ë�3¢¢šëŽ•ùZ:šÉiX"ä)¾S¼¹›Ï–ï2kóœ½ëž¾ju_æI°C7]�>q>c8ÿ}Ìz‘6§ˆ‚êXiD¿¡Iª*E¢ö¾ÁýF,’(n9ã©Éº‘"ÿÙ ,WÉèqä£Yƒí~~ô¹$˜¼×Yü‹Ù@“'�žŠ¿1øÖg¤ñjhÜÑ(б26¸ÎC`¿Õï·{ƒŠ´> Ú;îZƒ³¾Bp°ܘ›fƒ&àªßyÙm�|-_ ÎëN÷v¼¼´{¯�n±'’µª”ÄqkÐ:¡”„XÌ=Í4H–ÐÌ<òž‘ú¬œ3Qó°$¡ŽÿJ«_>H¸ƒ§^ñ`ÆãgìPËP4Р [&ì›^º�Oºy#OóÜ4³€©ü´�aS£šz[>Gßµz]¬’-›¥þ‘‡›Öù‰¢\T›™g�«ØCKK:°.Ãm¥†8ä/Œ¿ôE؆¸@O–'ð¯¨üúP,7çòaÿ£½CˆØjðªBÄÕf�ÍP¾¾x»�$nTÈÎîGÑ`Ó ñÝž|÷ÐA<ùŒÝYRÌÃ>ÃE~>ѯÚ2S›eb%wçáèFœMÿíA39up•¥Y™ï°€ÿbï¡}ÉRŽÿ59FÕ“¾%x%pÉ7`€‰Õ4U|ùýš.»Ý ¥&ë\ªÔ„â�®7ôE;• Ù(­ÕÝÌÔr{zÿ4]ØûúîI’öFgUóvwîe5É}½Ÿ™†7Øþ×÷s_Õ·fõÓÜÍ¿S?‹$éU$ŠÐ\�¸óê¯E1ŸæÀ¨t–_×z´ý³y#}¶¶Jê.óÌ6O�\­€ ‘ãÌŠ mý[]P’Êùú£äd%~VìȬĊåd..;P¾?^qiWdåA~q#(«Xºíb))=J0dX!XX�]‹±Dƒ³( ®Ö|Óv|ê.ÙÈ ±VãS}é�äÂ<&4×­¯}¬$” ¬('ÄJ\É€ŸÂ½X,¦]›Aëjùê²ÓÓ‹ONùtf%Ÿ9¶MUãXZƒ¹´pi ý^ƒ™eôuoìêý}M Ëë÷ r!InÊ:„ŸUŒ£ê&Sý1¯XVî×�»‡Ÿ[—Æç@yÁ낺xüTÖÆãçKá‰9�¥Ü‹€Uç8Ôgʧ°úÖª•³Aé¾e‚*M€à§xLik �çXÆVÿTéu^¾šµÔ?©ŽÞ…qüüj_ÉšV©‚%'ÕŒzò"•/fò¶`¤ußV3ì"y`­ñÌX­Œ$B¾‚?�£Ó#:‰uC#.Pò€¬Ø³T·‰Ü4Ùë9-äÂú×·¹§¹;w»×¥ÜÕi°Í(ö'Ž5ER�N]g×ah©¢ŒN§Çç»�KöÛô•y*f‹<Ĥ€ë4ŠÎ…±+•XeVJ2bB²ÔJ^(�) ÓP §…ÎgÎݦÞE!KÍ¥*;ý¨Þ•�¹�cŒ.XPDñ,::?åw™°!X¡O6øy58�D®€[̪àôÛWª¨>®¤ª_¢R ÿ=³¦•é>�&��Ýy�VÑu ­ “Š Ëï6 Ç?dÈ>6Å�\öb¨B6¦�ÛÚªXø*ô(WL]÷VfÔ¾®ŒTS2¶µeºÂ/F¼›÷\¤Qø-íÚ”."âM#xVwPÝ�Jú±¢ÿÂÅ4áñ%^ 6j±… ?uI˜[óæä”“™LeáMfU4}¨œKF½�²˜æ™'Û¿Ñ›™CmvÓ†û�™cèÂ(=J²¼ø…g�ój!õJ×}Iˆ”•xç§Y%^¶ž²üéwRŠx¦6-VôÑàe­�,†L>´øô+Iéî0!IÊ)% æ& ºA€?3áúiƒ±-¾•î?[Ó$©·ÑÄYp[ÄT• òX&c=©W)ì"¼¶(Z-ì|š60~*Çæ�qk]”žŒ"uIÝ_�j˜t‹=ý"@ÀÚŠš º†,‡lªÏJ(£¯1�8÷Ôå»�Ÿ?¦jò&¹C÷BÞ¤–Y ©‡85} À=)M�ªÐJ;Ú'wuèf<7¸–wóK¥j:\g•áMéhÝÆ†ŸM‘ä÷Kè–9,ç" ¦¼Âk5„Z¼©PBkŠB5ºD(ï2ÏCº[O*BÚßD擨¡pѽ¹10)Xæi!vÕ’MaM"ËR”‡‰ç^ò8QËÈŠ2Q"}J¾B$zÅbZ4ïÅd!ÄÍè¤îÜP ׇŠ>þ­ƒ1Ýê.ŠŒFó8¦ü�¡Bˆ®Zˆ2”AŠ)•$¢”ðÁ®H• ¡qñœ¶ÓEþ*¸þÿÜí?ZÛÿr>ª/»Û?~Ü|¾®R;ÐØIÅÏìÑë á)ýäy5‰8.gÁPq‚áâ¨m*¹ æ¼4X¯ýÒiÿ:hw�ÚGõE{€zÞPàļ¡Úí4Ð<+=P_ÉB|¹ÊÁzÕÜv̺“ç‹öJ.Xè�ÉŒtV´ÖÖgä¶{úº58|¥Ž éf’\XNiˆ…xÓŲG{£‹å‘÷!ëÀjÁ#UŒªÒ¤¾z‰ÕÏ\úð“m©h�bE 8Å·Ä_E¤¼É³²%VÅfÙâ”ÉK©&ÙñÔI"ß.»¹aæ ±Ÿpó¢Ä³«¸¢Èžn SÕæ¬­ø8Úfî·aW!þ¬�ΘJ·pÍ@ÏBüƒ!wùÃêá‚á´û:΢ßt�4ªLáO-Âk0uôsV¸'››{×+áÑÚUÛ†QÈ?ã�ù• ÔËLf”z“ûëêŠ;jŒÏõ;’íš²Iò!¿ôO˜áå2‚ù½@‚êNà ‰!*k<,€  TKü^h IGµÓ©æ`«pü�ÃV¿íôÛÝA§Û>©Õé:DSHx•‚}6K½±(Dýö@¬•’ ©’­~|¾@ê1°n<‘ã@1P Ô'`¦ÈJ^ãxßQLVÚú–ÇþøšŒ �t"çŒÇ2éD ðtbˆëŸË++sݽ$<ÿ;D‰”5©y¶7²zŒ=ó×ýBžå€ÉF*c‚ûH²až¨M¶¶ÀXï‹oº=Rãø¶Ýë¿wúƒ^ûÿj9/оn³,Øe€\…¬ ,ùBµêÀÐâ�Ñ\Â/Ë”nMÁc,KšiÙ. Ẅáø£ÿbºL"À1—Ío5è}­naMÚ}â[®Zñ4ŠByÀ›Õn»ToÃÊz9è�µµ~VYwŸ“ÐÀnÀ{µË¾d e~'#XÑõÕÁ´a[”õ­ ±ý\Ü;õxï>º¼÷?Õgçºìü÷ô83¦=¥©Q%‚~çä佳v_:o[=º Þù~ÿ$S…ú[`Ò2œ¯‹œå4Šáû_g',>¾¹ÕxÎþ:CÉþ"Kùü/éí½õó¸uÒ_­£‹û¹2ž=÷ùPC^ú ËcdóNuó°|e´¢îˆÚAÓzd®²=ùs_‹Ÿ¶úþ]…›ì1 ZroE”cxm‚±ü.\uzjqeßÒjÁ’Þï—÷^ñ�¿<¹½Îs$`Ù$Ž»Èbÿ[ÈâÉÃÈâq.‹Ç÷!‹'ßBßWÈ‚zswÍh¤nœ?¾£D¾ÿyZ”ȹa)ÈøZ±œëSæü~&ÍÓo!–*ͧ.‚[$b|ÿƒ–XÄÏŸè.Å$ñ½uñ'Kðéã¾| ’_‚¸ˆ“—Žâ|¾‡Kõþó}¦Ûñ;ŽÀ…¨H¥A©ûöè÷1†íWf©åj�g*wqd×›;vΓҷ*µd�“V{�ÕÝ?UX9y¡ åQ×ðß„: Êåí-‡³ìºo žU'†©~Ÿ^Ãþ+À•^Ú‚H§òòÄDîn7 ’Ê5,»¦~YÏš§E .y™GC—æ `òWKî¥2P ÃNŸe7+Ï"/â `KBwÿóÉÂÿ?5³d…þwwÔNófñ·–†óäz;ˆ¢ÞðýîÓå¾.ƒå& ´+ÑgQÔ„vlOm¾/Oé~½Æ·^ôE�Ϥ6öÊ”D]c©D’/® �±—Oï:ÎǸ6¯8–ÿÖ_m¾¡ö¿G^ÿÕv^·~­°ã•²é8»$¢S�B‰]BÞšž›º¦•ZonRUBsÓ8¤}~^;?_ªùÇÐâñæd“ý“‡í9ÓV ý±5ø µ3Øæè+v¶ßÎËO¶¥xœí=ýw7Ž¿û¯`ugWr$YvÚ¤�ëìz¹õkêt-¥Ý}iNI”<iF�ù£­ÿ÷@rø1¤$;qz÷Þ鵎4$A@€8{»ìèc}ØîÞÖÞîîÛeŸÄ3Î žíü¦G£tq“ÅÓ‹½P?ØAçà1û6M§ÐàÕ«²ö«xÄ“œ�Ù2óŒœ/¢ü#Kšì'žåqš°ƒv‡Õ±BMÕ‡â&]²ytÃ’´`˰¹ˆsFxñë_,NØ(�/fq”Œ8»Š‹ êGBi#ŒKé°ˆ zÄp,�˜YTH¤/Šb‘?ÛÛ»ººjG„m;ͦ{3Q/ß{uú¢{Öë¶cÙâM2ãyÎ2þÛ2Î`´Ã-£Q4 Ìf0+þ2dSÉ,�®2`à~�E‘ ò"½÷#,‹xæ/$‰µJ2>å×ö£¼WÈφi:«<ŒÓÊ#èË}–Áü;ÏnrÀ4r�)€Eì'Ëh8Æg¨zØiœ6'ŽA>ŽÈxR�"b#`Y)0*Ø`˜°`P°?¶|Àñl–Ž"“E'Ð_,—¥Ë©Ð X¿ J`6ã#¬„|=ƒêÍx”,ÔàåD@Æ'R]`…üp«ìû8ü•KmVÇŽbkp³›FÙ`Êül=ˆÇׇe//*ÀÇ<eñ¢H³à$®`Š@Dö°K?s ±��r^…â�ÏÒd ¤K¡vI”]üQÈ“Ë8K“9àê‘ó§`AE ‘ì"��Q·ÀØe”Ũ^Iˀ⊧qÍàñl ÚØ?)Ö‡¶ÔPe‡ç¼Xf‰€ƒ³ø÷ŒOì?{ÿG´ëJ¢Jâ,G#Xášå„ÒX�¼€ûöàË'ï·n… èã2‚:J�±IBa s˜ëœ]ñÏ3.W[T9^m€í �ð�ådò�prä&�`}CÝÕ}{Ðùâ«w†b7•¸¢“ôÀo>âc‚’©¤¯›]„Nü¥¯ée:éÞ½èÆº—/ÀÆà“h9+ä2k-¢8C›;,…,MøÆ�a£BñŒ†£jÆá AÀÙo –š°ÝÛy€£*ó\H£)ßúu\ Êv²ÅšÇ%<{JÇ:ýñd9×|© %sþ£7èw{ýÁq¿>è}ú#;b�ëÎAg¿Y-?9>}¥Ê_xÊ{o^¼èözªÊ±§Êy·×íC…ÀW#z™.A“¶fqòž�[hÏ€¹=ãÜ\«¢!è°�…nð/Ʀä[Ïð§´¹Çf‡Œ (Cc “ŠÚ›Z{Y‚'Ѽ„J(Õ…Q!f—™e\ðÆî&JtP(QE�žTxH3[&¬.MçH¬•hà#+4|ä�šÓ År>„¶€·AÍ|½!àêe G*ßÍA• ¼ÐÞÇ`šnKT7Ö‹´AHJxÂ)°™¹x �Ù ézÏЊ#çKÚu9G#O± �Nͯ­–àSªÿ+CCx¡êÖ:¾%JØKpüF°°Ü°+à É‹¸„£ß†+¸Tì$Kçeß´¾�U³_›ÀB ØÝ”qi²ZfÁ"Ê¢ù h˺ªI/l>È*kG§²¹ŸÄv•˜WüÉ�KLoØfލÃc[ÔÏ )â°fxZ°ÔŸ„�'WµÙ »à3rÞi�á¾ý�åhÒ�½&EÀÑÛÔ•:†£e° â$.êø¼q¨j¡´”“–\Œt¹*äucuÛ�ÌÉîl²v»Ý`ƒÁñù·ƒÏOÏú'ƒA}¿É€p�¢žðKCŒì5Švhh[uW÷ù ÊK ÓŠ�×G�ztWw‚°ü¤‚:÷à è�(ç$ØR/]Ð�PÕÔ:—Xšh\QŒæ‹ºíc9Ò±Kÿ–ˆjDüÝ÷üƦ=®öl‘¥7õÀå\£î¥…Í3áä7X^åf#Êx]ûâÒ#Ù`¦ýNåÆŒò!¦?$SÒÉHKÖ¾×8?l˜Dtž ¼4¼v-¼ËçƒQÆ£‚×µ³ÍžUƒ×@”0 ƒ©$TÑ¥V`4(Ñp¤ H-ïîF¹±ati¨ÃR$¦iþ~\€î›M)GGì%®áeSÝãךRå÷`íõá�`¼Šv Z`:°!/®8O´En� ¸:å€ Îš]k­ ?µ;U¯­G¦÷N¸Ë5 „fbÓ™©Ý©z­ä‹î5ö VÚÉ2‰½íûóÛ”äk?ÐYó @Át”?p³Yšç`�L)Ò³ �/}Â'�R¶+¾ mýáSrÇ�üzÄþ¸=\³ý®Ù¯‡ qèx�.� ¶ýûhv½Ý9¸Æõ\|k‘11Lþ£ /iÛê@±iðS2/y’ÿÛø—âRm�Ç…øWï f¸ Ï0Ò»¾C…MV6n²óî·¸SÒ={Ù})u«àx˜¸Ì2è_žeƒáròöËýƒwæ–È Ò¬ž¡‹);‘UKm/7 ­®¬Btµêšýj"Ü®D[²¢yت.G¤úqwl,�ƒúbZ µQðv_HQŒ_óQI1É¥ûMU½É: euN'粪AC$íÙëŽû/¾CñÌ\£Ö/…JbEÇ)ÏI$Åž¦xìîÑyO3Ò§²á¤dÍùüÁ–±\ÞI¬ä »nY0Ë¤ÖÆìÕdFç®BÜÝÇ硉ŠwÁêÛyƒœ(ý ÷¶@%;¬`ê]KSÆÈÖ’ìàÜ™3”íHÅS¾4ÜmëÕl+ëðh@ãRìªNêvoÚRu­Ts_µ„Õ°�8V)@³ON­º �àÍÕ]­y:»Ä{Ù,€iƒÿNãd +d69w/Ås+ØbDìØ3ÌJÏÁáúyÝfmM‡êBÍ5™ú—°r8}ݨ¬ÊgŸJP•¼[´fØÖiwÑJŒX,ó á! ¼ì܈¦ìݸÇéi¸}{áÒ§üÜ cª‚‡Uíý§éƒw>MƒA WÓ˜5õ¡Œ7xˆ˜p¹É=zqxÈV)Jq©Êe bEõÉߟBó (¶öQ0@T÷Ð’cUµ"[¯7: «*yò±¹pyeÊàKÍŽªàö¢÷QÄ.$mÎóÊ™Ê&èU—•[S*Ï=øæ)/@)ßAü| (᣾�;ÔæIi=¬; _qJ ``Âõ9±B¢´‡ÕoôFȃk„ô¼Òª!{pŸW-Ÿr¼ÈM Ì¢¡<†£È@o }Ì’o4m*eG¯–ô6*¼kbcÀú!yóÐ|ÙÖ6'cÁ3gF[l¬Ón°gìi hUŽmW|DÛÇаÆuÒúF ¿h·Ÿ”�êÅh£¶O¡a¨mx¾Lr}‘eª¤{#ÙæC¹Rž^9|êÚœ´Æ§˜hÐv­ò&ª =§@nëòA2ÀRÚÚx©‚È¥PWQ¨%̽ÅÄcàß=ƒü™ÈÕ9ãÖqŒ“éJç¯`Ì}%è>œ—§Ùt£Ü<ûj2½ùí~ð±ã¾bÓ òÖzú¸ýôq@Ô>‚ð+¯,/ø¢ÌÓ÷¦‰«Äðr�.oHg�Ìg5TÇvVÊe�ÄOøõ­jñ®mß>áZ%ƒ4KjŠŒ½Ï-g™ùza�œIðÓ¸¦W4#ç×éneôïˆ ð2ÿF1²™-_0RÔÑ –H—I>FÜ­'óÜØ&ò§…—8)Þ žužhåi8û¿ˆföæô˜GåÁ»Nš4óÞÔG&dÖ=Õ›6vhyø²—4i§ýЪp[Á/�DerŽÌœ›$bF!�NÊÖgÐïú²�ôÞégÁWñe\yçÃhäOî2S‡i9ò„U =ˆ»ÎÚñÖÈ0Zb ˜¢uWc¢š>S÷W9ýƎ胵ó£8Óáyqòì Ï/èÒæ«Ê¹ºiÂèÕc+êÄ<ý°Œ(¤õº2abÂÜ÷NyÐåžÅÚתøsÙC§¦/^Ÿõ�OϺçƒ×'úÏì(©©j9gžƒ<¦/~y¨#î;]"c ò/#š)U’¹¡ÕšÁ3au¬è™Ok}ÔHäSe`n‡@z�ŠIªƒuoWŒÞ“Ä<­ôñÀ;ÑdaäÚu×Kǹq·à‡¸¾®KÑ7<DZêu�çÌÓeýŸò®_Ëk¢KÈcqƒìÏÉÊù�K“î{�Ð]“dÝ�$%»Ä�ËA44�· ƒY}m:¥µF“Òp#³È(0!˜cÞw¤>Þ…¢Vu�[dæb5ãnÑ�¦v(SÙcI"Ê€r3ôŒ"39Ê$-Ö6ó ®\3 Ù {»½xWk:ÝWÔšøkõnã}èTP[ã‡V¦9Ga Á°´‡ÝJ!ƒGì¦Èë—…šíB_Ä£å,w¢¬ÐsÕ]ºû_BvOµa];V�u>»r¬zæÂ¸7ë¹'¥d˜‚GÙ8½J|7‹lÈ®XõU +"WíM| DmŠd£ù8Îê¡w”%lƒ¼!üú$»I¤5Ñ[üQh´ì‡°Þ€0!íg¨7e6zô™£Õ+‘×ÂJ—ñfî ±hu°H+´�57":Õ2¬,PP-+5KI*ŸM§GÕ›Ÿ5 1Âè,¬†%éî«îµ¯ d²`Xhw±_qYDeQ^³æË½bƒ=ã®ß“Çí'�ý;þzŒUk TÉg[ç�òJÓ*FÛ_µ¿Âˆnc,¾öÁ[ lh¿$5“ô.Myõ�%Bþë’©kIÜf.«®Aöá¾dİȾâ[ß).^Ðc¿þTÜ H1w1œ£€ÅË=Ôýô|R–´ž°¾ï{M¦ï†L¿cØkN•áë® ˜.á4\–R%Ê©jNEèªælY{ ªÇ/…ê_Ç‚ èvl“CJ†ÀWd¬œ.‡sÔÐfTõšžMn2uƒñ?; ßä®(k.²’´>… .„5/¯ ¼ia¼qÎGW�MðÖréËÒ ‰zAŒ#©ÒÚr]æµb[^XØ ï§»3ÖŒ1«œNÈÐþ¹`mdÙŒ'Ú?Ó³ÿH•y–%(<ð ¸Í?U!ǽÀÅMÝ9E�t[ÕÞ1Å +?o~ìAÅ;Ìý& ®¼JÏ8ïVxØÌ¸�¯ùõHsÐÚ¯ÜEb•Ëù ”Ú©c%ïÔöŠù‚^×ÕÚ^´¶¯[ÿ¢�a U\c_‘swßEUw³úÖ90ëh¢êmœr¦ØÀ鹇ó�›³z d³d5?êž ¶_,^h—¼DXO€Õ~|�Q‚™r ‰Þ$1î–Î&´ßërã£òe’øÚHX8�>”ã½¥Þ8Ý\Éí¿Üþ·Aÿ–wi§\±�¾¿[´îÒZçëÞ¡N1žø›‘iÊÊ]hÏMÚκgžšWÿA‡ã ˜òhv�2NÄôÄÉ´ÖôN»mÚ­ùo{0;"TÇlcÎZ¥‘;5fC·lMcÄ:ÜœÆä(§Ç£|X©.'Ƭ,©ªJ3¼8îuÍ«‚qCÙg²Xt׬*ྉգÄ5/ŒÀ=“Èñ ²ª\CÚ™È(lQÔÀÝ�&àr„²ª[Æj­¿ùÍú¸ÿ0ÉØ`¬ë ß‰`úÓIXn¹9d×;8µ«gÀëT´3Õ­ß|9¤"óöu‡î¨zÎ|ýÔ=?=ù÷ þ¦kPLUVa&›´1äתR�¨y¶%Ãáüì¿ë“ãW½;£í6Z…7µÇùµf¶rµ:Ý¥î›ë{Žûÿâ”Y¢ü×¢þ 2Žþu÷Ÿl��Áßy4ÊÒÜ#ÖkM(G±ÉAvÿi q_<8ÕÎÜzŠW�нþ¹UµÕè�,Ô¦Kɬù“/kÿõ6jýþîÑÖ=ýÐýÁéI6”ÿ~!æÅ|_“:B¢äý†–¥¶k.ÿ`[(jiTy¡6øŽS�^!¶ª½»ô@V>» Z¦òœLt6*®Mgé°K`¥7TÍ×W-DŸFåGx'=ÐÔÛ­¼JMÙf«îeë9Ñ%¢?¸„ö)â¦6ò3Ê=å°µîèŽÍJvàŠ*ƒt•QJ~áaa¢V¬R àêþ«oÜÂ…˜é�ƒiÕô~- Nÿô‹’§\�j¬Å÷%ù(VQ\²Uu¬b(#ˆ^§ƒè)½+D\Öå×Q®æs8óÓ³ß:úÿÐË€H¶¿Šûö?÷uþî[½Õá¹m¬Pn¶qiD C�Ƹ¶¯jm ^ãIgÿÍù™Ó¢ÉøM­6º¯/ãÑŒöã¼®4YopzòòôüM¸žxÌ<,xJ6C»I®7-bT cé§ãs´•(tTgÕË|ÔÅ.Xû>SåF¦®3:v»[¹1–û_‹3}y’/ñ•½ü_,O/Êâû´¤Àˆ¹†‚k•) iÓêL¹ÛKvéêÕºÚÈ#ÔÞÒ‡©»½=äíå‚o´Yƒâ®âÙŒ …sÚ’ 4çãiä&“Ó[”™¬UpÈáà [ö3@¤´Ä¾yÓ2ðœ1.>ÏõûëE’z4)xÖÞŒ×UÓ'$s¡ÂÓe·M[œ4kÿ�'cÚ['�A/&„2{¸=º÷›+Þÿ­Åä¾—xœÍX[oÚH~çWœ²J,Å4[õ¡Ù¬–$d×Z* [õ �íÁŒjfÜ™1mûß{flǘ¸IJ.ÊHû̹~çb��œ<Ö‚–SsZ­´àÏ9‹(è…¤$èøH±D_ÄÉÂ…6wgù u�~ƒ¿„Qd08˸̧\ÑP‰º(ôbâã_¶Ó†ÿ¨TLp8êt¡aêÙV½ylTlDK².4$ÊøÃXÏè•Oc Œƒ/–qÄ÷)¬™^X;™–ŽÑñ)Ó!#ih¥±–ÍÚÿ5À…Y¹‚(³Zékæ6 /ÓºFb�.¼:A±&¤J̲C=¾¦¡3˜«ÆédÖ�±`¿Bz9µ¡~A°ßƒ*»qf°Þ¾V•­ÂÿФ:‘æ$R4¥~«mѵL�üíññ^ xÛXlö‡à÷Û´ˆ …b¥òD©´ò€új§ÑF]µwToÀŽ¢œÈÀ“`�‡EõàÞGî(åVÞË›JYCÕ>„‚ýn3*G06Lrº(•6}¡¿TÁ–b2ñu†b«1W‚]Ç-ʾÄÒleùÎc.›?Û„Í1qIf¡�ƒZ 0=³ ®ãUös½lZÞFNl4¶±.:–iHÂ%ËÞÚc¿V2H¿ÐÌslDsûÊ e€v¯Õ’^‘´,Àìã‘ä¤,u¤jx7Ât<" ø‘+!ƒ*$ê䩃Ü: ½fû¸ü|ÕÅ¢â=l…ló^;d|LR®Øïõ£I´Ï ‹XˆDšõXm:ëx�öÐ% ~½æDÔæªÞRë/Ñ�ÚV�Õv³.C6€I\†Õž°•ÕÒä?ÐÌPZ´ò†ïßNF­ Òçlä–<�zâ; îMæ~¸f{Ò#¦ë¶)_•ý Á¦5uà†q 9ñkÏx¶›ØŒÏD9Ó‡ÞÏȾ%R~žœ– àÊoüÜ!`ÒÞu8§yˆÇO¦X1™ð:ɇ·ùá&“}LñÅJ$·6Ú�CÑ‹ éHEw_ Ö…í¯­½�Õ9ÿ � ÀÁê„ËåepJþ\œ"Ôf·m:B§4Áâ©»Åã£^#R– Vss¥ÈPÄkà�-¯»sÙ]†\Aøæ~�¬«xœ340031QpöMÌNõÉ,.)Ö+©(a8rì_Ê´âU¶ŸÍtÚ…æ†ø• lb )‰%‰ U›äIzz.2oâû5sß{\b1('3))±85>)5/9#7±([/™a×®6‰%]‹’‹›¶8„õ,.˜ÛœQ†¦¼$µ¸¨òP̵‰ö‡~8œ3ñí´0ÿŸü * r€®ƒ©;×,·¡úÄùŠ[D® ý6m ýY3±¸4© (?9µ¸¬8>-13§´(¨IôË3»°»jŸWßM?+6ËîÜånSš2óÒ�:Â>+®æ8~½;Kéú�åìgßZ1 ‡ŽâÌô‚Ì�5,œÅo$ÏWŸ«4}“žý‹Ÿ,[qi*Mq€šâÕ6ŸÞ-¥wׂOøýV§Ð/‰swzÀΩ�¿œxœµ•QoÓ0Çßû)N«4Úi/ ²4@D—NIºiO–“\R‹4¶CW!¾;ç,][hËãe�}çÿýî|9Ÿ€+ë•ÅÜÀÅùÅø(eQ"L&nï¤w‘b¥1ƒ¦ÊP�™#85Oé§³œÁ *-dÃs8µýÎÔ¼#…•l`ÁWPI�F’rA1ð>ÅÚ€¨ •‹º¼J–ÂÌÛ0�È�$î: ™NÞœükZåÛ~ÀM <7¦ÖoG£år9ä-éPªbT>xéÑÄw½ ò^më?«JÔ~m„¢<“ðš`RžbÉ— ðB!ÙŒ´°K%Œ¨Š3Ð27K®�T2¡�Icv*µF£„·¨V¼‚¾�õáÒ‰üèŒ4nýøÓtí†Nû^ÓÜi0öcÐê8Á|öƒñ Õ‰Âà}­,?A [CÌlÁ"Ä€\>éS‘‹”òªŠ†…ü†ª¢t FµÚÞ¤&¼ŒTJ±†›vç·¤†½Ï2†÷˜6ÆÖê´t"I¸FfP›_–ÃtÐ3\hX)ª/ô'Q\ Ô{Î]‡þ�{Ö‹mjÅò’z@˜!æM>d(E]Tsê"›µÂ’2øFå éŒ>\StB,Ã\T¢ÍrÊì’:†þ¹ŒXìE1;±ÃÆ~ø¾ÿê»{å|ö˜; C/ˆY4�…®gm?úƒÞ?†¹ô'¼Ûhck¾†«t¾àê˾½']ȶÂsne”n’ZÉ”šµMšÍ©lE; óÝß0å\”�ÂÖg‘mÔ^N‹¢õ>¸Îò,¸�ÚËÀ5©]¶<îQí¯àh’H÷imGvÎkõÖñOATiÙdôÝÓ«’Ù†ÙÄXG€c÷¨?øÃÙ…^Ï�§Ï©‘�ˆëbeNçÊÛNô]]9ÁxgsðË�­ÄÖî›-’ù©X®fÇ2£Ç…ɺÍa`ïžÒ벤 WÈt7²;$©bÓk/´Ïbks™¸“ÙØc·Ÿœ˜ÝMglµõÜÉNÊ'Åzœxÿ;àN3ük$¬2ªñ ÷\¹¦ xœ340031QHLJNÑ+©(aX³¯íø‰©m&jqµJ¯ùÌytêaC¨š¢¢ÄJ½‚œÌâyïÎ;^Ko{¾ñˆú•2}IÞÖø*¨²”Ìä¨*–¯_ÌblVXòf,mµ»0uã«L¨ªô´Š¤Ò´øÔÂÒÄœb½‚¼tîâjñSâÑŒ7�Î7“—w«ÈT½U\\R”™—5tMniðD¾ÿI1B“NÝ{n¿ïÞeàÚMé5xœKLJNám•¿ xœ-�M Â0„÷9Å,Ф=€‹^@7‚ëØ>0MÊKZâÝ}¥Ý óóÁ8‡%M� ð̾ZÜCB?3S,¡â|¹‚â#qO(/Bö£˜:‘UZhl€sðÈ…ßñi¥Öy¾ïÓîZ³QÝZ·3¿’>Tœ „mÍOõÝ&0̽ xœÓ×WHÎÏÍMÍ+ÑãªæRPÈN­4P°UP*KÌ)M5P²VÐGR â忥" —&¥d&—5U+ ©g”›fˆ.]k�.¢TXš_’š¢ Ô¦ÓfÍUË¥¯²U I2ƒ·xœë ðsçå’âb``àõôp ÒŒ ÌÁ$'”ßRlIÞî. ÿApÁÞå“�"<ž.Ž!3“3 ˜TþsñŸgðtõsYç”Єi½xœÓ×WÏ,ÉPHTHÎÏÍMÍ+ÑãJËÌI�/KÌ)MŠ„ ºèxœíZQsÛ6~ׯØ23ެ*’ÏÍSÜtªØJNSGΉrréµÃ�HHBC‘,ZÖdüß» BI‘²ìØ�»9óA°»Àîb÷ÃP·¯ê�V·ÑmµЂŸ§Âçà‹É„ÅÜQx–Îå‰Àêt­Æ3x¨t»[ 5о {£Ï©ªå±74½rð G£Óê¹p O›ä°8æR5צ(héïHILÚ-nœ»†U»ðm"u¦˜ƒY©u:^Ú:¡ÃDAÇÕY¯=efp‘Aeþt5ŒQsΙþjCŠÿó;¼†¯ Àç+(™ öX©V;oÜ´‹L¨ÎšnÈSæ€ /ÏÏÓO¸iܛƴð1Wk�Ñìo JÖ˜Bc¾ÎÉî�›Z£jŽU®uy~k•¦Lø‰äû.*æX$¢ýÙ×E„¹#»ã.<ãö®bj‰àRs�»s&!ÿ¤¬LÎLÀ€5“äO‹ùÑœYëÅ>iT`ö`œL"j•øµ’ì–p4K°Ž6ÓÜ Hãú5›in³¥ÎÈØÒfÛÚÕ%öuw­˜vuYHw–2¶SeGìïòßî(ˤ‰°c%R‚õ˜Kc@¬³šnºý!'r¢‰kw|ì�o?;ý5×âmÐÈгíþhì /Æ´M�¿ù{Í×nÉöºÂ¾Ë¶hËÞ†MüuLöÜ&qœJôþ�I<‚öVcàsŽ~Ï…v~—îæ"8?¬•\ qœ"Žœ4RŠWr¦xš0å™Û›Vš¥Õμ™æt:[5ÿxtÙÏ õAÀ 7┉4Ö_X’ ET• 2iAçÉbÂe:ÉrNïÍïJ†Sõ …ž³ …‘ C¦‹Ÿ³0xŽH”Ä«XÅEðG‚Ø´døNİ�UʧŠÕ >ôÿ C|GÕjB<+^òFp%d,x œ+& ,vç˾´6²ìË7F§}ÛvúÃ�Tf~ºý‚ïô¦|JÙ ¥“^Œ“ûg˜) )ÃȨ̀ {¤ ;z€�#óT¬<|u¦^›~r)ñgUPÌ81Å™cRÅrâ™ü&k½U0I¦N6 6Ê4zÍolèäŠã—¿ö©BÛ cÐ+ dJTOLJíšØÐ÷Ÿ8È[\µ öx„ QLò^ÖcéÓ¡TÿWP“¤¿˜N¯@CÂo�µ‘Þø CLõI´¿hÎ+Ðï¸å™ŒÑå™n�æ¢éL'ïÁzc×ßî;Ï Èß #³7..‚üëÓ÷g`Ý ¬Óu±žpõ WŸpµÏî„§˜€‹¦ÿ÷(Zäú-ï‚�[©Þó]P ÐMO“’]àüG'øõ#ŸÀ÷ß‹‚}äRLW æŒÂ±•î†èX̨ lF—’ËP~‰7@ö͈½/j?rß½w øÅï…ä÷Eóµë};0ï�êÅÞFö"}Ot/ ÝŽðJP¾F‰ÝH_£D=ÚW¬T séÙ®bé¹ËÐ ß?,@4=õÁƒ(V³9˜™+6ˆ 'Öl5L›m€nö?RyèÿœÀ{‚9œt&Ù¢ 2¡?ø>$ÙÿMô}(eÛ95¶"L»íüM]«…]W[g/©×Œq«¿ãÂ1K'½e ·zBêMr{Pv‘®]dœ^u+Õ¯òβ R[»HQáB¸uÄ+_IÎk¨ÿ3áI-Õ÷m´ÕD“ÕDºz®!ͦ×õr†x-jª.«‹~Xç'\’ÔÇõTÅÜ/;èWú5 ’×Y…±Ÿ(á×Që. kÙªÒ¢¨š$UËA»F!%W‰ ò‰[ ~´M–PbàÖ’F.‹¡xtñVü§×�Ó›ró/_ ütë Ž1xœëòmâœ�ê–”š—œ‘›X”­—Ì¥ ¥àã+¤å)äd&%%§*hdæ)”d¤‚ÄròÓJó2KJR‹K’�ÒÅš[Û™&G3sM>¬TÄ¥`«P]kÍ¥  0ù‡ˆ¾–BqjIqt,HXA-©8>=­"©4-nsq”y%Û"ki‘ÞB ¦·SrèïèŸê¥¢ÒÚ·iuyì̼7£§ýýgçç«»ó½ªÔž8©¢½óî×í‡ÝO²Ô“l~ÌXK›¢¬¥ÂQ@&«|]ï_¿x³lZ׺)ÞJ5ÕFÉÎh0ñþÉè„÷?~î² z= Œ„�âÑ-+pÿýåû¿EÕgï ’xœuޱ Â0„ÁEÈSÜ$6C â¤tìè".RÒöÚD’?X7''Áwp÷ ­"8yãÇ}Ç]W�ñm· ±:xWS%SàR+ÓFOi- ±¨§:²ªZ‚v?�kbK ½a ïñµQ»æyQ#�I‰5qô6@Y�÷ïVC ™ c�2všà$0Äʘϖâ,D&‘ç(l§ñ÷)îj’¼EûG«ì”*xœm�±nÂ@D¥�":‰"eºQŠÈX‘IÚ "¥HQš” t¶ûÐùÖº;$D™&U, À§qÐÐd»�Ùyšýýhoþæ3פµåŒœ[xr~QJS$™@Œ¯°‚6”5^¦š°d‹Ë5*ÎM/˜ÕÐ̵2Å#r&ç òûŸÕU¯fÝM{Ÿ)³NÊW!†qŒ7Lae_J�µÒ†¾Éžs â¡PÆ£’ÊDl¬KZDÞ6¤®íß=? Fgk'v'.Æc¼›¼Äÿo¡½�ºÃýõQ�S´í €\xœ›ÈqŸ}B.{qfzAfAêÄB=�ÌœœÔ…¤J�Ä¢¤Ì’¢Ä¢ÌœJ…äŒüâÔcÒªØ`ÞÆµ»,…ÿ/ûßÖ%›¬[i--2õ­ë#D+ZÔ–;\±)IhÕ¹­öáPÓ,¢¯'gÉñÑɇÑiÔ\Þxë�WBéÕíÝ‹~~Ùpl‰ÚZÑõ<š¨‡=u!HÌ{X±áóå„)¬$ßù9=n‡˜³ÂPRk‰·[çZ¾°¯Óþ‡Jcm£b£F¥®§t~0~„l‹GÛ÷~|èYWÝý>"1E•â¿ ÷ÏövÝ«ü›¬Î £xœ340031QpöMÌNõÉ,.)Ö+©(a¨Š7Õò­›—Þ˜¸g¹­JóIÛ³í !* r€ªâ ‹ŠSõ’ø¾¯}ó®ÓMTµ?¶‰¡*ã‚Ç”ÈýË‘­ì„¥xœÛ/üŠC@YÁ9¿ ²(3=£DÁÈÀÈt£€“nbJJ|jEjriIbRNª—‚BANfqI|AbQq**O/yr»„7†’€ Ï0ÇW +'3)),eŃU"ñ“ós 2sR‹âÓrÓ‹'[°Ê #3j²«³’&×dQ n$ùÉQ¬å©êE©“¯q„ñ£:Car-§œ�“§Ÿ‹gP-Psj^Jfš†&—Q͵xœ­VQoÛ6~÷¯¸zHašœ¸Áâd›ë(�0Å,»M×-Q6™ÔH*®[ä¿÷HI‰Œd™”¶t<~÷ÝÇ;Rýœý¨½~«ßëµ ¿§,£�gLé('RQ7F³�‰E¾•l¹Òæm\¿Ààpp ï„X⺠WÞ‹)W4�‚'T‚^Qå$Æ¿jÆ�÷T*&8 ÜCè‡v5Õî ÄV°&[àBC¡(b0–ýÓ\ã‹5r%<¦°azeãT(®ÁøXaˆ…&èNÀ$"m:Ñé•Ö¹:é÷7›�K,[WÈe?+ýT?ðÇÞ$ô~FÆÕŠ9ϨR é¿“˜íb $GF1Y ÏŒl@H KIqN Ãx#™f|逩ÞI L‚rK¶(ôŽ`5?L»é€’íQ~؆·£ÐòÁŸýq5ŸÁ‡Ñt:šÌ|/„«)Œ¯&çþÌ¿šàÛŒ&áOrîE¹0ý’K“ÒdFJšXÝBJw(¤¢¤¤r³”Ř_dIa)n©ä˜äT®™2[ª�`b`2¶fšhkz”— Ôoµ~b<Ί„©ÒÉBˆÌ]ýºkd\?¶‰G¦Œ-Œ­aDË‚(Ú¯þwWÔ“¶ÖíBl�ø÷‡d Ø×‡´E¡óBC²å‹"M©t se2‹±¢¸ÒÖ=Ò°f<²+ÏàNOáè—a«v,0�7tÚ uá¥H¸êxhy@X ]Ú±`±ÒרjÆ8¬\k\¤,�…ŠÐ€¸ø«>}F�o-ü6ŒFÓwÑÜŸÌÞ :­:^»°í<˜ý,UÓq›½/X«ƒ=’òxpᜦ¤Èô »  c°A£Ëѵ¯»N“Zèa™N¼ ƒæ»*ñKÓ§¹KIÖÈ+ÇD.p[@SUåŒRb_3Þ1˜wìTû¯ˆ„^M·ÝV©K¡óªÉ&Ð1B95‰Ë«s/ò®gÓQô~̽—ˆ¤[©iF "1jT(,ÿÖ•Òú*slUCR]HÞµ?‹.F~0Ÿzåä]ëžÓ^�YîÍ©OïqÛsæüø!¢ù�wƒgÙ}:üü‚  •g‘XüCc�ÕÒ«žr-±dì\,)Ñ´vI¥XGåM`NÝŽ xT4LæA€·< =›ÊA˜dŸP7íuÓîÞ4 ´»4öË;*;7ª[x¸SÕ$Ãë¼._œûNv ôÿöL%w÷Knî wààkaº.´eƒm§ðHa1£¦¤éZÈíovŸc2l”äÞ\Ò´ÓØŒH„ç.ÈÛº* Óú4*Û—& ä»FscãW!÷`eÅ—·{L“R)kþBAŸ ¿#d½q)î\ç±ÃÿIX݆ ¦æ(OK®%�›MÊØ•!£|©Wá§€Nð†isdކ i…Ò†,¦Ú0Ÿl¡ç)ýQ€fxƒÐ•±êiÊOXíZ!C4b1r;Æ_$| xQ×npYx^©ü ª½…tw s{Ê[ÙÅŸ6Fzi%Ö§Ý€otE j0aY\`êÙñ18÷­ìî‰F%�]‚zhÐH”Y¿l‚ÜúkÈ¥i|•·î†ß C©Þl—Îö!w¸iæí_jD˜¼Úæ>VÝ&ó«ÝgÍš>ÎÓ‹5ì)l¹Κû‡œ°)d°®8ëÕˆ!½%™Ïö{Ès*øn4æx�¼¿©Ë�{‚êmžü/VõÁÓîK‰ÜJ0¥ÛIìHZúd—¤nµ_z#$¼2G;’Ä…ïœì%P6r#ÂMï‰g8HD­xw-:ÿke^'s˜5l"ðH"vÊ/¬-€¥Eo'×’Ów² Zop„Qÿm|W(ÿŽOV–¢ãIjⵦ ¶¨¶8- sê …q$Îsbdù@‰9J-ÀT::lÜ,n„ iìÉ\GèÒ4’W[›?ºC+š—±â÷ų[:ÀöU¶ÎŽË`´¢†ª”Д94…<,å0'i; ¢zÓtÒþ¥2¼Öã~BCMØ?ik²ÕV¢ÌX•µæsD\ÄD=è½e¥¿)dî¸àY:Zƪ@H¾©fò³PàR¿ˆ�Þ±�A>:O:^¨Š·hÒßí¿`py½'IW´OivˆÙ�ŸÕ÷Oç/Ä|êÖ­pTæUÊå:Ù“ÇW�ÎÓƒsìªVó!«Ç׋j"×ܧ±˜Ú¨šZ“^WT¤Â’«�†rwr¤jI]™W (`ä¦%ÖT=g¿VËa·3_&³Ë».Ý8ïµBUôa�¹Hh¢ÒÉÜx¡ï%«GPáÖ}w“3i(öZ•5uø'”^ã¥ÂÓQØi1TÅ‹gK’Ø¿~¦A–„“ØiCûÁK}kí#9²ÿ»nž É èvƒå;�ÛÜ„¤–*žðü¼Bv5�ì×0d)î&‚ýá­T –wÁ:eº´±N²~?²Ùz �ÙzJ>¢BL ûä�EZíª”ÙH5z_5*M~žg�a¼§=$ƒÉ=À›>z² oRÂåÎYn‘ï¬�ïÇVãt}k­O08!��TÏSæ`¶A¢˜9�3x[Šæ ð}¢,é·”B¿�Ezà‚šXîþh ¶ç|÷†"í�n1Ê„K°•ö_ò{ÙC¿%æ6ž÷©U¼÷‰[Vm2Ó•Ç2ZÁÔ"ÎÌòçüŠLßì0Ķ_€ëÉÖš’Mzð+‹Dý9cà:u‡Ÿádò¶@eë�TKäÿ@’‹;ß]ıo9ï.n�Fk°*�n„ ßXÆÜ)²ç]T�w¨¿—â!áö�ãZ‰«O�è‘Ff‘ Y`ƒd±ÁßÙpÚMp]” pyšåu÷ÕÌ&ñOß�x:h甓MJó0ävˆ{¡_ßÏzç¢Õ¥Åd*¾BÁCê÷ŽŒÖ5±£Ü ¥í4Y!L–�`'µ¦¡ Úžªn–Z+k¥~µþÄ]œ´”�Øf*«jr?‹¿aæ¯Ká‚mYÃÎtN«Óö¤‰Jó‘Rì`âuþÐÖ7â¬muS‘ÿb\“ ÊÞç0_¾ÞÓ¬¾†ÇÈ•†à‘* …¬ó ŕñ½ 5žº‹ŠZ·ÅÃ(î$$“=±Â°¯„–œP±ÓÇ"€�°"i•‹²6Æã_aò\As愉”�W ²€à…»-�‚PóY�q8{_«†Âµ^X»�yB˜Ì(¾ PY´ÀµñäáÍüNÁj5GŒ›£'𽿌W¸ {è®h]2ˆ*@å°(,ÊÎþ¹õ×H\Ç3‰¼{ïa–ÛÏ˺º†¤Š¢cs‚´@VŒ£p�ƒhvÂ\Ö‘ü¼UðÃl‘í–VO¨òNÿ»¨'š‹+äÚ)í`¼üDo“ÄÆþd–sª=ó6,‡œ1UÀú`#Æ©Àº˜{EnÉé ¾JÀäqÊÏÓbâ�Þ½Ô^£å+`ºdäz®çA«•×9{ä¾Âæ5¾zåˆÉ¦¨énö®+™:Ü íùÄ|÷Hn ŸÕ²E´Ò�ì¦ð‘©ß·DÞ"ËÂ:ƒ^L‰­ËåðÏ'“I…Í�Cæ>¿ŵz‡Îñ<úŒÌG/I7¿^Û†dTÑ*b>óÁÃ*`ùé‘¥Z:L9m#mŒ÷�bîP6\­Qfå>…²¤òø.¨^•× ”ÖEë­I ŸhñÑ\êfÀ�¦8@\Ô4øòHÝÓl �ÒaÈr•jn‡ì"Oü�ÇÔAm,pR$Ç4’GÃ<¾…ðVýtç´ƒCµ¦³Æ‡`’øÙ�º0'”ºìF`ñ­\¹!ÄÞ1Œbë õ>ùÈ.—3okˆß>«Rû†¯Ö¾Ï™¼Ñ©S7–�­û4OÂR NdhWÛJÓΗR6ƒ7±Lì0ÉûYæL÷7¿‰ßõ|ØŠ¿“Æýù¶õwøs24ƒuÂ_ÿX¶³ó³µ¿ÌEa®·w¼õ’Äyôò¶^9ÉÖ͇�•<½2Zq��—½ˆK Cm~k_ a“œËõjuºÆd”"n¯<�=”wáÊrÜݨ�à3tƒ_ Ð>p]<5l¼¨I‹r®M®Ð-üàË"8þJ+ÎèdÇØh‡¡áƒíâF8b?Yˆ’3ÝVë`ˆ˜$ÁÎZ¸£¡…€äa�n¯!õCh|4*¨ 0çîB�ªm»‹×{H¤Ïþé$>“<ÕxƒÇ”v�ÓßPŠ}ô¿0Ú·)/;—}‹�³ °‹y‘·šÜ,“LH•ö!ÌÀ´~ЋėC-6™Îþ… �âÄ � DK3ƒ¶8Ù.m –¡µâµ˜!"£±aôÕ0Âdáªõ”!š'‰Æ FD$FAj¢²¥dªÊhBÞ‰lo(ÚÌýÒKhƒl…]£–‹ÐŠ}Q&é§ØÆáò®D2ͼ“¨‹÷G†Ÿ¾Üx#?Ö”\2ˆ‚Ë~ÕήNéú·Ø¯ êÿà Ù܉\;V)�H ¯ºÉ¹o¦&‡@ÝÒ'{Co#¨´¶uÐÄ�œçëRO£¯ âh—Ÿ0wlmaker-0.8.1/.git/modules/submodules/libbase/refs/0000755000175100017510000000000015222270711021622 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/refs/tags/0000755000175100017510000000000015222270711022560 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/refs/remotes/0000755000175100017510000000000015222270711023300 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/refs/remotes/origin/0000755000175100017510000000000015222270711024567 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/refs/remotes/origin/HEAD0000644000175100017510000000003615222270711025212 0ustar runnerrunnerref: refs/remotes/origin/main wlmaker-0.8.1/.git/modules/submodules/libbase/refs/heads/0000755000175100017510000000000015222270711022706 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/libbase/refs/heads/main0000644000175100017510000000005115222270711023551 0ustar runnerrunner54e60555cae53ad99314c7579dce1ad38373ecaa wlmaker-0.8.1/.git/modules/submodules/inih/0000755000175100017510000000000015222270713020213 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/index0000644000175100017510000001305315222270712021246 0ustar runnerrunnerDIRC=jIqÊ/öññjIqÊ/öññ|0Û�¤éémž§/º‰³yÀ|˜Ü6‰¤aê$ð.gitattributesjIqÊ047jIqÊ047|0Ý�¤ééºü}2ŸÒþ�åÀ­\{÷Ÿ4é×^.github/FUNDING.ymljIqÊ0 ¸PjIqÊ0 ¸P|0ß�¤ééA‹m…'.v×–m�õCt ³6æ.github/workflows/tests.ymljIqÊ0 ¸PjIqÊ0 ¸P|0à�¤éé" ûµZÐúÑÅ:W3”í—kXˆŒh .gitignorejIqÊ0 ¸PjIqÊ0 ¸P|0á�¤ééæË~âÐð’ÿ{¸¤'{¤ýà†Ø LICENSE.txtjIqÊ0 ¸PjIqÊ0 ¸P|0â�¤éé&Ç�¸�p*OŒ ó¦f1Òã-©6 README.mdjIqÊ0 ¸PjIqÊ0 ¸P|0ä�¤éé¸ÙV}jDþà�¥¸í�vîE# Öcpp/INIReader.cppjIqÊ0pÉjIqÊ0pÉ|0å�¤ééÉ�¬ ÒP]S•+ySŒÊÞàÐY^cpp/INIReader.hjIqÊ0pÉjIqÊ0pÉ|0ç�¤ééV@c°~ë TaŠBo¹¡…~examples/INIReaderExample.cppjIqÊ0_NjIqÊ0_N|0è�¤éé}Z9¯€;éiD¬ð٘ͳBØ1#examples/INIReaderExampleErrors.cppjIqÊ0_NjIqÊ0_N|0é�¤éé�a% ¯¸D¾U¹’š ˆ]µÆÅexamples/config.defjIqÊ0_NjIqÊ0_N|0ê�íéé@ò«ÈÿuP7P@*÷GÕî-9ß~examples/cpptest.shjIqÊ0_NjIqÊ0_N|0ë�¤ééuViÔ æ³ ¿G ”™‰1XÀˆgÞ€examples/cpptest.txtjIqÊ0_NjIqÊ0_N|0ì�¤éé–Ü;›Ãx‰~4�½)ëbšuqgÃïexamples/cpptesterrors.txtjIqÊ0_NjIqÊ0_N|0í�¤ééÔ‡%>á-°$ŽVST¾R¼µ`¡×$@examples/ini_dump.cjIqÊ0_NjIqÊ0_N|0î�¤éé.”Ç4YäïЄ!Ù}¥¦Ó/¢d‡ïexamples/ini_example.cjIqÊ0_NjIqÊ0_N|0ï�¤éé™(¸)Z9ãw@vçÑ1/¾Ìýåexamples/ini_xmacros.cjIqÊ0_NjIqÊ0_N|0ð�¤éé“»yì[®¥“ÿ•¶Ø*´nq/y)4examples/meson.buildjIqÊ0_NjIqÊ0_N|0ñ�¤ééže¤Íþ¾ö,|ð�X7æþÚŠCÏŽ'examples/test.inijIqÊ0_NjIqÊ0_N|0ó�íééLejvðÕ¾¹þ´–[D�ˆ�Jð8+fuzzing/build.shjIqÊ0�jIqÊ0�|0ô�íééW¼Œ4 æ1c— ÞoUB1°é³†fuzzing/fuzz.shjIqÊ0�jIqÊ0�|0õ�¤éé³2c¤Ú5`.&WöÎQ]¨�fuzzing/inihfuzz.cjIqÊ0�jIqÊ0�|0÷�¤ééH™öo[QÑNÏ5 „Ñ|ôC4°Qfuzzing/testcases/case1.inijIqÊ0�jIqÊ0�|0ø�¤éé#çºu�¡nSq|‡Bg©.��ްÂini.cjIqÊ0�jIqÊ0�|0ù�¤ééªHðÍѯÁÒgûÐÄû 5wÈini.hjIqÊ0�jIqÊ0�|0ú�¤éé=>Ö„ùyQ�]ßöÝ€º«;¦eÈ meson.buildjIqÊ0�jIqÊ0�|0û�¤ééuËüV©TЏ­)ôyÜžˆ|è´meson_options.txtjIqÊ0�jIqÊ0�|0ý�¤ééÔº´®‹ÝÀOí¼ïÓ]©ó€>ØO5tests/bad_comment.inijIqÊ0¯ûjIqÊ0¯û|0þ�¤éé >ÃBòxaô–0aü¸¨Nfítests/bad_multi.inijIqÊ0¯ûjIqÊ0¯û|0ÿ�¤ééGhšN\wÛ:�(ÞYRNjŽEÔD'c.tests/baseline_call_handler_on_new_section.txtjIqÊ0¯ûjIqÊ0¯û|1�¤éé¬ý•ÍÝWóãêÒÓ¾˜Ùefn!ÖJÓ+tests/baseline_disallow_inline_comments.txtjIqÊ0¯ûjIqÊ0¯û|1�¤ééj¶6#~8!�RÔ©‚ñ}­�&›!tests/baseline_handler_lineno.txtjIqÊ0¯ûjIqÊ0¯û|1�¤ééËüÿš¯XôQ9 œ´»æX¶'—tests/baseline_heap.txtjIqÊ0¯ûjIqÊ0¯û|1�¤éé%¹WßKfhbÙ{ü©‚¼l<£Œ0 tests/baseline_heap_max_line.txtjIqÊ0¯ûjIqÊ0¯û|1�¤ééËüÿš¯XôQ9 œ´»æX¶'—tests/baseline_heap_realloc.txtjIqÊ0¯ûjIqÊ0¯û|1�¤éé%¹WßKfhbÙ{ü©‚¼l<£Œ0(tests/baseline_heap_realloc_max_line.txtjIqÊ0¯ûjIqÊ0¯û|1 �¤ééSQn|¿tÿûРV"•æ¢ykllÊtests/baseline_heap_string.txtjIqÊ0¯ûjIqÊ0¯û|1 �¤ééËüÿš¯XôQ9 œ´»æX¶'—tests/baseline_multi.txtjIqÊ0¯ûjIqÊ0¯û|1 �¤éé%¹WßKfhbÙ{ü©‚¼l<£Œ0!tests/baseline_multi_max_line.txtjIqÊ0¯ûjIqÊ0¯û|1 �¤één³O1Œ÷Ù�žˆ=M¬ü.ÀŒ3#}tests/baseline_single.txtjIqÊ0¯ûjIqÊ0¯û|1 �¤éétÉ¿ê>³­¼½æJ˜ Îì_§�&tests/baseline_stop_on_first_error.txtjIqÊ0¯ûjIqÊ0¯û|1�¤ééSQn|¿tÿûРV"•æ¢ykllÊtests/baseline_string.txtjIqÊ0¯ûjIqÊ0¯û|1�¤éé6DÅôuúì£zô™ÝøÁä´r7C, tests/bom.inijIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤ééMhYŸÚk°{ßE3f£ð@;뇘tests/duplicate_sections.inijIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤éé6…ÓKj@%æq}wéx�<êOtests/long_line.inijIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤éénÊmcâå\ûf¢Â7í�°GÍ êH tests/long_section.inijIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤éézâžæºÇ!·(®ýG“éï:!†Ktests/meson.buildjIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤ééèݨÒé&(Ë€‰[ :_‘í«tests/multi_line.inijIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤ééÿì—½$•OÎ@؇Ÿù̬ê<`tests/name_only_after_error.inijIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤ééV!ì“sygnïâ‡óù2}oR™tests/no_value.inijIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤ééíQE€ÊÒÒ`¡¹„´§¨‹SÅi õtests/normal.inijIqÊ0$¸ÃjIqÊ0$¸Ã|1�íéé@“?H,Ès+öÂ^ŒéŸ¬þxÜÄÂtests/runtest.shjIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤ééì}· Vå*ÊñgéZ›2ȼœ�3Ntests/unittest.batjIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤éé�§Mf$œpù&ßMCvz’|Ò_tests/unittest.cjIqÊ0$¸ÃjIqÊ0$¸Ã|1�íéé O,oXu�‹«íH–ujüMe¢JÆòQtests/unittest.shjIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤éé¬1]SâA9gW˜9,ОíÞðºtests/unittest_alloc.cjIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤éééj΢nsñU©ùD2‡¶ÍC ätests/unittest_string.cjIqÊ0$¸ÃjIqÊ0$¸Ã|1�¤éé)e–8x\»ýÆmu'ãZ[å"+娼tests/user_error.iniTREEû61 5 3xpGÀCuQUe°Ÿ+¿‘éb‘cpp2 0 CÏ ª‚:GNªÜæ¿éQˆÏëÏtests34 0 ›Fµ‘ë&u `ù.ƒ§ŒÉfhž.github2 1 àýêþ``AðoµÎÚåjÝ9™gworkflows1 0 «iÄñ{<öf p¬°ÂÙNÚ¬îfuzzing4 1 º-ë ;#©�ר²ÃY+‘8kOtestcases1 0 Ò)ä!íVA)ޫР„øë°™½examples11 0 SµlêÀªZ¥ƒÇË2â3ËМîÚ`�­VåêÇ©ËÈUIÞwlmaker-0.8.1/.git/modules/submodules/inih/HEAD0000644000175100017510000000005115222270712020632 0ustar runnerrunner26254ee9de7681f8825433415443e7116ff24b98 wlmaker-0.8.1/.git/modules/submodules/inih/logs/0000755000175100017510000000000015222270711021155 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/logs/HEAD0000644000175100017510000000074315222270712021606 0ustar runnerrunner0000000000000000000000000000000000000000 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c runner 1783198153 +0000 clone: from https://github.com/benhoyt/inih.git 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c 26254ee9de7681f8825433415443e7116ff24b98 runner 1783198154 +0000 checkout: moving from master to 26254ee9de7681f8825433415443e7116ff24b98 wlmaker-0.8.1/.git/modules/submodules/inih/logs/refs/0000755000175100017510000000000015222270711022114 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/logs/refs/remotes/0000755000175100017510000000000015222270711023572 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/logs/refs/remotes/origin/0000755000175100017510000000000015222270711025061 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/logs/refs/remotes/origin/HEAD0000644000175100017510000000034515222270711025507 0ustar runnerrunner0000000000000000000000000000000000000000 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c runner 1783198153 +0000 clone: from https://github.com/benhoyt/inih.git wlmaker-0.8.1/.git/modules/submodules/inih/logs/refs/heads/0000755000175100017510000000000015222270711023200 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/logs/refs/heads/master0000644000175100017510000000034515222270711024420 0ustar runnerrunner0000000000000000000000000000000000000000 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c runner 1783198153 +0000 clone: from https://github.com/benhoyt/inih.git wlmaker-0.8.1/.git/modules/submodules/inih/shallow0000644000175100017510000000012215222270712021601 0ustar runnerrunner26254ee9de7681f8825433415443e7116ff24b98 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c wlmaker-0.8.1/.git/modules/submodules/inih/info/0000755000175100017510000000000015222270711021144 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/info/exclude0000755000175100017510000000036015222270711022522 0ustar runnerrunner# git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ wlmaker-0.8.1/.git/modules/submodules/inih/config0000644000175100017510000000176315222270713021412 0ustar runnerrunner[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true worktree = ../../../../submodules/inih [remote "origin"] url = https://github.com/benhoyt/inih.git fetch = +refs/heads/master:refs/remotes/origin/master [branch "master"] remote = origin merge = refs/heads/master [gc] auto = 0 [http "https://github.com/"] extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUpoZFhSb2JtUWlMQ0poZW1NaU9sc2ljMmwwWlM4eU1EUTVNemcwTURrNU9TSmRMQ0psZUhBaU9qRTNPRE15TURFM05UQXNJbWxoZENJNk1UYzRNekU1T0RFMU1Dd2lhWE56SWpvaVoybDBhSFZpSWl3aWFuUnBJam9pT0RKbFpXTmxZMlV0T1RkbE5TMDBOakkwTFRreVlqWXROVFJoWVdZNE16aGhaVGhsSWl3aWMzVmlJam9pYVc1MFpXZHlZWFJwYjI0dk1UVXpOamdpTENKMlpYSWlPako5LmYtN0ljd1lxcDhaUUMzYlV4bWlFa3NzN0pqdzVFZ19tdVduU3JXWVMtMDNzM0ROS2Q0Wi1Kd3dzaHRvUnB0X0FnVm96amdaRGhmZDlOMElOU1ExVndn [url "https://github.com/"] insteadOf = git@github.com: insteadOf = org-130065133@github.com: wlmaker-0.8.1/.git/modules/submodules/inih/packed-refs0000644000175100017510000000016215222270711022317 0ustar runnerrunner# pack-refs with: peeled fully-peeled sorted 577ae2dee1f0d9c2d11c7f10375c1715f3d6940c refs/remotes/origin/master wlmaker-0.8.1/.git/modules/submodules/inih/description0000755000175100017510000000011115222270711022453 0ustar runnerrunnerUnnamed repository; edit this file 'description' to name the repository. wlmaker-0.8.1/.git/modules/submodules/inih/hooks/0000755000175100017510000000000015222270711021334 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/hooks/pre-commit.sample0000755000175100017510000000316115222270711024617 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=$(git hash-object -t tree /dev/null) fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --type=bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff-index --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- wlmaker-0.8.1/.git/modules/submodules/inih/hooks/push-to-checkout.sample0000755000175100017510000000533715222270711025754 0ustar runnerrunner#!/bin/sh # An example hook script to update a checked-out tree on a git push. # # This hook is invoked by git-receive-pack(1) when it reacts to git # push and updates reference(s) in its repository, and when the push # tries to update the branch that is currently checked out and the # receive.denyCurrentBranch configuration variable is set to # updateInstead. # # By default, such a push is refused if the working tree and the index # of the remote repository has any difference from the currently # checked out commit; when both the working tree and the index match # the current commit, they are updated to match the newly pushed tip # of the branch. This hook is to be used to override the default # behaviour; however the code below reimplements the default behaviour # as a starting point for convenient modification. # # The hook receives the commit with which the tip of the current # branch is going to be updated: commit=$1 # It can exit with a non-zero status to refuse the push (when it does # so, it must not modify the index or the working tree). die () { echo >&2 "$*" exit 1 } # Or it can make any necessary changes to the working tree and to the # index to bring them to the desired state when the tip of the current # branch is updated to the new commit, and exit with a zero status. # # For example, the hook can simply run git read-tree -u -m HEAD "$1" # in order to emulate git fetch that is run in the reverse direction # with git push, as the two-tree form of git read-tree -u -m is # essentially the same as git switch or git checkout that switches # branches while keeping the local changes in the working tree that do # not interfere with the difference between the branches. # The below is a more-or-less exact translation to shell of the C code # for the default behaviour for git's push-to-checkout hook defined in # the push_to_deploy() function in builtin/receive-pack.c. # # Note that the hook will be executed from the repository directory, # not from the working tree, so if you want to perform operations on # the working tree, you will have to adapt your code accordingly, e.g. # by adding "cd .." or using relative paths. if ! git update-index -q --ignore-submodules --refresh then die "Up-to-date check failed" fi if ! git diff-files --quiet --ignore-submodules -- then die "Working directory has unstaged changes" fi # This is a rough translation of: # # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX if git cat-file -e HEAD 2>/dev/null then head=HEAD else head=$(git hash-object -t tree --stdin &2 exit 1 } unset GIT_DIR GIT_WORK_TREE cd "$worktree" && if grep -q "^diff --git " "$1" then validate_patch "$1" else validate_cover_letter "$1" fi && if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" then git config --unset-all sendemail.validateWorktree && trap 'git worktree remove -ff "$worktree"' EXIT && validate_series fi wlmaker-0.8.1/.git/modules/submodules/inih/hooks/prepare-commit-msg.sample0000755000175100017510000000272415222270711026257 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first one removes the # "# Please enter the commit message..." help message. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. COMMIT_MSG_FILE=$1 COMMIT_SOURCE=$2 SHA1=$3 /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" # case "$COMMIT_SOURCE,$SHA1" in # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; # *) ;; # esac # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" # if test -z "$COMMIT_SOURCE" # then # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" # fi wlmaker-0.8.1/.git/modules/submodules/inih/hooks/update.sample0000755000175100017510000000710215222270711024024 0ustar runnerrunner#!/bin/sh # # An example hook script to block unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --type=bool hooks.allowunannotated) allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) denycreatebranch=$(git config --type=bool hooks.denycreatebranch) allowdeletetag=$(git config --type=bool hooks.allowdeletetag) allowmodifytag=$(git config --type=bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero=$(git hash-object --stdin &2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 wlmaker-0.8.1/.git/modules/submodules/inih/hooks/pre-rebase.sample0000755000175100017510000001144215222270711024571 0ustar runnerrunner#!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up to date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END wlmaker-0.8.1/.git/modules/submodules/inih/hooks/applypatch-msg.sample0000755000175100017510000000073615222270711025501 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup commitmsg="$(git rev-parse --git-path hooks/commit-msg)" test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} : wlmaker-0.8.1/.git/modules/submodules/inih/hooks/commit-msg.sample0000755000175100017510000000366415222270711024627 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines and messages that # would confuse 'git am'. ret=0 test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. ret=1 } comment_re="$( { git config --get-regexp "^core\.comment(char|string)\$" || echo '#' } | sed -n -e ' ${ s/^[^ ]* // s|[][*./\]|\\&|g s/^auto$/[#;@!$%^&|:]/ p }' )" scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$" comment_line="^${comment_re}.*" blank_line='^[ ]*$' # Disallow lines starting with "diff -" or "Index: " in the body of the # message. Stop looking if we see a scissors line. line="$(sed -n -e " # Skip comments and blank lines at the start of the file. /${scissors_line}/q /${comment_line}/d /${blank_line}/d # The first paragraph will become the subject header so # does not need to be checked. : subject n /${scissors_line}/q /${blank_line}/!b subject # Check the body of the message for problematic # prefixes. : body n /${scissors_line}/q /${comment_line}/b body /^diff -/{p;q;} /^Index: /{p;q;} b body " "$1")" if test -n "$line" then echo >&2 "Message contains a diff that will confuse 'git am'." echo >&2 "To fix this indent the diff." ret=1 fi exit $ret wlmaker-0.8.1/.git/modules/submodules/inih/hooks/pre-receive.sample0000755000175100017510000000104015222270711024743 0ustar runnerrunner#!/bin/sh # # An example hook script to make use of push options. # The example simply echoes all push options that start with 'echoback=' # and rejects all pushes when the "reject" push option is used. # # To enable this hook, rename this file to "pre-receive". if test -n "$GIT_PUSH_OPTION_COUNT" then i=0 while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" do eval "value=\$GIT_PUSH_OPTION_$i" case "$value" in echoback=*) echo "echo from the pre-receive-hook: ${value#*=}" >&2 ;; reject) exit 1 esac i=$((i + 1)) done fi wlmaker-0.8.1/.git/modules/submodules/inih/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315222270711026355 0ustar runnerrunner#!/usr/bin/perl use strict; use warnings; use IPC::Open2; # An example hook script to integrate Watchman # (https://facebook.github.io/watchman/) with git to speed up detecting # new and modified files. # # The hook is passed a version (currently 2) and last update token # formatted as a string and outputs to stdout a new update token and # all files that have been modified since the update token. Paths must # be relative to the root of the working tree and separated by a single NUL. # # To enable this hook, rename this file to "query-watchman" and set # 'git config core.fsmonitor .git/hooks/query-watchman' # my ($version, $last_update_token) = @ARGV; # Uncomment for debugging # print STDERR "$0 $version $last_update_token\n"; # Check the hook interface version if ($version ne 2) { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; } my $git_work_tree = get_working_dir(); my $json_pkg; eval { require JSON::XS; $json_pkg = "JSON::XS"; 1; } or do { require JSON::PP; $json_pkg = "JSON::PP"; }; launch_watchman(); sub launch_watchman { my $o = watchman_query(); if (is_work_tree_watched($o)) { output_result($o->{clock}, @{$o->{files}}); } } sub output_result { my ($clockid, @files) = @_; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # binmode $fh, ":utf8"; # print $fh "$clockid\n@files\n"; # close $fh; binmode STDOUT, ":utf8"; print $clockid; print "\0"; local $, = "\0"; print @files; } sub watchman_clock { my $response = qx/watchman clock "$git_work_tree"/; die "Failed to get clock id on '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; return $json_pkg->new->utf8->decode($response); } sub watchman_query { my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') or die "open2() failed: $!\n" . "Falling back to scanning...\n"; # In the query expression below we're asking for names of files that # changed since $last_update_token but not from the .git folder. # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the # output to file names only. Then we're using the "expression" term to # further constrain the results. my $last_update_line = ""; if (substr($last_update_token, 0, 1) eq "c") { $last_update_token = "\"$last_update_token\""; $last_update_line = qq[\n"since": $last_update_token,]; } my $query = <<" END"; ["query", "$git_work_tree", {$last_update_line "fields": ["name"], "expression": ["not", ["dirname", ".git"]] }] END # Uncomment for debugging the watchman query # open (my $fh, ">", ".git/watchman-query.json"); # print $fh $query; # close $fh; print CHLD_IN $query; close CHLD_IN; my $response = do {local $/; }; # Uncomment for debugging the watch response # open ($fh, ">", ".git/watchman-response.json"); # print $fh $response; # close $fh; die "Watchman: command returned no output.\n" . "Falling back to scanning...\n" if $response eq ""; die "Watchman: command returned invalid output: $response\n" . "Falling back to scanning...\n" unless $response =~ /^\{/; return $json_pkg->new->utf8->decode($response); } sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; $output = $json_pkg->new->utf8->decode($response); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # close $fh; # Watchman will always return all files on the first query so # return the fast "everything is dirty" flag to git and do the # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); $error = $o->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); return 0; } die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; return 1; } sub get_working_dir { my $working_dir; if ($^O =~ 'msys' || $^O =~ 'cygwin') { $working_dir = Win32::GetCwd(); $working_dir =~ tr/\\/\//; } else { require Cwd; $working_dir = Cwd::cwd(); } return $working_dir; } wlmaker-0.8.1/.git/modules/submodules/inih/hooks/post-update.sample0000755000175100017510000000027515222270711025013 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info wlmaker-0.8.1/.git/modules/submodules/inih/hooks/pre-merge-commit.sample0000755000175100017510000000064015222270711025713 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git merge" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message to # stderr if it wants to stop the merge commit. # # To enable this hook, rename this file to "pre-merge-commit". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" : wlmaker-0.8.1/.git/modules/submodules/inih/hooks/pre-push.sample0000755000175100017510000000253615222270711024313 0ustar runnerrunner#!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 wlmaker-0.8.1/.git/modules/submodules/inih/objects/0000755000175100017510000000000015222270712021643 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/objects/ba/0000755000175100017510000000000015222270712022225 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/objects/ba/758fa16e7f53717c10874267a92e90908eb0c20000444000175100017510000000621015222270712027074 0ustar runnerrunnerx¥Zko·½Ÿõ+h¨%YR”½@œ:€£*­pm9°ì¶€k,V+ÊÚ›Õ®îî*±ûøï÷œ!¹/ËNÒ E$ó13œ9s8$;�’¹zõâÕ‹=ïª0WªßWY¸ÞDZ &Ó‰Z†øµñÓL§­Öìý¿öOÃ@Ç™îO:ÎÃe¨Ó#õvöCÿÛþ(ò·™nµFÉæ> oW¹j�:êåpøªÿrøò»žz«cõSrŸ·Z¢*ÌTª#ígz¡¶ñB§*_i5ÕŸ(OEF�jgZ«ÓÉh<��ù]Þ¨•ã? Þ¤Éu�·VÉšfÞjµLRµNR�Õ,“£Vk•ç›ìèùóÛ0_mçƒ Y?Ÿëx+žÓˆV«û¼Õz.ÕB/ÃX/ÚÞÙläý<¾è¨o¾Q{EëèâÒ›�GWcozîýrr1�LœuZÏÌåíÐz¦ãE¸¤Š8ˆ¶ ­¾ÏòE˜ Vo*MA~¿Ñõ¦,OÃø–må¸}Xo’0Ι^&i¦W§ ®ÃØÏ‘-~.©d…2«ŒÈ�ºÐù6…Y&vI PÊa›¶YcÖ³=ÓiÀv�G›‚ߨŒéf²”v{gá-²M&uÚÝ~Ÿó:qý×å¬cuðÛðÀø.µ¶Àiâk[eiË©8‰û §Ñ5U_í^N”}àj*qË é>½†¬jvxX³Û¸©“}Î|zSµ\þÈ: ñ ãÄD­×`ÜæbztFUÁø"¨ô`‰[0\¬Á�€Á2ÛÔ¤.—ˆ¡,€f“‚ ïLjøMú�…RªoÃŒ¿ýLùμݾ³.ûÅ›LO'Ó±7:?;O/g‚@[}ò3Ï$ɱ¿[𙘵÷D úóOµÔ«TâÐv5 ³À°×.ųv<�¨«÷Þ_ŒßM~ÏŒ”ŽÍ|H¨Ùó(æ¡Ù˜Ê^þ¥‘8EŒI /YCU{M–ì>f¨ ÄY¸#`Ü€•ÆÁæ¾§æ[¢'Û¦:öˆ¤’ýD *ê ‰i@Öw$¢=åm‹DgñAŽ\¨OÀ=BV×ÐÒµÔ�¥A}G±¤Ö%ÛC&DE‹‘0%ÆŽ`�A½¬E2uˆFƒ²3SJ [­ž½zÑ!yÓ3––C íÜ3|­Bõ½ôª¾zAþ‚Q×á ÚKª¢íhÅ Û-BËÖÆ.Ç37‹$Ø2µÁ¯IÌü^iŸÕk/É'BœŒ[lQÚ_·…‚Í@l†ßSf/†kÑÏ !ôVKQö[È‚K®Ì<ø+uŒgöFæõÒS5‡C…J¸¶éy˜¸�ä?b�½O¶àPFžˆ_]Þ– U2 QØÅúšéÄݘù|coðöïF1o3ز&—pm»ÑtC³~‹ªfSB1¯{¬lDÙÌ£_t¿©™R±Z‘Ód7€ß.«á)ˤr2îvª*4çÝĦõ28–3Å4&³¿�W6ñó—ä ù]¤†Óc í:iBêÐúÝB¤tRßvtJ´[´Ô´Qð?ÔõØpÖpTh«,­Ì9g"×'¬ E@ÿdi@®ïlØ(jp’Ò¢§aø)6‘0Š\ýJqB%N>]ðÏÁVÏ2á9So$˶üUqÜŽ4«³'Ìù‡ãHÕ8¥îcös1†dm¾Ý,8°Ø<)üÈî³#h%ŽpFrãd§ÊiAs_z{~VŒ£%ÆJê•r§qLI×C±kx7~·ƒœR~ïœmV5¼{ûöïÌ~iu¿}×Ì>³JÀõÛÒÇ4�UÎ’ðð7Î{üiN~Ò‰ ÓU ×„I<<»¸vÁ•�=ŠÔÎÜšqªø£˜IýÜØÑO–}QáÎs–õ9# nª“zÙÀ܇3Ý¢~`íÒ5+“Ó tà¸M4¡.°~€1ç'Šå›•åÂݧ5QÑ3ûMécÎ|pVg#??ÛÙPî°£dþ…{¦8¹Ï#?þ`B Ü.xÛ‚lµ× =ÜÈhœnqèD’‡ñVÊæš¯!Õ*=&ÛL±:È””@ȶLmP¿¿ÏW¨¶!cÞš NÜ,V‚A»˜{Åv�úaj¾žˆ—Žˈ3�QÁc4ð¢e€MHI×vJxëDí_[[nö�ÇÆQ@�ô=µ³_¡t·rWpóÀy4ì2y…dáélˆ\©“­£ŒJVû ZÛÅ£pª±O±üT O~äQ£¶!}E¢ó¹¼ñ;‰”³kYµEã ,7/ii¤0GVت5 üÅÉFE{ÎLpiQ/í’ÆÐ€j¯~æŸý(\ìÂ�\Hâú8îÖÄE=ÅÆÆㆯÍÕª™ej°UÒ£¾�³Ù&&€‰óÁQ%?iF™²Ï#)ê̆磟ON¯Ì]ŹO-ô®Ñ}%†?ï+sp|bϢ懴æî&�]üe2ôËŠà°.ýŽÒÕù /Yï=ð÷»ÉÅìÒ_\à9Ëé¥Î*mU\Ñ`åí¸ƒ ¶«§5;ITØ“šhøwu¼Âk¿›œŽ»r›·ûRÎÞÛUï߬zZX»ô«Üúu–·:ÇíUô\Úôx-ʇ:óHòõ·‹µKyŠ6‰ECšw‰;Ì.Wj¶žâZÉž)D¼L6¸™(¥ï§n$±£’;3ƒ‡{‡ÈÒ5.Âz�–A”df€%È]±=áÅ+Þ´Ì…êrKÅÂ[q6ÉY¬ïÌÅ^%“56En%¨MçÛåR§u¹27ã¼Ã#š„‰úÛ¼Ä§Äæ‘çF©<±)5^ØÄ÷&in=¡”(ÂC×w¼Õ_·y—ž1òI±«ð Àñ+uì‹Øî|i;ÝŸf„E±8õïf{­ÊÓŸg*[°îb*â €¿ÍWm' ß¼�4â”í$þ)_Ù8uUðªÍÿÛ` zä„ÑÆçs4g×¼+Ã>‹Jü˜¬1&¸" ÆœZ(kbùÄSe{vv²Kî©o ¨ôd±3š¥Ôÿ-v…ÿÓyYwlmaker-0.8.1/.git/modules/submodules/inih/objects/info/0000755000175100017510000000000015222270711022575 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/objects/26/0000755000175100017510000000000015222270712022072 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/objects/26/254ee9de7681f8825433415443e7116ff24b980000444000175100017510000000026415222270712026676 0ustar runnerrunnerx•ŽMj!F³öµ þW C³Ê5Ô*3 ­¶Èíã²ûx^îµî´Å·9˜ÁÜPZÌÒtN9ç]’¡è” – ”çàuPâ· d³¡¼iK[¡ˆÌ2SήDÚb°”Ö›-ŠxÍgðà_ýwÂ=q{®ñù]ã~Ür¯ Ð¡×Æk ïJK)]u“ÿë‰ÇU_Pùìí–®ý øáqî½Áìà5”U2øàx²øÈËN·wlmaker-0.8.1/.git/modules/submodules/inih/objects/pack/0000755000175100017510000000000015222270711022560 5ustar runnerrunner././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootwlmaker-0.8.1/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7dbf.revwlmaker-0.8.1/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7db0000444000175100017510000000047015222270711030720 0ustar runnerrunnerRIDX3)0+7$9>:#& 1/2 '  6(8-@!?.,4"5;<%=*  œJQÇ©&—˜&ÔÛ«ýc@œ}¿š5Õæx»NÄö÷¹–•â’Ô“ ç././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootwlmaker-0.8.1/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7dbf.packwlmaker-0.8.1/.git/modules/submodules/inih/objects/pack/pack-9c4a51c7a9269711982613d4dbabfd63409c7db0000444000175100017510000007144015222270711030725 0ustar runnerrunnerPACKAœExœuTIÏ£V¼ó+ž”K"”Ϭ�&ÑÆ˜ÕÏ€�ñ�}1³~ý8ÉrHúÒRuU«Õ­ê±OSslÇi&°4O$|Ì%|ÊQäéf,ÍBš�§‚½Â>mFÀ§Ì`Äq›0 GFI ž “�'#È%4G³LÈÆX8�EÛƒ}ù,Ǿ(|…Iûj§ºÀ©ÏÂæ“¿ÑͰ…ÿdý›ô}Ò~øjÚ>}ÕëW^ŽÅ}ÅíóO@rŸ‘HŠdX€$A`ô£Ó¨åxœ"ðí§ìûÿÉHú#Ë_ùPæà÷¿BRTÍHEÀÕT[ô.Žò7Ž ,Ã!–DQ’Eñ,�õWæÉoÙ‘ÉðÌM{)jQ»L9‹s£ÃÃõ¶²´§Îoü@6—ì\ŠP·xéŒô³4Ú.yª{5ÔRÕ§q°,îy…g‰zšLeRYÕi¹IœwVn‡%GD¥kÎÝÕêKq ¶°XÑ:1æë¶ƒbÀh vƒšWg�Éì¸êÕ‘÷Ü�ŠÂëR"ß„R€±®«óÃU•LE:ɯÁ%‘è|à”„…L‚êMŽø$ûK-¡“Ó¤Ú{ß[õi'X{�ºZ5oÌiw3�æù¥ ½éºÝƒ2ëmaPc_�“»“Lêíí.úû¢^ú~¾Î³î#añ�Kkn’ÞÆÆH¸Ô½9 äÎð¸f�i9ãèÆj3Ya¬9êD”kV®Åï �T î8ÁoçŠQS|j6]¾»E·¬Ÿ[øYY71*ž�³ñÉ® r•Ѩ×ÁžóïÒÌH¶yãje)gzñÝô°û`Åñ¥„�» $FTS•™Bºvg�D�¶W–ÁÛf„m2ù€çSIªœ®I/ñ)Ø�ð)to›½-c@äÏnsrȽyºÝ÷G¸ØøcíÑûÇнÿoG`âð1çÚØÓaŸOÏÏwÀ¯¿P$ñ†ýÇ;b’§xœ340031QÐKÏ,I,))ÊL*-I-f˜·\W'ÓæÊ538î˜u.I|¥òÁÄÀ 3J“¸ü}õ/!ÁñCþÖs·žf‰Ýµœ™nˆ0+3=/¿(•�ó÷Ö¨ ¿.µ 7žòvº`vDGOT•�§³«_°«^IE ÃéºGÄ?Nú_½c‰zµô’¿Ún@•¹:ºøºêå¦0ôî˜[À'£å/ÖÃûyYšá¥Çº+Í nJ.(`p>Ï»ªÉÊÝ�aÕ�gû_vœ}"›Z‘˜[�ôTðÖ±Wr$øWE-m>~Úè‘ñé %i¥UU™yé »t_/°V^)×}ŦÑ,Ú-²ý¡îÈÌËÔKfÈ甼ñ²71fµÿÉåÚÂgmÏûsBR�ÁÀ¾ªÞãÃÙ‹ë^Jÿ}áÈonyÓòP¹©ÅùyzI¥™9) vÒ×Z~VÎ�•¸ÿínîÕÖËRQ”Åç”dæçCBGäOØÊú�®lk5¿TÞ™×Qób ÄÝÀ¨*)f˜íÆ´uâkµR.Ž„ŸzÍË{N¦eÌçÌÄ<½xœË1 Â@Ð:9Å; s«`aa%ØÊ„üdÆv~Ðã‹éß;à¹5âvŒ¯S‰OU� ‘ö"¼Á˜k¬ ¬[rF��ÉÚdîý°÷a²ä?=�'}Õu|ûÙ—þ`%•«xœK´ÿ100644 FUNDING.ymlºü}2ŸÒþ�åÀ­\{÷Ÿ4é×^40000 workflows«iÄñ{<öf p¬°ÂÙNÚ¬î�H!�°xœKÏ,É(M²RHJÍËȯ,á3åá¥xœ340031Q(I-.)Ö«ÌÍapìÎmUÑ+»>-·ñ«³D ×f³gâq ˜¶1xœÕ‘ÏN„0Æï}Šy�7®NÄ@½c t¥ZÚÊÌ›øðB!vc¿9ócà}Ÿ?}ú’C¡'XØ·�‘ÃRуÓ^»=9ÈÔ‚I�|¦ÖDPÀ÷JðGVÑ Š ´�ß(¸»#‹ww@š 7}Z *%pl¹ª ‚ �bTæÀš²n+ÖÌñ‹Ó*h¸‚š-™Â6ÅsP zË®cÀg°¤¢\࿤`5S›Ä7cª‰\3$#°"B±²­‰€U+V\R@éYÅdY¶¤Õ²##ÐGÚ(� R×W'E!¤¨é �TLÐREÉ×W‰  †:¹¢%‹úDQ0›a&Su(eRªP€f ­lÐëÝQZ,�Ä/¥ub¥•A•sB0†°]TÇ6ÂÔ ·ˆ„G =°´U±åÓŽýa¹Ô«/!}„¼5%pUÀWKÛÜÐ*2°‘ÀÍ|Qð� päÖ`F¹ƒþìä'LºŒ$³!Ð:a‰h&`¦ãT�š¶/ô8‹Ò±[ÛSù ’?Ng3Av‘‚À$‚‰ý  G ÚI"hÄE¶Š2û¢"Ö°º,L 4ÃóéÅÙ~(Ìâx>jïeytwK'«�üz„æÖݤdÚÙTZ”0èô ;—+嬷 Xƒ-ËŒDM|úeÓÉ50÷Îl²Äȶ*QÒ‚"øºb™²ˆa@RB. Èð~m6x<‰ƒ8§ïnþÅú©-~‘˜U°(J»†àd¿M¾>8øÛäà»ÉS*£ÂØÉ�ç§ñ%ý<½½=q ~b;�‚ä„ÄÞ^£ ðW˜¬¥Ð¦×´9È–"Áa�ä…Ê �§úX-ÁAì!°'fì' m .ľØß¿lœÉûÌ!„²·[$ á݈O°ež›ŽL_¬Ga�è*®ÃB;*1ÖV.ÈÉbpØñ; ~'׿—w·Ó‹éÕÙñAxhw·çãïÄÛëË�ˆÀ.Ak †zø×¯¥Ê"%Ogçâàéí[üï|(¤óeábeþPmšfÄ7„At—Ý‚ò°tw)«!„G¡ˆÖ²€\ó§B™^¡Dæ§×——gW·3D":Ήf —[±YëhÝ�Ø¡gUfÏ�xS ñ�.ùùû›³óéOg³šÝ’›åø³¹^à9¤€¼G.ë�Ô¼*j¥³ ±€:¤À#æ ÄØBÇ>.¶ÈZ¶ª�ÏnOnn?ŽûÁˆÌpp݉cŠCãÁHÌʩ֋| �C C‡!ÆXÄ‹ÁŽ,ŠÀÖ�䣀ˆíR±;Ôyu=ÿçÉÅÝÙñëЧ„Ò$H…Fã¤Áˆ,#³Ê¡D¯î..ökô-’áKÇž9“#ø¥. ëä�¢xP*·X‘²Z@уZÁïè$B.Á¨ºŒ[¤^mò"j�Ôá~v{ý~~}5?ŸÞÌnçg77×7 ‚J[7  ÉWd%¤°â…‰‘ß`F𠛌e³W0G {¥*OІ8ACäoÓ%eæLa‰´–®ô‡“«wg7st‹«ëÆSTO¥à6S›*™ï”)lˆÑ ÖN%ÁQ_aäÚ´­¨`°ª�Ô|Æb°^Y‹� ;¼�‚ Ö ‚2®Î>Ìgg§·Óë+d]o§ñã@5Ãb± ”ªd,–¿|M 5q�…! ýÛ°2^„ØÞEˆu ÅG/š—ö~É•ÎNsGsx´b­d¾S)ú¼B{Xê'�­þ ¸!cY”Ë%ëÇg‰è¡vï6Uo�vASzm(¶$ð[c/ð»ÙªÓ[ äR>é´LùÐDe+·F¨·@Úc…òìÅ1À*†PaPBC”6�Ob�˜Éª·¬~�hý *`—'?‘…¿>8ÀŒ½Œ"_ààZ½ÁÈÀ�AïßpK|(4Ù CAĘ@IáÏDµðçÌÇ6\Š6¹S;-Áb(|dÝ'¢éíôä‚Âäi艕Ñ/7B¾‹,뀅Áº¢E@�t:iŒét-dA1Ø¡Èa+ßE;¬©2¤åNÆeqÞ ìPW——ÏäOËGâÊE‚Èô’âúzF'·ÜœÑí˜ÅU¯Gú<øïâxTYË=ƒÚ5¨ã*[ý. ¥¼†C/ʰ �«sUÕr]¤×8^8Êé$ˆK–ÑÉ-å1 vÈŸk²L®p ˆç¡…#IÌ£AŽ¿ZèÚë¨#¿ý „;äõ‡#_aѹkéó EZl·¥+ 46ØÐºd¬Áƒ8\yÞ©©@R@3ž¨'IŸØóSá†aÔÛãX‰7@Q›`ý}÷Ò}† TçY?p¶¬û½žÛæ`=Kì³ÊÈõ~Cp¤9,`:¢Ðk“XKî“ _>U©ÔÉQïÁ�|YkG½žÅ2"’>“ �Ž÷Ñ@ïmu+üü¸÷ì_{冡GßA°/rþ-ŽÅ ûfˆŽz´gÏÎåÉíép° «å"JóA� èãcq ¾ü²zÅ-{ÆÏYrK1`}h»œ‰LÒ‰¾f8¿×¼x`ãïý[@¾ Ì ‹ø¡ì/²IâÙ¡GéóAÆeþ™äH¡Gï>I°ÙT(°ûL 1gôÖ•f',0ªÅþ„)ôZÛ^ƒõzh+pd6À/²XE#¯føþxÿï�zö¿¼:‘ÁfšÓlj]¿�Qoƒ#ñ%oŠ7â Ã5¸‹[ú§KHš ¼ªö¿úTxôœ××G-Fêílt¸Â 32‡•wóàçø °9’3|ƒS‹g˜�ß0ª~³$ýÚè�Õ IBÄðį¾ªã †®x_] (ãë*?Ïn/(æã;?ïÃ"_ÚíØ™1†ï{ˆ�7k•r@ÌóV¬Ó†CíX×:«ß2­Êˆ^xJ4ÀàèEÅ£^2œa˰xa@#Œ34åÁ #‚@|xá”èÍñQSú´%=£ñyöÔ)a«‡ ¢›B¾Bw…¦‘¿ÒQÞ"?IêY‚Ï»«¯®?\õ+lÌŸI„£Ï*¹þ4 Ð]Ò�Éu‡ Ý?þ #o�I”̼ ¾ABTL©VÕ ßºÅbÍO÷щNÞOý(³ ÏZi®žʨ[ÈõI²/¡gZ©êÓW)½ËɃðF$àgì\[W”ý”$583á™=w®‰�R,p’‹ûr³ƒ¦¡òzt�™ï y¼Ó³s­’˜žÔUÈKIªfÀÙ—ÈÍ!7T;Üü-àŽÁË”<ýcË®g3?ó*ŠIï4–Å8š³lÏíÉ E©0*âÖ²…rE× Xnÿψn… ‚ŒËDçûÔe7Ý95ç–® 2?b¹¹­{×5™W½4Qw•Ù4Þ!€ ê¡PµeÐé{5�“ÒÜmýô ÜiÖÔ›h--"º5…Ì<žËœÎJÙÔáœþb††O7)PÇB(ÉeDç7ù6ØŠõ‰íÓÏ`a�˜F}§Ë÷N¶?Pù"ÑçdœY)L+”\¶ÏÅcÎc çô ¾A‰<ÀúA™íÀÁ–ô>‘6¤cß$FèRЧåûâƒÎb¼¶ƒÏo¾ÆªÓÒtøõ·49á1b~TÉ–œ \�Å$î2�—i q·¶­Qu2ß,M®2�_©õKT•M³OÞ’hwĦŒ+ UÖ 7XðÌ + kybúRY´˜£81S|,J�Ä!CÑLºP¿–º`ô˜Åñ„¯hù�E^¹±´Õk´BˆdÜ¡tFÁí‘(óf½!Ã!I®�1òÜV¡éâá’çG¾ˆSÏ®b$-^V`Lá¹Ñø�?xî½ô˜»‹Pø.ƒã”í ¥à‚î~7÷àŽ—Pߨ°Ù6jܘ?¬Æ¾y {ˆÌ˜Èªb"x‡:~Î늢¢¾õl?Ó'XqÕccl÷Ì lOpî»ëæù©/åމ½ð%_û�êcœ|À;1æ3Æ=YÄÜÓ Ü“ûïêµÛ‡•sj¡¤éŠÍtÆ"¢†ÍtWÞU'Ms ̯!òJ‹HTç±ÊŸSægðŸÊbÈs[ºÑCK±~°¡WkW‹‡¦<s"¼ÛÆëˆZC ÄIqâ%ŽÒ>2GõÀËG�ìÜãÃwoƒÙÀïx�M�“í cCÏ9jLFr…ûNu‚S^É&_µ»þŠð™—±àhÆ\ÛØpì€0žq�ÐqŠ]Q:¯Þøïsi�s÷ý«Qè¯(Hö‰ŽT†b@þàÐË¡]üøQsM—é_8÷ÏÃ.qkü‹š<ßyyítŠ+Þw4cóW=8£×¹æ  ] [Ÿ��ÐxédKó3$|߬l´Ò< ÕW¥[¼'“qm–gŒø í}‹Â¥ ¦0@°Abƒå(añ×oÃæ!ÁÙf=BV‡Ü„ý2òíT¿Gú‡âwÑÿ;|ô�RúšºlVM>ªr&Yd‘y6À _ ¾htõ=SY¾­Â)‘�ØSçžœã¯ÖÈ#Ú×ã÷´~gDL5ÞÆ›¥›ÐšÉ°í¢©ÌÐȹ+„]Ð ã_jì sÙ¥À+_Ä –~“…1Œ[æc^f×þs¤±w£ ÉsñìmË—x¤K4©_d¡’Ê)O£S!-°ŽšMu\ŠÓÉE•ÿðv¶„ð¾å¿5ÀÀ`ÁÓe×�­À.*@¤ =ý…¸ç»ºn¶¶¤Š#‡î‡4†´SèÏ„5¬Z æ ÓX ¶ìF7*¡#~Ua'Gú0Dae±ó´—޵ñ_"L­ÅêEõ4�8£ªX@Y™+n^†bÀ'Ð_v�N0�W‡y«öþ!üû xœ340031Qðôó JMLI-ÒK.(`¸V›åòOþAïÒoç–½seSæ¼fˆ®0ƒ�µq ×¥€Ø`é©Ú•Á=§î=¸“Oظ›xœÕYíoÛ6ÿî¿âª­Ôز“uf7.Ú$hŒeY±ìå¶B %ÚÖ"“‚H%õ¶þï»#)Y’�6yÖ=š‰t:ÞËïŽÇ;f8„8K€ ˜]Î`‘fR¡%p¦6-,޹R ØšoXVrÈYZ¨°×áêÍéiÌ…âƒYÂ…N)/Æðêêtðåà$c¥â†óDæ›"]®4ø'�Fß ŽFG_õáp.7Úp¥"]¡) ÙBfñXÁ¡àšÃ(‘ôŠÃ%¿%%�Yåà+Îábvrvyuêw:IÞk è ñç…ü�ÇVrM,9,dkY�» 9FnZ°Ò:Wãáp™êU9c¹ιX¡}C²­×û"qV&ž³l) d[OÄ8Ö›œ·(J'Y:o�¼0$aáÊkÐj‡‰Ü+U*–€+Çc¥ |žôz5Çx\?ú±Jƒåyl¢G� zö"^èå1áå¬PܯXÂ8ÂU~ЇŸ)¨çzÆ‹>b•ª`Ò{ÿA}ñŠðt^.´D¥ðHƒ}�èÍêßÕYC#4a©W~% ±òns0)¡aÒ’wF üŒUÎç‚ë²N¹Yi•î_üæ6&CG&ÂlQÙŸ*È¥Juzƒ©¢aÍ™Pô€tJ¬,D¹žcbJ·«4Æão%Ì ’q\OBø½B…e–ÀœÓÖ“7¼È$IÒÈk¦[@T‚R‘¤1Ó¸àÎ* á \‘X }U Í‹u* —”OÑ*ŠxÐf+q·˜¾²ÔH5;;4ôt¾s} £,$ h=ã[å”°xp`óUKe'$ÀÞÖ©29ýäí²ãÉñ“ÞÄHÆíÃ\ð%«0ÇWj£4_í¯­» Œ\DTi •õA¡g†ÚëCŒ5Gã‡JÁæX÷°T°,“0 ÅÚ°A+?´RæXÈhwµÖŒv—ì¸m²A­L:Ž™@IQp¯(Wfšj×Õlœ§C,~¨Xbñ¸eª’Å’ÃîjÞ ¼|3«ƒ\æ Š! =…¢�š„½¶3×BÞ 'úîˆÞ±«^sÝ©G.ýúÐ&ÛllÓ0‘Y™éÈ$b{7:M×|ƒµä;vÍ¿å¿mêݤòÿ'ÄÜÊP!îŒ7öŠ›�Ƀǜ\ÔŸ™Æ�ˆ”*ó\„2"vppxˆÊ×9²P­‚â–á¶ÚGcxÑVE´Á “˜–ã¶GwCve¡ý7€k2Ò„�bÔ‚®� éàsŽ"cÈ×¹ÞøäaK.f.ØñijD2y‹½Ÿiñm��N%ŒV¤ Õ˜ïlgpê53‰SJ}Z�Ý!Nµkßa;çËTЈçÞ9õCõ[õµnP}ëp¨ Åã1þÆzÛ`Gh…~Þâ›úÔ)|\Là½ó’Š ŽcðtQrþú ´ W]’]Ê¡t›i’e•ð aëhZàãŽ*!w4-]ÒhW•¶ÕÕý¼¯ÛØÜ`äeñÜÆqÚ̾+›Êï¦Íü\×K\î\qm•Óµ…o¸Ö,w\}—(ÓñØÈŠRœ™¦AJc V]ª‹ø„¨�¶d“88HusÚsc=ξ4ÂëÁt‘JÛVÇ7Ü*ÀÄöèØÙ1 |o 3kOBÊßBû[©ªœSõI_Ð�ý¾·ý]§à°~Cv�õ šq1¸OXp¶PûËÁž8uÄà &�‘åM�ú»}ƒK]þg¥8m!·E…æµ�¡»™ ´€vĨLò5ÌKµŠæ,¾Þ�ä®È�DÕ‚·§LŸ3åöÊ}âÒšyîkäï ºÝ¼¦ÈEsíз-s†¯‡U<N%·Id,ÓöNi«À¨Æ¢‹G òà9/¼Mð�iXÛµ¡¼ esátÿ£ð¿Òï§ï‘+Y÷·kŸEÕEØxÇt±AŒ÷=ƒíÒ!-ÁïòºÌïw üu1¢wþ6éÿúá»Ý]ûnQ›wŒþ�L“§æZ±ßêØ:X[¢édZ¡û³ÓôU�¡�òÈf¡Îòa„Û7ÿƒˆN^^\Dç//O/Î~ˆ¾¿Œ.Ï~‰®ÎN~œ}‰}Ý·%Ý-uh�­={J÷gô¯‚ãfÛïSŸü ºÕú#¹K–[Áƒ©Kà_qÕÛ�ÎJ<Èé¾´aÚ.#`sñ›ðªØìç¨ÚØêÎÅóZ±<¤Hþ {è?‡¹¼xœÍXmoÛ6þ\ÿŠƒ tr!Û©›Xú4/HŒ¥F‘ íöI ¥“ÅU5‘Šã­ûï»#%[rœ4qlùK§ãñ¹‡ÏQG ‡p�"‘Áx2†X¦23 Pèeߨ¾CÔ21Çá•HK„\ÈB:�á.?ÿÖ?—!fûã3#c‰Å^÷_ö�RQj´žG*_r–ðŽz0ÚÛû¥?ÚýìÃ!fp¦–ÆzÉL&%b, B�)ÁÁÊŒM&A˜à‚'�ÔMžF„óñÑÉäòd`®MoÀñNP*ìŸê $jÎÌbUÀ\œn¬È›$Æäú`8œI“”ÓA¨æÃ)f á2¶N穌 Clž¼?>¹Î:OÉ 3lÙÈ1 Ó2Bx3ù»Æ­6…ÌfMK¨MDœ7MW„U­aH ñ³Ôr*Si– —ó©JµOýYÊ‚⤾È,R Çç纉7xÿqÌ÷ààF|O^ŽàÛ·µ%8úý”¬AÐyúDÆõÀ˳÷'ÇÁùø�Ì[íÁá§ñùñxrÊOÖ|ðœ5Â0Õ9†^”¦x�«ÂôØSRÇwÈùz@ɘ.ê�íqüÀ9TÏ×¹zAp:ùt=xö êx÷öoÆ¡çÂÐ*MKƒääyWkʽ.ùŠ25Ý^¯w$Í^¾Ýª ¼‰2HJÆ?]!ÌT†Ž\CT /A.AŠç2¡iˆDŒ| T@Љ*Ó¦\hÈŸx¦¢ô:a*hÞUÙuþîä唂t€þlg¤Ü’J¨Q›T©¹(¨üfòŠê˜óaè¸DNL’v™ÕÁÄT [—4œÊa`ŸÔü“Dl>멼�§*”ƒW@ÏV³õ^wrZÆ1�1LDÏÝ$>-Í_˜j΀ïh/ДEfç(P“¦@Å )°p½žr€ö@ÑB–V>íx¤³¬œO)7×±bR‰, Ê…¼]ÂöÖ‡þ 6Y±©œ¨ÌäÙÕˆ±ªY ¢7G"d "MU(ŒTÕ 6¤\øÈ�ð3¯–�ù Ц5o¹V¾ê�j µMÜ,sä´rë Â°,h3ÔqÆ2M‰$¯Ûç9ïV ½v™Ú†µ¡œæØMè§hwåï 1.Ô|Uµ~†WBàÜK¦ iŠÞU·C øÛ$­iÛ'²}ØòŒ¥îÛ€›[œ[˜);¿gÃê‰æ1Bfšô–.a‘Hƒ:!ÞIÄ¥½ú¯éÈXÎ8£¢òRÅ¢z /u°=Ìߎ$£uxÚså\¤Ð}1z¹ßõ¡Û¯.h@‚×ÐÝ»Þ�FÝ^›4ÅHÇ.ÌNt¹ ÷ÑÆ«ýþ”6·fºxµ˜ÿ+®1¯öw£¦Žt/©”™–3n˜VðW–ÇÏÍ 7ùº“¦:NâSeØ�«v¸‡1¶©¬òѤõ£œ5´UÓµ«¸Ê¨‹R&ˆ^œ*zñRÞ¹â÷l¤¨Ãï‘Tº}ÓÞ$ic;`Õ…¡*"~H*%dTäm�ä`1pj‚ÒÝÈ©b܇š©RtÌvgqúméÔDPÛˆÃXP;ï¢àóÊî,ÚžE»làýi‰šTÆÿ_tWoCî7+n×Ѫ±ÖÂþ™²cã˜ö¨¡a(!�q‰/Òš¡NuƒiN�Y8tìFµ r7Ñ«¦-ÃEºìW U‘;’r¯F¶z.WóîIÖ<S42$ù’np[悼i@{G�¸ ¥ïh oÇò—šgç^Òõ÷+~Ó¯öŠoCjÅâºÑ6:/Hm4¦p÷ó’Ï ®]jzù‡©£)îð‰Ø·ë#à65œ ]q÷PEULÀ…4Éø,êX"�í‚oó™í"Ì5Ƽ cnHKìÎ�¼%¶›mïíȹț‹åCk圬uíO›[Øj8?ˆ¯H«»ÌVPÆf3>#rRêÈ®”ŒžC©ùÜ·> >ß ëŒ··¯í6Öy»R}Ýù‡(rßÜÁ¨ñÁé_÷Âmï©xœ340031Qðôó JMLI-r­HÌ-ÈIÕK.(`pHÞP÷š'Dˆ/Q¢Ë)çÂV¡:qCìê]‹Šò‹ŠÁº¢d-×7ð[¿ÌtYóáæŒ³›�Ï0„êJÎÏKËL×KIMcHVU`^¿Ã…y_èÎI³x:b·; Tenjª4¤$µ¸D¯8ƒáÓê"ÿKÌä´¾»_}§ky¿fTUIE CXæ®g›9÷»+L™Ùiq #ý^ª²Tˆû@ŠïXÏ>\ÑYg2a¯æë¤Y¥…é‡9ÞCgæeƧ”æè%3´«Ú=ÔÝ Ò²/hÏÖ„…×U�T¥Â‚‰aÊq“È'ï/´(JÞ¬]ºì²þ¢”vdã*r“‹ò�!à±C3ŠÝòq¹Ùó‹†úûÎü}ÊU˜›ZœŸ§—T𙓰[²òMôº¥“ÿOÝvCkK^¡~¥¦ TØ¿@CR—œý·ï›N͇¹æÏþÝêr>ß§\†¶¶uxœÅ•_oÓ0Åßó).æ¡ ÛÒMšxh»=€&¨&Ä´ñ™ä&µêÚ‘í”1ÔïŽíükJ»–'ò’êúžŸÏ¹±Üáné²àfN è¹ü©A3_)5Ídf—¦³é'¤)*H8Õ:^2‘ð2E˜0©�Bº¼îj$އIQ [U<'AÀ„�%e"Œ‚ßاƒ*ÿ �+CzhP›˜ F¢qàÛYaÕ¤Jã�RR…Là<‚ŠèmÒÑ(‘¥�ÉÈ[*¸¤) âà« ã¶_¡)•€‹ª²v0¤ÈXî!˜B¦ärƒ5‚*ͤ¸"-²z¬´¶ûÍTÌ]ÄBI#ÉÉ)�Ziž]D~«S(…f¹°û…½¯»s�bœkóõeHJ�ÊÁán—Ça›¹‚.ñyTp½î}?»�}ø<# í!ãÇB|ó.JÁžGØ“Ë;LÁz1hbØê@�7Rr¤¢cT"ÿ©J¬HíAíÈ÷TÊòõœ:®Æ¶Á·®akZÑ߆ÜÇô©"l 9hL£Í!EmmyW­ÖÐÝ~2öô´Wç·Mìpr׸è;]Ù²TÿÛ^[Lä×­a¸jökÄaTI3© ܧw› m¾3ƒŠÚE`Æ’hüswå�]ùÅFEêª''Ì쿽ÎÀ�á3ýÀë.öóv‹¿40ѨàKÏê·Qï’Ý;ü…£´ƒwÌÐ"¢Nùoù> ƒváU:_l“-úÑö�~±=ûnþ_¡þ9ëà Žн'xœ•P±jÃ0Ýõ‡ºØ‹ÕÙ1Ý2dH)� BXçD`IF:CJé¿WnëÁ ô¡{ïÞ½wBÀñ¦ìX5ý •©Hºm¿Pùåê:à»y ­´ñÒBÏyfí’š½«gÉž÷Žï-ÙĽ³aÃø¿üz…;Ò+úXv=U@Z‚ƒçûbß,¢Ðï° xœÓ×Wpvs×(NM.ÉÌÏÓQÈKÌMÕQHIMK,Í)ÑäâIå—ä'ççè(”¥ƒ•)(A%K‹S‹`Ú”ÜKJ*|Js ²3ó€* Rs3�(¥�T8ä@Tè%çç‚ÌQ.ÍÚr°,˰xœSVÔ/-.ÒOÊÌÓOÍ+SHJ,ÎàâJ×ÖVÐ OÌÉQðôó JMLI-r­HÌ-ÈIÕK.(PÐÓÓRúp9˜`f^¦^²‚n>†..=}t!; ®’Ôâ½’Š®¢\LMxœáZT”_TLžc z±8 "�pX*Ä΃<ìl­µxœu�=‹Ã0 †÷ü m]|¡æ> Ci—ƒ;–Nª´Û*–¸þú³s J=د¥ç•%8ttÇö‚è"{X%”TS ÕŒBÌ—‚>]C†±ɹ¢õz±ì+ X�fÏ =¥›ô–œi¸ÙI Ô-{w2ïµþП¶M4 ÑÕ·¬ëQ¶Ð Æz,¥ÕtaA³!Álá0a…èèñÈÉ㜨Þà9qË.ËBU?ø+@a6ÃiÎ…ž&¦Šw$þÛÌb(Ÿ¥»òåmþŒê“Ûz†¶ xœEŒ1ƒ0{^±rãÐ$Šò’È2° KæÝáÿ‡€ÚÙ™5扖æR™D÷4k“ 4[}y 4ÉC%v…nü´ÐÙÕlÙœéPÕîà„¸  j¢b-îEÄOıÄW|ÿ¼�#Ýï�Ð}/c4}´=xmSMo›@½ï¯˜R!À¢ØNÕKì´RrŠå–“cE+ÌH° ÝÅIZå¿wv!Èö…Õ|¼÷æÍx¹R”U€o²éj[I †øýEßt¤‚ûÇ{(‰³¯d«¶·�·MƒÊX,…øN*¯ûaklAmVýž…4©Ã,dÙÒ“B+-å¬Áz6Ôñ±¥b½A�2�2LVI½ƒ¹¥V̓J6˜ 8ûM�²î1ÿ|ÙÈç ¡Óx|aw¿V{¸� Ø_G%ÄÆê¼éâñ´>I`tÔ�hË8Í.4ûg¤O‹wŒýNÀ5S$éç0Éæ¤�ÉTÞ½ÏúNuü ¿Ø–³l2éž±]ª…°vFÏ«h ýðÔ_Ò9¯Ý› ƒmC­FÛkë�øÂíª‘¤b÷�ú�óJü†ø}Üí?�vYÔºÕG]5lo`}ɽ'#xíÎñÅž?8'%ãëšLû%ǹϒÜÇsñ®¿“Ú`ìõ¬÷éxY)<>=<Œ0n¿CÇV—ÔÜIYÐ( ˆB}óÎŒˆçR®¦žbmN—Ào•O´¤Ãð·ŠKÒ|胠VAM !,’�Õ‡Ï9N9GKVnCÿXS#ÓïË#xœ;ËP_KÁµ"1· 'ÕJ¡ ±¨8U!Q¡8ÄWHÎÏKËL/-J,ÉÌÏSHË iésmVTbŽVˆ¼^YjQ1H�­‚�µ‚мâÔ…”Ô´ÄÒœ’b�$µy‰¹©@…~¡>>ÖÈ⩹‰™9P‰É"ÿêÁ’™i H5Á‚ �V”šª¡Q–Ÿ™¢¥‰¬À]Ø\¼ú *¬¹ÀjŠRKJ‹ò€þ`æªåZUѹIxœ…TK�›0¾ó+F $€X’=g³—H­öЪÇJÝ*r�I,Ù&RUíïŒ\²\ûóÌ÷ØÖðƒ+€ת—çÉ0'µ‚^¤rw¬3w0Y©ÎðóéãF[¨·YVHŇ©ðb]'u{y],<°XËÛv+•l/y–mkèD/•w±{jåþŽ‚ÐAiù_xñØñË×Ò6 ªÃ¦ÁUÀ/Ì@m‹âTjÐ÷Þ¡E‹à<ûˆ ÷ž7‚9‹2Õ‘öµƒt6•†&áEG¦Çp;|N*>4�è£Ñ\X‹–TK÷Þš·ïo>ŽmÐd)‘‘]¢äCZÚ‡,q.(f¦¼iDד¦! uÉ'Á)ëå¢bW¨¦k¾ë{VYЛּ'ÊôZQ'DÙC¹«`ÿ™GbÀ$0*à×±¼S+l‡ì`½†÷;­ˆò\¡P‚ñ÷ç§×˜>2BT7�eàühü–n2 ž÷ÙG ƒä@åhŒœ•µNè8¿+yÂËtß%Yue�6šDªº«êe}ÿÿ*Ïúå8BbŠq f£)½×”®oÏæ—[-o:m_pÔªÌÎ|4¡ xœ340075UH*ÍÌIÑ+Î`HÍ*ûpußÎ[¦E»4v4z}�µÐ6„¨I+­ª)ÙÓcÂóÌ0y:3ï½üP'à /Y6·•˜™˜(dæef€•%3% ÔZrËT4AO-üÛ¹Àؽ &@ P’Z\’œXœZÌÀy‰_ó‰âÛ0G;Õ] Z~¼Þ0s/¡�9¼xœSVÔ/-.ÒOÊÌÓOÍ+SHJ,ÎàÒÓӢĴ]#=S£$0+=9Y!3/3#­´ªJ/Y(äºùpa.|ˆÒ·xœSVÔ/-.ÒOÊÌÓOÍ+SHJ,ÎàÒÓӢĴ]#=S£$0+­´ªJA7S¡$µ¸$9±8µXA7_!-3/%3/ÈÖUÐÓÏÌËÌ+spà£^³Kxœ}T[o›0~çW|cJ¹4#ɤ½$eU&­RÕîaÙÚ(òÀkÄ lR%mÿûŽ i ªÆE6çþ�ó™IˆŸ¹P ‡Ab“ëbýÈÙ_ž")«=Ê š+­&�Úì¢YY#k!7'ŽóQȤhRŽ ¥SQFùס¨ÞÊjòÈÜ(š)¢Üu!5–Š× 'ÉY�5ß­O´(åý—éjÑZ¤Í¶âµ¿+E¢!ó1U,•†q Ñ9 …’mùØÁéê+w¬hxà7•ææG�L<¢/* G×·S±,0›m¶êl;»©©ð0Õa8¥xœ340031QHN,N5ÔËÌËd˜ù-?:ðb½ßyÓ-k¾ˆ;›lói˸xœ³VHÎÏÍMÍ+áâŠNËÏ�U°V(NM.ÉÌÏãJJ,²5òósSmËsJS�j€‚±\ [§ü$®ÄôT+#.M|/¿Éxœ­Z{oÚØÿŸO1I¥Æ&†‹RÖ,X”Ë€%¯àÍô÷Ö/­^èå)k4zñö) Vë ¬ž —ç翵.Ï/uà ‹àÏø)k48« …„…ÌKÙòhÁÈÖ Fì�èA(ø€•2ÃA¯?šöÛÙcf·á�²˜/Þ&ñ˜Ÿ5Öñ†Ä\1XÆ lâ„¡6ËøU£±Î²múêìldë|ÞöãÍÙœEk”⌄h4šg�Æ‹` ¶ "¶°Ü›iÏ}ߟØðò%£½ÉÌ�ö{·“¾;»®'£Áè�©Ýx!VÀž�,ZKbùa¾`ð:ÍAÜ^_iC~ö´eæPš%A´2ǼÝ�ÑX9xŒZ´×ÇB‡#t™{;í»ÓÙuïß|ˆFz·ÓÙøÆ½ǽëÙxb ƒ ÉÏq°àp7^ƾ•ÿenôawø4Ÿ]&ŒYbñ6KäŒØ˜0±³˜uÀ$ò‚…ƒ{̹’ÒŒ%Æ ñúcŒJ¶ ?•É ËË¥7×ÉA³Áx¿žãë›>�5ΚpKpœ?qÊø®p…eè>1¶…,ñüO/AÌðø Ï4ó2ÖDùéÓ‚ÜÏà ÀÇ�£4í%Ü8>( å7dˬÓxÞáìúÙc‡Ë6Åß[xXK·žÏ8±%Y*K­‚ÏdbŸƒ”`âº6ŸÞäÈ~ÎÀ#ÆÛ8ˆ2 9H£Û!àÏMyF�—ñQI”¾J<„ey‚¸š¤pàK•¸+hÙÖ©#g�ŒÝ6@Ù1¡XDø R ± åªXV¥Á c�o²­f«Eûl›ï£§I»ºpò×ù‰°]"eA£qóHÙ4Õ–JQµ*F#Ó趪W'L?‘6šßÒŠ"ÍÃ:¤ºüéé©!·0“ýEñ¹¼ú�{Û†˜¤ »˜Í6˜…«Ê8ÄWi”È¿èTéP‰[LüˆK–1fà6 Lº„¢5§L‹áò(BëBÐó%›„­‚”¾{)®’dêm‹Á·p¹Fnœ¸r©ikÇ¡=š_¥5ÊgÜÁh8õÝÞøæ¦?šM¹�QmxðRWHØ…óήϬ#<ÿüGh�p Sò®(ü†Ï‘U’ýr= a²wßMúoûSAÅ–‘Ï™kòÂKg/Ï2q~£:wƒÏ‹€8 6Aèq‡"ÕÈß>90Ï =iž°�Jt ? ˆ"z²–J"lဇh[Ä,�N2Ì‘ x@¬Ñ¢=ÉCò:—郸˜HßîTØÜÜgҢ܋9ö³yv§Š’b«¬e4ÍðÐOYÖÑ„BW$ÆnÒeý÷g/Ì塉ô‰â2[ÒK„~1Ä¥ôæOizwñ/Ä<¨z 3*B·m1ÚÖxyœ&ÕùYCËcVS‚ÓÂÂ'êÝD…kI u)[ eJ”sÊõs Úåd]YL$&dæÓªÞ”HHÆ:¢]zV•ÁÛº¬äÖÂÕ^�~ö'­£qQsÊa+wè�‹0JmŽÚ˜#]b«ºù›vÛh(­ø^„¥\竵L x¢óOéyÂX4ã`ò‹2[Å’#wÛ…�5“XRâËô+߯üðµ1¦IQÄïk#ò+dzθ[ƼÌÐ<¹Èiº'±OþŠNt±é)ö5»pÙ1¦ÈûÅô•!н“?÷f+}‘Ê T h­“°¯¢`ïJq¤6V¥!+û³šÍ5xUϳñKJU¦¸ª2àTÚ]BÄÒl/&ì-]‰–±æ8ÿÉä#Ýujà¨à¦©ölÀ\ÉÅTïCˆþÁR(Æ}<’0epâeŒ‹ õ½ÓY†ªV¦]EÚR&øq°™QÆó ¨mâ¥øe 3]˜#ž¥wÍJ�JàÂ(»ˆ(p±‚ûîRbU.¿‚sRWå0W¹+U®•¨ÆÙÏš7ËŠ•N3)yõì|3¾1ܡޮ®ðA¥¬æ”îι\ç�ý·5ÉCjw_ÈÝoÞ|ÏîKµûmÕ‡BK„ù/5�®šBë°Ëî”OÚâ¬ÐÄ0�Ö°`š�ÌjûN¨* ¿uÀ‰V¼l½¡ÏKzèüâ‚4‹‡%xŠŽŠ¾\�Êl_îñ ÒüR`GÉ);z± ž�û„Âb¥�ånº‰èTÝ"Í3Š£Ö<ô¢OÂ¼É 1¬©Ã.ûd(˜3jŒ±RÊ‚(ç¥ýî)Æ«À ÎS^¯�¤¢LÃÐKa‹¥Í»§l�ÒX+q1kéI¹ü¨(Rêd]ê”E¦#Œ.î9÷¤�}éc¢¥ƒ%XÑ®w;I­u ÇwR–ûc£îø&‡"Ò/8¾?®9"…_Q€ûº]¹¾èC ›i,eŠ–SÔ® ûßúŠ{¥ÿè!‰¢d�ÜQÿƒê!êÿÖ%â9·ÎÁ57«ƒÂÑ’j��yL�õÅ­ \¥hµþ®£žÿó>¸Õàlgå ”SÞ}ò¨ºë¾ºaµ¥Vö{ˆàë¾:ˆ¾î ÝË?_};%Vö¥£êr¡Q—Ë�X­"óPýµ9ùÚDº£H ŠtËË�Ôեʒ­Pù«ƒÍˆc-åÉÄ'‚¸·ë“;=¥÷^,êpÃáí€�¥»jÀêÀ}(T…< ß«ú\M�î L&ÒA@–ñi*§¢Ô4öh쾿ÞÖ$¶Ÿˆá/Ûê;Óšº'Úaö³’ãs ÎélüŽò÷ÛÁd:sû“ÉxbTc5,eU¬qxÞ{wQíòôHvxœÃÜ'Ò°õv0ì7ùŠú‹Ãš;BÉ~çbR»™´—+–a¯#è´8•ï—Øxq@KX¾RìRÓÊÕ—,£ù[ð.†Ëû£’úqr¬ßÑDéUÕk_ˆ K×[�óôÃ8 ìÎ^ß^G ]ú.óHœ³üUŒ·àoî"ö(.÷`™Ä<åÛŸy¾\²¤ ³µ¸½§k:\ÎÝcÙÀþÎLTêç{_‰îy È¥Q‹Ôådâp»FùƼ•VÇbÝkÏ&ວ«�´y7©¿*ÊÝmFæÆo­«êËVšV/\Õšò,'¦dÞŠ¾X»þô%&Èå&!ìðèÄÇx —;H Ä%÷ÊæŸV^‰–Õ „ýþ¹^,ø(DSê¤ÞãðqmS«UŽ7IðÓSR­c$¿¾5¯´äHRQ“ýxSZ¢LñÊ®Â|naaÖrÒ4­zi›ýH~RÐÒ_'<ìÏT„+ü*[[Šš¼?ퟗ¯*ìê‘`+ö¼¥Q¾ý:�ë‚Ä.�¾k —Šé£mÍ¡BœÎ^V³ýN>pà%’t¾ðúI>…•ÿÑúŒ6¹‘xœÅYýoâÈþùüWŒˆt@–¯Í¶R›íž”²‹J r»U¯²{ sk<Ô3NÂõú¿÷yglƒ�d¯§J�”æãýþx^§{Îd"W¬ÝfZ®7±`�áxÈ"‰Ožj‘zÞìþæK{$‘hц"12’"½d׳›ö»v?晞×W›m*—+Ãý&»èõþܾè]ü±Å®EÂ>©­ñ<ËJj–ŠXp-B–%¡H™Y 6OD�ÅŽkh!ØhØŒgƒŽy6Íû¨˜Qöð&U?‹Àx+µ&1—‚E*ek• h©KÏ[³Ñ—ÝîRšU¶èjÝ]ˆd)º$„ç�w=ïLFà1hìòÎðQ&"ÿæuÏÙÿ*À¯'A­Y ¹$¡xqOÖó† ŸAd‰ªï›8Óôë‰g#Ò„Õú5ö/ïL$¡Œˆ}~ÿ/Ú„RuV?X®c•ü"RP�È5þŠ'a ~�ã¾2½RY2bc`¯D$Š|Å×L:¹;Å®Æ7£ÁÔ ǃñ¤ªee‹õJÉ Å�RË…Œ¥Ù2½]/T¬[pÛ?3™ÂmdíÏ2 Õ“f7£‘>äyu?¤ïÌñ ™ÿy8~wÁ~ýu·â÷ÿö«¾ï�}çìEgŸ®¦ƒt�å“ëþõÃpt3¤ßí)ž Š Ö4Â8Ï•š&1"ô›�åkˆ³òbõm¸ùþN׆ï?ô}¿É¾ÿž_ØØŽé`Ÿ“ÊEf5�;«7j8˳ØÔšÍæo‘dÿAœo7‚ìGÎBÂe°ÀTÄŠˆŠ²$0R%Ïc ag(عaâ ke˕ͶGgba¸¯™¥U T¢ V<=¯µìÙŒ*¨­ù‹M¤IÍ``‹…x[&�Fº$µ¤Åˆ\ÉÉ–(CtâK.¢Æ#“— Ê—zeftØœR4¿hÒÌ*©…ÕÍ2M 8*Q‚Z(.ë ’Ñé…pIv<ħ’Æä¦” ŠÛùïfãQÉðܪÝb{æ(ø·ˆéK?ûçIÆ*k�Wï“8.ÿ›ïóPüÿIjEøvðEKat[›-*išWÕ"©Œ 8ÊNw®ÙÈå5P€ôK²u‹9­°&ø"ï{ê[l))ŒáÌœÕîJúñû(Øßsµÿ�ºF}(¢\¦š,ÑxZI#ô†‚¨Ë Äj¶l8¡™¬¸ë<52Y²'tV_§®µ–�ŠU‚~5sˆ‚³V£Š~ÀÊõØqQóâɶ \j9 Ý±W.í¢ddœÇZ1�m¨fQ²`•¼­é>’ä~‹pOê6Õ"¹týÜeû-ü"x°zA –K´ÃJáu–-2}£à œÿ'�;x•‡ ÙÌqh„Üp¦’xK Ò5’0Kya§½êDÀˆŸò¯y×K…ÉÐH“¼GB$�¤r‹ "MU®åÔžÖ¬W9H)CÁ³YŠHÔ¸[tÌ ÿÚ•ÐIÝ-mÔ†ö÷Ž#ÚoÝLmDμÅp·}A;kT²…¯b”ZæÄ­žV.Jý‡ÙÀŸÍ¯ú¥`!]š¶"�‚žrÁŠ×ØÏ>bî ½òg‘ ä­<=fT á¥5è�Ä àÑíp4 X¨ BÐYÈqÈ+ížQ‚˜Ê)fk«Œ4ˆ8ô(©WKË¢nC*[xCeûŒM÷“êùD¬áÄ �ÿ+Ͷ«&»˜.x§0i¶ÓùAçˆÇâcÊ|Jùš¨59šªC²l/, v'ºRvÚÌ/•r皯¨íªXcOH÷¨V¹ÓÖ8]½ÿ{çSäµa޵L8•'µK|r$M”ÅN·2Dx^_a¬(‹]ÙyºZÜ`Q ƒq–ó¤RàX|$R¨¶� xL:o S»ÜyÝ^`PI·ô»¦ y`”ÜTȹ 2–f…ÂF±áÊCŠåFó÷™ Ôߢ�CØzbƒ4âAé 908\^:éüG)ž¾i,ßÉ~ÒfZþ"|Sj÷ÛLXL+h7Täž�ⱑm[cKà¨sŸØrí1S´'ÏŒ]‡bÃÈ£F´©Ð ÆA@*„+cSÃ�DÌ⽨èp:[h ."Ky‡;š“®F£Égÿîa4ê« J{ìí>Ô9²góÛöŸØõäŽ9ÎÀ�Þóà–õž¯¯éÏm“h;kCä5§¨£ Þë#lWj� ݽx{Zp?¡ÉT‘¾�Ð÷/Ä.âN¤¶Šœs´Óa÷0µsYÅ[-§5É¿PpÄ{›&g;”t (Xº‹-Ëœ#W  Nç~rw7Ïýûéàvøe0«hsú«½?«½ê™ì+¥ó(x©îÙäà’Î(͈vN ÁI‚á˜BáH‹ûìØÓ³o+­¨(rÃ7ÁŠˆåf|×¹xS±%\p*Uv<Æ;÷U™ÎN8øàÄÎÙûÄ^�½Bî…30ñKFU /_mî9'gQD!²ÀvÃk ‰ºQ*DóHÙ U$Ú!¤Jßñg¹ÎÖŽ¡+^VÓvMZ€åJà ˆmœË±×% :�Å( ß¹JØsu  ~)t^D!*ªÿ”Ö[ø›ÔέÿÔ«ëswõÅ?**Å"½*{5f­ÁöÌI;Ë�’B CRDz·¬æÀ<‹°M5½¸¦ªÂ �1àtØ„`(ßlb‰^'£#0JĈô !9г"ó�ê{¥a"�ä±m6ä+„•úPÍoH–�U'%Ãï|x5ò�%«ìZ~F¿èÙU¨‡»bE�càRïg í:š.ïï½ÅfÝdª†‹ABúrTªFë¸Ú ñËØqÿt[¼¬û‡¾®¼O³oÞzüÎuúoàö÷ÝI’GçCÓB x88IŒ ã}[ëÍdµy#ŒÐÆÀ~Õcèw~@Œ£g¥äÂz/–èŸÉüÁ_·ÁKÅä©� !¸hµ±Þ,¿¢å}š­Öä­VËU»!A4S‘É;Kã-&­†>Owso…ts,–í6|HÚcDDè 9 G&l¶ÚšBæ ƒÜÂ{Dkoº™-í†áœE{‚“V+kO7ÉôKcü’¥'"l§öG�{´[6ë±tê‡Ñ¨Å�ûÉ7“Õ±îÌ#„¡â !(Ûu{§ÇUG(Ömé#Iÿ%'‡Ä‰Dõˆ8U¿È_øÛÌ&sÓÞÓqv*œ@�u¼‰U‚å Iµò2ëõnÈþëKjÏ�A1ö÷�Ê¡|•ŠÄÝž3†ƒã@YÀÒµ÷ôöø ´÷0ì,^ËtQÐßr(xá£!é‰ÖX 9TšC¦‰f ¸ò­õ –D`¹Ç‚èòî…ðsºž¬¼;h—ÛÊñpˆ™yD‹§æ2êž( Û‡ÙÖþv¯œ³]ŠwÄLð­G ÷©wšJ3Pè'›&(ó‘ù‚ÛžùQP†¹¾¾»îëHQ…4ÒËð5Ô•í@¬ù–MU}‚ÊUí¬Ó`£Ü‰LÆÏR‘”ýrUI^• <7GóË•½«*3ÙÈz³c)’¯ñîŠg*é–FT½ÂþžafÞj‡ì»>…€Íâ×§D>, w„�éæ ¹*ªÉplæÆJZôé jšTïJ¶"â ¡Ä&ÉN‚…$¢¢�eÀ#l²S&$éd;( ”$ ~=¨¿`“0ÿµkYÂ~¢…–¾qêÚ€õ¼UÆžX<_L›Í*‘ÅP%Ó-è•kÕö„¬L¦m�ÖQ´ß¨±pš29d·È™¡AµŠ>ëb„8c{y(%ä[\ÐX¶äº­«ò-~pÊP‚ÕÞ  ¡¡Ñ¶ê:çUõ‚:eX¥Ó5[å²þ-a«t\˜óÅiÀ*ú#—Ê:¨ŠvŽloîÒeEÈåò5¢ÿµÌmAÅF:6\åeSÎ/'¨ráLûý#å²–�gû^¿Ž´°7ë :ƒbü_pÄûÒßj}rÎ’¬sQÒö?ª¤¦ºµ‡xœ¥•MkÜ0†ïù=xbXz í-¥�Ú†ÒÞ B¶GöYrõÑÝí¯ïH¶“u’%Ô¹ìbYzç™wFc;²fS4䃳‚ŒRëâò „PTÖj”&¯ý‘:¦Åà"¦ç}í(k¤­Óqð�tØ€¦Ê_B‡²AçAš†û¶¬­QÔšà}q±½°ÄžB'n¿Þ~Ï'ÖBÔ¶HcŽ·!5Ö›-Ìx�!N‚÷Q*53ÚJ‡ÁºÊ:xT�f ‚G†´ ³æWááSvæN:¿D‹A•W¢²ýZ"NÚîAÂÏ7å\û¦üŽhj„Íîðùv‡ëëôs³&<É)0›Šý\TŠL6Š­î9­ÕN�\£Ìb�Ú 3L+08Tt€š»Jr%Ýy1n]ðpc“i8ÅÇâ¥Æ™õ7~ ÁNHó07Yiî©S”¼¿´ª\Eôî?�P¦bUØ’1¬™ &!Å?Ë6 cEŽöj±”Ôþ|µŒìq,‘±ã‘¥vÖEÎ�ÎÙ×ïñ¹xI ¾ )1©Ø…TŒQó$¤ÃtÛD6ÝľâY³:䨕 „Q lª?‰ŽÇ‰æÇš]¨d}J�Öæ )yƒ{áyæðËÕ$I3×vŒ²æ[Al¾S ÏÍXÛhØlcãàá;¼©àµ =öÖ“™ˆ5!æªôyK¬ÉU$~™fÀŸøGrÇŠz¿´·H ‹S“K2óóÀJ3fùÅ”³Ü¶’kÔ° Z”Ð5åèýEp¥Å©9™y©ñ‰99ùÉz%% ªéSøsgüêç?{‘ûZE­®ZM6Õåñyùñe‰9¥©`mRó–o8²>àɧ“2Ö {Þ­;�®-¨/>#1/%'µ(>?/>/µîP� »ô¬DØ1Ü‹ òËês½â¢ž,…nFJf1ÄöÌ<ˆ™�À+ðwêٻ៿ºtyߌ›©iyŠ×¼.£³ÄÉËkÛf¦\g¡ØÏ$eeÓÇÚµB½j³1´¥&€ÿû?k}Ä—@ KÎ9’[v?‹Ø¦>›âøÜÄ °%`];Ãï{§e$ݬþ³²ikŽÍâ ¬ºŠRQA´UPMdZY\R”™—Ö˜W³¿äÿï ”¦>[T™Í–“s ]$ ë>°jÒV tQDùfÞï7'Ìë°õ]ó‡Iï@�±r-†ò’üPŠJË,*.‰O-*Ê/ë=¹ÿ•Ýæµ{ö>óšÁ¢pNøM|ýò¹˜z‰ò~~.83¹•üRúëÍâª/3ïþ8ødK‘¹³TIJiANfrbI*,QC²_äü[¬Ùªï»§-þà býº}TGN~^:$\@ [/{g9¨>+¬•-)PÑkÃôÊY!rž>•›üèiÌï´E‡ÌßNØà~–ó•Tmnj1PQRifN ãúyÏvWÜ®±î¯ûä—ï­Û¼aÊÀ·üÅÝ—^ª±hœæoèŒæµŠŸøV|5Te^bn*0ps*ãÓJ€9¼ MRo¦ïU™Ê$àÎáFûüŸgÖ¼²I€i‚ uŠŒo&WЦç½~Ôþù§Qm~ÐL¸º¢Üݪ@׆S—.%0,ÜÙ²eùŠîࣙ<_�ªÌMMŠJóJR‹KôŠ3&Û{èœ(Öþv(®çåü5ÿ*øî95«4/³¬,)±„áMív…°§Z§>¦¿ŒšmtbÏœ~c?tuÉ Ë}ÓTæ0ýT»ïëÌPV5©†íR<ÔN¸* ¥:ù¥½Ý«ßzL+Íú㛺ÈëØ§@4àEh2ƒa¬@ð#GËôð–:æ½½'ña#ºZhšKfÈ:·ˆ%¯øcèÊŸ.FíÌÛβ9s>�).F ëÔi1»ÿË-UýTIûéŠ=ñ�©±xœ ÉÈ,V¢Ä<…Ô¢¢ü".5§Ø;xœSPÈÌKIÍ+IMáÝ–·xœ‹.NM.ÉÌÏ3ŒåÊKÌM5´-KÌ)M5䊆ŠÁYÆ Ö Éù¹¹©y% åFåF\y¦ý´xœËÌËŒÏMÌÉÉOÖ04ÒäÒÓÓSˆ.NM.ÉÌÏ‹óÒòóm“‹¬¹2�*‹R!J�L J“«ªl“J«ª K+* jÒŠRS54¹’‹3“­Rm ¸G=–º†xœ•UInÛ0ÝûºÈ¦qEJžbpÕM ‹4(‰¶‰H¤#RuÜUÒÓõ$¥8jp€0 é¿÷G¾Oq‘XM—Œ³@ñ=�¤-NË刖‚»ý]·áö)N³U6Èpöiõi�m@6¡ƒq«�\¨‚�B-¸�´3ø*¸ÒAî¯gŠ}S_Nª©ƒ›ÇA\�6XmW¤"�Ä fÛÓ¡ãü ñ…©À€¾v¤–�²�d)ÀÔ)»HI±Á,HšDFfÓ)\pÑ6¤öBL¬ao)‘“†Bü“Ô…¡Œ€ ‹„6W‹‚T¹‹à¤>2¨;�ˆÓÛì㤆{&­¤9m[юעÄU܈žjI³B²[�JÆ�µî•å8gÓÕŠaå×qœÒ‚æÿÞ˜Í@Æ{i##üvý5¬=b…›�±¯°5–“-vÑÒa?øxba}ਠ^;V¾tØmÑŠ ×Ú~ h¢²Ñ `vkÜ‚÷Öo |m] VÜäÉû·™­Š a¦7¸B4ùH)½Á×ä£3°m½Ðëß߬\õCno:ò¨èa|îÞãµ k¹^Tݹf%QÔ7&gumGu%Ͼ³¤ï+‰¡nT¯Yf�aÔ vP> òiœœòþ ³Zo¶»ÿ|Š7�@ºÐÐqz-ø® û^XenEèâÄØ=�#:ìÞsؽMr ä]—ÝDñO)Ù‘Ëw]æ;é?qTK˜¶Á> €fe"7©[[¡—·Ï“ ^_srPÓËmyÿ?§{ÿé0„[xœeRMkSA%M›h~,ì¢-æÒ *’÷ZK/#‚(ݹ¬Eæ½71cæÍ˜Ì<“¸°nÝ5 t£?Á•Á_à_ܸð¸ÐÉ<š¼"Ìbæžs÷œ;¿®}¹zz¯ € iÆÈ[*r‰Ê2& 6‚ 3V¤Ó #œ7¾¢iJpvÖjº.%ÅLŸks! f@í³o�N ¦ †$Œv]Ç€MA÷U.Ò9{»À¶qAŒHÔÚmí9æNk§Ð×†ŽŒ^*5E T—lÙ}— Ä‚Êx+í;À& {c€÷€Ë9÷\DCÊ5�KÑ>¯ìÛO•`ÓËô”"1š4¥™&QíßÊÆ“Ï¥œvɘ›>85¡¤Æ%—`ÜŠ2y`Ña?®=Ò,1\Éæ{8÷xì±ùVºÅZÜ ?WžÚûÕõËJˆ›\UÉ ÿÊra8ñ›p‡¢ý^ݺ^¬‹ËW‰L¦ïÐ^YÝÚ(ñc,=´/V›'ä sž Ê´x¤ÆÒ¥4ÁÅ„$V1èlžHÉ�O»ƒåP“Rá�Ã35r“.6¼úKa×׎ Kî§üøð¹0ï.hÏÖöÂÿ,• ’²^` G.éfí�}]»iOk‰ýS;´�ë·ìûúmûµÚßõÀ¶/=ü�Óò¡ç‚rxœ[ÿ‚o‚ÝÄ�è‰gÃd­¹ôôôJR‹KLlM”M•Íæ¶›·3ª3NþŤc  ÉÈ,V¢Ä<…Ô¢¢ü"k®¤Ä”øäüÜÜÔ¼½Ì¼L+…T[…ÒâÔ"[CãÍÎÌîŒéãÒòóm“‹�L…ðü¢œk®ÜÒœ’ÌøœÌ¼T #LÁÊ3óR€æ§¦@¬+ÇPi6ù/Ë-% )Å%±`MÅ©É%™ùyÖÈj˜£`«æ™\‰{æ½xœ‹.NM.ÉÌÏ3Œå*ÎÌKÏI5T°UHLJ†òŒ€¼ŠÊ*®hlÊRRÓ�”ý¨Ƕxœm�½ Â@„û<ÅB^@Ï¿¤HaaP+ A%fMÎ=¹[|{7 j‘j‡á[f&…C‹A0J0IÁ³{±`P“¸Q«o‘õ”û²Ú­�Õ¶Üo 3I’SOµ´PÀ4»$ÕÔÌæ‹å*Ë‹�úò1 û#Ìd”È1`c¤†ã(ÁöŽEgÝe,9í g¾hi Ô4ÀÂͺ8 |øHBêH�vpˆ¯þþp(ú98µê«�ç_¢ù&¾Ó³Wû¾xœSVHÎHMÎV(ÉH,Q(NM.ÉÌÏSÈ,V()*ÍKN,IM኎7426153·°$�Ë•—˜›ª`«P–˜SšÊ >‡ºgxœ­U]kÛ0}÷¯Ð›æ…¥°—@Æõ˜™c�ÄYÅÇ–�#y’¼Jÿ{¯å$u2/”®IüqÏ9÷ž{u#®©Òh†JÆ RK±•ÙÞÆ²{>V;ì I7LÒbŠÊ¬Rtd±q¡Ñ1¨ /ì‘…�j6“¤œÂ=å+-«�Q ðxßTšá)zD8“[Ww)zrN¯È>{ ãô<¼ ¢€ÌÝ_$ "vó aŠñm5î†a¼&óU˜õ*˜ÊªJÜÆ[1’‹ýžr­®ÐQËA¼x>÷£dÙ#SZÔDpR2©4¡R 9ȳLâ$ŽÈ×`±Lˆ¿XÄ‹ÙäIJËxQQiŠçb�à›݆þÂXÅ=¬Ò\è0Jæp�Îtë<éÞ�shäkìÜѬ_-}¨Àõ¾Ï΃¯w¬�‚þÖ==„O¸¡qۻ̈HÚö+½F×´…ßÑMþ¥óyPæ½jz[oìæ;X�Cõä8ˆ0ÑœÞEsÍt†ÓTÂdGþš,}/ â¨7�ÝIã‚üɪæÚI�bòÓ Wþ6¶Â¼tÂ[ÁQ›w幉9e§ÚÿgX&7m^Ö“e•&%ß!ží©ƒ`mÖTjFš"³î õ®]2‡ÍgRGŒ÷¿ônîL@ŠzÕA]ÀA(Àá;ot¶©¨ýR=Fꀅ5ÆvÎIÒA9i-AÆ’5…�39ãWšŽiÚø‚ò°ÙTж¸!‚,RiKVQeãM¦¨Y G,üà±~Ðxä´y�Ë&ªÎôÎ¥æáàœõ ¨ö»xœ]ŽA‚0E÷=Å¿€ êâÞ¸ ,ÚZ`bÛ‰P=½¡$Nf1™ùïÿ©Gk"q(1Rèœ-p‚ÒFøÉEJsìiDj%°Ör98 å&»‚2‰ç×[Ô«£l²ÇÎê<™,ÏÞ»ÒÊèæ8»hÇݨÇ?©C$›ªMäÚÉ‹’�:WíµSeÛÊv·9fbýQ|o8W¬¶xœ‹.NM.ÉÌÏ‹ÏÉ,.‰åå‚r à,C^.^®h˜0PEvj¥�mYb޲„!DÂ$ÔÜ�ò½.xœ�’Oo£0ÅïþOåÀ¥Õ*„üQ,NU‘²=eOªœ0,V�ÍÆN¾}Ç�¥ía¥•‚™ß{óð ±o´_Êbû¼E­ ‰OÇ ��•€ÄíG×¶dƒp–ŠI…@>Dʪ–ŠweÎ4�áâP`–Ís!q-z!^&³ ¥hT×õ`¹ðªBÁµ]èKæ+ëB£ío®Þ_ã°RÄû,:ËLÎy´³¦Gˆ‰.ÚˆcM˜Í˜eRær„œMÃ7ê–¨âN£™ïÔ‘¸]»!•é`Ás ÌóO{£¾qgSE�»±{7P9Cy²H–Œ¥I:óA�‚ÿÆ\ £-‰$j¬],W¬dFóèØú«¡J ŽzÉØrµþvº© Ð?íöÛÝöù ­«h¸bµÄÁ(û†aWŸ|Í’¯^•öê`xtT®Qȸ�ÿ=´¸3ãì°1_ŠGÇélxØ÷møC®áGZ#jç6uªR­Çy&ê³µýŒŸ/:4œ—þœ•ñc9nr(o0ØßÊq9ãÏöΙŠÕ‰ý&¹ó†°xœSVÔOÊÌÓ/Îàâ*N-QÐM-åâJNQPRÑHÉ,ÊKÌMUPR©6¬UÒTâRR1RR¨QHÉLKƒ *èr-鄵`xœ­”Ñjƒ0†ï÷¾@e-ì²£b3¦Éиî¢pp6[š”$®}üE…Âè–ZÚ ÁòýÎùsUY×�­ª ×Bа &ØýÝÊ •ÂZn¬Û} >JÃk!94mmEhönñïé%&ÒèLÐ|v–My€n9% ]AZ$ ÷|/ÞùU_€Å¤cBLÓ–ûáaRíAÈ~]©¦áÒš1r9£¯@ <á,g€²Œfó©÷&Ví@IøÚXàZ+=Fç9"Ëe}+õKlK¹©¹î;!Õz‘#w“(~ñjËËÝŸÓõ;îc8Jú~ÅgÂÕé^”­SC,24ÈMÿ4ñpÖƒæ]Pª[Õদ®,Ð :d¬vÏtŒ|ì˜ÇH»×CÐ r3L‰?Úö˜o÷Š$߃á•JŽ„Â[”È/5Œ©à»¬ÛQE� 7 Ò¡^ÁqœÎ~Ûƒ!{'&‡8þR³ï³±ˆxœµUÛnÛ8}×WLµp,©Šâl±/ñºEŠ[†³è6[m Ð"‘H�¤’u/ÿ¾CJ²å@.Ð‡Ú âÌåÌ9s‘Ï"à‚pz †i£=ï}Á5³Bƒ�´ìwt›Í)#Î/[d@4)2»b5tqÇ´m@)å=ãR)Ïs¥ë¹þ@N°(M<ït4‚kN6˜f£w]@.òÑVW�@Td\€ïï-íßÖÖ�|ÞÍüþOÂ:87à½âvðÉg¼%uÍ> &œÚËÕêúCº¾Nÿ½\Ý\-ÎÝ=;iÑ dW#ÀOÓÅðQ0½˜†€Ç}´‘c·@­ß®™Âb389é�Ý5ø5Qš¥L)¹ z…¿QíA€‹ †Û�çŠ�pÃ3wX îÁùlÖ-5ؼe Wæ`Yþ‡Œ´-’·¤ÛsŒá¤é6±ŸÕD_[L¨»ÑE·]þÜìOªyþÜ)°,*Â…»í/ñðgC‘—©$eAj¿„ ð‰ßa éuúz¹¾|÷q?iÇÆqõ…t Ò÷{ž½GU¤qlí»pěɪbÂŒx­ävR#Ϊ) Oí Áuc>Y�XiS—<#†õlõ¨Êԭш«”âîB�ûY;ÌTŠr›’ÜŒ)î¶|fÇû?èV��¿¤xœ­VÁnœ0½ó4ÍèFê¥ÒVBUQÁTÀv{³q²–ÀDÀ&‘ªþ{�½¡6¶I”ä°ÒzÆžyïyfÌÇÁqèƒ+BLïÝ«j88Û³ó?|ñnëú¯ëí«¦9sœs\ß%~í)G<Œì¯×Í+Ô›‘8~ ܯSdÜŠ…ÁG§o]ïfyTÍâ]Æ0Fiø%1Œ¶ŸVó¢¶zDS ÀìÑ�Ì+¤ÿQMØÂ$Éö(Ý%eÌ!Z„Þ6 2a‘ ‹ Éé´A §ôdiÁ²°ã¸&»ÎîÊsÖ]Ûb:22Û«m� ½5§‰OQf?QÑ·8/Jåy–o7VUÇîuÝ�ž­pßw½"±îVôÖÝFñ ILÈ¿‡ð2‰r^«0³ƒ>TôºÁ=/*ÚÉxU� Uõ˜P.¢¾´‘»ž—¦ëdT¥œ,ªzü Q0~ÚaWDì†CðÃ^¡\Ý)¢°µ"[˜Î=—SBXدŒÃ„wX4SãœQK¸«SF ù2¢éóHÀÝI|^åÐã©%k�ÂÉ®18Ù­žâ½öÞ�‘ýv–lŸ¿--Õ»—�¹-yr½7%³ÆÉÞ¥r0~À@Í3� díQ�2Π}¶ÕL˜y±™Iñp=’ŽÊˆW¶É V¶™­%·?�0C¿ÂdÙI‰Ç‹vè¾jŽJ]©ºê1¡]DåOÊïØ#˜Š¢Kþþ½el.ôï6$ºYçY/éÕKVÆ ŽþgÊ•¿é ŠYxœ}‘ËJ1†©¥»ܹ’Ã@qZÚÚ›7Ê<…¨ #%3Í´�4©'{)®Ü›÷ð5|ßÀ‡Ll«´ŠÙ$ùOþïüœ<î¾oû‡e`‚  ZM•V�HtJwDPÑ®ÒÈDß/�yμ˜«­ëLÉd‹æ"ûz8²%�ø^­Vƒ¢ Šª ¯‚ iî Oi©S»�ê…yËíï-бJC< X†Eó‘;ßYu[J—ùºyÊ�çt8ÒÓå;Ë÷¼%ö+¡ïED±Øé7ŠÆšIqŠDJ "ŠˆÌfîœÚí.�L6Ü1òdÝl=ʹŒ%òÞÜ)v*zÚÕc§¶›NÙàp)úÀ™ +ØwŠz£ÙjŸœžýœ\, Ú­fão��f"¥=K#ÿà"6 Üý…"J\D±NØ>Q(â å­ý Ô;…‡Â'J(¢ôé�Lxœ{ɹ†SW_K!3/3CAWW¡$µ¸¤X¡<³$C!¹´¸$?W!757¿¨R!1''?9±$¿h¢zo}Y~f XO|.X\£8³*5¾DDi*Ts)AAQf^Iš†’*Õ͘<%  Œ&X­5XiQjIiQž’Y@‰Z..�5`[ÒŠRS5 –”iÖc±¬d<ÔL0¤nÄÁE©[à¦é(p>>>>>??????????@AAAº.: X>ÞYRNjŽEÔD'c�¬ ÒP]S•+ySŒÊÞàÐY^ªHðÍѯÁÒgûÐÄû 5wÈ Ò)ä!íVA)ޫР„øë°™½ ûµZÐúÑÅ:W3”í—kXˆŒh àýêþ``AðoµÎÚåjÝ9™gì—½$•OÎ@؇Ÿù̬ê<`!ì“sygnïâ‡óù2}oR™%g”Qm˜ú�ÍÑ Öx}-&|f(¸)Z9ãw@vçÑ1/¾Ìýå,oXu�‹«íH–ujüMe¢JÆòQ1]SâA9gW˜9,ОíÞðº2c¤Ú5`.&WöÎQ]¨�>Ö„ùyQ�]ßöÝ€º«;¦eÈ>ÃBòxaô–0aü¸¨Nfí@c°~ë TaŠBo¹¡…~A‹m…'.v×–m�õCt ³6æCÏ ª‚:GNªÜæ¿éQˆÏëÏDÅôuúì£zô™ÝøÁä´r7C,QE€ÊÒÒ`¡¹„´§¨‹SÅi õQn|¿tÿûРV"•æ¢ykllÊSµlêÀªZ¥ƒÇË2â3ËÐViÔ æ³ ¿G ”™‰1XÀˆgÞ€WzâÞáðÙÂÑ7\óÖ” Z9¯€;éiD¬ð٘ͳBØ1a% ¯¸D¾U¹’š ˆ]µÆÅejvðÕ¾¹þ´–[D�ˆ�Jð8+e–8x\»ýÆmu'ãZ[å"+娼e¤Íþ¾ö,|ð�X7æþÚŠCÏŽ'hYŸÚk°{ßE3f£ð@;뇘hšN\wÛ:�(á-°$ŽVST¾R¼µ`¡×$@�¸�p*OŒ ó¦f1Òã-©6“?H,Ès+öÂ^ŒéŸ¬þxÜÄ”Ç4YäïЄ!Ù}¥¦Ó/¢d‡ï™öo[QÑNÏ5 „Ñ|ôC4°Q›Fµ‘ë&u `ù.ƒ§ŒÉfhžž§/º‰³yÀ|˜Ü6‰¤aê$ð§Mf$œpù&ßMCvz’|Ò_«iÄñ{<öf p¬°ÂÙNÚ¬î³O1Œ÷Ù�žˆ=M¬ü.ÀŒ3#}¶6#~8!�RÔ©‚ñ}­�&›¹WßKfhbÙ{ü©‚¼l<£Œ0º-ë ;#©�ר²ÃY+‘8kOºü}2ŸÒþ�åÀ­\{÷Ÿ4é×^»yì[®¥“ÿ•¶Ø*´nq/y)4¼Œ4 æ1c— ÞoUB1°é³†Ç\Ìï•8 Œ}Žr¶ƒÆõ5cF˜ É¿ê>³­¼½æJ˜ Îì_§�Êmcâå\ûf¢Â7í�°GÍ êH ËüV©TЏ­)ôyÜžˆ|è´Ë~âÐð’ÿ{¸¤'{¤ýà†ØÔº´®‹ÝÀOí¼ïÓ]©ó€>ØO5ÙV}jDþà�¥¸í�vîE# ÖÜ;›Ãx‰~4�½)ëbšuqgÃïâžæºÇ!·(®ýG“éï:!†KèݨÒé&(Ë€‰[ :_‘í«ì}· Vå*ÊñgéZ›2ȼœ�3Nò«ÈÿuP7P@*÷GÕî-9ß~üÿš¯XôQ9 œ´»æX¶'—ý•ÍÝWóãêÒÓ¾˜Ùefn!ÖJÓyˆþ¡úàl*ž«Ù*HŠœ2wv/lÚ¯aí÷ˆ‘Z§GJÏfü_¯û×ö#/úù0è•×ÜŒ2r�“ñ‘Ù· ÀœNZ°¡êßX¦²h�Æ‘{㲂mEÒg>6re¬Ã\Aã9ñÛÅŒauû³‘wðèvé~YOqKµ|ì¶å,­C’Pbñ�Ò.@%OùòàšÚÛÄ¡G{†œšŽ8•µ¸Èª,’òC¶½Ó±èËV·Žèº–ú�ƒè¬$xxê€î3Uî!_‹!ÓDf8Î8xfxg:YŒ1QnWqí6�MáY<)7"aÿgu`¶'¾-� +y,ˆ5òrÜ4[b6YPp¡9J^¶bl.Í xi0›8þULjÈêaqYé`5_Ð3\68ga»c,R¨[Y#p.\cteÏiY,ý_þ\ÄœJQÇ©&—˜&ÔÛ«ýc@œ}¿ŠldV‚À.i4\£¦¸"N•ÌB€wlmaker-0.8.1/.git/modules/submodules/inih/objects/33/0000755000175100017510000000000015222270712022070 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/submodules/inih/objects/33/787047c04375515565b09f2bbf7f9116e962910000444000175100017510000000065615222270712026614 0ustar runnerrunnerx+)JMU017d040031QÐKÏ,I,))ÊL*-I-f˜·\W'ÓæÊ538î˜u.I|¥òÁÄÀ 3J“¸ü}õ/!ÁñCþÖs·žf‰Ýµœ™ŽdVfz^~Q*çï­Q~]‘I‚z> çˆ.github/workflows/check_pr.ymljIqÊ@&�doc/meson.buildjIqÊHjIqÊH|0T�¤éé\ñ,Ÿ÷H·3&Ä:yL2Lƒ2u¶doc/xml/gtkdocentities.ent.injIqÊI¡jIqÊI¡|0U�¤éésÁ�)ååt’— :…P•®¾á¼doc/xml/meson.buildjIqÊJ»ˆjIqÊJ»ˆ|0X�¤éégtk-priv/scripts/version.pyjIqÊw‹^jIqÊw‹^|0‚�¤éé3©“UµnYÏ@�8—œøç`9Ú•include/gtk-layer-shell.hjIqÊ€`'jIqÊ€`'|0ƒ�¤éépº»¸>Т´&—þØE€yinclude/meson.buildjIqÊ€`'jIqÊ€`'|0„�¤ééËû_S± _,“9â%gê\Mâ meson.buildjIqÊ€`'jIqÊ€`'|0…�¤ééäe8™5ðUÝ$t@LX€Îf®meson_options.txtjIqÊ€`'jIqÊ€`'|0‡�¤éé¹,ààÅß*©[@­ùëf]äÝŸ2�òprotocol/meson.buildjIqʃÇ)jIqʃÇ)|0ˆ�¤ééHÖ/Õ�Ñ‚G¥¸ s•Ã)p™£€(protocol/wlr-layer-shell-unstable-v1.xmljIqʃÇ)jIqʃÇ)|0‰�¤éé‰âY¡û¦á÷=éïânú‘õhÖprotocol/xdg-shell.xmljIqʆͤjIqʆͤ|0Š�¤éérc*+Ä"cò+ Ê»‰…P ç release_process.mdjIqʆͤjIqʆͤ|0Œ�¤éé'q{QÜé€jÜKøüú?Bo3$&½4 src/api.cjIqÊŒSÔjIqÊŒSÔ|0��¤é鎓¾þØËû Î?,›p”ÿ첂cysrc/custom-shell-surface.cjIqÊŒSÔjIqÊŒSÔ|0Ž�¤éé ?zË(å;×U9³£óø º¿pµÛsrc/custom-shell-surface.hjIqÊŒSÔjIqÊŒSÔ|0��¤éé"õ ÑŒpmJÌçð¼@kŸ#²src/gtk-priv-access.cjIqÊŒSÔjIqÊŒSÔ|0��¤ééRåLlé žioLn¥OW0 œ…src/gtk-priv-access.hjIqÊŒSÔjIqÊŒSÔ|0‘�¤éé ½T%J‹wX¢Æžàr¥?øʰ ®src/gtk-wayland.cjIqÊŒSÔjIqÊŒSÔ|0’�¤é鲬™ÿÒ²=åÚL\Qš/ÀXçD‰src/gtk-wayland.hjIqÊŒSÔjIqÊŒSÔ|0“�¤ééAcWOAlŠLò²Žxè x8·øTsrc/layer-surface.cjIqÊŒSÔjIqÊŒSÔ|0”�¤éé x3#NëŸ2ƒrÉñ¡t’Þsrc/layer-surface.hjIqÊŒSÔjIqÊŒSÔ|0•�¤éé mu×™Žàn‰i9FÚ®uèC—Ÿsrc/meson.buildjIqÊŒSÔjIqÊŒSÔ|0–�¤éé3w{{ÁTW­�tF ?áYåÐsrc/simple-conversions.cjIqÊŒSÔjIqÊŒSÔ|0—�¤ééO¾¢)IÇQ¥¤:ÉI&–ÑŒ£m'¿src/simple-conversions.hjIqÊŒSÔjIqÊŒSÔ|0˜�¤éé/±ù€Fµ©²|Yh÷ü9 rŒ‹À~src/xdg-popup-surface.cjIqÊ–êjIqÊ–ê|0™�¤ééü5ÄG‡!m¹+þ#E†¶T£ Wsrc/xdg-popup-surface.hjIqÊ–êjIqÊ–ê|0š�¤ééÑf’†ùU§¤2ÕX+v�óm®ÊOÂsrc/xdg-toplevel-surface.cjIqÊ–êjIqÊ–ê|0›�¤éé9¹ý;söÚ¶[¦¨²ñôbJ‘ÀŸ˜rsrc/xdg-toplevel-surface.hjIqÊ–êjIqÊ–ê|0��¤ééÍÚ°ŒÕë-Bù{Õ§ª:+ò“)test/README.mdjIqÊ�T2jIqÊ�T2|0ž�íéé Tߑ͆.~n‡´‚pOZYL3±z$test/check-all-tests-are-in-meson.pyjIqÊ�T2jIqÊ�T2|0Ÿ�íééø¹šNÒþÿÙù…Óº{ıÐÒtest/check-licenses.pyjIqÊ�T2jIqÊ�T2|0¡�¤éé ^e99¤âé„313þÃ0øÁ?Vÿ�6test/integration-test-common/integration-test-common.cjIqÊ $ájIqÊ $á|0¢�¤ééC€…¸ÊÑ@$÷ú�üdåž6test/integration-test-common/integration-test-common.hjIqÊ $ájIqÊ $á|0£�¤éé³ ë4º�)µž(›œ0Õ=�P^yzÙ(test/integration-test-common/meson.buildjIqÊ $ájIqÊ $á|0¥�¤ééïÕÒ˜2'5.ÍŸöI*Y1µm "test/integration-tests/meson.buildjIqÊ¢ô(jIqÊ¢ô(|0¦�¤éé Ý�8$¡ Í„©�zèáyº¤ééT3test/integration-tests/test-adapts-to-screen-size.cjIqÊ¢ô(jIqÊ¢ô(|0§�¤éé •ú— »i¼9ç/Ë�‰$`ºr8h;test/integration-tests/test-auto-exclusive-zone-no-margin.cjIqÊ¢ô(jIqÊ¢ô(|0¨�¤ééƒ0›.ÂÅMÎI‰ö]ÀàÊ@¼ˆ}'²Ctest/integration-tests/test-auto-exclusive-zone-weird-bool-values.cjIqÊ¢ô(jIqÊ¢ô(|0©�¤ééžfQuPËx×�¿Ð$œÞ¿vUkf=test/integration-tests/test-auto-exclusive-zone-with-margin.cjIqÊ¢ô(jIqÊ¢ô(|0ª�¤ééU.xaX.ÉŸy2pŠÃ×~.r1test/integration-tests/test-close-layer-surface.cjIqÊ¢ô(jIqÊ¢ô(|0«�¤éé6-€Â2›ÚG½ÀË¡ÛgIÒÍÌ/test/integration-tests/test-create-subsurface.cjIqÊ¥R0jIqÊ¥R0|0¬�¤ééEêÊÁÙ8·K"8†•Á>Zê%1test/integration-tests/test-creation-properties.cjIqÊ¥R0jIqÊ¥R0|0­�¤ééQ¨jƒ× Ù°nHóëcG¥BžŸ=test/integration-tests/test-exclusive-zone-below-negative-1.cjIqÊ¥R0jIqÊ¥R0|0®�¤ééÛ,qm ·ž3–•m3ÅE¡•ž¼9)test/integration-tests/test-expect-fail.cjIqÊ¥R0jIqÊ¥R0|0¯�¤éé|¤6ŒìÆIÒÅ'½?“Ë’0i†Ï�5test/integration-tests/test-get-auto-exclusive-zone.cjIqÊ¥R0jIqÊ¥R0|0°�¤éécôyð·Ô“8–‡ñU¢ø©j˜Œ9test/integration-tests/test-get-explicit-exclusive-zone.cjIqÊ¥R0jIqÊ¥R0|0±�¤ééó+î?Fû ãÁ{SëLÐTÀGì8test/integration-tests/test-get-keyboard-interactivity.cjIqÊ¥R0jIqÊ¥R0|0²�¤éé‡ÄÉÅÚîàg~½Ó†-Ÿs°-5?h/test/integration-tests/test-get-keyboard-mode.cjIqÊ¥R0jIqÊ¥R0|0³�¤ééœvã�ËhsÄÍþMDJ«ÜÅúMÇ…"'test/integration-tests/test-get-layer.cjIqÊ¥R0jIqÊ¥R0|0´�¤éédýq‘r41 ™Ì u6:)test/integration-tests/test-get-monitor.cjIqÊ¥R0jIqÊ¥R0|0µ�¤ééòÊ»S,ÈðÂtüî·Lܼ=Г·œ1¥ÔÈŽ‹íþqOÜÎú.test/integration-tests/test-set-size-request.cjIqÊ¥R0jIqÊ¥R0|0Â�¤éé þ´Z�X¾-¬\b‡Éo¹VÛˆü,test/integration-tests/test-single-anchors.cjIqÊ¥R0jIqÊ¥R0|0Ã�¤éé©H¹-LŸ¶Î6„"¥ñ °]ýð“Ctest/integration-tests/test-window-with-initially-attached-buffer.cjIqÊ¥R0jIqÊ¥R0|0Ä�¤éé+âzY¸úYÌþ&lxM¥ïð-,\test/license-ignore.txtjIqÊ´”vjIqÊ´”v|0Å�¤éé‘D-îNDÍc„·÷ËJ ðóé‚test/meson.buildjIqÊ´”vjIqÊ´”v|0Ç�¤éé VZ° •Ä;Þp) "3Ï1Ì ÅOtest/mock-server/meson.buildjIqʸï¥jIqʸï¥|0È�¤ééB«ì Àj¤R‡pÅÜâ‰Á•ß×ÀÖtest/mock-server/mock-server.cjIqʸï¥jIqʸï¥|0É�¤éé Uå\N¦?5jŒÚË5—ÿî©„ »test/mock-server/mock-server.hjIqʸï¥jIqʸï¥|0Ê�¤éé5š�–ùëZ\NvÏášÓ&âÿü—Štest/mock-server/overrides.cjIqʸï¥jIqʸï¥|0Ë�íéé)'¤:à·:Û®-N"Á™˜G¡B0¡Útest/run-integration-test.pyjIqʸï¥jIqʸï¥|0Í�¤ééUÀ áW0²[EÝH®ñ Ã[bDtest/test-common/meson.buildjIqʾîûjIqʾîû|0Î�¤éé¶tžJ f✽‡ïÉ÷;¶øN?ëtest/test-common/test-common.hjIqʾîûjIqʾîû|0Ï�¤ééà hRØþ sB¯ÄXx¯?³:ódtest/tests-not-enabled.pyjIqʾîûjIqʾîû|0Ñ�¤ééÜ£%$¯îSYá…{©’û¾_Ætest/unit-tests/meson.buildjIqÊÁ‡¾jIqÊÁ‡¾|0Ò�¤éé$]9'p¢®~~I:ªcŒB÷aTÝ×"test/unit-tests/test-get-version.cTREEœ126 8 WÅoËœ"àEØ»*@sŽÈeµ¢doc4 1 ù‚MFK¤8®!qo›š”ž¾JÛxml2 0 vÊnï ùŸþc'¥JW@¨Òzçsrc16 0 :}`ƒ,ZŽu¸7|•8(ž „—test49 5 ¡¨êfÕj!剹 7þSóunit-tests2 0 4ÚØ¢—ñìë–(Ip÷„P‘kómock-server4 0 ŸfÞHè�ÛÚ!7(}prÛZ9)4test-common2 0 »Ja=GŽ´ù3÷•ÊôXÔ7~integration-tests31 0 yÿÊœÁžÀû+ïp*°š´èåwPintegration-test-common3 0 l¼YjéH¸¨ú†ð•g�´ûgi.github4 1 àŸ7Ç|N Ñð-óÅŠª²þworkflows3 0 ¹wƒò>]ÅÉð¸?š¾"œq×Ó’wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/HEAD0000644000175100017510000000005115222270712022343 0ustar runnerrunner5f71546112fd284aced13e7b2391a601204bcacd wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/logs/0000755000175100017510000000000015222270711022666 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/logs/HEAD0000644000175100017510000000075315222270712023320 0ustar runnerrunner0000000000000000000000000000000000000000 5952eac2b084551e4fa340c98c8c3aab0215b9c2 runner 1783198153 +0000 clone: from https://github.com/wmww/gtk-layer-shell.git 5952eac2b084551e4fa340c98c8c3aab0215b9c2 5f71546112fd284aced13e7b2391a601204bcacd runner 1783198154 +0000 checkout: moving from master to 5f71546112fd284aced13e7b2391a601204bcacd wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/logs/refs/0000755000175100017510000000000015222270711023625 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/logs/refs/remotes/0000755000175100017510000000000015222270711025303 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/logs/refs/remotes/origin/0000755000175100017510000000000015222270711026572 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/logs/refs/remotes/origin/HEAD0000644000175100017510000000035515222270711027221 0ustar runnerrunner0000000000000000000000000000000000000000 5952eac2b084551e4fa340c98c8c3aab0215b9c2 runner 1783198153 +0000 clone: from https://github.com/wmww/gtk-layer-shell.git wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/logs/refs/heads/0000755000175100017510000000000015222270711024711 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/logs/refs/heads/master0000644000175100017510000000035515222270711026132 0ustar runnerrunner0000000000000000000000000000000000000000 5952eac2b084551e4fa340c98c8c3aab0215b9c2 runner 1783198153 +0000 clone: from https://github.com/wmww/gtk-layer-shell.git wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/shallow0000644000175100017510000000012215222270712023312 0ustar runnerrunner5952eac2b084551e4fa340c98c8c3aab0215b9c2 5f71546112fd284aced13e7b2391a601204bcacd wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/info/0000755000175100017510000000000015222270710022654 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/info/exclude0000755000175100017510000000036015222270710024232 0ustar runnerrunner# git ls-files --others --exclude-from=.git/info/exclude # Lines that start with '#' are comments. # For a project mostly in C, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/config0000644000175100017510000000200415222270713023110 0ustar runnerrunner[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true worktree = ../../../../examples/gtk-layer-shell [remote "origin"] url = https://github.com/wmww/gtk-layer-shell.git fetch = +refs/heads/master:refs/remotes/origin/master [branch "master"] remote = origin merge = refs/heads/master [gc] auto = 0 [http "https://github.com/"] extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUpoZFhSb2JtUWlMQ0poZW1NaU9sc2ljMmwwWlM4eU1EUTVNemcwTURrNU9TSmRMQ0psZUhBaU9qRTNPRE15TURFM05UQXNJbWxoZENJNk1UYzRNekU1T0RFMU1Dd2lhWE56SWpvaVoybDBhSFZpSWl3aWFuUnBJam9pT0RKbFpXTmxZMlV0T1RkbE5TMDBOakkwTFRreVlqWXROVFJoWVdZNE16aGhaVGhsSWl3aWMzVmlJam9pYVc1MFpXZHlZWFJwYjI0dk1UVXpOamdpTENKMlpYSWlPako5LmYtN0ljd1lxcDhaUUMzYlV4bWlFa3NzN0pqdzVFZ19tdVduU3JXWVMtMDNzM0ROS2Q0Wi1Kd3dzaHRvUnB0X0FnVm96amdaRGhmZDlOMElOU1ExVndn [url "https://github.com/"] insteadOf = git@github.com: insteadOf = org-130065133@github.com: wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/packed-refs0000644000175100017510000000016215222270711024030 0ustar runnerrunner# pack-refs with: peeled fully-peeled sorted 5952eac2b084551e4fa340c98c8c3aab0215b9c2 refs/remotes/origin/master wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/description0000755000175100017510000000011115222270710024163 0ustar runnerrunnerUnnamed repository; edit this file 'description' to name the repository. wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/0000755000175100017510000000000015222270710023044 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/pre-commit.sample0000755000175100017510000000316115222270710026327 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=$(git hash-object -t tree /dev/null) fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --type=bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff-index --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/push-to-checkout.sample0000755000175100017510000000533715222270710027464 0ustar runnerrunner#!/bin/sh # An example hook script to update a checked-out tree on a git push. # # This hook is invoked by git-receive-pack(1) when it reacts to git # push and updates reference(s) in its repository, and when the push # tries to update the branch that is currently checked out and the # receive.denyCurrentBranch configuration variable is set to # updateInstead. # # By default, such a push is refused if the working tree and the index # of the remote repository has any difference from the currently # checked out commit; when both the working tree and the index match # the current commit, they are updated to match the newly pushed tip # of the branch. This hook is to be used to override the default # behaviour; however the code below reimplements the default behaviour # as a starting point for convenient modification. # # The hook receives the commit with which the tip of the current # branch is going to be updated: commit=$1 # It can exit with a non-zero status to refuse the push (when it does # so, it must not modify the index or the working tree). die () { echo >&2 "$*" exit 1 } # Or it can make any necessary changes to the working tree and to the # index to bring them to the desired state when the tip of the current # branch is updated to the new commit, and exit with a zero status. # # For example, the hook can simply run git read-tree -u -m HEAD "$1" # in order to emulate git fetch that is run in the reverse direction # with git push, as the two-tree form of git read-tree -u -m is # essentially the same as git switch or git checkout that switches # branches while keeping the local changes in the working tree that do # not interfere with the difference between the branches. # The below is a more-or-less exact translation to shell of the C code # for the default behaviour for git's push-to-checkout hook defined in # the push_to_deploy() function in builtin/receive-pack.c. # # Note that the hook will be executed from the repository directory, # not from the working tree, so if you want to perform operations on # the working tree, you will have to adapt your code accordingly, e.g. # by adding "cd .." or using relative paths. if ! git update-index -q --ignore-submodules --refresh then die "Up-to-date check failed" fi if ! git diff-files --quiet --ignore-submodules -- then die "Working directory has unstaged changes" fi # This is a rough translation of: # # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX if git cat-file -e HEAD 2>/dev/null then head=HEAD else head=$(git hash-object -t tree --stdin &2 exit 1 } unset GIT_DIR GIT_WORK_TREE cd "$worktree" && if grep -q "^diff --git " "$1" then validate_patch "$1" else validate_cover_letter "$1" fi && if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" then git config --unset-all sendemail.validateWorktree && trap 'git worktree remove -ff "$worktree"' EXIT && validate_series fi wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/prepare-commit-msg.sample0000755000175100017510000000272415222270710027767 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first one removes the # "# Please enter the commit message..." help message. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. COMMIT_MSG_FILE=$1 COMMIT_SOURCE=$2 SHA1=$3 /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" # case "$COMMIT_SOURCE,$SHA1" in # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; # *) ;; # esac # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" # if test -z "$COMMIT_SOURCE" # then # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" # fi wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/update.sample0000755000175100017510000000710215222270710025534 0ustar runnerrunner#!/bin/sh # # An example hook script to block unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --type=bool hooks.allowunannotated) allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) denycreatebranch=$(git config --type=bool hooks.denycreatebranch) allowdeletetag=$(git config --type=bool hooks.allowdeletetag) allowmodifytag=$(git config --type=bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero=$(git hash-object --stdin &2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/pre-rebase.sample0000755000175100017510000001144215222270710026301 0ustar runnerrunner#!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up to date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/applypatch-msg.sample0000755000175100017510000000073615222270710027211 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup commitmsg="$(git rev-parse --git-path hooks/commit-msg)" test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} : wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/commit-msg.sample0000755000175100017510000000366415222270710026337 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines and messages that # would confuse 'git am'. ret=0 test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. ret=1 } comment_re="$( { git config --get-regexp "^core\.comment(char|string)\$" || echo '#' } | sed -n -e ' ${ s/^[^ ]* // s|[][*./\]|\\&|g s/^auto$/[#;@!$%^&|:]/ p }' )" scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$" comment_line="^${comment_re}.*" blank_line='^[ ]*$' # Disallow lines starting with "diff -" or "Index: " in the body of the # message. Stop looking if we see a scissors line. line="$(sed -n -e " # Skip comments and blank lines at the start of the file. /${scissors_line}/q /${comment_line}/d /${blank_line}/d # The first paragraph will become the subject header so # does not need to be checked. : subject n /${scissors_line}/q /${blank_line}/!b subject # Check the body of the message for problematic # prefixes. : body n /${scissors_line}/q /${comment_line}/b body /^diff -/{p;q;} /^Index: /{p;q;} b body " "$1")" if test -n "$line" then echo >&2 "Message contains a diff that will confuse 'git am'." echo >&2 "To fix this indent the diff." ret=1 fi exit $ret wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/pre-receive.sample0000755000175100017510000000104015222270710026453 0ustar runnerrunner#!/bin/sh # # An example hook script to make use of push options. # The example simply echoes all push options that start with 'echoback=' # and rejects all pushes when the "reject" push option is used. # # To enable this hook, rename this file to "pre-receive". if test -n "$GIT_PUSH_OPTION_COUNT" then i=0 while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" do eval "value=\$GIT_PUSH_OPTION_$i" case "$value" in echoback=*) echo "echo from the pre-receive-hook: ${value#*=}" >&2 ;; reject) exit 1 esac i=$((i + 1)) done fi wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315222270710030065 0ustar runnerrunner#!/usr/bin/perl use strict; use warnings; use IPC::Open2; # An example hook script to integrate Watchman # (https://facebook.github.io/watchman/) with git to speed up detecting # new and modified files. # # The hook is passed a version (currently 2) and last update token # formatted as a string and outputs to stdout a new update token and # all files that have been modified since the update token. Paths must # be relative to the root of the working tree and separated by a single NUL. # # To enable this hook, rename this file to "query-watchman" and set # 'git config core.fsmonitor .git/hooks/query-watchman' # my ($version, $last_update_token) = @ARGV; # Uncomment for debugging # print STDERR "$0 $version $last_update_token\n"; # Check the hook interface version if ($version ne 2) { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; } my $git_work_tree = get_working_dir(); my $json_pkg; eval { require JSON::XS; $json_pkg = "JSON::XS"; 1; } or do { require JSON::PP; $json_pkg = "JSON::PP"; }; launch_watchman(); sub launch_watchman { my $o = watchman_query(); if (is_work_tree_watched($o)) { output_result($o->{clock}, @{$o->{files}}); } } sub output_result { my ($clockid, @files) = @_; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # binmode $fh, ":utf8"; # print $fh "$clockid\n@files\n"; # close $fh; binmode STDOUT, ":utf8"; print $clockid; print "\0"; local $, = "\0"; print @files; } sub watchman_clock { my $response = qx/watchman clock "$git_work_tree"/; die "Failed to get clock id on '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; return $json_pkg->new->utf8->decode($response); } sub watchman_query { my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') or die "open2() failed: $!\n" . "Falling back to scanning...\n"; # In the query expression below we're asking for names of files that # changed since $last_update_token but not from the .git folder. # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the # output to file names only. Then we're using the "expression" term to # further constrain the results. my $last_update_line = ""; if (substr($last_update_token, 0, 1) eq "c") { $last_update_token = "\"$last_update_token\""; $last_update_line = qq[\n"since": $last_update_token,]; } my $query = <<" END"; ["query", "$git_work_tree", {$last_update_line "fields": ["name"], "expression": ["not", ["dirname", ".git"]] }] END # Uncomment for debugging the watchman query # open (my $fh, ">", ".git/watchman-query.json"); # print $fh $query; # close $fh; print CHLD_IN $query; close CHLD_IN; my $response = do {local $/; }; # Uncomment for debugging the watch response # open ($fh, ">", ".git/watchman-response.json"); # print $fh $response; # close $fh; die "Watchman: command returned no output.\n" . "Falling back to scanning...\n" if $response eq ""; die "Watchman: command returned invalid output: $response\n" . "Falling back to scanning...\n" unless $response =~ /^\{/; return $json_pkg->new->utf8->decode($response); } sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; $output = $json_pkg->new->utf8->decode($response); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # close $fh; # Watchman will always return all files on the first query so # return the fast "everything is dirty" flag to git and do the # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); $error = $o->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); return 0; } die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; return 1; } sub get_working_dir { my $working_dir; if ($^O =~ 'msys' || $^O =~ 'cygwin') { $working_dir = Win32::GetCwd(); $working_dir =~ tr/\\/\//; } else { require Cwd; $working_dir = Cwd::cwd(); } return $working_dir; } wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/post-update.sample0000755000175100017510000000027515222270710026523 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/pre-merge-commit.sample0000755000175100017510000000064015222270710027423 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git merge" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message to # stderr if it wants to stop the merge commit. # # To enable this hook, rename this file to "pre-merge-commit". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" : wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/hooks/pre-push.sample0000755000175100017510000000253615222270710026023 0ustar runnerrunner#!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/0000755000175100017510000000000015222270712023354 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/05/0000755000175100017510000000000015222270712023600 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/05/85f0efc57078d9218db850d0493d49741ec982wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/05/85f0efc57078d9218db850d0493d49741ec980000444000175100017510000000035415222270712030463 0ustar runnerrunnerx+)JMU025d040031QH,.Ñ+¨døéA•”¥gæç�TÝñßÊV»SÍU¬[ãW˧¹Ûƒ[÷ÚÛÓi—wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/7a/0000755000175100017510000000000015222270712023663 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/7a/cb28e53bd755113910b3a3f3f80ababf70b5dbwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/7a/cb28e53bd755113910b3a3f3f80ababf70b5d0000444000175100017510000000275315222270712031052 0ustar runnerrunnerx•WÛnÛFí³¾bà<Ôˆ8MžZh‰²ˆR”@Rqý$PäRܘâ Ü¥¥è¿÷Ì’²åøÒT�-q/s93sf´®Ôš>}úõ—ŸÎ‡””R“¨�l²„ÇJf¢Ö"§¶ÎEC3?Ðo©Ý¾‘›ÒÐÇ?P¬v¥t-k#šþÈB4[©µT5K*E#Ö{Ú4)Žä�¤ ÊÊ´Ù‡Œ¢´ÞÓN4ÔÚ¤²–õ†RÊ ŠO¶O«Âܧ°0­sJµV™L!�r•µ[ŸÖÇæk:5¥ “¸¿qrf•ä"­HÖ�&`u/ì^šRµ†¡M#3–áàPVµ9ÛpØ®äVöøºõ_³å­†l§C[•Ë‚?…uk×®+©K‡rÉ¢×­ÁIÍ‹X‡ý8W iQU,AÂnëë£uö k6ÐßCdõÞ—jûÔ@T´M •wrȬÆ/"3¼Â–ªªÔ=»–©:—ì¯þ­[‚ýt­î„u¨‹q­ 쵘Û(XKºÐö[ºLáÀIcQƒr`ÌKŸàb»ÖÑ—ÀN5Vé÷¾¾?1õ(žO’k7òÈ�iÍ?ûcoL'nŒç‡®ýd:_&„‘&74Ÿ�ÞП~8vÈûkyqLóˆüÙ"ð=¬ùá(XŽýðŠ.q/œ'øHhMæ”@a/Ê÷poB3/M!Ù½ô?¹qhâ'!Ëœ@¨K 7JüÑ2p#Z,£Å<ö ~ ±¡N"hñf^˜¼‡V¬‘÷OÝ °ªÜ%¬�¬}£ùâ&ò¯¦ MçÁØÃâ¥ËÜËÀëTÁ©Qàú3‡Æî̽bë"šÃàÈë­»žzv ú\¼G‰?Ù�Ѽ“(¤ Ñ2Næ³U<õ‚`/£‰;òVÓÁ;ìÉZ¼¶�ë]^Ñïs{Ž¿÷åÇ‹9s»8@�µHõûj¥Û¦H3qqXúšo^\Û)mk@4ƒ�ÙïÚ‹Y�ZmÔ6.Q q'�ž/]üÀµE#ïÀU/Üîw~DÈgÙ˜ôÜ„~¼nyfð÷€ð:?§J”Ù³m˜ ˜À ¹ºÿY0&-75”õ„¤Ó­ ÿ·c�f•SÖöËÞN÷3³Z3Ýhª°´D´MwöD+âNÉœN‡Ø:£ÓçÑP·`°×÷¥áctÏàqïÍ ¡bÊ:¯Ö¢Pèl×Q§¸2·hSð‘»R[C9â}lŽ]{Ë #�××jD¤Ú[µ�Ä |¯ÙÖma+ÚÚ¶’ÕŠ6n7ªm2P>ãÄäŒ+ܟ؃ºH+ùMüˆI=Rœè;µkw4<n„éÞàXÇ·^Gâû8ÒÐêY•ÖÿõX~,íPŠG(G §¡ãÍJm$âK¡¶Â4ýdpÈ[:_¹kRt$YÛ¦¥Û¬<³^å·23­7p:lz‰+–øD0èŸ7««/«ç‰|(Z4[$èð®«á kÐóÓžî:ÂèÔ¢X—Gy†tÁ4eÓ«]gò›[yÕèŽ�'–2ô]éÑ^˜�-½ÌRÛ ³Bõ@–+Œ]æ•jUáÐcý A«Ž/8XPéó”ö‹2úˆ�6}º8ÈyŽ%IsA¢Ò¢ºçp,�?1:ì0ŒñÐ.a~ÂÔ˜‰¢E‘ žãEGv ˆfõ’[túªÝG/Ý´"=}šv¶TÈL;*eEª-�nªÆ…ù´_ ‰;Ì«Ègµss!†²á-¡w1­|a6˺‹8Ë%Û@¦£¸ã0—ÌàØ±9qŒ9fÀ>²¶i TU¦˜ïRB5ô\È#.iõ� a²Ò«Þ²—y™Òñd†µEØû�­è¸÷i†¼¡·ð_ Ç<Ãcèw˜ôVÔ€ �m,mÑËñ�áÛÓ}‰-Æë¨=tùÌ@‚DL£ö ãÁ;�)¹° óåñå_¤y&wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/79/0000755000175100017510000000000015222270712023613 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/79/ffca9cc19ec0fb012bef702ab09ab4e8e57750wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/79/ffca9cc19ec0fb012bef702ab09ab4e8e57750000444000175100017510000000216615222270712031237 0ustar runnerrunnerx…”}PTUÆ—%PiŒÁ $š‚¡%Ì<Û. `âh‚ä¢d9r÷îÙåÂÝ{/÷ƒeÙ¤i£Ô¢�Gª)Ìjb“ù� R ('ä+A§!d:-v×aüãþsçù=ç¼ïûœ—g!ôQ†(ƒ%J…"8(ÈG9š’«‚TKnuuäø¯–{·Ý�Zùºò{�‹Ï9`jŒá9ÀӀÑ8"ÊqÉû¡²b—–̲£éžƒ¿*%%CC;m8!0 'ŽH… �¦  h ÃX-A!Þ}úc—óDÕš›/5dÉ*ŸeCÅë!Áª�ЦI�Š‘ä��ℼÎÓ•uwoMÓúªÃûý¿}¤Á'ÎßD³]*lkNë>p±]v²÷bêcqÉkœ¤9HÌYÀ ¬ÃÅúãäiX¼¼±ÈàíæÄù±;CÎÚP,ÄxˆÕ<Þ©s 8q-²º¦yqñumTG‹×凂¦ÃÒ dyb®ÂMÃMµWCͯú†šrk�×I÷ ûYSvMVA’Ö j1^l¼ÝõLÒÁî%WËŸ”R›'GðÈÒ …ÎE¶ Äy Á©W¥è–š óru�Òå–MŹ…Uk¬ÕZˆRñðtYœ=úST‡Eâ_žÓü¡‚0µµ'aC8ÁÛeÑS¯qsgNhÞ¡‰¸Ïþv/Kz"?ÛžN†�¡Y G%¼Á/z�…GüsêF­1v$º]²³Æ3rtAXG«ÅÖ7Z®�õk3ª6�"®¬v·‰ XèÜØ‘6õƱæD®¾e6æ•-_öX¦c.½ëkﯣ)‚§Y¤¾—rœu R>¾ü“Å—] žÁ/¿íf¯¦0ô &€ OëÀÿ?�AÓùXïUŽ ãu¼Ã̘9º§*Ì»}a5Ô`É#®Ì½ü¹éS]½Ži-þq·…}[˦æPÒ(ôÝ�¶ž Ô´y,‰Nrzzm½)wÙSÖTÖz‡ÎÞ�¬=ΞøæøÆq�äÀ‰È¡�g²²%¦ ãÔY;‚†fy¨<+ˆÍM¿û)K�g;±"áÖW=f”åÛT¨ƒ”š$ÎÜ~î·;y³Š�ç«cLç"Îô°Çúªž…'Ò¬¸s wçÒéYA×i�IÇä_y­õœá95j«ÃH´Ó¸¹Å1J4ðë¬ úô™Ck�ãû±tö‹}}Çòì ÌâÍm ýmNû}gF„ŠÝ·» t/„,?ÈbĨlå`IàŽ„0ÓÁ>#þFUá×6Moü_Ê$)RÜ{*©ù“õ~©hû#ÖÞ>‹Ò‚Põç[W>O÷0ú¹í sö²W‹;°0­T1E ˆ×å˜O*K;>xï¯Ù”­=­6)âJKB«~ÏJ¿Ûs þðøêMÕ¡Fºb×õÃ3ÖgÜÏЋ{—@�„ÀHÒ0žÇðD • Ñ@ñÝl®ÑE?´Jƒ2}K'|Ê÷ÞÏùˆ%¦±wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/93/0000755000175100017510000000000015222270712023607 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/93/befed8cbfb20ce3f2c9b7094ffecb28263791cwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/93/befed8cbfb20ce3f2c9b7094ffecb282637910000444000175100017510000000374015222270712031251 0ustar runnerrunnerxåX[oâHÞg~Å™ìÃ@dBOK3ÓÛ‘pˆ· p&ÛO–±Ë¸¦� ùÄŽú¿ÏwÊeCL÷<ìÓFD@ùܯ_±JÕŠ~~ÿËû ®ÉKdA"+e.(–© |Me(²BDTe‘ÈéÑõ:t� ÕvŸËuRÒûwïßÑRm)èYf¥È É\äYRe,)¹Xíi� ‰,Šs!HÅ&A¾•Š‚lO[‘`P«2�™ÌÖPULY²}…ŠË] ƒ,¢ (T(È£H…ÕÆ%ëcó ê–‰ «¥á¸êi%‘R’¤ Xm„íd™¨ª¤\e.C–a�(L«ˆmh§r#�f×þlyUÀ¶Ó¢�ŠdÌïB»µ­V©,‹"É¢WU Ê‚u`-öc r*Dš² »µ¯ë4 kAl ß„HëÝ%jóÚ„(®ò *ðD !ÓaÉ'ly¬ÒTíØµPe‘d‹_MÚ<<VêEh‡êgª„½:æ: Ú’:µæQ‘p`…¢ÑQƒrĘ�Ÿàbµ*Jd_"[•k¥§¾Þ4F<8´œÝ{ÏöÂ!wIóÅì7wäŒèÊ^âû•EÏ®÷0{ò {ê}¦Ù=ÙÓÏôÉ�Ž,rþ3_8Ë%Íä>Î'®ƒ3w:œ<�Üé˜îÀ7�y4qQÐêÍȃB#ÊuÀwO�ÎbøÉö�;q½ÏݻޔeÞC¨Ms{á¹Ã§‰½ ùÓb>[:P?‚Ø©;½_@‹óèL½hÅ9¿á -ìÉD«²Ÿ`ýBÛ7œÍ?/ÜñƒG³ÉÈÁá�Ë컉S«‚SÉí>Z4²í1[·  ^h2cÝóƒ£� ÏÆkè¹³)»1œM½¾ZðrᵬÏîÒ±È^¸KÈýbñNpp̦Ì7uj)jmu›�pÀžàtkËȱ'�…ôL_sJ�Î?MaÐUX¥ÚôQ¡iÚ/ª<Bq“\Q¬Ë/ý]°OÑolsùÒÂP?<°ý lüß$·Ç‡£ó‡­ŽÛN¥‰ç~(J=”èº6Ô׆úÆPÿ‹ØÓGº2¬þ9š«,.¯ÐoþP;»d_—µ«sØ�‘Õù£Cø—_04#µ£k˜î£'ñùCç«¡-zQ2êœSã«ÌÐûÏ-µ§î[ut�Ñ÷Œ:þÜ¿}‘yYiÿ6–Y�Êÿ êj¢Ú¤µ¯g³>êßr¸anïí3}mãö=Væ½Z™v:Z‹’®w’ß-ú–ák?%›/c?dJݱ÷ÉvGcÇ«­oMíß"Ù£�1¾YÌ­#"5GJk&¦ôù¿ÎÄ÷ªiÃsj%Jψjô³ª_×°�5WR÷†>¨{ Ér³°iÚš«ëé„÷ï¥dlá"×àÿk:L£îÒ¦ÃQ“‡Ï( Nàë<èò8МÉÝ›b=ˆlÊ`0 7ÃnÞ`Ï£µ*Dþ‚͹Kv'mÐv´ 2‘RX¾$7ÁZÐ*¿¬sDÖ£ñèÊk6(Ë LÀµªâHíwÌ b 1 Ñw%v½®~¨e`¦wŽÐr³MÅ@‚œq¼HÔ®ÛzÈ)hiÑ»¡±|a q,5 \?2l¢ Qk « e|—h›«R…*¥©R�Ý€Uí€5‡²Š�…ö Êà/‚‘ýˆ­6@CŒýXµ‘ŽVÚ{�¼ˆFo£°)æÎ3*#kÚàKX•‹ Ð j‰©Ú¯çÞʤϭ ô’îû�оыÈTiD<™°ïðwH¸o<éŽ,š>M&½Ã«©‡“ɬ“Ϭ#QÍ”igŠÏ#AÐoáÑëZ<µ¤Yb$Â4àÓmì}]X@ª˜ ÇÐ<6µo¾Ö4ÇÏ"UŠ�ÖëA6†ÑåÅP¾Kƒß:¿›E¶öþEE¯×û@¨ðeµ S<'“. lÀ_¾BT[Üò¢¬×AÍn¶¯?»wç6é¼Þ„ýd¢öŠõxë´Û¤^æ: oØÑ–j–ëéöøád)5Y>Þ}Gb8äzwøjÅp_—G”�suÇþìîßuH[‹5z–fa¶sg�0Ð_séÂýK¹Ýñ¨Æ,S\-â}�Î*zƒpZ ¹tñ×2¾×9dÑ•¸%Œý! ÷�=üDÝoh0L¸%²õß§ùø{JÀpP $ãÆøzKÐÍóL´ÑE½? Úð¼××/=1W óõª–Æ#:S»–ëû‚qÑ‚WAúÊ}>|tñCÁ¡Ì4¦:§ÔÀ�SÜô9‡Çt“ôZêVуÔ;s²nŠŸGÿ¥º?_r¨mä�Èü<üfùÏMŒ×Ø»�/(ÜÆêVÑn˜’3n\.ߘª¼Ø ô/ÇoÛ¤Ã{QÕi´ÿǺí‡ôæÑW¡ & èÔVðXÛ×S]£ ì­õ�‡4âh‰ é�o‚á®°Lñ PU™%€H;½Ðp?øé¸ ã…øRØ �•’²Ü¿kà�ju‘ƒÝf·ã{gëSäõ½v€¾*Q ~þ©vààð>ò/Q˜(Q�®v�7¸àÞž,�º /¯OÀïçúN°ã fî¹…p›Ð_?Òñt¹\ÍJ9ÝM502Òšq©×Šd‰Œp­üÊ·D_;}*æ././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/93/557fb56e59cf408d38979cf813e7601f39da95wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/93/557fb56e59cf408d38979cf813e7601f39da90000444000175100017510000001032515222270712030564 0ustar runnerrunnerxÕ[]sÉͳ~EG®­È’lg“e“#‰2 U\©5@³fØ™AX›ä¿çœÛÝà JmRe—%è¾}?Îýìö0Œ‡êÓ>þãoN?¨þ,H•޲ Ñj„Zá×0é(ÕcµŒÆ:Q·Íþ�ú€?ê2^<'Át–©Ï?T½x1 ´z¢L'vÉ�NæAšqDJ3�è᳚&>–Œkj’h­â‰Íüdªk*‹•=«…NRlˆ‡™DA4U¾á(®ÌÈ_O²•ýh¬ü4�G�zj�–s0ïg<�ì§ê(›iuس;ßË!cí‡*ˆ@MƒkKld³x™©D§YŒH£†E£p9&îë0˜önùSr¾L!ù¬©y<&üW‹X‹å0 ÒYM�’.3¬Lù¡(¶F9NãD¥: I!ß"ëš;YÃS œoU$ç®fñ¼, T4Y&Ž„R°gCerâOz”ñr>‰Ã0^Q´Q�Ê›žY³õñ½?ŒŸ´dlÅø�‹„cZûU:ó!À ­ápè˜9™ âr˜f°~,âDÝ”õÄ1qã©^çªÿPïzªÙSwÝÎ�͆×P‡õ~?¬©‡fÿ¦sßWXÑ­·û_UçJÕÛ_Õ—f»QSÞßîº^¯§:]Õ¼½k5=|Öl_¶îÍöµºÀ¾v§¯ZMDûÕÇ�–TÓþ+uëu/o@¹~Ñl5û_kêªÙo“æˆÖÕ]½Ûo^Þ·ê]uwß½ëô<ßÙv³}ÕÅ)Þ­×îŸàT|¦¼ñ‹êÝÔ[-9ª~î»Âßeçîk·y}ÓW7�VÃÇ8«_´âÒd ¢L¥ „èà#\g$™°afŒê_ÿá·ÇÇ M—þTÿåðòPÿ•_)Ž«`<ÕÙ å_P$úç%Bª:¢Õš�k¯¯Ž„��YNV—±/CŒœif”÷6OO­ŒXk®£…QÉ"½BÔ–SŠl�.N—•[ÖÔ'ü1Çüã_ÜÝ ‘Ø0Aé�±Œg[ð#ú1¹¢ž2å Rx8SSÎa�’ �3‚ãÅ"N‰) åäÑéf�Œs”" RF+Ԝ٫ŽÞ3a�0é‰ N›à#˜F$/‡Óê!’`ŒíÁe‰q«<©jóEø ½-4¤àÙ*V#„k˜L >‡1@j´Â\kÕâS-Ö¥¯Þu³=hx—­^î–×Ù£x‰xœü$Ø9ßt|.ê—_®»�ûv㌮¤†þèqšÄ(7 ly”Úµ·Óïwní¾8Ë� E‹/íéwî̆,^¼¾ºó£×ŸfRbú¯ïB¨ï~´ïo/¼î"O¼ Ç y’'ÒßFz!ÉÞ"ú�–ó! ˆ˜OLPœ¨#d(ïLÍq"j¤hÊÔJeL–Ù'Ñ¡öSà ™ n£’âh<ÖcFŒÓƒìy¡w5ŽPÿ<€£nÚMK#»W‰Î_Z ¿ôµUéKKŠú«)„�ÿN…ÿfüßÀ⟫qêÁ5«aê!h ZÞU߀ Ô“LÙU.H#ZGÕ�“½’ÛÍfSL1 ì½»„×7í¼ØöŽ7í/ZáÿŹ¡Ô_ÔÇjx­íñÂ÷;ñ+»�V_Ø^TÛ¯^tv¿èçaì'ãÛx¼Ã_¼¯�z·1¸í4¼JTÄéö\bX‡¨D�t€ÚÿÑUú )­Æw™®÷7Ô×½æ�ÕÄg>¨"ð…(¤ðÓýÒØµó*ê%ICÊH®{DÚòÑ&¡[Ô߯ÏdÑn¡Xä°æ’’ˆg2™-#ž�à)ÝŒÕÔ>H�:ï$pMy:V+ÿùDµÆÓ%2z®“•À"‰³x‡Ê~?¨ï÷R×ÿY–(kœ Úíhåµ90°áSµë”7äæÄ†Ïûl(:öüáWt¼¢w­p]±±Þ�û?ÅÉÀÚßõÑ]�<�¶Z€-+r„¬³=A�¨ØW¸^Ð#úzh�ÐÉ|<ù^Òùt‰K¡`Ü:uãN6�/³Éÿ36‹Ç¿Èæ(‰_a+NÑM�fÿ;f Lì`6Hy(pV¿E¹6 ãÑ£Ä_=øÏh—ÆJJZTv �Ã$HÐeÁE]ö»TJoTóÖä9r¾ëwï=IÂd�ñfÎp騒²û™ý%û ÐrÅð&­á/«0q ûàÁÓ§Õ‘Æí4¬Ž~¤CÌ©ãôÿèlôþD$(ˆ¤x%€�8DÆä¸aD÷#¶áìø+¥xÚé ù!/F#D~¸={R£eÂa84ž�k Îs0ˆÔà¸æSÜ �Kºµ�-Œê3ÿ%:Æ�£Ì!¬a¦äð((N$ÕÑj  u7Ú€�ÁÄŒ;ßµï[-3ƒØˆ% Tx ®Rp)Ä«‰4û Ö¨Œô ÜȯŠR›¹ŠXw²Œä²×$hH©ÚTIÝT“±Ô‰jIJLn¯ø¹Y�`ið€+3¡1ó4 Ø\éæÀ YAô„˜3ÆÐNlPA„tr÷öqŒ i¶¡YRËE&$q“¡7iN ÃbKÜÜe5Ëb^ ¸iæšÂÑ–iÕ6¿$ß+Æt Èc‡)ÄNÆd‘¢pÄ~I€È�àõª!Õ¹Y'.eâzHH1­w3¿ù‰4°ÔiÏæÈCÙƒ °Þ$¾/#r»GA´,‰1¬m£J­pv(nˆ‚{4…I)NXRëç†)X›Ç2©ÊoŒ�±üxµ×�ÖgóRÖ5†€ÜiäÕš D`z]ÀаˆVqĨd:SD‡¼³u¥QãÒÀQo‹ßUr16Á™[Õ@nÑJ?*Z ßëY±É];=]tò*8(|î<”ÛiX¶ï€q/ˆx¶(a”1öxCž›Ç(.âäUvÕ¹[)h¶7‚bs¦Ã²‹´hF9€Á‘8&ƒBh�½£`¬ßçòÚ–>ÒšÞ‹¦RÌ)×X]½D÷qªI{ëÄ©ûõ$ôFä1£ì™Ua5ÌÆ�·Æêƒ]·aÎ̺‹\VU&“ôcÉ­&ö86ˆ:g6x›?Ê–(ªQ³F5Þ�útü8À«¨üy@|Ù­?³5ðQnºÌGÞO¤ÈÈB‘~o‡ V¶RÜ#VNYæÚ[#àŘ˜0Û#$½Õ¯ :+û�ñ ž`®´öpN¨©n¶?­<2pø ¨@�e`*……Ë6Áã¹ù`�ÅCïa† :¤™ã¡{ÇVA_žsWÑkVv¿ Ÿ…Ñbáž/ߦŽÅ%p¥†Ä¼Ðx`1Z†(hHP@}é86|'æÚmý-•Ø>ŠÑûñYÍšòÆ¥â6QëÖ.d ©TÊÜ]˜4„2ÆË)‡p+ß]Õ[=OfÚGÍ·ml¨ þVÓÛ`©•ogÄÆÔPMMÝì¡lÇ�.k–½Š.Š”;zÉ]è£[Ò}§˜SGb´ n·sFB?~³Ä•‚1qÎñ4*ˆ^ÌÖŒ¾· #F³…Ù$`Hw1?Ë�½!b>��j9°7×D‰Œ¬ôUº@™#Ã+$ß…ÐåI_�®›×Êñ˜|ñY† �$žÓBÔK¡0ƒŸó 芞"ù„ž¶ðtËæN¥03uc{Pßòò[mæ OÙ(˜x[#ï–ré’wÛ^B=0=ãE—y74Ä;!é©ÉÜ·ˆKmnxG^­sE•Öüï«æ ÷°íŽ{®`Xªì²Øc™$¾d¢áÔ­xô†Î*x 2¾f“¿¾FëiTÁäÕ{äê7·ÝñÌAªÜ:9àñ´ ½¥–¤x-üJÐݘ”/»y“¾is»T16u²‹ö*Ô¾QT—¸¤&+ÁÉ�]"ü6<ºáŠ#±a+²áÈeòtFØÝÚ]£p‹Ñ TJX^ 6¯ŠªÎËë•k&éLåvÔ¶…;°åä.îwúå�U¨tÔ ÉÝôhJ ½ü¯|;ð§3u�7¡kM–":z»¥™ö«oõJJ©°A¡…+-­´Mɨ¥å¯Ú†±«w ÑÚM?c‡6÷EU•ø«TUE¼,m* U¡.ªåzàµî%í;Î &|ù¸9k½9øcg- wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/5f/0000755000175100017510000000000015222270712023666 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/5f/71546112fd284aced13e7b2391a601204bcacdwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/5f/71546112fd284aced13e7b2391a601204bcac0000444000175100017510000000161615222270712030622 0ustar runnerrunnerxm“I¯£V…³æW\)Kë5p¹LO�¨/ƒ Ø3Û›3ØLf0Ø¿>~�d×µ¨Eé;%•Ꜹ­ër4ÍÁ?Æ>MËÇ,—ÅÍŠ1„)…ØDˆ""ŠxFHc�c#6„Döi3N Ïð¢€2Ò4¤a,dËR(L³˜f Ë0ÿó/BJ “4 ¹DŒY*¨$ ‡ Gg,Š8Fã÷þp‹¶nÛe ‚²Ó|ÏËñÇ×à[�þ hžâišB >(�¢ˆø×5_঵)ß›¶O»êùã­+¦èÛø�,ïò¡ÌÁÇWIêFßƒÃæ\}³Çžï¨¿æ À<¬c cIÆØ–l£š5\ÉŽL‡6?M¬_Tã27m,�,,ŸÎ¹¤‘lñ+ÝO¯£µÕ  ç}¡§ªm8եʼnƒRxÏý䊥‰9—ý‚LoÈâW¬ËÓÜŽ;ROäÍÈ`É1ü@R×RÔÙ7Ç–Ïîã a~K#G'M3г¿yY¬­$N„J“”B×Z#{©-ãÞßÊ K€ã9(UÍ¡íZÊŠ*Ì«i«ø yL´cY .G?©Åó sv²–¹ö:qçëpC€j¥DS¸fÉ/š}µº%Ñ/&j™àø˜ýÇÊtDÁÁ׿…®· h/«+ádáùÚ+§…e³]±;gCyüÁ/˜�³> �%-èjØ�:ifˆw>µ«]e4'; oŒÊh;ÊxM€ƒäÓNA| ŸCgà˼» .ì^�sFæSŸ[Ì•LwËŠ1‹f´—$F"5/ÔÉjÞ¿x¾– 6{”ßuTÕUÙ8ÆR+Ó¼Y©‡‡ú¬j�]V ³ñõUä¿âb×^ó“ÿRëL28�A>דmõÍÛdø~Ľ;,¾³^~̘Q„¹:[VºãªbÞ%4ÉðŠÇu·Í.ÛæeàlTC¼æþÍö‰ïƒI¹?Z!¹Øù¦ž»Í¼þÄܵq;F$Ì`âAÚ½½¸# Ð/íJÎ8Ò¤ùž4§#};j”^½ž4ܵ¸B+>* Æó±¨óî‹®‹^ ÉÈl<@Rz»Ú¨ ZH­¾•ÔU§äCYà/…u2âßL¨{å÷‰ Ì´ÏSÐMUúô>¥Ãþ¤y²¾­Ark›¤íÉ%ÉÎõÏ®lr‚ÈÇÛÇ>«°I>AñîU þ¢pH??¿°¤îmÄwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/65/0000755000175100017510000000000015222270712023606 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/65/3939a4e2e98433073133fec330f8c13f56ff90wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/65/3939a4e2e98433073133fec330f8c13f56ff90000444000175100017510000000317215222270712030455 0ustar runnerrunnerx�WmoãÆîgýŠ ´CŸuN?õrh‰¶ˆ£I�¢Ï5’€ È•ÈšâªäÒŽä¿÷™åR’¯wiQÀ¥Ýyyfæ™™õº–kúáoWW¹<§¤¬:�ªZA›ª„¯u•‹¦õM!Zºó“ �ã—frÿÚVÛRÑÕôjJ+¹/+AU£DkD–¢ÝU]WɆ-•¢ëWÚ¶D ‡6­$7”—Y»)IYóJ{ÑvP�k•UMÕl)£®XR1¾NnÔK„YSPÖu2¯2Ø£Bæýà3Åþ~G¶*Y+£a�i'…ÈjªX@mŒ½Tª”½¢Vtª­r¶á@(¯û‚1Œ×uµ«ŒV×ñwŒ¼ïãth'‹jÃ…k߯ëª+**6½î$;>Ô‰u8ŽKÙR'êš-TÀ­c=¢Ó2ì¹�“"í÷¥”»·‘ E›¾màI�N!‘2íñŸ"W|ÂÈ7²®å ‡–˦¨8Þîï¦l î³µ|: ¡Æ�TÀ«s®« ‘ ¥5W]™!€5H£³çÈ1�1!Ä~Ý)T¿Bö²ÕN¿ŒõÝbáÑ*ºIÜØ#EË8úìϽ9Yî ß-‡üdÝ'‰Ø “GŠnÈ é“Îòþ±Œ½ÕŠ¢˜ü»eà{8óÃYp?÷Ã[º†^%ø 4Œ&%phLùônè΋g Xv¯ýÀOºñ“�mÞÀ¨KK7NüÙ}àÆ´¼�—Ñʃû9̆~xËwç…É;xÅyŸñ…V 7´+÷èc�o-cÿv‘Ð" æ¯= s¯op… f�ëß94wïÜ[FSÀ±3èž>‚?¿³Ä�Bc…IŒ¯¢Œ“ƒêƒ¿òrcÅ ¹‰#˜çtBƒs²^è V8Õõ¡"á„Ý#薹簅ò„o„¹¤—“É_ 1Èâ�1Àõ¿Ph¸‹\îv²yWZ“Éå%%ÕŽYD»ªFç&(8�Y^RN­³ü‰™ÜöÍŒ5!¯¨SbŸ*ÖþH?L§&§—­Ph‹4—_ãòDq´™Vr¿ ×ãýv-e-²†ñ›JÙ…½ÝK=æ(-2•�M~Ÿ~ìgYgúžùd¶ðfŸR0it¹+û샾©6˜Mˆ<=w?�Ÿ ¿žÑ`•íü¹äh“%ßùþûÁß áSß{Ãá$êNœxÙª§t‡™›þ«¯Ô©Q£zã«QwòÇdr«ž0í ùrNy+0ƒÓBl²¾V)æ Ží11'‚à |›|JШуÍ~‡ó´/öñ&M¢e€¾ ÎLÖ´¡b+�×ÙZÔ°ÃÊú³Öµ,#y<î„BTíS¿·uæ9ì"p¯½ÀÖªgÎáÆú±Û£ÔÙ(DÓå±®¦ï¦¿X?Y¶`Ù+¶!f,—çxmýxÉ6~:… ó6mš…Ž�›ÒõC/¶‡Ð±š,CqÿQ‰cXËû‰*Tù- ï¯Lø¦bƒé\,Cv¦(O|U5½H±‹”DK˜Ž²OòËŽ�h‡þ”ëƒÐ‡·Ôçü{%¼ úÂH˜u¿Õ�¶²þ’6˜wÙv¶/óI/aýàÓ_O†BÀÐëLWIÇ10Ó¨þŸ„h„J£)+•b� MÕŽÕª™)W*kòR¶¦®Ž!Û£§«…©7¿õÒë(Ixàr;¾¡ìh”E8ÿ›‘«éô«64¤oš\Æ}öb|2úœ�É:Ìí‰øU9tœmÍ ‘èâÈö´«¶MVsQ~o؃®CVŽÏ“(°¹oÓÖàµ;ûd‹‹èæ×�%ÿk ÎN²104íJù’‚æfº ÉØxF˜÷?/_°tH6õ+?e¸3ð�‘MÎo6IØOüfâ”a?·ÐÙLp^A<@mþ€úåxâm{~Î_žÇiȱ0›ìé!DMZ^ ¬D?ÒûÓédqߌì×Lç7æ�*3d ïAÀÂC´¥¬x Ð4=Bx=̮àþZß—Å´/Á,ŒSœWôÝw´ÅRlóÝ~ÊðŸ~ÿ+j}q‘õJZgÌô+Á4²Ý1°1*^Úx~p~€f‚ÞïÕ“ô°ð�ãÄ9dõóïØ6-^»(¬mùÍsVc¶þÏA?¨y±›·É0Ø ?Øš«ï‡D¡âê©«n�Ðð¬-O¿?´<Áwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ab/0000755000175100017510000000000015222270712023736 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ab/ec0cc06aa4528770c5dce20f89c195dfd7c0d6wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ab/ec0cc06aa4528770c5dce20f89c195dfd7c0d0000444000175100017510000000421115222270712031216 0ustar runnerrunnerx­Xmo£VÞÏþ§®š!bGÓî—¦‰qð­cG˜4U+ õ̓ 8ÙL5ÿ½Ï¹/@pvT­v�áÞóòœ×{.«4_ÑßúéÃßF ¶II"«’BÐ:Iá5M"‘•"¦C‹‚n¼ GüÑ8ß¿Éf[чóç´È÷ÛDÐ}’U¢Ð$·¢Ø%e™äKÚŠB¬^hS„ ‰mZBP¾¦haS•S˜½Ð^%òU&Y’m(¤ª˜²b|e¾®žC ³˜Â²Ì£$„<Šóè°ø°b} ¿$«Ú ê/4GÿT*‰E˜R’Ašj-ì9©¶ù¡¢B”U‘D,ÃQ”bÆ`¶Ód—h Ì.í/ù¡„ŒÓ¦]'kþÒ¬ýa•&åÖ¦8aÑ«CÊ’¥cm¶c”TŠ4e pK[t’†µÀ7Я]$õ>oóÝkKà¢õ¡È NOœÃeRã¿ETñ #_çiš?³iQžÅ Û[þ¬Ã`?\åOB¤bœåðJŸË(H$*´z«Ü†0`…¤‘^ƒrø˜—ŒM0ñ°*+D?Aöy!•vm×.-æ“àÞñ]òtëÏõ®Ü+ê; ¼÷mº÷‚ëù]@ ð�Yð@ó 9³ú‡7»²Éýç­ï.4÷É»¹�z.Ö¼ÙxzwåÍ>Ó'ðÍæM=$4„s  P‹ò\ðMèÆõÇ×�ì|ò¦^ð`ÓÄ f,s¡Ý:~à�年O·wþí|áBýÄμÙć÷Æ�ChŹ¿â…×Ît*U9w@ïK|ãùíƒï}¾èz>½r±øÉ2çÓÔUª`Ôxêx76]97ÎgFçÓ€}I¦ÑÝ_»r úü�o>c3ÆóYàãÕ†•~P³Þ{ ×&Ç÷ì�‰?‡xv'8Øg3曹J »Z¢®#vØŒ®±\¹Î²žÙ+bé¨×û^'õwyôxVŠâIÃm¿×C]�šÏé5²O× ?Ð%Íî¦Ó‹^ï)OâçS-¿ˆ"±U&_IJ"þ9íýÑ#üSTqX…àÜIjIvz!·wbWŠÊâ}›ÎQ̪¶ Q¡jˆ·.z_IÄÕQV²E h#*p™…;a­m"4�CZA;¨Eödõï�‡)òbyå-n§ÎC_ëKÖd}§ˆO%6e1qgzÄH¨34‰Êø*¹4l%H¯^ö"kRNÕ~Q?ïDY†1@“’ʾøý€þ7G\Š$“C&!ú‰zPT�ôµ DöÅÔaFÈ:„…’¾Dc‘âKUô 8;M—]«‹Zž-ë0îú‘»n"‹Ý…rd‡‰Ûmׂ$J¯ó üüÒ>û¸8âe”² ›ïßwÅ1„‘Ñno)Å5*Ã\þ–ükÈ{§t JǸ‰6¿u|7 ã#àBØ�—‹À¿V‡Pç‹à†‹µË°‚ýäML*’Š«Íg" Fóøš5ÊQ_"f¢¨¬“nÌJ›NœŒ*a¡*�Õÿ2ñ—“›Àê{ä‚l)íŽaOsœ},“Mâœ4ˆ.(z»¥ ÀsIï²wtr"ý÷T �Ðwßh!M³5N°×ÐTUÀ›·¥r’%1›6À¿m>ï"i}ØSûˆ£6ŽƒSJ¨ÇD]±Èp¾|K.'Ž6¼“?ma8ÛÕܶ÷Q¹Ûrœhÿß’­²�½À%�(|DB¼ü‚—/ïšb`:`|ÿ^¥±bfF}"Ô~Qý¨�4«Šü¥ß> þ¨½Ù¶IS6ÎikhŠC ·-¾z,�¥ÉnŸ2=N™pêJ1-¨­¯ãCÃô—ý×LK�Á²ÁÕ2L—«$èVCR 5 õkº­šÈêN«Ó£Õ{“Ø oDÕ=¿5 ÍÕÇáiH�M�#Þ®ƒª €*UqÄÄ:WŽn¤cÿ«j:^ÐõÖ êNºÇÇJ�¡ež<è $ãÄRûZkѲ[§bË Kžu§µô&Ì­€±%½ßÌeÏÅ ‹eÈÇ~,þs4—éT¯§I…RÒŒP§§¬ÿg›üf�6ÅŹÀ•‰1ï“Lx]Ƽ¯ë m¹®I^i)^-ð"FÁ†Êœ]¼Ñ`†Çs ®Ãúè¢bÌ,�¡€µGYN*%¹ÉáXÉp…o�b"Ãg™�yRE.ã{ä‡Îˆ%†ª]‚#HXzEZ_všì4ât™¶´ËñOê¼ÔCì�?¬ùªvÕ–õ ¥·­ù_Mi*]¦�@yË-zï´ép�Æ1º%ää/µ)–ÚÃȤ‰g×4ÑQÚ°š ÀTyIS*qyìð½KÞF�%‘i\‚9=>µÂ©Gðä­@eÂê«>gâ¯hd£&ZÉÁ®Áç´G¾!«ë·ýÆ�÷´5†(Ó:™¯.ö´ñ N~�Ò–ò§§{5wÕ×{9»ß«5>´ –Ý�¨c¤L‰kO7˜M»±0žÁgÅÊ2/-áÅ!3B4€Ö¦9”µg {û8þ¡cÌ<wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/95/0000755000175100017510000000000015222270712023611 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/95/ff63a55f66e8829f92795b35189eddde2255fcwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/95/ff63a55f66e8829f92795b35189eddde2255f0000444000175100017510000000244315222270712030652 0ustar runnerrunnerx�–íNã8†÷w®â¤UËö#ef¡š•:ôƒ,ÐVmįÈMÜÖKjGŽST­¸ ½�½²=N›1ŒVDŽ��¿~ã“E"àyÞ—_Úgл&£Áx0ëƒ~úOôýúþlpÜ>ZpsJa¥žš©dÛölÐëß Z›–BÂFH Œã¿¢˜à˜®gk–Á’%8–AJ¤±,J$dGe3[Ó$9¤^‰t'Ùj­ vUçâ⼉¾Â”**áŽ(ŲÌSÊ#|¾a›I <†?E¶Æ„¨/8Ib½ìk­ÿ×v;¯ØY$Yª²v$bÚJwo9S)V’l4êRâV3±TÏDÒ.ìDá iÌ2%Ù"W¸#¥Wo ©W܈˜-w:–óùÔšro2½aý0ßÃ-Í2QN%I`š/éÙ·,¢<£@P$D]bX슉C�2? ÀP`ýBá.P†…‹Õ·Tf(:xåj‡‚ À£©¥7 A¤údêH½ƒ„hUóZšAÿÇõF†×ÝÆx¸ÎZ¤¸³5ÖĽ>3<‚…<£Ë€Þøz³YohAá̓κȇRu>:6¦Š°$; ðˆ�!eÚl)xDÙ DèÏRÞO³\A#�DðU±kÔKÛo¯.°%p¡ð,ÚH‰Ÿs!ªÏ£V΢PO Å\¡-°Æ�-Õ†‰²ßE¦oÀ]ÀvÇn:ží4à~ÞÓ;l[Ö)[¢=—0êß„þ¸?yý»ém8�ù?ÂkëÇG³~<ŒÓy”ä1…“Hl6‚·Ö'–¥v)Õ5ñÅÈ#á(~z`<Ï>ž/¼{êZV» ?nõû`Ï$aû[dÜÀÖk¹nˆfñ_§Õ±¡–sIJ2×­— ·^躡mýmŒ&‹¿(’à}C¹êZ/ûe‡h$ÆÑöxKhÇ‚àÉάâ§ð¹@µ'C}¿…+ªÂÃû²¸V]:SøîEºLùîà|ƒ¦ÓÅQüÁ³­½Î‚ozh?ô´/Mô5 Bý\ænµµº®‚E°< ¼XY'i” ãB–éHsŽmÛðÛ›„HŠ×„"ã{­¡V,Q¯3TÀu±Æe±æ›ˆcD\#⑎97"_ŒÈW#ò»¹¨FƒÙ1˜ƒÙ1˜ƒÙ1˜ƒÙ1˜ƒÙ1˜]ƒÙ5˜]ƒÙ5˜]ƒÙ5˜]ƒÙ5˜]ƒÙ5˜½ ³g¸#•]`¤² ŒTvÑ1ê`¤R#•:1ëTÔÀœŠ©¨�‘Š©¨�‘Š»ê:ŒÌU×aŽÁ\uæÌU×aŽÁ\u]Ç®z #aÕc˜cV=†9aÕc˜cV=†9†ªU�ulÏ`ö 'x³g8Á3˜=à žÁìNðŽÌ IÉÓñjÀ¾GòD•Ô›;?jø»~Cvႆ9Ïò4RÑãuÿ²oPt}ÿ—M¡h-ö¾'è¤C¢¤*—üM¶¸¢±¾ëµ——ûþgYe?<û¼ÑíSÃTÉÚ»"Øf2š,ÍïÐIþW»,;MÑgìKü’* kG˜ú¯µÚO{ùY½XºùGÙǵ4¥à° ©”BÖN|^ôÎRðû'õ.“мï¨/ZŸSÊñðû㓯›ÿdœ{wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/09/0000755000175100017510000000000015222270712023604 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/09/4c6a051b3bc4869512d18689f4894001d4de94wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/09/4c6a051b3bc4869512d18689f4894001d4de90000444000175100017510000000151615222270712030366 0ustar runnerrunnerx]TÝ�£6ï3Å(}É®Ø�žúÔÓ=8ÄÙXG 5Î¥û„ 8Á]‚#l.ZUýß;vØœ®RÄxæ÷13¸êL¿ýþüüËÓ=ˆV[P½Óƒ‚ƒîàk§kÕ[ÕÀØ7j€ Üã‰9¿úØ:øôüé snµ‚½î�¦”­NÚZmz�ÔªAUïp$¦41¥À nåpT18²‡³,˜ÊIÝëþj¤ò™Îë³æà.ʾi­©µD~ˆXS(ò•ØN�°åù7¶¤K˜‘ßg1ì™Xç;˜ÁI&^!_É^á+Ë–1п¶œäØf›2Š1–%énɲX`]– H.4‚ŠNPŒbÝ 6”'kD& –2ñʉÌc®”À–pÁ’]J8lw|›é—›±lÅ‘…nh&‘c@¿á k’¦�ŠìP=ú’|ûÊÙËZÀ:O—ƒ ŠÊÈ"¥W*4•¤„mbX’ yñê8ä(˜‡´IÝ~MCù>‰`yæm$y&8¾Æè’‹[éž4ÂYá²â9Âûvb…ïYæë2zEñ­ªoÁß°š¾iYR’"Ž'û)Ù�ô)Š~�fþŽÀkÀÏÿÁá÷P›ÓÉô�í,ŠpApÓàŽáUÒ˜Ë=~{þÿóíè»Ñ Ô¸Y•¬ßÊçù]ôOø»æÁ¨…7BÙ¨ƒ;W^ãó»Ï! ×ݺpçÀ}/Oª´g‰‹ýŽî­ìä»Ê£r¥? 'ókùTMŠ‚rQ‚—ôÏù�úfXÿêðËëºVüE‚¢Lpè ’|-æW 7íqtý�£Áwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1d/0000755000175100017510000000000015222270712023660 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1d/ae3f75c2c8dca9a236466d4643a46a996df41cwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1d/ae3f75c2c8dca9a236466d4643a46a996df410000444000175100017510000000417215222270712030744 0ustar runnerrunnerxÍZánÛ8¾ßzŠA ¯ãHrº{›Ü.Î;©±nØÎö‚ÃA %Ú"‹E;ë]ô�î5îÉvHI¶$¦r�ÚI�4�FÃáÇ�9ÉqÀÆ`Ù¶õîo'uhß�n®º×ÝA{Ôí4 s×7#èvz#èôÝ‹QÿÞ€: )…©x8ޏ¿<tÛ�ÝæÜƒ ã0gœ‚âŸs"|¢»,1šù1LüßÅ.€MTˆ€¬(?Žg4R× ­¸?� ¨]�mš?Âá‹áŠð�¸3ÿt•aú¯iÈæ´ÉøôgYɦäÿÿ‡íÓ ÈØå~$â—y´­ò¨"ΦœÌ%° džÅl" §ç°b pIœz~,¸?^Ä/€„Þ ã²Æ9óüÉJÚ¡G9ˆAù<–Í“W×wЧqŒï®hH9 àv1|W–îû. c )‘FdÁƒñJ¼”P†)¸d_ñyÔÇÀªö%å1R ­¬¶4`°#jDÈp`‘ì‡#D½‚€ :HË5%ùOuNކMk=ìJgÆ"lÙ cb[ý €1…EL'‹ !C 5ð±7zs7‚öõ=|líëÑý9:‹÷tI“Pþ< |l(rÌI(V^FøÐ\¼Ç"í_zýÞè^6á²7ºî‡py3€6ܶ£ÞÅ]¿=€Û»ÁíͰۥĔgäIª×<¯õéQAü ^p�#ÊÀƒYRìp—úKÄHÀE5fô~¶3³$°pªZ�|I±'ò:! xä>ÊH°ÊnV¤öB·Ù€w\"QvÅP ,0Æ¥?3¸ ã ø…Å‚… øÐ0mË2�­–i5ànØ–-<1Œ·þå9�«Î¯ÎÇö}¿}Ýq�è~wätÚ£¶s;èýæ¼7Þ¢�¢h«Ý0\è �—Íç,lÎÞ†XETÖ�eá p®¼‡�dàX‘q@E‡OÏ ãä~KµÜ뀩žIà'3ÊÕèWX¶š¶Ý4áXýuÚ<5¡¶9 (‰©wdTUê,[Žm;¦ñ§º?#G(XÎÒ†úúé]¦îŒp¨‡dNs�3ù¸ðCѲKß+'1Ž6é”3Æb,bÇS>¥w8=àDñô;wÁ9 Eñå�â€¬Çø[FÒI¸eˆ–rI}ÆD‰Í‘s´ª¿�sÔ#âÅ)œB·� T×Õ3-Òsб�üîÇÊÃÇÿ‘ºiÿ»7túíáè¿ÊÇc8±!äwŠá?%}}‰c[ q:ÄIX¬ nê=8�‰j²þÁi�82Ñ8S*œtêr|¯vªFã´èÊpÙ´†ïà'8¶dýøƒÃ®¶)?ÉWIQl‚xHB9C G>g¾K)ÀÚ‘Œ‚A0²ÆÐGCXÖúhËC�Õ²ÆNÍ–†¹¥)¡¥aniJhi˜[šZæ–¦„Ö3À˜Sò°žZp B�È&¨Ü�ëͰ0ÃÏÉÊSgÆ‹(b\Ðõtÿ)I 2Œœÿ³¤ R‹™äé”:r*<ÌyaªS«š§Ö;ggë冑-\Jë‘íyp¢öT˜‹bLÒ ™¦›�rk––TR2ÏpE¬ZX«é‹ŒÍò.[cÔ�TíÇ?¯Q&Œe}S‡rÎxíM/Ti6cz�7GçøšŒ±?’äûIR¹dÈ|ebÌÈëêªHidë¤2çëÒ{¤í¹|á2c ç|£ñ�¼w§רOIåìL.t #]õV“,—=Ò½Šß=²÷<Ñe ÷¯!ëËô¶�ŠFö-Òs�Äì(+Y;*Js`1ÉϤÄ$Ý¿i1eŸ}‡Ó6*6b’ž¯'&Y;ŠIs`1á'¶a¬?¸+§õmˆßkOMqßJ6Lw /%¬¨ŠˆFaãõ”„0QH’•ë·fvѺWÑ·Gž—àÒ�¦Ãëh 9¡çiÙ1½aårB:¼Ž’ =ÃÀI&ÛãÛ¾^J }Ó‚ÚlT^SÛéhä·K!ñ=q%õ£¾R’wº�[[·GòaœHð–Üfg¦€ûèï;ŸoÆ×·ŸÝ—·!�êäjÄP‹î/Lçn1ÙíŽÃ�`YS8‚SV¥çë�_Y;Ž_EÌ�¿é²ô#/ÎäÇorp¶¹`³]gùHU$ï‘ÖçeŒ<Я¤_v“¯±Š•A>sÃi½P�=µGwœáòMÉ­^Ô½«K5� �qðÚSýKRK¾Ø+¤–çΩ$ûkUúÙT"o¾!•ÙE¸íƒ\¨Rð…ù¼Á�Üå“×"} ¿� <[ó*}÷HÍŽcVÖŽcUQ³Ÿ1ú–â­Ê H]U_Çý xàœwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a4/0000755000175100017510000000000015222270712023660 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a4/3ae0b73adb00ae2d4e22c1999847a14230a1dawlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a4/3ae0b73adb00ae2d4e22c1999847a14230a1d0000444000175100017510000001041315222270712030667 0ustar runnerrunnerx•ZÛrÛÈÍ3¿bbW ä./’·6Ù¨ÂTh‰²X‘)IÅqi],�’X�€–™§Tžò›·<æ5Uù&IÎéÜ(Æ›¨’5�ééééËéî,ƒh©ÎϾýæÛŸ½üyoŸ&½¥öâC¶�ÂoŽã4æ[?U:ÌüD«µh…ÇÀ_é0ÕžÚ‡žNÔÛѼÛh\Fñ!ñ7ÛL½:{u¦fQ¼õµz燙N�‰Nv~šúQH[�èåAm£^[­­U´V«­›lt[e‘rÊu’bB´Ì\?ôÃ�rÕ «�2£Xi´Îž\憞rÓ4Zù.ø)/Zíw�Ù͸¥NU3Ûjõbfg¼hÉ"žvå‡à¦!°eöäc÷ûL%:ÍEm­‚½GòáÀßùvN—­§”|Ÿb”³­v‘ç¯ù¯–mÅûeà§Û¶ò|²^î3P¦|)úls½(Q©rð!·ìµ”Nh¸ tƒõ­Šdݧm´«ï*Zï“KB)˜ãEP™¬øƒ^e|CÉ×QDOÜÚ* =ŸûM/0<ôºŒ>jÙ‹±leUÔ-!ŒUíPºu!ûn" úP/_åÛÁîöË4ƒá}è>ŽYïx›ð§ùÍPÍî®çïÓ¡ÍÔdz÷‡ÑÕðJ½Ìðü¢­Þ�æ7w÷sŠé`<¯î®Õ`ü^ý~4¾j«á'Óál¦î¦jôvr;âÝh|y{5¿Q¯1o|7W·#x/˜Îï´¬FCÌ»Vo‡ÓËp¼ÝŽæïÛêz4“ç5˜Ôd0��.ïoS5¹ŸNîfC,¶ãÑøzŠU†o‡ãy«â�þjv3¸½•¥÷�~*ò]ÞMÞOGonæêæîöjˆ—¯‡�lðúvh–¦.o£·mu5x;xCé¦êO…ÌJ÷îf(¯°Þÿ»œ�îÆÜÆåÝx>Åc»œÎ‹©ïF³a[ ¦£r=½{ª3¨³1ç�‡† U-R v�M²\ ·àóŒkÄ]A’ÆK%`’®?FxíÞÿÈ4€@B5CÈuÕ `À_½éppõvØÝyðÑDy0¤�}ên´ê+çž?.”E+2ìpšú ÿÛ�¹Éá·N£áïèf*Jë!¥Ê¾‰Ýl›�¦‡´ø¹Ýg~�?eþNç¿áºq­tZÐfÛD»ÄÃ;;Ä $Ëÿa“C Ù˜;th<<4 ‹ƒ‘á.ž6:Ô *pwKÏM[Ê�c €•ЕpxdÀ`œ‹1qŒHaC�úÜ5V�vÃ}¼XïÃU }=|hà`RÍ¡Ÿa’DIsºGî´<´. 1( O¯ÕFg‹OÞfÅ’háùI³¥:¿U/CËÁ9—P”|  %J Ë"߯Þ,¦÷ãùèípq5šveÚ(SK7Å<P�îXÉ ñjŸ$@p5]MV.@UiwµÇ ©%[b—ZÓ� µö(Á>ôÿ´×¥ f©\ÒlW÷C7êá]/p:é(ƒ@œ¨c7ÝÁ†:Žúš{nFi:‰}¯ÙjÉüµjÒ‰ºúL“6�¸·¬J©¢—ê�V)KàÁ„1<VsnØZª­�ð”Â5Bµ‰ÔSÁ·È€œøÌ$Ç‹ 5DÝ=Ò`'Gk®Ñ5~Ö4~wñ¿-böŸè ùE­a¼ç¤¬ñiÕGӈ˸‹t�d)kS¬á ;;37ô³J½z4^fé�/ÙÊa¾‰ONO%¬»É.CµÑŒ[VL×Ïtð`A=¸IÚ¬ ÇydR&æh§Å>ônCžàÆÿ¨é�Ù6wµ4Ð:^0ràegݳs1 ŸY]ôÕ·Ý3yCÆ>ÅF5´ÑMàa3'ê©’IÕ™àwU·‹+~F71f)¼†ÜºÂ¨I1¬é\!U"A wà�žB&Ï÷¡rß·"µ„,Õ,RÇê;عÙ–Õ; 2PiÈcó“À1X£:Dm€F¡%ž…/T>û,†¢ ¦ ¨œO CñÆú�È&yÃ\ÏÄLÚMP‚ಠÿY¬WþÚ'f¢Í%ô,)ƒ(’.£„Õ¬Ô’Š›¨#G°ˆ]f{õ�ê¨@‡²Ï–êõÔ+ÑtbÇ…NÞÀJ%Ý/Ô+Õï«sœb%™ÐÁ;¡ÎùïÜOMùÝV¹©,gŽÈ*ÅÈ{„ȱÏ?þÅQ_)Öüüã?Åt܇˜ìó�ÿ )sB#­<ÿÍD—çô-³ïÃÏ?þÕ¼]FÞüí`÷‡È›Žpü�µc‚n?à)ÆPf¤÷f«›Æ�Ÿq,ÍÑrE´v)í/ÏŒDV‹&Îç¿ÿ‹¢ÚÍ}�/ŽW" „†€ÿ7i KÉÖ m‚^$šY¿ê�†ÿ*òàžp'º§'+|»ONûªqÓ<ë¸Êúè±Â%‘§$_aàiNáÈÔUÕ3sœ@ŸÁÄ×§2�ˆꡔÌqñC"ðûðûÐNf·QÄ»eøõ—8b߆#~qÔAª+^j¤3Ǭ£ô.ÎDfùr-90C0Q»9h”¯å 3)¯F&Hjõr‚­GHej"­({k×îd4JÃøg[8”cÊ8!  H‚1b Æ­c†îÒ Ér¿^ëÄDöœb*¢4 ˆÙåÓ…—E#¦jB‚…¬Œ’+ð_X‰^ó8¬ž¶~€ßÈó…¸RæV–C[Ò¼æ±BI+™·c(D3½ƒÛ¸Qî²:Dû¸CEÕ£8¾PÛ,‹Ó‹^oƒ”¹_vWÑ®÷´{zêm²ÇN%;öÐqïuÚûõùKù:¶Å�_�ÿúì»Wg¿4ÀUãˆ�¸Ÿü % ÿîaóü'ŽPàô^��}×;û¦wþMoVì±sƒÄuuhšŽŸvÑ>é žÝz"sîé,* Úc±h¢É]·gMÌ•.r�-X' Q3p<½Üo¸HÎËä9×£‚Ú(“ЋSU}”÷ÝFk�#WꮉÝ9Y*2ä¹™{Á²ËêØã¹Æ_/Šþ÷Äã(Ô«—jJ�²  ;ºL\æô%”{.ªÞÕã&‰p|b]§ÂE¼ò¥ƒ)‹�Ãw£W“'ýS—A´zÄs1YvCOÔɺdß.²î\¼T±O¾xÜ’õ‹y8]H6i¿I¢X[çZ¬17u[EY_,+e‘U¥0 겤?�£õ…|UAçJI“OA…|Ϙ£‚M±“Já&¯L ¦Hai,’V…�‰b'ÙW€M ¡ÄeDŒ\ä׳³³Ò�H œea$¾R(œücqïठP¯'¨tdœôlüÔh#_µ¾ÙÕq|U8¬" ßB“† 꺻uˆ’ 1:NšEÖ»¸¶Nªªb>a¼˜ëF]?]¸J¦f…5Å)6!sÖ^}'Iakɾ­oȈ¶¾§šOÌ,E@î¹4šDK†Š/žT`1Ú~²¤‡Ùê’å©8Z½…%ö_Q’0£¹ÑPb þìsò>QN̓9³OÕýüºóÝi5Jr”,JŒã²²Icµ“z³e��qÁ®Y°éì³5V;1GÄ:Â%[UY�’g‘xÍAA‘{y]ÃT„b‡CÅ]à|¾¿/Á¸A� Å£�É6Ž&>^È!Ÿ¥'øPñ"‘Kà¾/É ˆ°%G_±HäÊK"àjE91ê#Äó3bÖO¥º q±-L(÷Ø�0÷7 ƒy}hàÑH‚4’×aÅk0—רgÿ/×ùi8| ×Ð�ÅJºµxhÃäB­ƒÈÍ*:Ë’Ã Oªlâ kÞDöóέЬþ´Ò8€«ÐÏMS:üãøÆ;ÁÛZp ÷&G ¤hÕ–°¦®Áa‘Ôê(UñCÆ‹v„BáÄJŒXDª¬BºÊ^s<ªH[V•¼Kø/qU¡?!ü‘ tEa•¬TßÇ‘H§ŽyÔÅ"ŠQ4€ÕÄÄY·Î´%§PÂRc@3öx°¨XFkÏT×¢gsÆb'[ª¼»©YD’ÄþüuE¥œítÒ¡¢”Ž\0³ušŒn_#«¨Om #Q“ip·ô«ÿSƒhå tw¹;ñªî³%þW}~Ù¡~Ž£šRÎS~^Ó DíǶ-ÿ4}k“ÉPrÉQµU´OÿÝÃD-ž¿P‚j–³¡Ç©•(²Ü ÃS&6»È=3«i®‘¨Œký4Úƒ�¨4‰•5À_>ºŒçï+gɶç~rò+N–qZàN7ß²Å)o ²'Ü{™0ÑXÒÅýÚê’S ÉÓ¬ )%J@Š 3¯"4v_!¿2g‰(„ÐätY;ÄïŒKòîKNÀmk¿Ûƒn³ïWØK�=iÜ›)­<|×Ò›÷¥e#Aa†F¸ye€®C¡øŠc²­b„£"e_þyàoº˜×” Ô,�ˆÍÃŇb^ý Ž\r‰®]Ï �U´ #6ð¹ò.«|QpM©¥2L·8€ØàB ê4NÅs œ[ˆáN…?BÈÄÙ9Ф›¯rœLy[›ãnyckoks…VàUn"¹%Ü"ðôž�ëxzå:¤m´}êŸràzX›ýTr´.ê¬ÞáÒ7Ö8€¬Ðq©rùâ¶‹–�3êfëáüâCY¶ëàç²è�eIôööº 9X^;¿“›'Ž–QÇ•Á«\¼àP4MrÆìópVíŽÈ�å8ôT>PðŠÏX¹yÏà\Þ /Ï�  �¿˜á>ßA ñµƒ£àÍ>Éè|”·Í�êÝ׬I^߇­õ OLœDú#¾ïÈ)�`p`ï˜L!‹=Ô,á¨3‘5ÿŽ+_Îb}~QÅXR/Ø_àVÂ,QQtÉ‘•æ�¼kËc>ˆÎMí€Mù!‡EV|HËvQé||87ì›k8^bó¹YÎ0îd«á 7v’.å.g�ç@Q¬h:fõV}4,ÍîjוU1åR®˜Ù~ˆnòpΤª,IÅmN0+Eæ%ì33È+OÓÑIý‡Ý ³£¼�9ö•ܨ›Ž¦R)€àä&³YI‹4X+@Û .”/þOå¾ t§ãî³ÈÁûÊjÏëÚN·„@ ‘y…%Ï!®×äÉÓlí’ ŸWйjUS‘«‹kþDUtõH«¦bžpTÓ6žki»¨‹Õ‹g.‚‹]~±°Å‚õâBîñ-Ò½‚^GèåðU¾&/Q 3_ øïŠ7Èg9¢¶4Ðe™©j*1&„TWŒûܬéL¸°gãô'´bùó8÷ˆ�6Z…ãó¢·Ä,fo^CV>‹)I( Il�5q™rœmy–ÿ!~íwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/78/0000755000175100017510000000000015222270712023612 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/78/3323167f4eeb1f9f3283721ac904f1a17492dewlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/78/3323167f4eeb1f9f3283721ac904f1a17492d0000444000175100017510000000350615222270712030441 0ustar runnerrunnerx�W]oÛÈí³~Å…óP[ í$èCÑ- Ðe¡(ƒ¢Ö5A‘CijŠ£rHk•¢ÿ½çG_޼Ànà$&gæ~œ9÷ÜËE©ô—/_ÿú§û>Å+©IT�¬²„ÇRf¢Ò"§¶ÊEM?îQ?4P›]-—«†¾~þú™fj³’‚žeÕˆÚnyõZj-UÅ–V¢‹-ë[r‡ŠZRe«´^ ‡Eiµ£�¨5¨E“ÊJVKJ)ƒ+ÞÙp|ZÍ6E„i•SªµÊd {”«¬]#ø´a¾¦ëf%èjfO\Ý'¹HK’¬ Dm�me³RmCµÐM-3¶á`SV¶9ǰ_.åZZ|Üä¯9òV#ŽÓ¡µÊeÁÿ “Ö¦]”R¯Ê%›^´ vj~i€u8�{U“eÉ$â6¹£3{Ø °� ‘ñ»]©õy&€¨hë . Îä ��ÿYÃo8òB•¥Úrj™ªrÉùê¿Ùk‹±ž.Ô›0 uw\©ñÌÍ-˜Hº«µKz•"�HcPƒs`̯ö9!Åv¡ܾÄlTmœ¾ÏõnÄØ£Ùt?»‘GþŒž¢éÏþÐÒ•;Ãó•CÏ~<žÎcÂŽÈ ãšŽÈ _è›òþùy³M#ò'O�ïá�‚ùÐéçÂiL�BÃh<¥­)ßùM¼h0†e÷ÁüøÅ¡‘‡ls£.=¹Qìæ�ÑÓܶ0`s$}ˆeè¹láz³Í|¥÷½Þ'Y@B Ü/Jfc/’Ù<¹/÷>aIVâƒUîXEWY«µ¾½ËòV·u‘fânuu²c[Ö·eºµÝÔV`Ý¢·o_n³RB#Î÷/›×Óýg‹Çâ=þÞ­þÑëÝßãÚ¶<ëüB³´bÒë´åOÚ”XJ挣"x–ÚLûzI@B• �ý€Ž¹ÂŠcfíõg‡>ß�ä1 “­R]ýI �ë©Ô:s‘fRöiÁæ;Ú茢‹É'€,1�\CÀêFc$Þ]§®J„�à¶ ¹„hšsïœ� µ ²Ö©xè´›Éè]ïP¨àLûÏÀO*±¥k`ˆ ˜Ë©¿l^“®´n:5Ÿ¡E˜áà†Œhš|Fl[+LbÜ@„ªyç§ëx‰aöáö!µ{àì__�ÖþÙ¡} öt¾ëtƒžÄ³šª­ÂØÞ›’¶¢o[ t}ŽfסÇ‹æÆ€|R5¦[ ¦mù¡“c!]öcÊÍT^ŸŽ{;Wt:0ÁL®ûÑ%„Aãy×XLñr»³aqéíçû‹O´â#0ºúº Å©†¬ºj삌õ£”ý–~}p]» Ï™®z9:€À?½kž 7,€8éúÛïsrl©¦Æ>6~èH¦‡]vÂÆÎ÷]¶Çmï]SLºÖsÉîeÌî}3ýíÒQpüôbÏš#÷D˜fbE¶¼™C¢(ðÙa1T5í�®mpéc8ÍÍ x‹O)« ¤¶zØJnî(„qW8jÆ�!åü…ËdæOÇâ®wìEýŽl‡Êe­àB±Þ¬]Žû“Àw`ÁÓÒé@uÐÿq)„Ø././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/78/f25781d3fd03663d721f20e881f45dd7a327d6wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/78/f25781d3fd03663d721f20e881f45dd7a327d0000444000175100017510000000261515222270712030531 0ustar runnerrunnerx­WÛnâHÝgE)#­ ËŲ»4#y¸$Öˆ€ Ê“eì6´bÜV»M„Fù ý�ý²­ö…€{.Ñh¥D‚ruõ©SÇUÅ:bk°Þ¿×ë^‚ý°œÝŒ¦£¹½ [0œÁt¶„ÑÐYÂЙ�ËÉ£—° 6â©�pºïÎGöðnÔÙ2;Æ Ð?îX¾'M§ëL X^t—9ìX@ôeq@8ˆ-Aø.•¹È/7Ó˜�4Åg7$&Ü‹à>[GÔ—§'Ô'qJÀÃü¥S`}ÈŽ%”E Æ ãçyö�P œß¾'Áªn+¶YoxB&À�%’ô&¢>@ä!:(Ïu$ùŸW℆×l¬[gËÌl‹11×gE°&�¥$Ì¢– �ÔÀÊYÞΖ`OaeÏçötùØGg±eø”ìIŠî’ˆb¢È1÷bq@ð2ÂÝh>¸Å#ö'gâ,e cg9-0žÍÁ†{{¾t{÷óûÙbÔ�\v%Ï2È7©>ò|c@„G£ôHÀ#:E”Q[oO°à>¡{Äè��šªèýn1«$/bñ&Ïù’Ê.äÕBÌD ž9E öÃ2ç¤:±ßiÁ•c$ê)ÂR,ÊcŒi(¶0Žã-øÄRÁâÜÙºizÛ°t£ [fØÕ´w4Dy†p3ü쮜ép¶r�»û‰»²'ötè&öbáÞÏ�/î­ö=iŒÒ}‹3†Žý( \øl·cqg{¡iâ�y¾4™/À½ žV4سƒµ_y‡ß Aä¥)|÷I_Óº]øR*Ü‚ž÷"Z4•›ågØ[ÓìèÐÎ?õ:VOûé�îÞrMÓÕµ¯|_¬r“�‰ÄÂõ%Ú¾ö¢À2$¬! Cll‡Õ&{ʲ´zÇ®åã6üÂUxî�ŸŸë¹ÖU £¤M¡ ɹ:òÔÓ¡‘ÅœDÄKIÐ|#iòª·°ö H%mc|§)6ncÄæ,ŽZ`<¹Ï¹B\ÙÜçB#í®œ)l\. M( ˆMÑ—A+Âñ|€¶ÑÇòâ¾t�×SðA>*Ž‚Eh,-á•ß+ß½d°Ñ”Q0†Gu¯F–NÊŽÆŒWîˆæ ]×±d¯>g¯%6D �<`pÎ ¦‰1®ó;O,†b1‹¥XzŠåJ±ü©XþR,+–÷u‹¡`6̆‚ÙP0 fCÁl(˜ ³¡`6̦‚ÙT0› fSÁl*˜M³©`6̦‚ÙT0[5Ì–¢´Ô²@K- ´Ô²è)qÐR‹ƒ–Z´¨qjl O� ´ÔØ@K� ´ÔØ@K��n�ç:D‹‚¹®:ôQ0×U‡> æºêÐGÁ\W]O¯k - ºÆÐGAX×ú(ëCa]c裰Z×XO·Ì–¢KÁl)J°Ì–¢KÁl)J°Ž˜ÖœxOÇÖ €K‡—E¢ÆI�Çm3>ëð;ïஉ›Åi–à�AŽíþ¥Åð(úþÇ`"W¯ýºù¸É§ À �»ø×²�Ÿ¹èÅ(‘±ËøœˆŒÇ's ׋|éùîNt}}º�hÚ™c±O]¾u”žFrÁgÁÎv´KHI–·œ^¿0°+îòI§_ã’�ç~q™Eó÷FãÍ«Üe3Ç×þxšSAw~—ñÝ%7›\V‰6.áœñÆ…ç{DUcp†Í>>öÖ(¸b»x‘EGbü ¸¹½iéþQÈ~ñwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ce/0000755000175100017510000000000015222270712023743 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ce/5a5c0137b37823ccbca0455ea340f0be55ac27wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ce/5a5c0137b37823ccbca0455ea340f0be55ac20000444000175100017510000000260315222270712031032 0ustar runnerrunnerxµWaoÚH½ÏüЧ“Hê¶Qû¥ºÜ�ä€ V��lÓ4Ÿ,ƒØ‹ñ"Û$�úßïÍÚ&†@U: a¼ÞyóæÍìì2KÕŒ>ùòù—�ç®dA"+e.h!SAxLå\d…Hh›%"§±vèê«Íc.—«’>]|º @mVRÐ�ÌJ‘×S&"_Ë¢�*c¤•ÈÅì‘–yŒ)‰A‹\R š¯â|) *ÅÙ#mD^À@ÍÊXf2[RLs¸â™%ó+Ô¢|ˆÁ0Ί‹BÍe M¦þÄ ,¸ֵݡ/ÖØrÃðŠ1²¾á�‚‘é8Ú•9{_óë{“[ß¾…4òœ�…Á+ ÌÌ+Ǫ\!¨¾cÚcƒæØ¼fv>y ìëi5»›‘¥‡àÏħÚžËaô=7ôñh J?Ü™ÞØ�e�éÛ 2ô=À³œ°`Í\¶s­ …¥Ö¬wÁlŠ w\–é éq÷&sJ?v:¿Ö…AÝey÷>�Eþ>kõaÕítP(0ºW2é¨,š§ªø–ó;Rﺼ‹lk—bMç‘ÄÍ Œ¡Ù$ê�Î5V„jÆÓYçßáê1Ô™ž{ÙÑ#ðZϩ੷gwÙùÑéhÐd)Jô84“m4‹ó(šÂKîZ†÷3õýIì¿jëðkäù6JÀä´Dß,®Ó1èâìR³«X3óTC¡†k+ÌÚ�M_›x~Aʼa¸¼ò¾C°@çkŒQƦé¯o Æy"ÁO‡DX÷6~f*Pµ\Ei<)õºµÙn´·n‹!ã1Ktîë"�â„ ’\Ÿ¦í¢˜{ dž.{8Ù§x@ †!Ú$uò02Ú�Ž¢eÔkRXºÓ­a\“ÒLte£#xÏyp˰³ˆ$Úùl“<®¤«-Ð)!è¡– Îkª!ÑîÓ4Š7�mñ)„`d¡áôêØ ë!�#¡0îñpøM«*ZXíhNIÎÖ|í8¿*}Ë�^ÉûüOPg l¨=EH‚ØÅ%nÐgÜÞ½;Ó«�ç^§Cæ™×6ol—h6\çXßXhÕk¹kíâ~¹�¶Ô}{ñ6>N1Ï8ýöø›ç£ÏGŽWNKÖꜥ»OubÙmOÏ7ö>ŸÉN鉮!—�7øŒÿgôž Ñäbü£ºÇß44¸ëççÊì¥ÞáIšœÉíÚ÷7æª.`>Q9Ršûª==U¿ªï\”øKE|’âcâ½íMÅ././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ce/386f49aede773484c42f08d9fec29905cb7156wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ce/386f49aede773484c42f08d9fec29905cb7150000444000175100017510000000030015222270712030755 0ustar runnerrunnerx+)JMU020d01…”ÔÜ|†Ö›Ÿ×Þ30¨yhx$+ù̄⹋rJ ÌLLrS‹óóô’J3sR$~W›s2Í¿`ú[eêÎPEû?»·Ï�*+ÎÌ-ÈIÕM­HÑzÉ ¡Ø^ì6Îû{Ÿí]Ø’°§Ýn/#×aW[PÉ %ùðÐnÝüÕb‡˜|=šfúÓD⼲ĜDÝâ’ļ”Äœü¼T†ÙOÖ†ïý¸èŸæ­/3;þ ŠÏ¿Ä°VPwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/f9/0000755000175100017510000000000015222270712023672 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/f9/8046b5a9b27c59010168f7fc390a728c8bc07ewlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/f9/8046b5a9b27c59010168f7fc390a728c8bc070000444000175100017510000000731715222270712030534 0ustar runnerrunnerx½Z_sÛ¸ï³?Î�ædWRréS›³g[v4Q$�¤œ“'EB/©#);êM¾{»�¢d§éÔsmD`±»ØÿX`§sñËëׯþù——çb¶Šr!“"ʤXD±øŒ£@&¹ Å6 e&> f'âÿ‰«t³Ë¢åª¯_½~%¦éfIq%…Ì4È�ÌÖQžGiB˜V2“ó�Xf>@¶XdRŠt!‚•Ÿ-e[©ð“�ØÈ,Ç‚t^øQ%Kዤ² þòtQ<úàÐOBáçyD>ð‰0 ¶k0ïD�ØÏE«XIq:Õ+NϘH(ýXD °Ip­‘=FÅ*Ý"“y‘Eáh(ˆ·!ñ`¦ãhi ´œ÷ŸçÛ; >Ûb�†Ñ‚þ•¼­ÍvGùª-ˆPÏ· sdÁ¶i/ÓLä2Ž C¾y¯–;†!*� èk1ÝÇUº®î"Zl³$!¬ SˆŒ)þ.ƒ‚FˆóEÇé#m-H“0¢ýæÿÒj›aÞŸ§’7¤tœ¤øe™³˜¥Z=•¯|l`£a©�8dLCfOØâvžÐ~lÒŒ‰Ö÷Ú5L¼ë‹éøfvß›ôÅ`*î&ãß×ýkqÚ›âû´-î³wã�3ˆIo4û,Æ7¢7ú,ÞF×mÑÿt7éO§b<ƒwÃAcƒÑÕðãõ`t+ÞbÝh<à Hgc1A�jÐǺñ¡?¹z̽·ƒá`ö¹-n³á¼Òž¸ëMfƒ«�ÃÞDÜ}œÜ�§}�¿ÚÑ`t3•þ‡þhÖUŒ‰þoøÓw½á�Iõ>‚û ów5¾û<ܾ›‰wãáuƒoûà¬÷vØW¤°©«aoð¡-®{z·ÄÝDŒÁð„Á4w÷ïú<z=üw5ŒG´�«ñh6Ág»œÌÊ¥÷ƒi¿-z“Á”r3=‰+Hf#Z7ê+,$jæºÔ@H`±é’—ë~o\PϨL*}yròWmâôk¸ìlÒÍvÓÉ·ÙÂdwuêÎÛ¼H×Xp» òøÒyôw1|‡ÖÚ‰Â4ùžhzÌw�[8qÏ5úOOÌÛÿƒý±1l“µ¿GìÀÊn/Ô*±Ummè`˜UpûA–òVWM²:TP®@ÛÙc1Yç7öM? Vi[†Q¶eß•,N F‡*Ï£„'$0[ �I/£™à‹ì¬�~PlQ¤é¦ËEaŠô Î]I!n¯ßãDÄ‘D³ÇÆ÷ð I¥?ù…_ŧK lYéZ™>Ñ�$„9U[�øRxÛâq+±öwörù cû;Ôtb»aj¼7D2•^M=Ô¥}©àv¼E_À/�`‹ÃÎ"…°[`ÿA™QÒÚÎ¥ ÜâCï“h9Cmñ‹ŽjP‡ô  ³�NS²3k�ïR ¸ól€P{i ÄÕ•H¾/–#ÀŠ;*y’8¯ÂB4z!Üû¬ôQÅSaÐfG>Uhm^â@¼¯C�ƒ}Eô¡ê(ɾ50¹NæÓ‰BÂüŒ“” Ã~È ‰\ÛòÏyY­Wü,*hFy”§:éàÈÀ¶ûG)ߪ°N…Duà§ ë>l8Jngº*¤(´D…ŹX陇ÊôÃaÅøN�Z[¼@ ar8-T†ÿUüáÌ¢S£;ÝÙÑ*£Pd•¨\,àm9žAu«ZðßXѢƚxξ=t‰¸�Ô:½*k¨–UwŸ"”WÚÜþ¶©hHsVNž6V”úÜÂuQçÒ³[ZGÍ¡�â¨.ÒÊT2U¢€%N·s€!zS�R.plfeb­ à,F8Û2W‡Är¡b1Y\0é¹:YñÜÎåC”Qrè\R ¦ ÁŽ€ ud�V¨± NÝp AW•1¨f ؘÍåµ:ÈdKä�¹ôÐjœK”÷¦2.MÓäJˆj*}ÄBBÁÑ8§Ï=+¦V“ñxÞ)ìô~Étª q}“RÔ‚ÊxJ–¨ø¹5Š‚ž±a<`Ã¥øKÊåÂßÆÈÆ‰,UT�VH ©Q ›lÑ}CŠÇ±EQË홂֕¸Ô Ƙëâð1VTò0_ZªÃOÌt«ÂÔž@4ØY�<�޶I}‘ñN�‹jÇõj@µ�æ-Ø #=jlWs Îd€Zϯù<†ìÿJôªà_³û¢è)-†&R-ù+¡×1å1=U§¦Ìîiz|èÌp¥› G·—îªßÑõÛµ‰˜ŒS›Ø Kó&)4ã¨óébÑb©I3‡Ù©ÞŒ¥Ì(‚Q÷’³³nÓ8#ºwæ:H©BWñ+¹HÕ%ÿ‹»2þWãçßOaN @YXšªaÕŒ¸và…DL1~`Þ”¹ BQ»¨ÒRc‡i̪ <¼ê€* ˤ�F`TÙš•E[0I¢êß&²˜ÙïÆÉ»L1šÑý€£$éÐq¶d¢#B¶wíäI=O§ Ëg ©´döÓ”�Ô˜p⚎�+£µ;áü G–Ôj_µâXÑÛXëSúÞ?˦_Y5}/ª¼µ­gh›wì蚿Ϛ5ͳZÏ Ò´ØªZ®¹\HWðéÎõ7c —gƾ=·Ð'!¡³Å¼�KÆ()(éˆM!�ÛP ¶ù<á*¥¨ƒË35í,yâ&£T“³DÈïf®Œ¥O›tµÉíTEöÄEÙu&†ÔvJEº§¡rÐ+M ­4J²©ÍÀq©o®æ±O©’Ó&P'h,£�¤YÑDTÏÕÈ>)`´ü˜ï?ž”±•†aQkÇÔÆ–˜Se²Ÿá�ÎþjŠýjöÈ1ã¸m9tÌþQK’;9=ô„'88lèqKÒ<Ëð�Û‡fÊÕúÒ 2\¢S+ºEOôqÎHÙ‘Ó] Åhg?®Ð#§"Àq/ÏopXç÷x©ÄkV~Î÷A;”%s xUL‡§:^’Eªö¾¥êqGŠªŒ&ÙR¬‹Ø±~#õ²©%3´¶që4}XEµPÒ0?¸ô9ìüÎÖ•f(fºLrQÓðâ7ÕjÒÇ7îë&üïO¶Ù.Ýþ¸—,F�WÙUîÓÃ3ªôÐ&@3©a­©4—³tEÔ‡¹@ϹJ®Û̓t�ùì­šùHwôCñ@Š8öç‹8·Hž·ºÑ0Œ;ëDÅ�.W45¹¢$NçîUz¸a%g}¡Œgïy žÇ•O]Î\O?;qöf£;šCšlhÈáY_È×Ehž8�-E¸Å <¾è¹Lç]1Kù^õ�Ô…1™7õÙø* wLxŽ|Á+<ÔŒCƹmàýPŸ¤1”»7ô¤mësع=‡?û¤N8ÜÐô?-¢ª â˜v<¸ù³ûê>·˜¢¥Î²ï)¨jKË¢ŠÆ¿QÀÝ3ÿ†p›ÈGí�|{ê¼Éh ³þμñâ¨x^vãInjú€´!Ò¬ì¯öz}ö2réṬÌpj¶F ““�2IYO(aqÉѵ1—�n*-™8«'•7üš­öº¯yAú°Å¢t/_¨€:Û�"ox"Ç5¿!5¿Ørc™º_1ûÊšY—z?î7EmLiÛâa›µÇ7aËPÅͪºËÌñRÔKêe´œ Ò¹JÁ£jÄtt4’xrë]áåèÛÞÕ{Õ’« ¿žðЙˆ)êò‚†8ëhAo7xèf»{–¤ÞÃ=ßPÕþ� ššòo¯Óäg”K)¢g†“÷ŽŸÞ¤¨“�¶ñr–nH©O��˜^|ÏÑ¢ü‚W૨hvºFÛ£\lÑhƒ•[.ãkœ¡èQ¦©n);U Ëž€¸¸P-�ªj´_Ôo9„{VÁ« ¬’)*Õã·“ÿ4…§¨wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/3a/0000755000175100017510000000000015222270712023657 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/3a/7d60832c5a8e75b8377c9538289e080d841b97wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/3a/7d60832c5a8e75b8377c9538289e080d841b90000444000175100017510000000101715222270712030401 0ustar runnerrunnerx+)JMU072`040031QH,ÈÔKf¨¼ó²!ëŽ÷�?¿$í�ò�UÔöš@$——äçêg¤æäè—¥%&§uLÞ÷ïÆéß çìufLùÿfSSr¥ > U§5žZ_´ؼøó®]û ¶Þ†êH/ÉÖ-(Ê,ÓMLNN-.¿àbO�P.“—ð™çö8ÈgÏgTÞ„CqÃSŸœ—‚œ ’ó2ó}ò–ú‡(ÌiER\žX™“˜—45DÕ«»mê«xÁ+_aÁ¸ýGV¥ ÆÊbõ~¯åç5I�dù¸°dÒ=¨ÒÜÔâü<½¤ÒÌœ†ÜÒë3ûäufZº1ßZWúÂyºØ|¨²âÌÜ‚œTÝäü¼²Ô¢âÌüÙjwZ'¨=�Û09ÓÑý,-%‰I9©%(–É®³/=tâÎÊEfn¹nÎK²fæ~ÁæÑ’üÒä �+¬üàˆ‘©]ᯢ1õ÷ŸÐ:ÈVfæ¥ä—ÇgæäÄ'ç$Üj[Ä™lÓkäóZhƒ\í^ó¸×1•SqèƒèÔÿÉKãÓ^4ÍŸTm*1ïî=¥Ð?8tÀâÅÆŠOá�—ÿ2§ÙÉ+¼hü{}±ú5ú¡6sÄN{Å:'DJ¨ÂÂ(¶}�Ü»Vå0L�PÅ‹ M Tg~2�>røS�åŠöðÓûÎð]wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/08/0000755000175100017510000000000015222270712023603 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/08/5d96ea059c541a127838325d87bc1eee852356wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/08/5d96ea059c541a127838325d87bc1eee852350000444000175100017510000004602415222270712030451 0ustar runnerrunnerxí}k�ÜF’íýÜ¿‚ð IW–ÕË 3€v$ËÂÈHòþD°«ØÝ\UkY¬–{þAû7î/»I²XÌ32"ª»%Øõ¨+És‚,Ö‰s’¯ÓEyšýåèððëÿóÕƒäÙ¯ï~~ùâ§ož½{ñüaòüçä§Ÿß%/ž¿z—<õæÅßß½þí y�¼Íóä¼þðåª*®¾zóâÙó_<ºœ'ge•\–UžKõÏˬ.Ê¥Z¼YãÝE±NΊ…['«¬ª“ò¬…Xd×yõåú"_,¶‹þ½\]WÅùE�ü¿ÿMŽ>N^-ë|‘ü½¬VeÕƒª?‹�èŠÖ³ªXÕë¯få<´º†%¬ªò¼Ê.›*Î*µëò¬þ˜UùÓäºÜ$³l™Tù¼X×Uqº©U±u’-ç_•U³ —å¼8»n>Û,çy•ÔyRçÕåºÙ–æ�—?ýš¼Î×k5ö2_æU¶H~Ùœ.ŠY³öëb–/×y’©ío>T›MòB·ìWyµVû39îÙ¶€µ×ïeu³UR®š�~_U}�,2U]²]ïQSCóÿí7vƒÞÚ¹úÞÚr.ʕڲ …©¶õc±X$§y²Yçg›ÅÃBíšäý«w?üüë»äÙO¿%yóì§w¿=U ×¥ͯòª¸\- µ¡jWÙ²¾VÅ7?¾xó÷Ô*ÏþëÕëWï~k6áûWï~zñömòýÏo’gÉ/ÏÞ¼{õ÷__?{“üòë›_~~ûâQÒvÛýÜ€8wõn?ïÆy^gÅb½Û¿©/z­ª\Ì“‹ì*W_ø,/®T�Y2SÇT¿{½_fÏД�-Êåy»Õj5Gvwx=MгdYÖ“�U¡£ºýšÛ�új9{ô0ùú0ù^í¨ õU¼­Õa¡0¾/Îê‹äûEYV“ÿ*×u¹|˜üø,I«Ÿíã/�>L~}û¬Ù¯þ£8S‡çYòòù?Ò÷¯~zþóûôÕ�¿¼Nß?ûíõ³Ÿž§¿¼yõÏô‡ƒÿPËKuÐŽ/¦à–³Åfž'_ÌÊËËrùè⋃ƒúz•7ꇲ™ÕIúrþá}±œ—_©ïû}v½P¿šÄõáÓƒƒ¯¾Jþ¹=–_=O·g‹¢“�—ïþ‘\?::zô8ùrû¯Ã¯ÆxÒ«ãôè(}|ðïƒÄ]P¿D#<ù²N‹åºÎ–³ü©ZaWdòàc•­VyÕ|ºåkÕ"o_+EH¨{µ©×Íøn‰�‹t½©Î²Y®Ößý{°È¿~ŸŸ÷ ¥WO’ào{Áº\-Ô¯g±[²ÿÀ^tU®6«Ýrí_ƒ…”B÷¼‡Éð×`©¶êÓáFôš æç‹ôcû='ô¿Ç–šo./¯SϲížÉWª^çóôJÙìšþƒö�DÉÓJõˆT‰ªR²æ³/_¿Ýîòw[¹1¢yÁøf¹.Ηê—^,•´-‹ºÈé¬\žç›*OwBð]rØÀ ¿lŽ�yâRʘÎ6êgyÙã\l•/çÅò<=Ýœ�åUšÕu6kz€ ²_¶ùÉ) v-’}ÌÔ(¼3ÕÏò~‘ÝáüNýBh¶óBý§ÙšÝHò V ³.š_‚Ic¬;¨ºJ3ôK¹V4åòÇ\)û=Í*{ û]¥åéç³Ú†d¾e6Ëâ6¹:ÀÖéR ï X±ù@/óR}{Õur¾ýGÚ#­®í’æÐYë.³õ‡íÚoså (uªtTé¡Ú-Y»_Ôw^?9Ù}ía©~]eÞÚƒì|£8>JÖ³¬ÛÓêÏä2«Ôzé"?k1ÀG­l8ÁgJ �ONËZýîšw_Õr^Tuû7ë÷?vå`Êæù2U­¬¨»Ã¿;¢ªü\çÍ\®²f?v °#éŽÂèvŒc}8à_½ù©ßÄf¥\`¾u4›²Vže®TxÞXú£‹¼1Ím¹›Ö–l»ÝVÕWÑhIóc»PÝyájtóoÔÁ—-Ï•OWj¸=Ü›C¦Ê® åÞšSÕ:/º×NÛ±ílŒ>kî§nHmêKkÊ é¼ýɺÚïò�ÝáuY^åi]6¢Ý–9øÙ6¨»=ÔAzï)|[«¬š½úo³ßvèÍÀæwѹšÎæ¼Ø6-%~‹E£LͪêU©¨•éV-¡JÕ·—uŸ¿|ž+¸òú§²nļû+=Û,g/ìy[â¶9*û¬›ãîC¿ÿ°LÕacªžgg*ä”—Ê•æWE¹Y÷Yà»føË¦à¶+6l·¬këvƒ£›¨–ú¿-]§µÝñ÷´©¢û¸ù¾º]Ð zÙy¯Ù¢TÇq®Dj·R«'I¡Žät­¢Ï,oEX ÿÓá&Ÿh;ù ÆN>™üä¦UÛÝO8ÈÉO6¿Û�Aì[Ìä'�À&&?ÙEäw bò“�óL'?ÙÅŠÉOþ9ýäÐ5Öȶ@ͧ–ûi>tŸÆ¾ ÛÈ#„�l Ü˲õÅ»ìT¥†jfµªgjšL¥Ã‹â´‰X�isº²ow®ìèåʾ�\ÙäÊvóÓ,ß4Ë7ÍòM³|Ê[7ý³�`Ó,ß4ËB(z–ï.º2Œ»²'�‘Îm7·SVçé‡yž~P³¸Ûsmé<Ÿm¯·P¶®=ý>j'â\žîèkíéþ‚ñtG_Ožîóótœ#«ihÓyß鼯:Á“<˜ÎûîN2Oç}w'Œ§ó¾Êïv§‚õ™Íó鼯ðyßÏÇž awJžî¯°.[S§Yí¥ûKÕvפõ´fÑ^ÞyÛîüî¶pµœqéœkÉž±[´ÿ œ5¨-}·`ûO{)ëÒ=ãÏ=m»ÚUÀ'žuô¾hWèÿl—vÙìãþúÈãG�1.ûøÎ^éÿ‚›Énð}õ;Ñün�Åì¯,`|�Éãƒîz øèY=_¦‰Ýq[äþÏîgp"}÷Å‚ãdï§Ë:ÛK¦'{ÿ\�é½ôsº¬³¹wº¬s²÷Ò—u~>öþk„½oNÕ.YÛ¬ÛËô×Û{œ—N?:ÜÎ窻rÂFó8=¼«³¹“ÏÜÝ4p£wú Égö÷+w¸ìçö¡1ygÔ¦›‹¦›‹Ô9÷éæ¢é²ƒîNÌ鿢í­W@$ÿ„—ðÃí^ꯤvÞ•>™ÒÉ”~Âw¼"»»I(òtg|w‹¼¾Wyº3~º3~º3þ¼é”Û³£ÿnïhü|¦P¿Aš×ÁI·o3‘Ú�xnŸÕât­'�Nv®UÝ^¾Ý�Îg'µO,:IO&Û:ÙÖÞ¤&ôíNݳ6þ¤¶Õó›3Nn|"l¨‰qBL&L3®È×ÉÜ6ª0ÍÌN3³Ó̬šeÔG1ýÎÌ~‹4·�|ª§RõÏDÜ=h Ìφûý ÆÏŠßÕ3ÆÖÅ¿òþ)eîA°ª}�ëÉ£Ãovfùèc–¿™Üòä–'·løàþ·l$Tc©É-—ÿ„«šÜòä–ÿ¤Iõ9�îQN“Ó ÙþÆú¼·6ͳš¦G±Ê>Šõó™pþ Ò“ïžlÜÝïÙ:kµ–êÁžë#NÔôµ{F‹;,w›˜Z� ß—Ucòj™-š×,'årq}Ð|Åçóê‚óå¼ü˜ª7%«+º÷§«ª¸JÏU[¹Ê«uó†ïb~ï~òïv7«—·ÏZ'¢Ç’¿&_6_‚ú¿â,¹Gš¡nUõ5Ö:èl­^ŽR§Íßý²W͈ïÝoPºß£zàëi©�›…šR.‹¥:H·‹«j$‡�?VoCÖ ÌªR/°­GíÒ�E=»Hîµ€})‰z µÚGG ã»–|rh}rd}rl}rb}òµõÉë“o¬O¾µ>ù‹ùÉ¡Uó¡Uó¡Uó¡Uó¡Uó¡Uó¡Uó¡Uó¡Uó¡Uó‘Uó‘Uó‘Uó‘Uó‘Uó‘Uó‘Uó‘Uó‘Uó‘Uó±Qó±u´¨OŒ­PŸ[¡>1¶âÄÂQŸ8êG}bã{C-cì õ‰±7Ô'ÆÞPŸ{C}bì�“ÇæQ§>±j6�:µŒU³yÔ©e¬šÍ£N-cÕlu'�ÍcL}bUhcj«BóSËXšÇ˜ZƪÐ<ÆÔ2Ö^5�±“ÇÇVÍÇÖ‘plÕ|l ÇVÍÇÖ‘plÕ|l Ç»š“ä´Ê³;iM’y~–mu/P@ã?fÕr ð—Ùuzš§*ÕmV+õÆÓ|'÷t­£k�îÿí¯ÉÑÉã“ÇZ¯ûFQÌUS8lÛMcò…’ñ¦éÀõ޾ö­÷¸k0¾õŽ<ëýetµÃo<«};ºÚãÏjߌ¬Öˆ‰gµ'ã«íÚp’ öå×#«=>ö}'£«y¿�ãÑÕ¿õlÛÑøjO<« “oý`óû#B�m�Ã*¯7Õ줧4ï.ê{¥ÌÒûÎ+}÷Ý*«òe­ÞÝ»V;™åƒÅ”O º,!]ÕսȖKa­óÅY·­½­ ¢ë­½w¿ßM­ùyü�òøí¦Ù’÷ÿóÞ½à³6ŽÒÇî·õ|ù7c º/«%9ä’>A°±Y¾E°sY޾F°œpYŽ1ßË×<õÐjĦ<á’#H¾á‘¨GÆ H¾å’~ƒ`ù —åèÁ¢|ë‡ß\f…¡aþôÕ8�{󑜧yU•Õ½/^-ÛÙkwòêù÷ŸªáìTŽ.]þ1*æ+õz‰¼"ŽðíZCÝ»hÇIô¶F·4c €ÛX@€QXZnm, ³,p0ÚX@L1X@:m, ™a,(�6F ”–A È_ Š� D$ÍÆR†ÁÂecAÁB�iyr€Y€€ÑÅçªTfÔ¶­Õ|×–lLSjGªTj»‚ ;Œ4�ÛT<ëw”Ž‹ÛÙ5µïQ ¶ÖŒ¡*ñÁ¡Zª3†ªd…jëϪ$ª­Dc¨Jš¨MU"…µÔi TÉÔ¡Sc J¸P ¶b�¡* C¡ÚÚ5†ÚˆÖR±QXÜOk`ªÆ°t�óFå᪕›zµ©×Êl½}]¬Õ‹‹ÇU±9 0DH·c")¨‡}Z· ˜�‡%÷-âÂÐt[„¦»´ 4Ý´¡É.ˆL¶tAd²Ã "Ó _šîÿ‚Ð ;Ʀ»Ã1lº–âÌâ�#‹Ê;ö¢ë^YP7í.,˜ªù¹ }*á2‡Ð½HGp<(›“àPÙœÿÊæŒw·lÊxï˦ŒwÆlJ‚ofs\5›“â¹ù¤GÎ!¥÷¬_o_{ؾ†ø ¿‚_DpïzåOÉÀëª÷áá�èb6Þ‰.æä�èbfÞ‰.åç�àR–Þ .åê�àbÆÞ‰.æí�èröÞ /æðMxº“L¾¦Sχý•±zq¥ÔÃ>“ê±;ãúuI¶ ×cûðþ=Ž™Ÿ¨Ìü@eæ§*3;P‰ÙÉ€JÌÎTb~J 2ó³•Y 1�©ù¹�@Mo^ØôÐ\§¿}JÐ.>€Ï ¬>Ö›šÈ)Pö>"„^,C¸áÅB„^,E¸á¥b„]*G¸Ñ¥‚„],I¸áÅ¢„^.KxðÅ„…OdRšücRº‹`ùF¹�@FÅ—wÔd{0¸�Là#¹ù©‚ÎÍÏtn~² s³³�š�.èÔì|A§æ' :7?cйRƒœŸ3HäôƆMmü95³ÆàSBج?Ö#;žHܾ�Àá#‹>±Ðá#‹>©àá׊>|©ðáË>±â#�‹ ^±â` «5)† *Ù]¬a'‘Á° 2ó²È *; †÷‘GÑüüLÂãçç??›ðøÙù„GÏÎ(RŇX¶áK#b9c„C*mŒPHeŽ ©ä1B!–?F8ÄRȇ\#K$nº®“r‰YĘcÓ‰‰)¨á¼Œbfçs‰}䓃R?»HTÁÏ1Uð3�Dì|#Q;ëHÁÎ=Eð3�Dü<$Q…@6)ƒŸ“˜eÐ{+635ïgÎïžK¾ËKíË×úOÓ+Ê-*w¬E ¶Z‘¸ëÞGTòà‹Å$¾XDòà‹Å#¾T4òÀKÅ"¼T$òÀ‹Å!¾XòàËÅ �X² èMŠ?°€1]ÝE‡–7­@HA9æ%X”�7àè>Ä�eç';?ápØùɆÃÎN4rv’á�³ ‡œŸ\8ìüÄÂaH*,z~B!ÒÓÛž7™4ç”v7¯¼xùúí¦}ææÊ/°ê-¼EÖý Pº;oÐÁcÄÝð nPàõóÄÝð màÁƒÅÝð làÁÆÝð kDÃÃG�»ÑAÔ  ëgŽ»ÑAÒˆF‡w£ƒ A@O!wÜA€�#wÃØAÁ×Ï%÷àƒ”AÀ/\°ð÷ ¶zfЍ¹À§¦¼ÖìCÇHÄTxŒDL‹ÇHÄyŒDJ—Ç8¤ÔyŒCJ£Ç8Ä”zŒDL¯ÇHäT{”EL»=,{PðbYÔE¶Hgåò¬8ßTyª^]žWùüà@½m´8_æóö�Õ£ïÜiÞ.áG›bœ+¢MÝûËv+9æýÀ:£à�†£àµuFÁõÆÀ댂º��Ö; ­3 h5][g:Pé0:´Î(t Ït`�Qð@™1ðÀ:£à¡&£ðµuÆá5Æàë§ë0nºÞÏ?&¥“�`ûA57r~Þ_“š ÷ŠÌÎûá#¹ sóbÜ„™y1n¼¼wü¬¼uüœ¼uüŒ¼5a>^Œ›0/ÆM™‹—#'ÌÄK�Óû™wþ²ypt†èÖkr‚½‹–ºE²�EÏÝó[PtoA‘½¼…DöíÙ£[Ht?nAѽ·ÅðÙ6ÝSC,ºÞàüsÇ5&#†WîVÔ–H_Üñ+ºÝKoe b|l“àOƒ˜ßÄŒ÷“AÈxŸ„Œ÷AH‚¯ büZ“âà5J×1¯oÚ¬ót¶Y×奾Œaëƒó°6˜ jÍOÙåŠx«QXºÏ…¥{®QXºÿ…%{±QT²/E%{´QTº_…¥{·QX†�Ç¥{:.]qþÎæ“8ÃëÙ+ j`¤ï³kQÐþPdþÓ†Er|"™‹àÉ\_IæŠ÷›dªxJ¦Š÷§d*‚o%sü,™‹âsédÿK!£ë¿×¯òå¼Xž§§›³³¼J³ºÎfñŒ˜±ö!ØhÙS³ˆMcÓ½r›n˜ÃØtׯ&[ç04Ù?‡¡É&: MwÒalº�c3<5œn¬GÁéêŠs×ò1�4,¶API#}¶§ e€=#"ŽÛƒÃJðÞ¬.ÀJðã¬ñÎ\€4Þ£ �Æ»uR‚o`%8xVŠ—— %¸z-½ýý¬¼¼,êØk�ûÍéÖëT‚=ˆçæ»REM¼É÷î$ß²[�|§nA² º…Èöå"ÛŽ[ˆ|nAòÍ·)à¹mL¾Õ†˜t}‹sØç˜\yŒu·¢ –ýtW0´Û½(qýÂPÓ ¯ŒÞ†3Fs0|0šƒîzÑt�‹¦ ;Z4ÿ¢9nÍÁñ¦x†Å�ÐuÙë;³�™ºNM,ŸUÙeë;‡k� ¹ VÓ|ç°Tßé…¤ûN/$Ýwz!é¾Ó Iö�^D²ïô"’}§‘î;½�tßé…døN?&Ýwº0éú†ó�CÎ1¹2|çpEA-‹ô�Ã:”'~ 2k;„Dp|g4ÁwFs|g4G¼ïŒ¦ˆ÷�Ññ¾3š‚à;£9¾3šƒâ;ãI¾3†„®Ë^ßyQ,Õ,çËù‡÷År^~|w½ÊPažSÖ¬z �ÉqÔýœ²¦t·õ¤£ƒ‡-ðÀ†2àõà x`Iéðàa <°§txð°XU*<|Ø‚�l+]?lÁ@–ж` ;KG[0à�µ¥Ãƒ‡-ðÐæ2ðõÃL|`yéøàa 2[W™zˆM®󬬀Þbtv°î˜Yôôl?¨Ô­¯á'`¥@I}ˆ@RQˆZ<}ˆ@E1ˆ@/}ˆ@81ˆ@"}ˆ@+ÈP}€@1€Z}€@ÀPû|€@1€@î|ˆ@÷0ˆ@á|ˆPêP�ZÔ¼�@Ý0�@Ç�tAÃåùå˜B=LvR­”o°š xE†ùA*gþ‰òÄ0!ÈG2b|$!ÄG2ÄGøH‚øIß# á=’�Ý#(Á=–‚Ûñtñõ†öU¹Ú¬ÒUV)Ci&áªcJ-(Ç4/ ±’@º“ôÒ�¤�î#=€déÁ#»HÙDzðèÒH·�@†ƒô!Ò ¤�H—0œ„Œc¢4´�p-A¹Št�° eíàŸ"Þñ Î1 Ÿà£ð ®1 ?Þ3FÁÇ;Æ(øx¿Op‹Qø¯…OqŠqŸˆ% KìˆK\«KŠÊez™×¥zÞ/e÷Á�íߘ©ÇÕv�-Ä-œí1kŽ>Õcl‚ÛJYÀD¥ŸL*�ž½ôÓÛI¤Sš~`F‰4`žÓO,*‰N~úY€q%²èQ? °³$8Mêg&—ÈæNý4ÀúiÀ„ªŸb*�žeá6™È¦^�<{Pöu��7׊β¢*õ󹦨søN¤1 .h´ióΊݪž8œk !!G!kÕ!íÆ ¡!¹Æ m!…#C9QÆkCÑ éÅ� !µÅ i !C�EAk5 BMÅ@�¦Ë(nÂI=¦„[o� ‚’97á,GM"8?™­p"ã óLFÂŒ“‘0ÇÁdŒŸõ`ÆÏƒ0 ãgF˜„„¹&#aö„ÉH™OáRfXè”ô–â�séî«ó¹€7÷`�õ$ÁVCsçžšEüy›îÐÃØt�Ʀ»ô06Ù§‡¡ÉN= Möêahº[cÓýz›áØàtÏ> N—Xœk÷��i¤Ë·{`å4Ò¹{ RNÚ3"âÞ=Ø1¬/ÀJpñ¬'/ÀïæHã½i¼« %8{V‚»`¥8| Z‚ËçÑÒÛ�×éŸf³gÅB½±˜= ï†ëa‚�‰æóÝ%‹Øü 4Ýå¡é&?M÷øAh²Å"“~™lðƒÈt„¦Ûû 4Ã݇±éæ~ ›.ª8oïæ“E—µw£ h¤³w×£,¶{@Ä×»¡#8 ®žÍIðôlN‚£gsÆûy6e¼›gSÆ{y6%Áɳ9 >žÍIqñ|R‚‡ç�Ò›�×Á÷vܾʣ<;[çuúûÁAóü‹óù‡ôcûHŒ´¸\-Ò�Ùõ"[ÎÓUU\¥çj9ÏÚcíI°éÐ\»§fÛƦûö06ݸ‡±éÎ=ŒM¶îah²wC“Í{šîÞÃØtûÆføw8ÝÀ�‚ÓEçà=äcù0iÔ׳¢ €FºvOAÊB{FD|»;†•àÜX Þ]€•àÞXãý»i¼ƒ �÷ð¤/ÀJðñ¬'/AKðòŠUÌÍG±Š¹ù(V17Å*忣H¥Ü|©”›�"sóQ¬bn>ŠUÎÍÇÑŠ¹y,-½ñxÝ|]Ô õ”óÙEVaTÐ.§Ýz[¡ˆ77‘èNÜD¢ûn‰î²M$²§6�ÈÚ"ûeˆîŽM$º6‘Îׂ¢û\Eœ«m©Æ”ãaÒ«P»èí¹Ö–^yÔnçH¼Ú.€Hp›D‚“ \b1ÞãÝ]0Þ¹ ®,€Hp\DŠ› Aœ’’.T^”­V‹b–5qzô1[§êúå‰NËr‘gËð% `uuýB»ú˜Ð *mBÔÛo®ˆaÇ¥Û§q\º™Ç¥[«q\²Ñ‡%Û®qX² ‡¥[²q\ºAÇeص0ݼy�é ‰³r€£wÊØõRêXSP #g'A1½ *×>í7NdVàÆ²ü!ƒ�àl_É`‹÷œ ²x?Ê ‹÷ª 2‚�e°<.ƒ�â9toL££w”oE¥…zjæƒøÂá¿‹ù'c¢‡e‹{i/¼Œ¥öÂË8k/¼ŒÁö‹øl/ºˆÝö¢‹¸n/ºŒùöÂËxp/¼�÷ãË8r>]‚ã�ù�LJõÄ+\þ»˜ß —>¬É0ëÃAqÏ>„�äf:x7Óϳ¸™îžÅÍóú,jžógQór‹š™ XÜÌŒÀâæ&93?PÉé­ ›&Ô»–õò­ú‚&4ÆXlnü yõ…èª÷%œèbI‰.$œèb9‰.#œàR) ."œàb‰.!œèr  / Lxºè’òƒ¦SOo|Ы ,cŽ ¬¶�×åî#;hô8f~r 2ós•™Ÿ¨ÌìÌ@%f'*1;/P‰ùi�ÊÌÏ Tf�¤@¦æç5½a¡RB“N³Š ÄX—laüŒ‹�p™„à— p™|à‰l‘tàÁ l™là—‰p¡dàC— 6:]fãsdLw,€k J*#ÀŠ k‡Ä3�âe&2/3�y™i€ÌËËdZ^ Óòr™–™È¼Ì @æå&:1ÓÿSˆém åþ·� ,Oÿ;Ÿ©çÒOØ@c�M°mñ“€]ºx¥�I£2Ù`”B&!ŒRˆä„Q‘´0Ê ’Fd’Ã(…L~¥Jã2YÂÇA—îøDa×0&¿î\acÊ4#]Øu^ß^@EŒéª;=‚âËHMFQF>1FÅS’�ËÎLELvf b²3S“�—r˜ä¼TÃ$ç¥&93µ0Ù™)…ÉÎM%\zf ¡ÓÓ›œ7uœçåe^W×é…z±ãúààåüÃËíG˜ûWOWu5Ö ]Ÿ2õÞÿÏÈþ7,ß4( ‡OÜ_狳û_þÍK²‰âÛ0ˆŠ£¯Ã qP(އ)@舧8Nà wPŽÃ zÄ3œ¤'a�>( ‡ß„)@¡P…)`!q ÙCC('ãÇì>Eú2[h5ú}ûîóÍŽ’éàvTzP1]¨› ðê4…Ã%ÕÉP©I$±6H†ZM!qɵA2Tk ‰K° ’¡^Ç“8%Ûà*6…Ã!ÚÇP³ã9œ²mp U›Âánƒd¨Û—t$†r“Xâm² µ›Ââ’oͲõ®²Ó´X®6µzCD¦^¡šÎ[õ”p×½as}o]W›Y�¤ªânW¿º\-Þg׋l9O¯ŽÓ££4Ãa­n•Æ@BIöbQFaöb Æ`BÁõbÉÅ`B}õb… cäÔ  ÔÓ ô3 9K/$�K $ÔF/&PG &”B/&C(P>?(Ð> (:(]êpSæçCÒ1íz˜M®&8g;90,DMI[$2 n`bX“ݤ6�…0yM`‰Ÿ¤&�ÄOFHâ'� $„Ée a™ÀB™,¦Ð&…ãhèrí�ü]åËy±Ýâðé.‡Oö�8x²ÄÁ“½ žîqøt?ˆÃg8B$Ý èÒr…u¹Š›T+Œ‰¯ Ô²¡*SÒáØnpÇö‚C8¶Âq}à�ë‡h\8Dc;À!Ûÿ áøîÏÀc{¿]ž¢œŸâ“ž�ïSË JÊ–éK¹·‰[Õ =_³³_›îöÂØt§Ʀ»¼06Ùá…¡Éî. Mvvahº« cÓ]›áæàt'7 N—É�‹;-뺼Œ3rÝ:c‚*( ,/×U*iç,D¶£³Ù¦ÎBdû: ‘kí,@®»³¹Ïd{< ‘mó,D¾Ó³!ÙfBÒ…,Êïu”c 5°|Ýâ‚¢Es}]Úømwœ ÷Ã2Ð –�î± t7ˆe {B,Ùb ÈþK@w‰XºWÄ20#š‚îtÑ ¹ÇyQÕ×꾕Ӳ\äÙ}5`»Þ˜< j1Ë@¶…JúG�mM@¶{4ÙæÑäzG�kM<®s4ñØÆÑdûF�o-D¶kˆtýŠ2�-ã˜(©gŠöB·�§kWÔ*šol«Ð¶±Ûs‚®‡O÷Œ8|ºcÄáÓý"Ÿìqðd¯ˆƒ';E<Ý'âðé.‡ÏðˆHºC ÐõÕë‹eQÙ"=Û,ëY•çËô²TŸ•ê.gÔm#~€1…”bšmô—-b"QðtK‰‚§L<Ýn¢àÉæ…N¶¢(t²1E¡Óm* žnZQð ‹Ã§Ú>]~qöÖÏ?&¥Ý©]A©�t½þš”öŠœC÷ÃGrܲ7ÁI‹q\¶w¼£ŽwçbÔñÎ]ŒšàêŸ Ž_Œ›’äÈ IA‚œÞƼ)¢\eÍÛ¸ªü¼(—³¬¨Êí_)ê)ž€±n'ØÍhÁaP©HVð!Òã�‘ž|ˆôàC$û~ ÙêûÉîÞH7ô>Dº‡÷!2l»’îÔ�tUÙóå˜B=L,ù¬+¨`‘~|P†²Áƒ¿E\÷1Ì@ðÖ‘ É@ðÉ‘ ñn8’ ÞóFÄ;ÛH‚�d ¸ÔHŠ�¥ 8N<]�q¾2mçÇc.bÔÞ­>¦Ü‚Ê,à-»z妅+ä3-\!·iá yN WÆyZ°2þÓ‚•q¡¬�µp…©…+åKm`!w �é Ið¨ñ˜Þ�Ë$J)¨†ŸÚm†é%·{UâÚ Ç¦cÙ¸þ5nÛ¸^6Ž�ëkãØ˜7ŽŒéwãȘÞ7ŽŒëƒãظž8Ž�í�#é¸^MGï ^ßܽK‰> ×ë ‚=‚æ˜a¡"VÙH÷È@º9öÒ]±�l‡=xdìÁ#`Ýùzé–×Èðº>DºÉµé:†s·�qL”°pUAÍŠôµ° e1áŸ"³¯0ˆOð®Qø·…Oð§QøñŽ4 >ÞƒFÁÇ»Î(x‚ÏŒÂ'8Ë(|Š—Œ# ¸G,]gQ~±ó­1Ó¬°ðní1yÔ_¾gìÊwެŒ´`e\¤+ã%-XGi¡ŠøJ UÄ]Z¨2Ó‚•qš¬�ß´qe\'Ä¥kb¼÷ìxÇ$L¬ÚÚ(¨ ÿÙm„á·{TbVÕÞn$Ó—FmÓ£Fq1ýjÏ»FQñ|lÏÓFQ1ýmÓëFqq}oÓcÉèÚïõÃë:;Ïçéf5Ïê|͸®Õ 4Ö:»Í;+1È!dºG!Ómr™î”CÈd³&ûå0Ù2‡€é®9„L7Î!d†wBÓíó4]EqÚI=¦„Ži\'† bFúig9Êæ:?™áu"ã ÞšÉHpØLF‚Ïf2Æ»m&a¼çfÆ;o&!Á3 .œÉHñâ\J‚#§SÒ;Šß—gWÊ–,æõòñk½ÆX³l"DÛ­ë”1ÛN<†Åvâ1Œµ�a§�xtí„£[g'Ý0;á6Ù‰Ç0ÇN<Ž%v2Œ° H+¤ýÕ„c:Ô=̈– 0ź[]qã0õ_2NVã…Ð)®�Nq¨xtŠÅ£œ'œà2ñàG‰§¸G<:Å)âÑI®0žâQðt ¸½‹<â­¦�Lu«Œé¬ ¬rü^W¨ á³¹ŽÏäZ> �ëù,@¦é³ð˜®ÏÂcÚ> �ëû,@®ñ³ÙÎÏFäZ?ˆH—®ï×1މ4ÝÒ‚2Er];ƒ¶Ýi×@¡â“ ²~²Dâ“] Ÿê‘ðT'ˆ„§zA$<Ù "ñÉ~‰Ow„X²' Ð¥Õë WY•/ët½©Î²YžÎÊËË¢®Õ´àE¶œ/rõÜÚóÍ¢\ž'Âo9a¥«ºSlA}îmd_üýÿŒTìж¸]f$x/u0¡±|ß>¸¿Îg÷¿ü[ð¨‘|G_Sø€…�ä;Öo”ŒØ>àp£øŽÓCÊæIwL¡þ8Šî$=¡ÐûIwø …¸ëH¾£# 4ß±„¤_Ã!ðæ‘„'±?‡}ô—|9/–çê1èWyZ7�1œÕ�šÿ¼œx£þ‘-Ï9ª½8 Z¼[è+ÃÚ㻋cSÚ½ân+$6Ø\t «Ðè@oAÐ�¦B¢ƒ­Az ‰vh)ºAcA°�ŽBb}Á �mÐVl Ÿ�Ø`WAÐ�vB¢ƒMA» �ô h&$>ØRÆùn²—¤ÙrvѼcCmÓË*»*êk^Céðn§¯ì6@®«l÷�·¹ÄRbZ‹Í9ì0ÑœˆþbsÛL,'¦ÉØœÃ^ˉé46ç°áÄq¢Ú�M9ì:±”ˆžcS[O%ªñØ”ÃþK‰é>6ç° ÅrbZ�Íit¢hRDr�ÛQ,)¦ Hoª'},–óòã–Z¤+ ?‹¾4Ø¢ëL>Öýö&ë~»“�u¿ýÉǺ×å#Ýk�ò‘îµKùH÷Û§|¬ûíT>Ö=÷*/í~»•ƒö¦úÕ6î\¨W®Ûvõ¬ÍS?4“ƒ½�Ž5Ø ™47ÊÛ´(¼¡Hå!ö-q Wyˆ‡­‹B Wâa÷¢‡–‡xØÀ≃1ËÃ;ìaÞ@ÖòðÛXóäÙg^ƒ<{Ìk�f�y Òì1¯Aš}æ5ȳϼyöš×DQr …Öm�dزÙ±ÊBd( ‘•,Dn(²¹ñÇä �i,Dvx±ù1ņdI²¸èT¨îQ2A»õ4Ñí(�Ú­�LXºëÇ2Ð�<–�îα dÇ�% »h,Ùc ènË@w°X†+ESÐ�&‚‚.ºA÷Ø=àù¨é^m»•> ÿØ•*j -H¾ƒ´ ùÒ‚ä{H ’m"-D¶‹´Ù6ÒBäûH ’o$-H'icò­$ĤËZœ—ì8Çäjh&»åoßMvu;¹ÝyO'ÜJù#4ÃQ¢9žÍÁp•hº¯DSÐ�%š‚î-Ñ w‰æ`øK4ÇaâICB—ã ËT¯X©óö.¶îbù·Íß1w±µ·sÇÚ bêk�Ú pÛÐÝ-Û½‚ä°¯#±H€1¥’XWqZ$ÀªIì‹3-`^‰$ö5— °³$Ç¥”0¸Dë I‹X^‡ãÂG‹˜`"‡}=£El1‘ľLÑ"�F™Êb]}h³ëLd±¯�,{Pïü÷UY©çÎ>še‹Åi6û ®ÿ;-ËEž-ÞíWNû•ÕólWÞ=Ak½}¨ì»7¿¾pªë÷Ï^¿…#ZÍ­cæˆsD+†9¢çæˆþuš#ú7eŽè‚9Ž^kHr`ˆ~œ4‡mwƒ[×_lL·�õÜá´¬Òåf±Kfò‡JËæÞdçÕWlõ›µû…8�ÁŸ~}ý èCÐÐG 1 @c@Æ€>üŒ}ôúà3ô±g €CÏÑGž¡x^{¹Ûý›u^¥êMÖI¡vkO•̲užèƒG;¢�sD&æ8N¬!} €!ú‘r¾*Õ�byÑžô—¯Ô(;U ï†å(|S¼•Ø>ä�²¨½yžæUUV÷¾Ø™ pcòwßÙ«'˲Nv­9)—I}Q¬“—ïþñÅý§ ®Û%÷‹Ò¯`Qbx,JYo€EÉô °(Í¿¥ 7À¢tæX”fÝK£7A³÷_?]—qóõ½8¥qš¬.7ïeßFô�ˆ;¿Æ•ZÍäÛ:¯¹�ÃeRñؾ7©xì›T<Ú\5¯–Øï¯Ÿ®âá6ÏÕsqÊëôl³œ‘¢˜ÒØ'žÆ^>ʺ8C=ý}×ÇÁ-N[@Ÿ ‚²”οz¢¤ ÚÓ$Ñ6ǾZ^e‹bž\åÕºPéôÕs+—þqðÇÁ�WœŠËnÖ9­«l¹.š7«�5ï%Ø>Áë_¿ÏÏÓÝ2W‡˜ÙÌÝâÈ;=oà.Ùy$¢|.8û„ÖÇ2ZŸs Bë)S48Ó„Ö“®(hp~1­§mÐð¬bYÏû¢�õ¹Ä ²žD Ã3ˆAd=§ˆBç ƒÐzR ΡÁ¬&[Ÿ# cë¶€ÂgǰéΗßÝÜc²ø0ñ«°íöò¼»•éÝÎ\�ø6qCGp*éÅq†47‚Si2Š3(ÆœJ¬QœA•ŽàT*Žà Ëw¥’weH×#(•î#(ÂA©Š2Ø "8U§@q[DgÓBp¤¡ÞCªz Š4ØTp¤ô¦ãuô}J|Ô½™4ÑЭº×)pF³�tº©W«hl hwk hoj hgi hch h_g h[f hSe Kdޏ¶�~dÌ.² “Óúeûâìæz“›<Ë�ÇëŽÅø±z§"~Ð_Ñï˜í/Ä@},â0µoðcêÃ… |�SÿP˜ ïû1õ�� ûºRÿìP�ºoû!õ û²RÿÔQ� ïú1µJ 0A_õc�Á�ê¾9õ3R;ÔqùW÷ î~FtYÃ¥¬~KbDía²•LceÁ4ub‡øÎÇTNùcóíŒFÈK=æVK1,„„D`!d"K| "�ÄçI|Ò!�² �…�f,”üB¡Q�øé�)sàGI—ìpFé68�•›eMJ*`Ê+àÚT#chc h'b ;aŽhO GèG ê%7ý/#|áw4²Ào%¸Àdã‹Y Äx�¢Œ™h<ÀüXãæ‡°VT+qE²–²+èx��>á qÇ­í =64]!#£¤FÍêtxèVË牽… Ë'©'â;s'¡3ÈCðsÙT‘¡ ƒŸÛŒœ„‘ñŒœ´Dcd$'!#EщŠFÈIW4FNÒ¢1²R‘’“À¢)é]$œÆf‹r½©rõFeÊ�ÌÛ•§öI¦°—¯‹uuÚ¨?ZPã|D_ÉClïœ'[‰ž\6}9P’—U u9Pù‰ËÊO[P~Òr€ ¤,ª@Âr J¤+¬@²ÂÒûadªêiQr÷0éõ´kð*UõëjJÈ–@˜2ú�`gι¥ÇÄIOqLœÔÇÄHKqDŒ”GÄHGqDœTÇÄICqL¬IÅI?h*ºÊ‡SO1_äéºÜT³<-æ¤ì3„˜Ð'™€Î7qg¢Œ/ýŽž‹Vy+YhX‚l"òb ä"/¶@:òbó3’šŸ”¼Ðü¼ä…HM^l�ìäÅ–HP~p�å§÷ÙÈ45$GfªN¡w‰jˆq ¹jXI[ÉéÊàg«†#²Ikˆ óÖpÄf央!v +'�ÑYiŒNÊHftRFJ£“r�•“Þ謬$Ç å¤:-½óxÞúB=¯p¶©×i±¼(N‹º¬¢Nm¹Ö¿¥lgÞ-¥oDÑW൷QiÛk hÏj hÃi h»h h³g h«f hŸe “dŽh‡£GèÉ˲õÅ»ìt�z°¾ó›¿[ÏU"É�nM�áÑÇ+Îðè§\ü Bêã îž ëß ÜBÖ¿70¼�*€«®(\}3UWÿÚ¸ðŽª® .¸æ0¬Å .9 ±Â!ë+CÈZìPÈà‚C?2],qYÎÅŒ r5váÜ\˜s±ßiE êêÆqUʵÝ"©ÍŒæ#ä5_|RcÑÅg4]|:cÑr‹��ÈX|”,Æ#$¤0*!½[xó×¼X¯Ùuª^? žO÷¨ûŸtžÏÊ*«ÕÓêbNµ ±¶�ëÎå23±i£ªãL›Ø´Ñ4´Q4´Ó3´U3´×2´W2€×1G´WÑ#ô£gûˆ«²:O?ÌóôÃÇbi»˜ëƒGÈÝÊoÃríÇ><ˆ<§Êu�ü(Ï©I(+"_¨�+õ¯Eâ_‰þ…#H`ŒàÐb�âб0‚Cë‚FÄ-a(#H´¢H@tŒ ÒŠcÑ12†EË4ŠDJ ]òqñrX…Ý�QÛ_Bt7—H‡•L�¡{@1æÝ;ª3 Žõ£4¢%¨ä:ü>ìÑNÍ„;$q|é”:âÓí^ʈO½{)#> ï¥ BJÞK„ô¼—:(©z?…Ò¶t!ô¦ŠMáÍÝÕM~gÙL½r›ßpCS€ò %o3“k“¯“l›Éµ17´›6´6´o5´×4€?4G´§Ó#ôCfk�ÀwIÈÙ`m”#»5 ½�lÍb‹NÕ,6åšâ2<‹MÿæPVͬA ·ë_.~$R»ÑõÏ…î Ónt­!ô‘íF×B„B÷h7¼–3¼?:»á�(âð½¡Ùƒ¯¥…ï�Ë>] IAð£y÷–°¢êþ†?)¸Ä[`‡û÷ÜùѯE»qGüDŽR”tÛùì ;Y³-@�¤f§X:5;¹Ò©ùi•ÎÍO¨tn�TÊ ç'Q9½SŤϺ\-ò«|ÁŒŸ=Ì”?‡/¥×ɰ�²ÚöÚ°Àj™#Ú$éúAògÿeh¿:Êðܪé+½¡J§#eP:ÉÂÐéDSh_Æžb¨^2‡:à%ƒ¨^KÂGž¤ãIÔ�¯• …?Eø@ q£YÔE uE0F‡t¹&§Ñ¾”:âh¿¦;�ö£“ cNŽ~ö‚Žø©„BiD¹£a?º§XÚÃÇ’‹S*¹H4¥’Ë„S*»L<¥² T2½LD%ÐÓXLH]•«ÍŠ™P[Œ)ž~ñ´ý&‰Ù´; îôÅÇÍdQ·‰ÑSÏJâO¹H†ÈEÊ£D.Ñ0ÚÖ°§$jbKÆP[2ƒšØ¢ÔMŸ&¸lô´ÐEs'@§÷lrèlÙãg»š;n¶C·š5»â&�N’þY7Ö{”F äËî ´.åÝõß=%ËxZ‘LO+’&ãierd<¯L‚ŒçÊŽb™ÔELï=ؼø/p²5½zÂH�Ò”?ñìh|Ÿ„i   Í­y£Ø8ÇÉfŒÎ”lÆèdÉfË—F%{H™~©¬ég�Jœ~±Üé§KŸ~ ¹ :Â!–D�tO@Ê£F (ß�5V¶³©±À¤þøFÿ$êÏL®ÆfŸŸ4ØCŠ5%°-¿vºå—ÀOºüø©—_ƒ@(‚Ÿ†YEл`T2îÏñ Dc5eãÏ!ƒ/”ŽÊXÝ®?ÕÞTŒÊ`mOVKLÍ`³Úw\»;¾åÝN3�Ìp³­3¾­oKì+2 OfKØEÈ„f@A)B&63‹ ÎÌ*„¢3³ ©ðÌ-C(>ÓË 7ǨݞîHÏ=Î�?‡èÜ›ÔÜܯ�²X·ë“úRã탒ˆ¸KÄÚÆLç£Åe:ÍÑùdƒr_ǾR²_4";ðEó±_6;d“±ƒ@8»d3ñ��Þóé�¸¯%ÕÃ4ܯê‰Âýð¤ïù|/7Ñ2ûÉ-è»Döí+OÜë‡íÀÉ~¾Óp»s·Ÿ^&ïöøÑ[/“tÉôB—Ì/”nÉüR¹–^€P¢¥@ïkÞ,»YËót¶žÇ\½[é³H©æ‡õãIõ”Ú{Y´/5´¡4´4´ƒ3€ó2G´cÒ#ô#b³\çKÕQ‹e�œÏ?¤ê¥>óòcZ\®éÇìz‘-çéª*®Òó¼Nõ7~·n’ÝÕµ�ØÉgLxØp0ÀUz ¤q¸þi! |òïŽÄPýsD�éýºÀôOŸëëÓ?{xx’ Mk <*É…§Œä„Óª„‚�A2àèR† m;:dBänå› a;ÊIÚTKzßu¤ï¾cì–}KâàÔ4•Av[eÇ­ø¼³ŽO2H`BFA"Ò™’+°Ð„Ć¦Ë¥7 ¬7§Û·Ž¤¿ÇĸÞgž̬ =ˆöämVÐ~ÂÐÞÀÐmÞÛÑÍW�Ð T|Ùw+ ÀÒöÑ4yøáHÀç~¸{òðU6¾þ©¢ú´Ž �Äý GàÁÄàÁӀ¡Á¨rƒQG¢0"H6"]øp2"3D£¨pµ›K�uÒÂvj댼ýóh!â÷vœºEPÅ ¸ûìÈŸ, ^ž�/¢ð )# Ÿ’5â‰K@WYLî¸&æŽë)wtÏÛÒ®D'‚»þމØÜq�ê¾·Òk¯÷Ük øQ¹ƒ€•;øQ½–€•;ø¹ãZ8w@<‘ÜEr”ÉD‘ܱE¤wÄèÜ�S¾‡í9]ÐЯ'-ìºã¤…Å:yùî_ܪ&[²Ó²ªïÝÚrZHÏ×Ã`p-œ;Âð¼ÜÆçåŽ0>3w x¹c”€®²ÞÜ1+—gÅù¦j®€j/ÇŠ ÖÊÚbžù#Èàdðj(û»¿['B¬úö‘JHÂíX€$œOHÂ!E€$Ü�HT\ œ† Ñ�°ðÜ‰Å Ì =ÈŒ¡ÒÓÌ*#ÒŒÂÒs�–ÞvqáÆ¢EÍíe¬?WÆÚ~ëw8duî½ËSi"c•&2gQi"=•&2iQi"£•&²×Si„ÂVG/Ÿ¶,\©¸e å-Lo΄ÀÕSW·ö-E®Ž|Rã¨ÙêN›Ô¸Ž?ÒnN�YÑk{LÙ«ûT>|ű±ãW$7€¡éè*ï�`gÅïêìÖºøWŠË\w _Ÿ}ø²¾ò»•¼ÌòâåWý–'øáÌÅç.>G¸¿ó9ÂQ‹ÏÎY|Žp[çsЖÉ-¯F@Ùj •¬Ü¨ô~‹KU&kD¤2W½¹!©è î Pëìª?Á4»È–çyÔ yìµ§¡Ìû«ô� ú–¨ö!zÀc˜#Ú&èúqr~Z–‹<[†ßÌãøêïV’² ÜGc—` wv –p˜’` §) –ps—` ç) –p ’`Qý=pâT‚E ÊL€[«lvÜ€üápõ­Uã¸ZílGI\6.–Œ�¹(dtI÷§®Mu–Íòt–-§ÙìCÌY«þUIýºSâI\fžC«.Œi× Gèɺ®6³:ù¸Ø ê}ìÁGZGÁ _ƾ—6Ïæ@/6"v±9 žÍ�ˆ\lDàbs Z;›¶Ø*j…›h ÎB€Üe�Ã4§õ… S—•®½ÈÌe°"—SØMݾÁìelÅ$Éþ Ío)~_M’,ya ÿûøœ$¥šG~-nò•!Ž(GIWjˆˆ.ÜÞduVe—:W©lÔ­uLÀò@L9ëÓÎY/ÈÖï²ÓEŽÉW¾ƒànÅ,O•ñíJýØnRŒ*ºÄ¨ÂÙKŒ*ÜïŨÂILŒ*ÈĨ¹LŒ*ìĨÂ)MŒŠÖ<5Èd¶QpºÀE792Á Z†ê朧€Iì»wlMbßœIØ9ÞøÃb{æ¼a±���>ýPWmºC“L(ô€«lèÐÒ„7"®7ëU¾œG^騝4ÅÀ}Ç@ëLœž/–8ß6xƒ â<Ûö`AY†›3»ƒ8^çÃY�Nw ðp‹g€‡<œÙàáÆÍç2x8‰1ÀÃí˜Þ¤­ÀŒ ]KªËN‘m«Koôþ‰ X»�Dé£ñþ¬ÝÊ“nNº©~N´o�÷øF8é¦sŸâºIÌölU/žÉi•g¸êÍ MQ¯ÿ}´SÀ˜TÛ•Ué½éîV¿{‚JZåõ¦Z&–ÿ.ñŒ( óŒ(ãæQ®Ë3¢,“gDýn=#ʬxF”ÓðŒ¨ÃÝ3fT­� ´D&Ù]œÌ#öw·ÎHYv¼,‡ó‰I¸ß �„óŠI8·�„ó‹I¸ �„óŒI8×�„û´ "çH°h9�mß;·gãrÏ–ô`Œ?LvJovî@0µvÙ¤Ñc¯¸Ø]“FïjR.“ú¢X'/ßýã‹ûO� dÒè¨ÝusÍg•±,á:ßóM¤:KšÈõüéûW?=ÿù}úêÇ_^§ïŸýöúÙOÏÓ_Þ¼úgúÃÁÿž—=¦wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/e2/0000755000175100017510000000000015222270712023662 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/e2/59a1fba6e1f73de9efe26efa9117f568d6100fwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/e2/59a1fba6e1f73de9efe26efa9117f568d61000000444000175100017510000003540615222270712031113 0ustar runnerrunnerxí�ër\וßóxŠSøB)!!Qòx<¶ä)˜—2©"!k\©«4€¶ÝH_•Êäsòbùý×eï}ú€”d×dgßÖ^÷Û9O�»_ýÓo>üŸ¾úç÷—ãîÝp6M'_ï=Þÿ|¯NN¦§£Éù×{ß=ô›½þýîWW³ébz2w“Áåðë½÷§çoçÃñxï÷»»]÷ÕÉôêf6:¿Xüž¿ºîIþÙýŸÿÕ}ñùç¿yôÅç�¿ìþu6š/FƒI÷Íÿýßçóãáì|ÓÛ¼hÿ÷zp6Ž»ƒÉbz>™ŽÇ£Û^þ¯ƒùÕpÖ½Yìwß�†³ÙpãËŸû>' &~2�]Mgƒßøò?èåìÞ .çËÉy÷l<ì.Á«3ý›«çÇ«åñx4¿xØ�‚³ÑñrÁ�sýx2œ0Ê&ãLŸMgÝdÓ,#Î`ç®»|hçf%`Å>d¶öõÅôR'²™Ê©ÙÙr6ai€¤£O¡­ügîX¿gàÜôZÇ<™NNG:ûü·~“G<Oß í\vÔn2]°mߊ.Å6ã·�>©€Óü“áû…íëj0€WŸvó‹Ç<ŒÙ7¢Ÿòäby<_€3#® üµmDz§ ¤;úæY÷æÕó£^?ëßtß½~õÇçϞv{oø{ïa÷ÃáÑ7¯¾?êxãõÁË£?u¯žw/ÿÔýëá˧»gÿöÝëgoÞt¯^Ûì‡ß~÷âð¿¾|òâû§‡/ÿ¥ûc_¾:ê^~{xÄÄG¯:-Ó>cìóîÛg¯Ÿ|Ãì8|qxô'¿Úç‡G/5÷óW¯»ƒî»ƒ×G‡O¾qðºûîû×ß½zóŒm{y´ß±4?vÏþÈ_Ý›o^¼°�i̓ï9Êkm¶{òê»?½>ü—oŽºo^½xúŒÿðŒ-üáÅ3_�>yqpøíÃîéÁ·ÿ¢m¾î^1‹T¯úV»¾y¦Ÿµîÿ{rtøê¥ÎôäÕË£×üù�#¿>*Ã8|óìawðúð {¶­=ýŠecF± 1öå3ŸIð7ˆ•kâ�æ{ ÐÛÓÓg/˜“{{Ù°Ï*_}& ¬¿G°•ÙÙ”¬,ûúòíñ`>Ü« ÿ ¸xÓW§ÃùÉlt%tÅ./³›¯÷NfC ¬aþãbzõh¾¸yjÓÎcd׉$bvP6W† †ï¯¦sðx0ïÝ9‚´�� 'H]ô5Á¥æ¶‘ÎÈê ŽfÝõøm.¨‰§ðœÉéôzn„‘;Cd½Á›Åìö»C'«Ž§g£ üBÔÆ¹G'�ýĘØ`‡QŽþ¢ÿ¸¼‡)Ú'°¨yw-6ÈcÚl:ý 0ÊÑæË“ ¸š^-¯`–“å\W¬Kn®É$/¿ñø2îÁ7�•{áÉbÆ)MbϦcÝû`¼D|#äËŸ�Þ ' ˜» ], ÕÙ¨ÏEl©:!À0Ëí”Éïíîh³u‰9®™�C Ioýñàí‹Ñøt6œìm^ŠÍ¼e;oÁÁËé|ñÖàRÖûbm=Ý·_(À± hs9¸ê¸ÌX\œL'�bBô–ÅG€6:õEß«�jYûËÛÖF#9�i}�$¦‰;õi’”îX9ðÿ-lQïðW·-�ºön$ÜnVŽi:Ÿf3¤ëaÁ}XÁpVŽúºžÑ�Ï‹}õ™p5ñv6üK!ð3.f¯[Ü\�¡öçòú«èÌoë,*/´Á1F쀕ߜςY»>×£šª¶åw0+�îü2 Ôô½†‚Ucã åý割åE3WA-Å;.}¦³ÝC}ÓA¹mCóÓáð’;\N|ª9èã,Ið=w_[¸/¸çÃ[²™%ü¥QÁüašµÍɶ1;w0/½~àN.†'?v#y Šb$ä‘èr¶ÿ-ð„Æt�9t(Žp3T�‡—N ùàév´®j;áDðMXtöR”jåÜù†8ÉžÝ~¹#׃�ªûJÛ.U”v¸$ìz`†d %—˜ëp°s± �CÖG]NêªF{&¤/WW{�§Óá|ò`±»Ó`�k ,{(ÖXlªÅèrx6C)ßáƒÙ?]Žá©Ò®�UÎ$ÛŸ?8€°”`¾—h) ÃæOïË™ÅÌ‰Ä ¶¸†ãAº¥á×ø æ;œ,L7+jÑÁye^VÃdXƒéi°1‹ Ÿ{s™[ÑþÊQÈïÇ@Öfe†GÒ\¾ú¬ˆe÷:–?ƒ´åFTßÛÊ•õQøu3AªKG\gî.lˆˆ@›3«R73[ÊË–ÂøjŒ.«T�Üs×¹µSÖk5!x“Ùùp¿{mÓ¥•iö¬©TøÇ–áäÄbY÷1^â.Ä䄈‚ß�æ#C" ?žÎNq-¦ï,L\Çú“)ë³âšœH¢@…¯ Ó9V’˜È`Ì5Æ|!v0«€ÿgãÑ•áå`fʶPû•ø| %bˆµÂþQ ZÀ¨ÄŒøâ09œ¤¯´m\�°ò'˜Í(çNzÐ0¡`È2—Ï8 |_*tgJÍ¢CexøÓp·ÝtƒÓ?ssšpêÇíß‹»»¢¥¾ÌFÓ¥hȤŠ#@_„s6¿Ãˆ×—?ОaSt €Â4ì*}_~øB¸ôýrváGLUYV¦�³òR~',€“Š�Œ‡øzX3¦ë�NPP1Mxnv6”æñ;9UŽ3%ðØÕéÂéŒÍ2í†ggº\NW³á;ƒàr.m«€ç9Ó‡¢[‘#÷Âü¨¤Âl]©cy |Ô‹ÎnýbÀJIx2ùÿ2œaqà˜Aþ,„Aüë­þvJp¿€½„±va‡ $� l†ùo…Æ. ýeëçFVÙFáb8‰yòT64é¾ÁçºëIר€?Áû“výhrµ¬Þ‹Ö ”þ {#ÙÛiÃ…1Ë]X¾פš¼Ä«~ædÞH:£ò ã ¹À+q ðä¸C%¾C)MܸC¿&i7†VœXL±|Þ.¸Y˜�ŽõfÈPª™ ]ß…b†¢•YÑ]£Œ�úúŒ„ãÇd�ÆÓ®'S¸ýh‚ûH8%:I;Ï &鎻5»ó!Ö8zHÕ²bª}Ã_z[^B9D°uF0Ë·†™õ ¶’ é! #µÎhx~omâztº¸Hº¯BÛ#1ЛâÕÙU�~¼ËÄ+7ÏæÏîšîÈÓpˆ{âÐ×iDx_90<2£ÓÇýG:Š cMN·Œ»¢„tR[Àâ¤q+8lwWŸ›_Å y°Ý7I-¿•Bøíîô)%÷¸¥Ñ×F.¡­ˆÊ%©óò%$[ À¼(º«]Öž| îÄè°´%Il­r¨ÛKßT¤%P¥Öwl4ñËÒÂû͘û¾Â@çÊ�“�÷Œù›Í3zŽ£êì÷šé>ôy¯‰îG›Û¦Z£Ë&Bâc*-¢â• êAu¯ÞÀª} gVq‰?ÞòÎñt±˜^–×¾ØòÚxxVeñ—[^²ˆ^™êW[ÞbWo{ÓýÖ}kýw½å]MÚ_þ·¼³ö_þM¾|›Û_BeõNG/r,B‘$«?ˆ>mB}ñÚÕQ,±Õ±!1›�´½Ý�ä&(”¸X!WõéŠæÑã/É8á^•=ô¸§qÄ.•œ0¤ã'ÃýóýîAÞàL¥Ý�-D|êÞÙÞ¹LÕ�U£+˜êQâ‹üÍ€ßg×è‘>Ú¤ˆ�©µ~œœE¦ŽiШâ½évw†§JÕ@¶‡é¯?ÌÙÁpfŠ=ƒÄ˜‹Ir_?™ï.9’ûÉD¾_ïÅ‹-¬ˆ·úÉjh”‹wÄ|ÿNù-Óù[SþÚ¥l%ý>¹Õq…ðÁNó®�â pgL‰œ„{Mæ@%O…ê‹vã.àe„¿"Û»–Žcà9T¡¿ ç‹|*…;9;¡õÎ%%œX`UÀSÒºNÝ[Wˆu×­P¸Íµ®ÌUÚ&ÍϨ»P÷ÊMÃCØÉÅàÞÝÉã•…ïKÃy;h‰8õ©8W*wW%GøB2ÀÙHòâB€œþG¦ü?{ÝñhAÈ}|úõÞb¶”wÛÝ,[ò[Ò™„l)s(±…Д;ÌÕÔ>õŠPlå[õÛiŒS»CŸÎñ*‘.yfhÁ8)Ü#¿œøÛè°åÕk9„AQE…JÁ•=z ‚È[j.ðT8(öcʲø>p,#6ܸH4ï?Ð3© ëzv®”Àð™ïwòà ßä$rq’³GÀ$6Óhл;&(Éi <¸žÎ~T$}ð@á3› O†¸Æ”?È©$}zDñê*`‘À³‹V¸ÂGd,š­ÌÕC÷@§gÑàs!�E÷Ù\qæí°Åc¹qþœ 8ÍÉÁe»ø¥°„Ȉþ¶{üéîÎsœ˜»/>íÞàí_~Ú½¶”ŸÛ‰£Õ WÔL�m3ržN +�2© ù*PàuØDÍU g»î© ŒÓÁ——Tà'_ºÐΡ֌—NqxÍâ[¹°ãÆ¿hsÁ�4· d9áXÚç(ý ¥ >ò>P³ -¶Ð�×xøk&R“ë$›b 0ï²ä½m4ЀT¤¤åá©ÃnÈÞL0lëÀ¨n£f&¡N×=Íð’FpÆ6 ÁTÎ\ØVxò³P¦|£šÈQvˆS>8±�0¨{ze×Dè·ˆ°B9Ÿ�&i6(É"ìh'ºc|3Ú�mÀ_<ô´~º»·åÇÔÖzW²å˜wìîÎÓݽS—ØÆ�—J”¼^<�,õ»îçn¼ì�ý¥ñÒû�€—=°þûÆKEÑvù«íh‰Ëu8së0>¹~“—ôx“#æáý¤ê’œu�ä$OÄ—œ#¬è¡÷˜æ‘B„—…‰uÂVº¹Ê‘[‡¸hì¬OÜ~’ìnÓú.ü%…²ç9ÑþÀœkCëç‰èù6q*E‚ZN™ øU(b §ñ�ºÚ†6£•‘ÁÌýe$�¶ .(fEöz$ŽñUkùñj«U.ö›Ÿˆ.NH€.Ayqãõv™n‰.þúftq‹ªhˆ0>zíÞeâéV¤Á;³6T(£ß‹­ªi¹séÞƒñÉK�ճЄWS°�xÌ80WШ.aõôì �—§k”¸Ù‰Eg®u’Ö$&¬,ÛŒ2‰_…¹ªb2ý¢( 5žmxÞã_oÇb|x£¿L)yRßG]æ9æºÆÞþNÂC3ެ?ÂõBããA¸'Ð?†"ã8•&¿¼Eµ�Av˜fÔ¶£|Ä9îrãrîäEéßêÚ©|Ö38,¨�ñ3Üh伉ˆ—²52â4žAe®“ê� ®“Äf桲U•¾ZHôdàÑ#æJöech–óX9ÇpàÕw‘*bqªÈI›(§”kùä$…p•ç:2ªElîg"R•„`ñ¦/Lò»&"5}´¸$%“Tén$7C©PÙa×bWf 0y± j9£ÅË#Uœ .�åÃæ)ÔM{õݸ8«»v:'m‹²B'­´âwŠ«P\-Dp›/ÔÛÀÚ ÇŽ,¤îQòð¶ÍY“ /!D… –>†Ž„=R쨢k\ÑÅ‘]‚àçz ¦ˆŽ8#î‹�ôë šS¦üí†�7—Kè‘Gë#{áÚ"à¡SÛ½s¶:¶ÙÀ|«—,œf‘V½uq÷»Í&ôK¬I\A$¾™”×"×Êk­OÕ¨³y®¿K6´Ãb}ѧùãA¼›2µ€€„»¡RÍWCÚ­Dõ¨SLYÞvͰNÃZ¶2¸o¼I®˜Èâk° q½õaã -�@àb&tþ³+¾ïÌ¥–˜aN)’R>/+àƒI„À|léu!X•Ã�*{nìPÛì¿ÛÅ«šGOûty¸I뵊�-ã"÷@¯ÀË áp�øN‘7º›*Ñm‰’¯aùûÄóM™"[äk“¨ [ZÛº¼gòjL‰Ó! ò¥ˆf7{ÄàT*Ì\½‚Ü™ ê-ê)ÊÕrG�G8qGrïJÃ1‘Xƒ¦*�úô’1Y^³ºòˆDB¬¹NÛjÑþ-ƒ“Î�I¶ô5e4ÚFÅ~@š~�…Xî0Ëîp,G.Úyc¢z’@2’6Y˜é P» ï±eÚæÅm9�}ÌE"¯mÜ�Ãi~(ذ’ïl¦ø\³ DV[/ÝHœs/ó„Î÷¼›Ä ögJ›ÇÆÅ™«r‘¬„}dÉ;§Cè°-éS†d4¡L<ÓKå¼�¾WãÚòéL½‰H[S[ ×€«;GÁ4oòc«_-iû¦â*í:ü´"K�ˆi/Qb()Ö °÷Ƹ‘Ó锎ex)M÷µT"æHP: 1²mu«ó«,µî,Ùf8h™%Åk‚Ú‰µÉ?®i$^¬9l¦P› qO˜’³²$H€`okGCÜégX¥çˆ±9e/÷Þ&r�  9{µ�òeÖælV���&d™˜`˜4¡š¼”gZ]qwç•tèM[޽5º8ØlÅ€P³3§«ýr™Sä�ž7âIÌ7»4/�µéçiGÇ:âR£ÝJ78ç*0�#½Š*B¨G�Ú"cɶ‘¸VXdÂ)±eF©¿ûЙ,„§ ×gbÙNA †˜òá.Y�Ù‡O&n]m.w,¯N­âëô†PEÂBÕr„²\wÎWØIÄ9<@MŒjj šÌÍá×\<â8Ù¬¹ çÁ’s�¢Ÿ§¸V|†›o"q%£’AOß�ƒÐªö>ZÉ� 0W ¬|«ˆ©s³eWcB©MÞÑEúY¢úÌ ä"ÉÖ*Ϙ) ,铟 jõ‹ðâT¬}·ŸB8´úÂɘLI7ýt‹v�¢ŒêèvLµ[°ÄRw·rÍùg ;¾¦­Jš\ljk\�Ó´ÒžëùF7I¥žßsÓ kõ>›^Z¯å¹Ÿä¢o„ì·0î�Yê1¡ºèô+ˆ­/ˆßêêQ—ò:¹[2?‰'ˆ…Ñ^㎟ð¶ºâàM óre)C˜Hè6§ÛîŽy£T±-ë�Å G•]K“2yP>ÂrV3 +$G0IÿujîbôÛ^·²Š ÙmÊü¶âáîÛ æáÌ…Ã�•Z®ZH¹U�òÄZÐÃ.MëÈžyYLŽB‘ fB•ÀcõìîÐ? ú€IµÞ–Öxx‘ŒÃa;É®*j"LQïÅDqSð&p#²z�2˘W7©Òò7úm™ço£ñÛ¢,8önI˜¬’ÿ ð+ 4‘Ð(†>cAºÑ%íaÔdöèÖbíf²íºIÄÈßiR¢�s1e$ Ð©RÎÃàÃðÁ³¹ã²GM¨µ+tú3"ùbŒ–\B™�:; ÜŽÔ™¯«ƒU”eîM:/"FTDìZV�"Q\ÿÖ�Œäc‹ÒJG©Åô¾ÌéÖÆ!ÚmP�¹@ YVˆ“ñ�zOIJÿ;ØË|IsGp¥*¹ØN}¸Ìã ‰¸äYtÐÑÅ@¨‚`ò¦¹(F]Óʯ ãùÏ‚ë T'_¹¨SÈm·X¾±ÄT´Üa–⤠}Ñ Za‚`Í �Öæë€_²”‰ÁµœyØ(h(›R½EÐMN—hiØ%ÊjM˜;Ü!Àˆ¶0‡´oÝ0�ßã|•öš]­m¶{£c1¨ªû@´qæÿ]^áÑIˆ(Ì U(Uó¼çv¶)¢>°3 à¶mÃiÂ9)ÅK¸¤�#XLÁòI¯¹Zaæ™’¶¦©&O4}Q#Õ ¤á-"è£�ùùQò‚ ’¦‚�Cyª@”N.F¦»þ˜+70Úu­Õb¾:ÁÇóa¬•†­”$ù0;ËE4}>¡ºI2ʱ3ãAŽ*0Ë”Ñ? Ó¸Ul¦¡�¿RŒL>¼{uJÁzïˆmH(œÇ4öÌôÐÔÒ÷Xš Ù™/`“½[yÒbê]•I½T#%÷Ñ@|“s�ªª©�ÌhÆwƒ¾°¥èÕ—r9§‘ñá¢Ùq’îYÖÒÖ' &¡.¡5žˆB3Àè€Æe˜/F ¤5èéHu6 w4ÂUsVë¬ _ÌÐikáÈF‚³y"�M(ý„u½÷{óñ›ÎäÀ+0—q#‚uc<¯@¸*1O >r=‰iÖÂ-a ‚— 1m�éRÎùnàö201õœ^?‹•ª ú;«oÿS³žØÁ ákeu=Ä0½+:¹'V%lübö¯Eµvûûû>QДû;r–($ë1…ÍO“a‘Ã3Q †î6 ëUÚSÕ&m˜sÀ˜ÔÏ¿byØ>¾JÍÂEoxÛÓÝe~Œó?ïÖE1“Ëľ;#›<·`¼¾s ¦ç´Ý+ØC)á¿“"™ðÔJ™j!Λ6{wŸÑµ^¹?T™-…ïÎÍ’�t“÷œ6ΤӿAeÐ{éŸ\y',ý„B™`ÓÉ�ÖÑÓ:Ú7«ä»�•ÁQ„«s5ÆŽÇæø' `xËšŠ[ÓdˆmÑVk*>´˜NÇÇÓ÷’sLa&6&�9²¤ÓÎDlÂC}y�ìV­¦M¯AÛäY¤ëò§ÚÊLeüÂóZ—bº<÷N‰1Þ•Af¿®ñÎxä1,¤~¨6í– Ø“dbY¡ñtHª(;(ªJ3¯¥Á†æö¯ »¾O’Û(pª¨Ä8Èeƒa­°�ðfÔ¤.•¬c‹§¼0é=ð÷ Mã·EDoŽÏ Áã.ð’‘Ìd•C”&z嬱#ä+6XXàf¶�¦¢5_åì°!˜¯R��3ü�i|B×aS#L­ùÉŠŠt«ìšg®§0ÿ…‚G�RîE CHCû ûa]ûL ·»îÇ¡� Ÿ³êWÁÿ² {�"MÕ‚ùnvØ( ÝáÓu€¤¤_ìTqé$Ÿ„)øÛ»3v^_t‰‘.i+ý*Ëm_Â|-Ù´Û²v½^­¼¶-Q·×òW ‹_Y¯ßòçh iZSÙ_é‰\]ºÿæ?mÙ¤x¥'åãÒ©ó¶dc¿®;…x]º”‘)_Z‰¢ Er!ˆ'œþ `â@*bbØÏ+d$ÑÖåFäË´”ŽÕЗªJó-nÓ]èx8`3žp%ãJüÇ>6'ö§È[>-¹I¨»’¤é™oÍ×z`ö€Š×äzÍt†È ±Jü®˜møý ®Äë¤H¶ìõ¢ù{˜ÎÊMwÍ<ÀÌBE(¸0Ý”ü¢ß^Yˆï¹y¤MࡾÁŽsB Ä\‡xªv‚ŸS\ûÒ?E`W€ôD¶<,òšÓ{‰ìÜH#´ÉQÌ‚žªÜ-´Ë†\ë¹íÎ6ݺõŽÆXW¿¶L´æËÀaØ&¤««Þç;EÙºrñ– àì&d[8ýM“š†å�‚Åz Q ‚‡yVò}q»®.äJQÏR4lÙb(¶š‹ Å-M—Kíi-)RoSµŒêï�‘¥Ö^.Vƒ«ï¡i“Qé!B>ølà¦é\igøá‡ðO–ˆYÎÌg�qfä§Ëƒ¡Ú!É-®V$\}ár‘’ƒ¦ïŸÌåйÑ$Ü…¬2¹õuÊ"ó’°Ú·#Ž¢Ä78aõ%p¾î*©ôÆò6,ÂŽjhô¤o.IËôÜðG™nÔÀ™”9ƒsÔ�ÊÈyý´Œ%{±jIoÇäV*€)šÖãæÛb¶û0®€r}¥xs@ã¶þ±ßä í|Nèá‹+•ÉÔ,ˆ ‰Ë½¨öÏÅO:~¦ÇCû$l]ZöŒÀëkÆE/T^¡‰uÉ-Oý— áD}ù7KJSÿ`Œ+;Ä/¼9œqKËÊŠ~â_l‡xóö}AÞ²óúø~@ Üè\�JWñB./gŒ; Ž{Σ™7\ e�0ãè½An °he<Ÿ`a{N'Z'Õ£î ¤ÿ®�$:h#§d8’¢E\µäno¼xë8ÛêVXÞC¬wšLò!‰ŒîÙGIêªs›&)e%f‘ä—ÛïÒ9jHa1»{QÐÚ¨µí½ñW¼T�óI¤©_´ÈìÀÊr´AU‘š2–:,Ÿxä!Œðhkðâ?Šû˜J ä=û˜1k É^ëVµýJP 3àå�_ïu%k£0}^¼a DÅíë|âüˆkŽ�*î®xÝÛȹ¸O^œ^p÷ì“$>ÙO=Ša pçqyÇõñT9q@=}Jæú0ÒyxºjÔs;¤o|½wKϱþoÓ×kË�¥wIÄ›ëŒë·÷ˆPÞˆ%§<­-B O“ž Dé§T¾³–& sôk#Ì*´;ŸÉqøá¬ÚaÐ÷.üdú«Á–�½vÍ(´.¥ükÃ@f�Ù¹Keÿ,•a€{€; `”ò›ÝÒœ$8ȵÿ¦ú¦o†Ñë_Y¹u`¤w«êd,ÛÔj%³#ûѵhíâ5@­}Còïx0ß�Ä›|9 çùhfÚâS >¥�ŠÙ2®@e}�Ïa_­%¬ÔÁ> i•wƒ›¼Ê¾à:*²'àÖ ½Te‹¯sB wÃ�¸‘ 'GHÊlÄÇ„»aw ë=wÿ�©I±}«Í €Cùˉq8ËšbNÀ;ç"¶‡}!ÏÌCÿ`)É/ê0Õ&$—E"U°¸"{tsx`¿hÓàª%ÕZòŠtC±a>¤ p�gÈË“¤^2”šOWsW·i[—«s�š|/¥ZMWõÚ ×´ä �³ŒŒçÐQ7ã-«7¬€H€Sùia 7a)óIA’©!ápòꀃÉþù‘L§—µ]¦�Ù«å8ìT%WÓ¨ý^W0…È䉴šòý¶À†�XÝh>›.¦|87u¿bÌ_´”R\ 0£WRJÜJÃè›_?ˆÑ·ãÚ´Þ†lÕÿì>ŒždNHÔHª;2a³Rüw>ÿ‹Ï;‚ýR|>™V¢Ü/Æè[:ùY}3á/Êè›uÄQaô!+ŸwgÓßù|ÿ¶5ü_£[2o|>³rîÐè‹Xuuñùoc¸iO!‡%Ö-ìhMÓ'2u%…uõ’äÔ‘ÚSy|¶^ð‚`©VC<Ý‘CSõzs ï«#{“{f_ž¢¤Ó„ã*%8M�Âð,2FC„?+yd¬À©¢ª-}_ÜÞCG2�‰P‚¬e†ŸX‘Ö3¨RRŠuâ Ç²J3MO µÒõC›j-¸«M–qžÓæâìéú¿Ö+Z²ûD•H½À¯ås­d2µç┡È*xhÊ’eñ{Þ† ÅŽÝyëmjO]#ñÛð §LŒd6b~ Ä ÞZøXùÛý´¥ ;½Âx’;u‘�T]�ü€m J¹Ù¡w| áš®`gIEIðºáñZôÈÈŽzö”+™÷êœÍª¦|³¡enÈSùrNÏ.Á©ˆÿŽ,i…ÛíÛ¤òåpÛ ¦$©”à`>¥u¼üþÅ õ*[09¥*L¨4\˜_"±§¿’Q*‹XÒ/Qf/fh0^íP‘º‡|úhæj\Ú!±‘ènÀi:-*¦²‡:† :P£!éı•$~!¹GFɱ�„‚³±ïÁ5eyü¨uëÍ’ÎH×øüjU3']Ò™¾-ÂiuȨ�÷Т•Š"6k2Ê:Qn™Ý»,´7œ�Rè9ÿ ÑGk�9 qà£ýÑ•M×êêõ*%²æ‘ˆËN¯8B× j*°:¯˜¶) G™ôf¡D¦ÐÑ$m°Ïúíå"tŸ£� º´#Eì³M�3úbM¯–W¨[ò¾†_�/É�/ûœ{OVÙÝ)Ý(‡î?ZÉUˆEoOº©Zð°í½:)KÈwÌ¿¡è½4¾ þ;¯÷2Ôý†K¾Çz7rÛZ—QWùP¾»œÔ±?‘óÖO‘†›ú:[],8n�TáÓB¹Ûí>™¸Ö ¾º™ÛCˆ§¡fzF—<¾-S(_!°½LUW�ÿvwç?ûkõ X“”9aÉ�JsLRnØóæjÍs¨[qY¤Ýðɵ7ƒ‘ƒîÁÙpQ2qÚmË"«�Ãv:ês`íµUƒ‘´½°B˅ؤ¯i¦žW$]­Q¹…N¶X£÷Þ®©®Çvä�)ÔÛ�ã­Nö;¨LÊM¶µ¹ËªZ×mÚ‘ø`ÃQÓÓȸ|±#9†ålÔ$9‚ðyõB0æ=픞É%É y£&[ä\È:Ûj4w=ƒI �bçñ¦§ê˜|ãå\ÜMhöcyŠãéÔˆÂÄ3 õí!q=<f<4ëÂd©¼F¨Ó'—¾ñ‘F ë¡ø1Îv6S]¶òNF#ª1AD#nÉv6LpgŸ7á9VÀÏpoH_#öëœÌ ûƒñâÑÑàøa÷ì=âZ¡K(Ýù~C#Ý™|W÷ÎýÂ÷èfv1îà¶·7„Óu­Øµ~×)8³ME¼”çœÙly9v<^i>Ÿæ~�`†eÆ`ª’¦(f(©�KÓ£ÐËÚ@vÝ n§ô±ÙA«ÑOèT𞨢y×Äiü¦¸Q:�.¢g{AµÜ{ó3›‘b*y‹è»Þ­êB´éoõbíánò({ߨWÈ^¤Rd�°¦í Ú¸úî2�_•Á±�bð‹8¹Ggãôwª•x¦�›ÎÈâèþš <Ü}Â)©X\(¨úöÈ.Šm3ï|ÖŠ&Y0k”0pÝ\°ÕSj‚•Õñ9ï)k \Ÿ9¬¸§Ò»·ª”Êl›A~L¡¢1rFõJFމ#5Úg´õsb詜jW' ¯ç¹Ô¶¸ö¦{*` —µ5Ûºëi-¾öçìáìVRï%Ðÿ´ko*{&㫯ßMÍ®·£ew“oQUë�|ÅSkÕ�ÀÐ š9Ñ8ɦ�¤b>¯¸¹ÜÅ)ÿ/‡¼=BDŒ'8eL'Ä Œ-SªWLPÕö›¦V›ìœGÞ+o _l[YwÏ»ö±ÖaA¦ß™‹½ðà˜©¥ ðÜõÎÁÿ¬©ê*=¹¢ˆQ$Bö¥eu°l.é'aI…Š�.æ$g,)Ì[­2gµFÞ;Jô«®:å ü=}òMKÔÏΊªo•*qï ÊgFIj¾chðUù3¾Ç(´ö¯ Ü÷3olw¶ mŸµ+¶¡y‘Þ•IýæµÜ6ÊW[tÝv }Ù‰ûêH’ªÈ!F3¤ R„½´tѰp lA³ÁäîVcºzÑ›W—Jò*p+f™£ Óh…âçdÈ×¥@”›ŽM”�sEex (|Wi68®ÒÌ`±¯ß¬jħªÂÌÿ&ŽäÄà�83.Gú†òdì×·ö½Y'«t<m!Ïœ–/> KÌPµÖÍöWU”\-kñ`¹òýºZÜ×ì1´x‚ªcg†S£/¥7¡LßoCzáp0u*Žf¸ìçåFbBÛ—=)מüÛ¨-Û ¬Ïa `¤çÆdÉI\ÈA&’¢ÀÖ˜„lð³êf?^×(¸Å©b Ï#Pû9»¹Î¥�Ÿ(á Vã:K#½”�`k»‰õR‡Æj±w°µùÌ�ü7ånõnjeeGGì9º¡h-o«è[Ê£èi[‘™¤]ìAú åžïÇDA/uOúü¼“$¸Uø7ƒÕnîövvÖ§º€F Gd”&³&õ ,óÒ4Èš,Ò¡¿q´ÑXJQ"œ™y¼"Ýù¢}˜Ò•¿7·fÚ“1ÞÂr¥«w»Ø¡W†D7šªÍn‚®Å/öÝ-$«¶=ÉѲ(ÆÖ–¸ì`@î­Ðûv€´T¼ÚôÁ”^š#åцs([ùrÈ*ðÔÌVhœ3„êÕY« ,îÏvP&„SZYÕ¡=h:³Ë�DãDS¡Vìžm-¬<'{€ëÿCZX™#À{ìЂôò¸Ä(²¦¿áå9X ÷ðÀ÷Ý“evrû´¼ÜäÙÎËÄb¼[ZË “U;åÞ�c¯¢o„†ÊªPb*;uhc±‡ÍöûH® LD:›Ió#ùfå13^ç¶JÐu°îè¥�ó¿kÕa -  Ó]V¾%>)tÀ�&�Ôµ‹»+ Ñ…*f®øúéD3O¿sá˜\K:‰}Ö!\ulÒSX,û‡͇OÊëÜ‘¹©‰…–j¨©†ÓÊL0$”%¨�ù8hVo‘;W«mK¥RrL‹ô�ÄÈJ ¤ÈY Ï?(jô`zn,%¢rÐ0­0Þ*®ë÷Œ| j¹¹PqÀ®×Šü½xr­ Nt«.ð·ÆTûĉy …·`­p¶7¨ä»4.;iXB^‹˜@C UzE€ÃöQ˜§÷i²[wô«¢ÐÄ {âazØQ]X£•ʧb€¨eõv=\j–tœÈ¿N™ìIØ¢j+GÒ ¬,‘H–JxÕÚå|œY§#ü‰&87I&I`Ïv¯P±&ÍîÓü@„OW9lÈ̆d×U(Eã gz ˜Œ”uñ0{ÉÍ^£,3ÑCj$Éw>\3&‡.ÜÃ2îI£Ùc¡áì‘ßó;„ðÿíñc]ÊXM­écVƒ'`Å(Dmûè42ãi§S‡¼)Ÿ0AéÞu‹žÐw"ݯ» éyUË£ädglF“M–Ú©RÅ[Y†&ãTé>¢ÃàZÅÖ WFñ"çÃæêÛòžqÇ„¸³¢Öø¼ÅQÞ{IJu�à ¤hm ‘YlÈ>Šb¡Žð¥þV/yû!rB1TZ“­ùèHß@7WܺQ6BØæ6pÒoò ü8ŠÄ¹me :½†Gï:÷ó÷>³¥>½²b}‘>A³±¶¹Jà@:ÂÖ¾!Ô\Ì ·ÁVŒ‘ø±·{m$ûø×´‘l7×_&Ð nÄË^SÉþ"MSÉŸ¸ÈšÏõ2šb®ì‘=S@²q§î3O ‚‘„ºm–x·|{Èp:­‚–p Ìétå8Þ$"®,É#4§›0­ûŠçé¹r}>”˜²!|Æm,£çFMï�›mFµU@‡Mf š öw ß+UÁîíyG¿ú,‹¥~¿ûÿ̯˳wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/c3/0000755000175100017510000000000015222270712023661 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/c3/a0ef12330ea71c38a156c86f89abc60017e905wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/c3/a0ef12330ea71c38a156c86f89abc60017e900000444000175100017510000000150015222270712030625 0ustar runnerrunnerx}TÛNA íó~…�xa“RªªZ ‰«j�ªÀSU¡Ù]ïfÈìÌt. åßë™Ù…„V}›‹íc»ª„½OŸ?¾“¬ÃvNQ µ‚ZUv'Ë”,2€-øî¥%A{;C Ž™—-¸B� óÂAi˜¬fd¬‚ôOh ø1ê˜uhF?³ñHµ´°Rœã%…â–ÊÌú�ŽIÏ„XAcTaŽ*Ç¥áXIÃûš[Í\E€ÙÜ ³ ÑtÜÚh¬šè{~q;½;¾¿½þrvð(2aÔ±Ö¥ oçÜM} ßX‹6[‹*©”tdFud5=è`UÀÒp‡tåuîÔåðB©Ä‰3*AI *oLÀz…Ý;çZ"‰ ¿`åˆ^g”Õt$�GnP+Ë�2«½ñûh¼`‚Uœi�IšJ$ø!µË·žPÂ� �üYhº=ˆí£‡°œñHç ½—&6ü¶§×—gt§F÷¬CI.ä'©ý$ÒÈÒ†ï¥Z 4œ2 oÒ ¦j6hÿÝè {bgÌà¤/y2s�ˆÁVhrÚZÔˆñäžh‘`õðôÖ4?'ê¾F×›à:ñ^Œ¹¬ñ1>md}G›“ÕÀŒã ‰±OqSœ>Ú$…åƒåáb/-Å´|ãBJ’VE” n” ¿éúih¹›ù2Å@�(`ûé) ÏøuãŒij´wv4~ï�€ççè󟔈5ÍWG´]›È×™L¹ÿµHÓ¤… Y¬m–>Í·#˜òæïCör÷,wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/12/0000755000175100017510000000000015222270712023576 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/12/dab08cd5eb2d0442f97bd5a710aa3a2bf29329wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/12/dab08cd5eb2d0442f97bd5a710aa3a2bf29320000444000175100017510000000340215222270712031031 0ustar runnerrunnerxuW]o7ì³~Åy¨ X Ú>ôh×VS£qÄ ’>ù¨;Jb}G^I^TýûÎ,O²,»@àXò‘»;;3»·lÃR¾ûþ‡¿z%oÈ[³³Qî6¶m%Û”Ód±qIm�CÜ >lBg%É+ëü0myfšg$ .ÛÙdòê•,‚ÄÁ�WUÞù¿�L¯d9¸¶Ño+9Ûnl´RéwðâÞä ƒìÂÇÇIœëÝz¹i1âíVœÏvMv¡„›|3“«ÐïÄx±ÿº”�_?{HV®µ“ogrÓõ­í¬Ï% K“pu²ÑÙ$a%Á[ Qº€dkÓ¶KS?¤Éw3¹D.'ñæ—M*Þôú(·©¢úº³)øY)Y�º«£ë÷Tªzck êjë“M³~WIg�D‘5kôÁ|±²LLVÜrèy‡F™ú�§Ö›ek½Øßî´%nUº"—6.éS@«\}kœG#J“€€®où¹\ÇPã ¹ÓÓê4þ ðõÖ>Ãý±L”S ™"\5-Ȱè•qmäiüNsëÖn"TLü×Y–Öz,HD¯�36Þ¤�tèðÓ¿àÿsÅûæ„.irÐ2ôYÙ2¸àL ê­‘ƒ&]²²Š¡Ó⢩ɼ„Lò–Éð–Ö-£‰» =L`ùå'³kù{º>$Q]H 2x‡RqlkêMÌ®ZÑ1´`5´à©OÙšæ4,ÙR‡cÏpN£RN«Ó÷“¹©7ÏNP`}i’úg°%ý¾/�#“tŠ40ú…Ô­rZ„´¡6-¡NI’*ò»¼¡,�EGÙ¢uC›¤8Ö´¶¸ªZ˜™†ç÷LËÙŠÒ¬Äþ‹™,Í]¡ÑYªõøB^=j‰í€&Q´Df¯qíðÂ^ðHFrä^êmíV®>t´KfM�nÂ{[Ză£4Cd‘HЬ2”D.Ü4ȳd-u—U K² ±C”¥mÃö|&Ú¬BlVÉCG�:BW‘CÛO[ž4äÄpÅJÁ+ ûd¨öPÇ#3å ýIpŸ‹)dbÒXèb�ª�Ù§ èÎK·.£pøf\´ÒtÁR£§8UŸŽ"A(À‰cfë|oáiäR]σuÓ_*¨/ç‘Æð ØyóŰƒD‘HÌä½IIªéÔ 9T$“Ñ*R¨ÇÙu–iò‚-*Ói‚Z‹áEÀ¦I«x%óç}y^ÛAÁ} 9Ô¡}ÚË%L0f5ÿü~~µ¸¿�ßÝ]¾™ÓÜëÆQ (q»Á,�¦ÞÔøiá ª¨cçð€A!Ûè2L‹õ8‡Õ tmP*¤€¸+Á;޹nÀ4uÌæ'Ìaí5å_ÊwC(RÅ(â4utÿ7Òšö@Ýâ³ 2tbJ⢴cZ�Yq`Î;r€²¬‰ôˆ¨¿¥ñ¤VRò×‘ŠšJ($tЛރ†©�éé:D.,åÊ™Üî•É6ëƒ8nÖœq…©%p;cSì{BKõéò¯·—ï®ï¯ç¿~|óó7ð­9†-.i(£iCG{Ù4r“?ª^štÀc~ÖaÀ쉠ìÕïó«?¤Ðárqóç»;¹úóöýÛùb~�~�P#ð¼ <~üøÙùÈžó l �ä1 _¨æ�‚‘ö‹ C:nѸ;èÔì¬ò}Ç¿}ȧÀMZÁ{|¢­Q(‹Ñ™¼�“——=M‘ØVøñòæ@yTocWàr™! ùds"1�u�ö',WÑB4Æl1^9}�wW„}�»^M(a}ÂŽW}¾~sÿáã»ÅÍíüþúæÃ^ /l±i{GW9)AÌ#úV±;zo\aM„¾Õb4ùÄâ9Rºë¹I =ÕꇃËjüˆ>ÐɉïÚ�WsOöX½q]Ƴg¿â^ÝØþŸŸÜ&6tЙd0±X︤À¹’0۹璉5†q˜T™çÊõ’1xª¥¡®!*ì$íÇޛȵàñÀ×Û{+ÜKùÈ#¸çîCáv4U/ÏŸK²¶à“ÚÞ“t‹7ßÓËxgÐù C>xÛ(Ž{ ©.tjZèÇÑK;Ù[ë᥀[VØÂ±Ž;x<'�W>¯½b‡TŠ6Á&.uX ¶sÜ{‚Ÿ‚»\Áyì:°d£ë¶ó0$Uub;eGA˜žƒ÷1šOê±ÝøZŽe1£ ºð73¸�÷è ÜcËùÌc™˜ôÇ;ð¾ãÜwBÒ`I£Ñá ï*q,¹7%Á}Ó[{“õ¥…˯¾ÃláKÈHY˜Gû× ¸›Œ¥Ç_Zì÷Ðp’ñ¥óÀfˆ>à’è°òS2R�ñ¾TW³É‘£M¼wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/0b/0000755000175100017510000000000015222270712023655 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/0b/9f66de48e88ddbda2137287d7072db5a392934wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/0b/9f66de48e88ddbda2137287d7072db5a392930000444000175100017510000000023015222270712030664 0ustar runnerrunnerx+)JMU043`040031QÈM-ÎÏÓK*ÍÌIa‹ÙÀ9õˆõ½ÍJÆç Ïðõ‡)ËOÎÖ-N-*K-ÒKfXý†ç@Ö’ ö‚£wñwœzÿú�kXf0<�ñ[fošÕsë´éôÿïV¶0qóì†*ÌU”™’Z 4OªÚÏ×Q1B~eçκ¬öèÿŸé]þE9wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/4b/0000755000175100017510000000000015222270712023661 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/4b/cb95c16e4c6b26dc859026e75eb093694147cdwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/4b/cb95c16e4c6b26dc859026e75eb093694147c0000444000175100017510000001513715222270712030614 0ustar runnerrunnerxí]ínܸ’Ýß~ ",l¯“¸»=Ù›dg°¾¶“q>`;ä— ·ä¶à¶ÔP«íë;˜Úר'Û"%µH±D‘%Ú;³ëAÜyªÄ:uŠ’Ø­‹yvÁ^ý<ùyò//·Ùþ·ó/ï�>�îŸî°Ã/ìó—svtx|Î�O�ÎO~l°mvÇlV\?_äÉíËÓ£ýÃOG/n"v™åì&Ëc–¤ðçMX$Y Ýùˆó«dÉ.“9[²E˜,»óð>Ο/¯âù¼êz�-îódvU°Íƒ-6ÞÝ}ÍÂ|ž-Ùû0OÃéUÆþc*fÿ9K³›øE–Ï~åFš‘ÿý_0p¼×8¹œæÉ¢X¾œfQübq/{µÈ³YÞpÇ.s8±evYÜ…yü–Ýg+6 S–ÇQ²,òäbU€ÿ Óèe–s‹7Y”\Þó¶UÅ9+®bVÄùÍ’Ÿÿðþó7v/—pì}œÆy8g_WódÊGŸ$Ó8]Æ,„)á�0 »¸ßqWÎ*WØ» ðÅ|¾eqÀÂúmœ/aŠÙ¤¶Vî0ÄfXðÈY¶àqدïÙ<ïX5î÷�ÿÁ‘¦¡9ÛB)ܹÊpfW€ çz—Ìçì"f«e|¹šïp˜öýøü×oçlÿóö}ÿôtÿóù�·Ð¹¸Êàh|—PÉÍbžÀ‰ÂçaZ܃óáÓÑéÁ²ÿ÷ã“ãóüÞŸ>:;cワ²}öuÿôüøàÛÉþ)ûúíôë—³£L0±šg‚Nõzž×üŒâ"LæËõü€@/ÁËyÄ®ÂÛ>�“[ð1dS`c=½�Á¬-pÂy–ÎÄYÃ|q²—ôzË’K–fŻˠQ‘Ã,&õ8�¾Øa?�Ø;˜¨ë9„â¬Zƻ䲸bïæY–ï°¿gË"Kwا}ÆvÇ£ÑîóÑdw´Ã¾�íó3|¹±ñSr ô¼dï?ß÷œì> ÎŽöσ¯§Ç¿6~‚ƒI lí8ét¾ŠbölšÝÜdé‹«gÅý"æ¨�«iÁ‚÷Ñõ÷ð~ÙqQÔ�o76^¾d¿UŒ=>d»âs8OJÝxþ‘ÝN^ŒÇ/vÙó꯿màÐÁí$�ƒÝ�ß77#Ì�¨Äi$é²ÓiüÍVIZLÆ,‰ø§ ën,¹{ÛÕê¡EC EøÑêoµÃu|‘…y$zÔÔ.E¶š^‰ãâ/éà?ï5j0‹—Å*�ƒå]²ˆƒÛ è8ذ€Ø\uˆƒ-€"¼˜Ç…˜…àv,ìJM¼3Ìéa²\€6³mþGÝß‚h} ÓpÆg)ƒ›ò³Ò‡mß„K˜Êú„ñƒò r«�mKS/µÞ]Å1DošgsH†Y  >užfi‘¤«lµììÒገ"œ#?›–Íu€Á˃U¾ÑÚžŠÿ«Žãû›pÁ¶þ�ÂåÕ9ÛÃã¥h>�-"6zKPdÙi^„‘hÕåkÉäðë|aÕ´B’\fܨݨòüë1p¦ŸxÕKâü€Á™¢ BCé È@’FÙ�8k‘,Áe6]Õ÷‹ì†-â4‚x çñ”×§j¾ê‘P‘/‚;Ã�”‰L´ÉM™ß0)1h.Ú•Ca òxé­ RŽ9Ô@Ãá(®ØÞèÅ4œÏ/Âé5Û.Áƒº�ÃÔ"Sâ¾ –´Úaš�ÞS¨ëÂ×ÈΫ=(<Üù ŠÃ.Ü|@ÜÙRšÎeÕ¤œX('»žžõ¤­�ðXÁd$áœ�¬Nr,änœ¬Â.ðD‡L¬ŽÀê#¬¥± ’ÒеÏÝ<œ@êÅÿvGy¶x"Â'ŽÇë€CmŽ“YDi$Å]L"«¥2 ÊLšE¬™`Zë(¥œ°&[ê|üC+A#^‚“ËKXøqNæpΠõúè ?üo B\–‘¦Ló2†”­×ë²5úÙ\·^?Õ-^øžêV™aOuKY;>Õ­µnÿ¥ë–*š õOu¬.U¾ëØØ¢Ž=·­c£WU!Û{1ž˜ ÙèÕS%{ªdÍUÛS%{ªdOW`y<]�Õ7Cz®À&–•‹~©Z]ÂYÈWÂÊ•¬ "îâu\0‹ûpr Žu\=B©Ý[Wݽ]¶¹Js¸—q´e*Á{Áxï©?•à§,n UI.=ˆxº˜ü¿q1ù¿|ñØYÖ7QeÉoßDí­ k”¿àíÔwðÈC<2KáÑ6<fY:¿ß€6‹®ƒ»òy ÜW†»Ú|·<ö*‚êùs�D›[ìwq'žmO9N}ïޱ_Øs(˜ Qóg§›Í(ö ?T…»³åýëÛ ä�Ù‹€®ûÞòµ›[@_^d€Ì;qWn’4ËëîàÍ6íîîÂ]ߦ�4ŸGšÏcÍç±æóXóy¬ù<Ö|k>�5ŸÇšÏcÍç±æó¤åóDc ´´ÎZZg-­³ØÓp ¥…--hÑqZ³}Z³-­Ù€–Öl@Kk6 ¥5{»mÖA‹æs›uÐGó¹Í:è£ùÜfôÑ|n³no·Í1hÑš‡mŽAmVÛÛÛ�h>O4&L4Ÿ'&šÏ� Íç‰Æ„ÉÚgÆ.ò8¼^K+c°«$\Í‹Z $�‡MC©¢ð7á}p«t¹Z,²¼ˆ×rÿGY:ÊâQêþ¯¿°1ÌÖ^£×u¡åfRÖ‰?X<ç5§6Þ½ê66Û}Ý1LÔ6Æ*k¿WåBqh·B†cÕÙä1lI¥*ùvã±³†?Jm6ß¼yÓÚ³±Ä>™mCan E¾©C�„åÍeyJu ´«ôõ4ˆ¹ûžu‹SáJǶþusS_Âócõ†í-aüù¯-GËiÀ# ðk à1xôÊyâŽÌ/üÍÈu ±Yçy–o>;NÅj¨æ;>|¶õ‡�6åé�2%ÑÆF½ŸÁÄŸ$zʬ}q L¹ ¬Z�I,±ÇjX¢`Iİǒˆ¡€I\°S¸Pƒy µSn£¾U}-èÎüªÏÃˇ­dTáRai¯ƒHÑ7ƒH×Q¤°›P”P+(ôxßfPûð8Â6£ ²‚Çq§ÞCTõFTù(ÖÕ ¶þKmým³¢h¾N 2\%VÀ:D½N· âoÄÃß�GgÜå†Z¬¬"`ª=ƒròWM}ù_uéムõ ”2°Mx](›³¬Õ¹I8D!Ð�ˆZ ÑIÐ/ưʊPu,E¡úà#ìÆLÁã]æJ=ECÕ¡>1!� ®a‡êª½¨tªàzQo6•£nëSŒºßŸJ2j§¤\—–�fíQŠ D ‰¨*� ý²Q[Âc+ëFݳŽúÓc+Gm·”Žú“¾.ôJ‚>XWõ¨ìÃuÕ \:kpß7�åC4ôi‡è„“Ëuk áÑ`Õh£%£ CÔ †ö~±fð`ÊJ!º•2!þôèždVV“¥}± V‡~@W]èGtU#"�˜t|!n­ æïÓu\ÕòÇ~]_µÃ)çƒVÎúÑqîõ¤•¤/ý°$½1ÂÒ©Ö§?fqr¬õ¨‡Œ]”óA.{Íê85¾jè84PÓ:P� ºiž‹nš8Ð"�Èvš)¾heÔÌæ«Xnš)ÆáiáƒÖ4Sxè]3Û¨ž4³ ëI3%X:ÕÜ5S˜ÅÉaÔL1Nì™Â4IõA®!š)œÀ%Lò®™î‡j¦»Å¡šéd‘Nd\3¥@P”Rj¿•`ÖG©7N|Ä%¨¢ä×`-ıˆ ˆƒuO£“¤_í$cx¨�“z—4‚Õ–Ôæƒ.z&™.ULj¬]–¸«N¹¡»j’5:�ZˆþT?±"öè¬wÅ 4Uœw>¸ä*1ÒoÄ4Ï[í@š›£:ETtŠš((ôX÷ÈHe�ãŽò+<¬êë#ºÖJQÙ„,®çcГ3 8'5°ÀsÊ3�XÆ+¿›T&¾hZÿ²’)ý•Á8{|ðÄ.�›¢ú�A å¥E IºÀHÒ€€Ñ¹Ñ§Š1<Ô¥PˆŽ {ÔXø`ƒ½j(>sñP†­/T, p7EQ<µ@wÓt:£µQˆMRã35uÎ=Är•Õ/IOå;±(2Ó F‘ ŒNŠ™Q�á¡–dh£ŽðAk}QMC’ª ÃôEŲwÒgt'}qA§S©[_ê­ŽSÃiçƒZD…©ó"1Ø�AЈŒŠF§†�ÊÔÖðx£2SñAW�©m7ZP·xQšÌž¢5.øµ±Ä§“ Ñ›jO£­ÎTÝq¾ù ”«¾T Ó„¢': EGz¨{ô£²‚ÇQÕ�ª«�àZëEe¹žŽAwK,àœôÀÏ)ÿÍxt ùÞúÝgÛ¼o Ãyãƒ"®ùßrl˜tƒQô �¢ (�=úв†Ç[Õ‰Öd°Ö‹–mÐ�V˰õE ÌÞIOøNúâ†O'¢7íß1´œö8œ�>Hæª8mφIŽ�¢98Šèàpt‚ô¨NÛtUvÚc|PÂZwÚÆAÚMÔ§�feÀI{HœÔÇÕ�^ˆþ`¯ª°Õ l,NI¤sÕ!Ì»aZÔƒHÑ£HŠ&uCÒ‰Ó£K˜Iœª6aã|PÅZŸ0@B°æa:…!ZrÒ«A–œt‹j‰NCD¿ê{B¶šU÷Çééƒ|®:U{4L›Š!0 RaèïÑ�Ú LUkê¾>Bl­/µQHõúÏa:R£�ô ÑIúét@ò_|C¬za–­Ècpêø ‰«È^ Ó‚$Št@Q4A‡¢¡GdSx€Um�ûû½µ>Ȇ!¥å�ÃtBFêvÒ 'd'ݰE¦Ó¦K?œ„'”Ú�ÃTHd��0ÈâPbÐÃk£ xô9ðP7¨óÔCæwC¹çz7–{v£Xô€#ù\¾�O$tý^ÃöÖ²7Î pÍèÒ)—ûÌ4 JNk ”¤–AèAîÉêÒB‘ÖÕ»RYÙÓGX­óº4 ¬/ÿ–Ù½`N¹Ý‹æ”Ý&4zè‘ü†‹ x¹­Èï�Õûn ù]öÆÉხù]ú3,¿5 J~k ”ü–AèAîÉïÒB‘ßk”=}„Õ:¿K“�ßåÃò»Ì)¿{ÑœòÛ„F=’ßâ" ^B‚Kï®6dxÕç‡.¸¦xåа×A(I®£P²\A¡Çº'Í++x!Ïe6T}}D×:Ó+›õ^¤>hÇ•œS¶[à9å»�Î,ãË×ÐCÆ—¯¥7%{Ù'‰:8'{éÐÀd×@HÉ®¡�’]F¡‡¹/ÙK+x!Ù+"”ß�^ú¬}ž—®ñ<¯¦bXž÷ùåy?ž[žñèèÌó È²9/ï–É^öǙ⃴Œ/½ò‘ö=÷5(ºÈPtX©@i p[ ʾ>Âáµ(TÓãA,� a‰LP‹~d:eºucF.²Á»ã¤òA¢jp§¼ˆF h€f´�HFƒD¿�bpKxl5Áà]}ÜU/¸ÝF.ÄÔøP XŠVØàR”¢—ND'ª/‰Iz™‰ûƒÕÛ%¾fâ�‘‡a¿ƒ.�œW 5oÀP|txÑ�ì3.,TÍoåtØ�4‡jCzoI‡IŽhF”—™èF¼P\3“é׌þ«�¯ñø¡¨‡ZðK<Ô„_ÚµMx%ܘ n²(¹L✯�¢ëOÕ§óûEl�§E’¥¢˜íÙ�Q`ía�VÈJ× ˜æ*.;�!–ÐMñ2AK*d-ñÍ„-‰’-¶Â¶l¯ÄšåáEp'4§¼,¬åg]wå{Œ�uðæßEžÜü•Ò\Ç|h•kÉ’œÂ‰c* ã ! $ÑÄ $QG’HaBRˆ !Ñ)ÐS¦$KxlÕ%u÷t£Ëï”ì‚KŸ†•& ¨Ö©(9à:•#;\:Y�B$LÉM¼±±‚;G“qPI¯{ãdòÁ’Pðð -²H´pÈÑàÐcn#ÜÑÖ°‚WÞÓG”Ý”�[­XÌÉ »Ëk›!Ý¡ßMw50bÒY�(ÁUxÃÊ ¿�ó �°D€WÓ_Àóë8L ’ Ùäƒ5®Ú û6L$Œxµ0Rd£ �ΔýÐ âáßak.éC|pÃZQtó zã°•‡ŽgiÄIuÈVœtˆb…Î7D™Q ò°°[¬èÃpjú`Ÿ«2é¾ S&#E™Œ€eê¤3¥G™tƒxø¥%Ž>Ä7¬•I7¢¡7S&ÏÒˆ“2‘­8)Å �o½ÊÅóðÞê:Ju\ŒÃÉéƒôI8çSœÚ€ÃÕ©�8\ž$D:_œôIXÄ9Ð)PbŒ†JØ×Ô£œ»AWhHzØš RN§3@§líЙ‡(UyELÃùü"œ^¯_Åw7_·Ÿ<´ÆãLõÁEWµj96L©ºÁ(*Õ�FQ(�ΑujYÃã½~ŸÊ¢ÖX¬°V¨–m��V˰ÕS ÌÞI‘øNJä†OgW·ñZ9ÜY*ï+ÚhŽñ8:×^9<謦Tx‰«�=jóتU’!{TéV¬¤G¶°Ê-ö!¨; œˆýÿô´½“FÅôDÔaÖ A—i¶Jù=l{¥#þô”^z'MÕmÚ°žˆ#Á>u¢8ŒæI ÷gðPôÕžM™ªÇ<*¿Ü UígƒlqµR…àʲÅÕ‹,³È+W*°W�°•{—qQÀF/¾…ô¬úÓH&m ¾òö±¦v½ÒÒ\ÃdÚ#oÌ1ÁIÄ1ÃIl1áI|1á)$éÀ£Ó¤çŠK³‡G6ìHDÒù †õÅ–f®‡´¶a\œ� PÏY˜˜cgHe´afS¯ :Ñ�‹¯õ4òË«[ÔÊœŠ>¸F!~"ž¨5H|ZXƒ„§Á¢sÁVt¸-<ÊÒíg…>¢oÌyƒ bYN1Gƒn6;BÓ”…»Ùï6MQz±éì1) Ìß.”„s7=iÆá|óÁ+²ª4ÎyÒp� ˆƒt¦�Hç‹­Ú4q`šÓŒñÁwåiìˉܴzZç4€¶fhŠän‡¦NNvèÌC” ¾àpæ÷Aß…Ê×ÛÃõ|ÿŽFd\°\-Y^ÄѦòUòt~úí•™Ö�F.Zš¬·rVaÑçpYä«)lÿ.®ù—CÄÜ­¿ŸTM ñÚµ$Mx�åAx3‡ç¹ÇÉ´NhÄK4=€Íý3`¼@é‚ÄŒQŸqžgùæ3øRõÃg°a÷Íd$K³‚­ù˲”WÉ’½?ÿølë- •áÙ*ç€NžéGëáÆúábd´ê‰ÖîöäÆåÖó_û ÜHë°�Úšq*ìü9‰‰TI ä�R³Qç¤~Y“œ&g8EÆp¤¦hSL¹{¢!=˜NI–ðØ®e öç›B¹ ŠîÛ0Y1âQÄÅH‘˜.@:Yz„F7ˆ‡_ýž¿>Ê=¬õF7)¬7[¿èx–FœtˆlÅI“(Vè”Côi/‹UiP¾¿ö3ˆ}U†/kCpbú ž«.i® “%E•LxQêÀ£¤G“4{xäá+Á|kÓºû „µiÖA%´¶aJ¤ÁÙ™pÒ!¢ 'r·A§˜Aƒ–ð%¾3/ÊVsó�>Ö‹!�´1¯vËagžâ&®Eë³í…mnLwÁJšd+Ýžî•´ÉW¹Ëƒàú%ÿ“òÝmâjý ~W^Ïoc•Cpó¡UÎå«9œ,¦=nò<ñK(åÙI@=Ì@2%P—$B˜�T´‘èè+S�%<¶bÍÜ�¤éî#èöª±ËëFói`Qj€ú`Ý ‘=®[ñ±Â¥“)8)<»æ ÝþGÆ¢çÃ<$–žìNÃeÌšw¤¶7iÛÒ¤¡t„>S|Küdl¼¿YÎÇ¢Èù3¾t5Ÿã æ#�*ñüüíäDÒ°f–6kw §p_Bk¦¶e¦™Ùæ}b ÊÏ>¥çV¤ìûØ4•8'ÜLfûH3› µé³9t7C×ô=üŽ†Î§û.9=á¯Eäá�@ ÞHÃcAU—Ò)©Ê„§®o¹�[õ´ ¢ƒ>öáƒjýðF‹ŽƒyÈ—c8W”�¨H�ùçÝb½çKJ‰àvdUjº‡ÿ?«6òD´÷ÎÁdMòX5=ñ IÞí•ß²ãÓŽ)Õ}Ú¡Ÿnû\º ÷Ðk½ãEFÀÉ-÷P)üÄQØ[m²¬—Á�5¯{Ýs8j_‘ †aÝ=Õ¥ŸøÛ ./O‡ƒïû?Nö?gGûçÁ×Óãß‚ÿ7y+wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/bb/0000755000175100017510000000000015222270712023737 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/bb/4a61173d478eb402f933f795ca01f458d4377ewlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/bb/4a61173d478eb402f933f795ca01f458d43770000444000175100017510000000013415222270712030515 0ustar runnerrunnerx+)JMU°0`040031QÈM-ÎÏÓK*ÍÌIa8P¿àa¸ó¦h×»ë>rŽ–Mr�*+I-.ÑMÎÏÍ*Î`(™ç%¯�öhÎÞö÷'ë¿[oûágÿa?"áwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/54/0000755000175100017510000000000015222270712023604 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/54/254a8b7758a2c69ee072a53ff802cab00cae1cwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/54/254a8b7758a2c69ee072a53ff802cab00cae10000444000175100017510000000536715222270712030727 0ustar runnerrunnerxÕYûoâØîÏù+NSiJ"’yý²ê4# Ä;8“‰ª•eðÜù‘4»šÿ½ß¹ûLšiW­:šÕ—{Ïûñ�üûðŽfÉv º ã\¤úÊD¤›0ËÂ$fJk‘Šù3­RW‚6-S!(YÒbí§+Ѧ±RJéŸIÉÃ%µ ßb³¥VÅšY à'ëuìoÄ ]\л­l5” ?�½pé-ý0¢ÖË4´àôù‚>ž|*­¸0¨–å½9Ê'µ^eþWÒ¿ðDÓ“7/*ò¢›šÈÝ8£VËøïäEÚçÚHm2nÖ–úN"BY>à<;+~ÔiÍo-g}°œe_.ëúÿØY¶L¥òÿ¡“it;Ý~áAUÞí¶¡«Z›ÞØ×M{hË®ln5–—ËZý†‘ý««›ŽÑ¦"WÂ†×ØÏ„4yaÐXŽ˜‘®O&}UÞëÃ0н%Ë÷Ɩʵ^j˜1V­D”‡e/m ÎZ7Pž«‹Æ†õÊS»¯¯¨6 �{ ¦: J”ûzŸÐß³u¸åáÇ3u¤ÿìf—¡o=æ.}©5È0i(Ú ±M߂Մ/L4ÔèòÔð0†ïJ˜=c”=S ›NÓ˜UUŠ À(CÍ¿~«U]Ò‘(kÓõ4%»ñJž ·V<›˜HÀ U¯I`UXÙá¶ Î0x¦Âžyy uÅì)›a7Àó¯BÓñsŒ¬9:O®>œë•Ýf72ªšVM`‹y)ŸQÛ+¶À˜@�Æ­ÚÓvév“Áªd+pÇ©°O1Od|Ÿg'oI–E,—Õ†6¡�ûEk.7|Ò3'üÔÉ Å!MÃáÊf ÄÒ/"àß„-µO ,W°®À<;½·‰Y;¶fžû ½„øL/¹aë%±�à@ÆøƒÝ‚ò&®ñM——¬LV`kA‹K#Dˆ!¯ò++ü|õ£’§«ìoïAüFïkgxÛ÷œ‘ã@5_[y�|O"Þ2M6ø/6° i½‘�ßýÒ¶Hûxåeá*ö#¯0VµÞ�ˆ©Å/keÝðÁ *òŠ4È)iƒªfr,37à¬åÿT¨ÃŒîïÎé ú®F'¥pÉœDXÉɼñåÏØàzI+´*çyд?ÁLž—e¨,@,�§±l<‡d¤2Æd3Ó²r‘1Çv}1Á�ø/ó^¡'##Æ2™ÚõA•èK!!\›æ!B’Q~�Îi®\OX’ñ`6Ð8i‚©ëoá„ð�¥pJxL¼9ƒ?¼Ü0ûs³ˆ¶Ÿ½—›¬Wþý.4;k�©ÇƒD¤‡ói;É6Q%œÍ8‰$°)ÇÄR6„Ó.dl‰�S?H& �ø�Ø?d\î1?˜fÒ¶ˆ}ÊAÌ)Ê£T¦'œŽnàœ¦)>;¥1å$„«Å$ <¹ú<ðhœR H‰4" >L²|á@B¹^A�Ãø9Ÿ] Îw_Rž"Å`•»­¶¢A„L€Kä94u!At°Z×–ä¿üp6hXgëãQæpæ,ÁÌæs}Â&)�.– �ÔÀíÐ9»¼qÀÝÁ­=Û#箋ÎbÎð)]Ò"T%a€‰"ÇœÄ"Cð2ÂE|r†KìÃó¡s'S �Qÿú—c°áÊ;Ó›s{ W7ã«Ëë~r%®x–Aþ•êŠçJŸ>$ÓŠ€;<èQ†>ÌÉ’â�{4X"Fª±¤÷ÉÃ,w�HÈâYž5ò%Å^È« Áb&ZðÀ”‘`ß<æœÔaìµ[ðÚ€uâQ\ ”ÆS1‡AÈoÁ– ·àÂÐMÃÐ K7ZpsmË 5íU0EyNá´÷ѽµïÎíQϽºŽœþ،틾۳۽?¹gÚ+t bÔîVÞ<öÂ…OaÏcQÄâö|OÓD–P¹#^›…'À=õïoIâ͹bAŒ"Ä”"Ú#‚ÀÓ�ºšvxŸV*ö@Ï“0(jÍ©ó–VÛ4Û:äuÚ‹˜Ó�’”úMíûܥ嚦«k_4�`ú(TûR¯¢‹¦™ÏðrRði(ˆûY5eÒ„IY¦+ïŽÇ© …£bÍ}gh~ƒõÆEW†._"cÐxÁCè&E.ù,}ð{²*ÉÿºÚׂ”^�œÇ+ ÞC`q˜ihA¸÷îCAuh*Év±€W–lwF…»*nà7šðwOOF- >ƒwp`Hl2Å)4Ö«à�|T,E’Ä}šÈZ'\ù»ô]Êk4e”œ'¼SÑ„adé$¡DAÌxéŽhöÁÐu~Ûp@¢Ö+<‹‹7‡F°„X³‘;‡~œï¹a1‹©X,ÅÒQ,¯Ë‘bù]±¼Q,oëCÁl(˜ ³¡`6̆‚ÙP0 fCÁl(˜M³©`6̦‚ÙT0› fSÁl*˜M³©`¶j˜-E-h©e�–Zh©eÑQâ ¥-µ8hQãÔØ@Ÿh©±�–h©±�–½®:´(˜ëªCs]uè£`®«}ÌuÕuôºÆÐ¢ ¬k }„u�¡�‚°®1ôQÖ5†> «u�utKÁl)J°Ì–¢KÁl)J°Ì–¢«Â 0á”ÜW¥°ßMÉ"e�Ú¨ñ8¹Å�*|D2wBÝEœ.’„qA«rÿµh�wYÿ˦�·½è Òi刭sÁã /ìxùðô”p|œ·jM[÷î­›`¾²ñtllE) §«¹ê6»tز9å­I?Æ 3Ï³ÑØ~>ÙoæPÞW“ÉúŒ`æRÎoì ã¼Ý– °·×ìâc2Ás)šðWIé’á l“IŠ zKšZãS>ï‹ÿŸ¸ÝÃ�£`n­ôÿB N¦Oëæøx5-jZ9>îoŶ‡VKÝDð—”f¼ùëN¤oÌËÏÎs¶;ÏÙÏÁsþbñœŽYƒÀ6MR–íÇë_šöúUþ8—gVzñiCÓ¾õùcûc(¢½4ýßÌeÇC)?É1z÷²óŠÆøÍdõÙêûÞ?\z„|wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/info/0000755000175100017510000000000015222270710024305 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/84/0000755000175100017510000000000015222270712023607 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/84/51b3ddf596fe30e626bd4d86b346b1797765dcwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/84/51b3ddf596fe30e626bd4d86b346b1797765d0000444000175100017510000000214015222270712030615 0ustar runnerrunnerx�U]oâFí3¿âо�È›d#íKW}pÀ€c#Û,Í“å�Oc<ȇ¦Uÿ{Ï’ívÕEHhfî=çÜO²JetÿñÓ§Ÿn¯).eK¢Ö²´“• +™‹ºuu!Z¹ñˆ®ñ¥©:¾6r_jº¿»¿£HK)h+k-šÁd-šƒl[©jF*E#²WÚ7)L ‹v�¤v”—i³iEiýJGÑ´pP™Ne-ë=¥”ƒŠ-5ëkÕNŸR(Lë‚Ò¶U¹L�G…Ê»ħšùX~K] GƒÇøÊ�"­HÖ@P=€�¤.U§©­ndÎŒòª+XÃù¹’90°»‰¿eå]‹X§EUÈÿ Ö±Ë*Ù–’¡³NòåK“X‹ã¸U µ¢ªAB·‰õM�±aäüCŠ ï©T‡¯#AŠv]SƒI�O¡�2Ãø»È5ß°ò�ª*uâÐrU’ãmÊã=ÍÔ‹0õ5®•†^“sS£¤/íðÔ–)ÈÐ4&k GŽùêBì²V£ú8ªÆ�þ;Ö›³ˆ¥CQ0�·vè�Ñ: ¾¸3gFc;ÂylÑÖ�—Á&&X„¶?Q0'Û¢GןYäü¶�(¢ $wµö\w®?õ63×_Ðüü &ÏEC4(áå:ð›ÓÊ §K Û®çÆOÍÝØgÌ9@mZÛaìN7žÒz®ƒÈý °¾ëÏC°8+Ç�oÀŠ;r¾à@ÑÒö4ú¦Áú)t˘–�7spùà@™ýà9=‚šz¶»²hf¯ì« )€àИ ê¶KÇ\�ÏÆw»�ÏaL?q´e_\·näXd‡nÄ ™‡à9�ðàœùìç;= §Ú¨¾T&œ° ‚¾h™9¶,”ÇÿʘKz;ý<4�yG` pý?h ܇\ª¾)Ç£�F ýŒUR¨Ó5f�?_ž^”,(GgeiþœÜM®F�ŸÛ[,0�ñËŸ1LÍ ¶ÕI¢ÿÒN«ƒÂå•rÌÝ3ñ‚éø�¡±Ðä{Ñð8ðp`�t<(ê(ê‘ÁF7!É ÕGþ'žª&©ÒWÑ$³ªJ^>ÒÍ^èó]×ìÒ\\}þ/ß?Š}r:$YÚŠÞ‡/Úÿó z�£:vÇgX6i#£¡”~¥Eü˜l1 Áv²×ÏIŸÔâ4y{Iâ`í¡_½«�ã@|tÍ©�öPì>„Ášï±Tx}#?iQhîBÛõ�pÒsbŸ�¿Ã"µ8¼§á3ó@´.‘éLT“ñšSa�Æ?Ha0ì· °ªØð=?'é‡Á1šZ~°7¡bì7 ÖÊjr±A´ƒÅ·Ty¥Z‘üP´+–Ä–ïƒ5$}¦GtlŸo#"Z:ž793ãêB¦1¸X}#ãÏV';Õ =q.SßÃlv’ÜhxuJ0JCÃÌN|¶èߣQìDq2Åž{°§�ÑÄм�«5ºýHz½ïwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ac/0000755000175100017510000000000015222270712023737 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ac/6c8b0e4fd760401466e3ff94d76537f658a7f6wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ac/6c8b0e4fd760401466e3ff94d76537f658a7f0000444000175100017510000000022415222270712030675 0ustar runnerrunnerx+)JMU04¶`040031QrutñuÕËMaø­d±ÆòùǸL�&Ý?f�§er™�BCóWýYj•§Nx.f½Ú¼e[x°Ð"¨¹©ÅùyzI¥™9) ¿<<ßÛ†*Îô’>'¾ÒçæŠ�¢†#Š“‹2 JŠX[?¼?ZPqS±wGÀO[Ϲ“M,Ö7åwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/2c/0000755000175100017510000000000015222270712023660 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/2c/e0e0c5df2aa95b40adf9eb665de4dd9f3281f2wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/2c/e0e0c5df2aa95b40adf9eb665de4dd9f3281f0000444000175100017510000000104615222270712031311 0ustar runnerrunnerx­TQkÛ0Þ³~ÅA³Ø¥°õ¡PHKûØÊØÖ§Œ"ŸmmŠd$9iÿý$ËŽ“�”ŽÍOºï»ï¾»ÓJê\}ºþüA”°e¯’©"·œ)…&+u«ŠIBÀ�ÑU~·P î [OŽ®³ ]Þü®¸V¥¨ò 3‚­$NèQM‚Òâ»ó i/”z¼*DIˆWâ4×Òzi‹Ž�n¥I%{E“Ú¥L[e]�‘n®²—µ¤dIÈ^í;†ãê{Þ�·ðK •7ÌÕvÒ¥ö %�ðsÅ{C æX! M¦;<�¢._Š*êO�Ê=‡. µ+߉ì!lppÔJîéàS…*çR ry�¬@ãýòÿÐ0§Í$ôó¸×Qªn]ÓºXÐÙýÝ�ǧ»¯�³4e5]Æ fªví©mˆK9��!iÌ~ÌæOßžκãýóüËCþ0ÿ>»<ÉšôZ44ÿAo$zSo ù+½#k¯·1bÃæ\øöfü-_=÷»ì , ¹ð� l r/°˜‚_zð;+ƒ�dÜÖV¸h_DaÛ0)ÁǸR›µ…šm(Ü*ÝVõ° ÃrÒÚ°¹5¼ÛÏ%é[zâ†xvd¼ïN7Íp³;ÚnÌOŒïáHgÊÑÚ0ÎE|Èúœûc8Zgpcý{7vö\t\®!u¬Ù¿‹ÙS¬Ë®ž“^Ä�àáß»Þ!ò‚ ðqwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/63/0000755000175100017510000000000015222270712023604 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/63/2a182bc422631cf22b09cabb89855020e70a0awlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/63/2a182bc422631cf22b09cabb89855020e70a00000444000175100017510000000114115222270712030450 0ustar runnerrunnerxeTMoÛ0 ݹ¿‚ÀK€ÆF±ËÐ[š)°v+ºöˆl1¶}A”’úß�Vì4ÙnEñ=>>ºÒ®‚››¯ß>}†ÕËxøÝ¢ÖP£ \�DW7<Š¥€Ð¹ôeÏwIk” EDŠP·Â6H° ΀1ô¯ù¾¬[¬w.E¨‚°u [ ¶Š€0(¤[Ø4ª¯1dͪ‘Ál^ÜmÊËÛ�\¾ê1ž˜Hî@X ÉK„&îf>¨=W.ÊñPR”�TVIiYønB„õ)áy9ÿþ¸,Œ|›ü›fÚ£Pš¦¹;g ó¶;õÝ ›;dÅ2$+ÒA+X5#$^ÊÙgõxA½VÙ?f È4óÝf@óJ#ÃIðš ŠàTl3(Ç0Ì$—ÛÎÏK|Æk¤¬ÂG·^‹È£`ò< Ûd¶"EgDTõÀÈ("”¹×»d<ì1�r”…µAr¶È o“³CÎ~=Ža];ã¹^¥´Š]ÖõßÈEúâ~þsµ|øµÊ©ç§)LÖ+ïS¹BÀ·I£§Û²dï´©*8^ÌápÖ)õn.‡åØq‰p›4´ðr†,j¢r'×G�‘x–üèpíšqÖýL^D3Zu0qäÈž][,òОúšn3„ ªaϳž3„ÁÐàXoÜeÙSõj%¯éóqA©ŸDo Aš€Þ]ç@OƒZ—´<úNhÞDÉžñE@ÉjUï@Ål­ü½”lf~ØG––7øDÅ ƒ0§o“©0\ƒu°ç_nÕûô(‘óÝÈè$W_í)UZ±Ãê^ýÆâŠ wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/6c/0000755000175100017510000000000015222270712023664 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/6c/bc596ae948b8a8fa1f86f095671381b4fb6769wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/6c/bc596ae948b8a8fa1f86f095671381b4fb6760000444000175100017510000000020215222270712030700 0ustar runnerrunnerx+)JMU041e040031QÈÌ+IM/J,ÉÌÏÓ-I-.ÑMÎÏÍÍÏÓKfHµ´\òèe‹1»¡ñ¿Ã?Ú‡ýŸ@@SCCëéSDEU¾ÿêý#"’òTpTSnj1ÐܤÒÌœî×&»æjn�§1{ŽÁUÛþ€¸Êª›@k;åwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/c2/0000755000175100017510000000000015222270712023660 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/c2/6a43994e7328d7a6d3ab28a04ab493f85f2e67wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/c2/6a43994e7328d7a6d3ab28a04ab493f85f2e60000444000175100017510000000533115222270712030663 0ustar runnerrunnerx…XÝrÛÆî5ŸâdÜ™�ŠH‘“é8“NeIQÕH–F’ãf8rI,É�,ŠD³×}’>ZŸ¤ß9» @Z±¯$bwÏÿùÎÏ,³3úþø»£?½¢ËÇŸéZmtE+�e4ćzÖ•3¶ _�÷o¤:·äæ•Ö…[Ùú·áª®K÷&ILlòeSÅs›'éUóëüíM\K9¥ÌÌ*Um¨¶´®L­‰ÉvO×ëu¼¬Ÿb[-“©²ÌÌ\Õ�ÀQ½R55/zbv/—¦^53áéÖj³Î“uVEeek;·™KfÐ5É•«u•4…«Õ,Ór%c¥#ÇJGíAô|̳Q¼cãHѵÉT‘RK™¶¢T»§Ú–$Tz—¶ÐEíÉ5ó)G¥*t†ß…­Íb«Z«,+U CÇô«mh® QÓÔl$UÌW`°±MEkS¤v Kà3˜Tœ…3�.5Ù „?M]6õ!vu¥k0ÇçœÔ¼²Žm¨ r™ª½ÓU ËÒV5´ƒß{Æ¥…VuSiG¦˜gMjŠ%•¶lJÜd ØÒr|H¨´ÿh\Mlõô¿ÿüæ{\Áh×Áçø—MÂöЦ^ÑÙ!�}ó��Sņ`Ùe£ Ž8{+×åíìw=¯!F 5Jü‹ˆ …É ÚðnS¯lqH¿¨LT†Ñµ† Y\V¸ ¹™Îìzãs;orØA"« ¡uÎÑçãÈØqõ‚#Aür°¾n“¢{Ø‹=¦Á/_÷Ÿâå«Wt¯ÙÊ,ÕÛfé�–*ùÂά”[±Ó,®8¶`¡|« ×*aK]àA¡×46Î5pdA—"ógåé‹“ÈCÇîñ™èÝ«A77…É›œ%«lÚÌ!ŒYÀ·Î‰Ï†&Õ’ %tJç>µá/å­˜ÞaîØþ;VÝG�ÎÔ™ÒkÑ¡Ÿm-¢´ ãÁd2Û� :�LÀïÍ "€›µHÙ™rR'ð¶�3™<'“®O&?Ÿ_Ð]¦\®„`GpLÎnL5™D/‘²¹¦\m¶Œ™"v+*Í6À¾…j2Xf®ô¿—i©4��H¬Q#8gÿ ¹'’TQoò|M–-A#¥Ñ:šbÿ4i9µùwßÌ6ƒqîÖf©«y…³ò«Ï[/¼J*ÜŒ:“ôÑze¸Éh•Í,3ø SZ3ÝK�÷Âï—ø"ý5ÿÃõñØ+BçÉiéΧºŒ¿_ã×ÈTŽÜK�!å ë”ç‚›ÙåFxΚÕ]c�8‹¦|,bô;”Ø=/G@"Äÿ'¾£ŠóÍÖ[>Š�„Ú©“ŽÆî,ž'ñ·ß¶7_êè»§�ý’ë½0¾|w{s‘,­ÌÑ΀¾Jp'‡¶¾#ÑçÎ×`¢J#ÚTVÄ3¦^ÁjþŒ´K�Iö"|׿’þ̳NI¹éí„f÷ø M"ö-Ô¶W;ƒmªjEC´c‚¤ÛÊÀ†mdŒÐ’»,l ¤ë㸅s8ö¾Èö—Ó»+ò\€/²Rè7ì@ Ù%…˜ž+«oí3ÙQRð.ÀÑ}SÃüØýÈøÇBí¡K®c/0ç± X8{û£<ŒÅ!:`QTÓå»÷t­�ÃÏKqYFw\ðæ�’n's:‰�¸õ+$R{€–çÆvA ˳I4�N*DØ)0ìñ¿¹zÄcäqY!ôŠº›‰ ïH¸;\`çÀ›.&Ü,VfÖøÝUÎýQè%Ê–íΦ‚…9^ïî¹´84Öoèàí†xÎ÷–„-Ê´`ºCºB\–V.ßPnÓÞFÉûnOø5Vg’0á»<ÖË5˜ó2Ët©¶ ä·+W�¼N|Á¹n}F÷²=Q™\aÊ€E˜ÃFª«Ä�ýX±ïß•]‹<àʤ*"›aUH€Ø^æså�u¥æ,ã‚ë8èðN �Z úx ¹´B/ËÁ‡QÙ6ZE ›åQ¡Ê @D<Ú3ßø—øm1¾"LcÙ�˜‹B˜Re÷‚ƒ„Km3«Ãøó�×Ê²Û¹Ñ ÃÝOÀ ¬aw×=¼“…¼€ât c´G&L¥&S<çW9BÐwƦv:[`Fdo3Æ+T|�Q‰uBàÊ–�—â™Òbb­ ÐØËëàŸü¬œK¬Ë•à8ÙpÝô�p÷kõ h‹ÝØ*çXö™g�uU°6]²a,+£‘ð›6�0þ÷Ýqmwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/98/0000755000175100017510000000000015222270712023614 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/98/077e48a9786d85af131b7662569cc1fa883079wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/98/077e48a9786d85af131b7662569cc1fa883070000444000175100017510000000404215222270712030412 0ustar runnerrunnerx�X]SãFÝg~Å <ÄT°ñ†¡jSË3K– ÔÀl¶Š¢ä–ܶ;–ºUÝ-{œ‡üö=·%Ù"a2x(@ê�ûuÎ=Wqjb:éõz; Ë¹Ð3™šÙÞÞÁ=}ÖV¦R89y.t;g�þ3µiH? Mýn¸×¦K“åŸS\d9¹"Ï�õrBÿMÞÐrÐé;ƒw�…Qôñîñú<ŠÂšrÁ°K™XSl¥XPnåR™ÂÑRZ§Œ&¡'´2vA+åçäçÊ‘ÑòWzZI¼Òß{Zh³¢©±°ÁJ*´W))ÿ½£Ú‹­=v¢×§‹Ü²E�sÉ ‰ \‘À±Tzù]í6ÜxJÕR:R°ˆœÌ…^âøÜ8å�]ŸÓÓÌ/†íT¬¥m»¹LÓçÖ &q‡¯²Õêø�+ÃÙË^§ÛéÒ\8Š¥Ô›�‚ï0F,…JEœJj,)ÉBÌgöx¢œ·Æ…“*7›à­TšÂÄCõîL(íñƒýs‰hñ!þã·tR†{¦…G šŸ¥3úùáˆuŠl´]"´––`¯@T:i{•IšÈ\ê‰ÔɯLâÎùQjÖ4Á?lûÓåÿUÄtëÝs‹cÒ©b¤ G§¾ÃFÙüe}�n“Ûåäöt—xNnGüG¤â¼®MšIX.Ž-E®hªRé°ê½ú/e†Lê~qDO½þÙskî}îÎ��_IeÓØcå\!Ý1ö4ívÞ䈛Õ|ájŽä24êÒFjµñ$¿ä©J”O×µ™ròMßKh k×O;¥ë'5:Ùõ*y"M�Ô RËv„”ƒQ3ÿ*ßFQ Œrü Ô}óú Ú½úþQu?ý,¬z8á–¡@ …mrk¼ILº ÓÈÅýÍv…Ñí‰Ìö ¹Ž�°€Î#k°l©üðK|íTœ• csŒzŠ#'}T½8¬u8fHOÅ(0Ó¯nÊÌDbmm«˜LšKg8¿6)ª²[®~ AöGuOJŠíõèJ&\À]\JS›‰Tý&¿”ØÔ­f”�?}¾¿¿¸}¸f«Ã!,’²RzbV“™ÈsPAË¢ø™àêjE”=Îô*§ÌÌUøhª¾`•+bPíT$ø;¼Æ�Æ(ðè—›�Ww¿D÷w÷ŸïÇ n¾ìXã�OƒÝa58;dX]LË –OR P縂¡,ß�©ý#��âuíêY;ál7gÒΰ÷O‹‚�#Q††ËƒcZåÆ Œ)ÿÅ™\L5@s°‘Z m†Úã äê¹ÂP'Œ·#�"¢Á‹xë–;_úÉïî×~ŽàWãÖ ×IhÙ�Q¶ÛCH~h×\=ètˈ P?´ù¿íþ‰9uü:‡̃ fj¬x„±ˆruþgÛ“TIí÷ PÌ”6� k®¶}ÆÓŒ9•+ ÷û±Ê¢2LáóÂ7žÏe²@Óp Šw¡— Á],Ç©÷lx~6,xÒ)»;}4Ë>•+(¾@#MK‰ÛlcF ¼Hí£U—ê»ÞÎà]¥4T·sR±âYÝ�Ï�ì8¯` ıR9 ¨Œ\Z¯�Ž�'£� 8¬l®ið rÑF¤1óm®9ì|ÍÙ௮)y·D~«üس”ìýøAéDžÓ¨ Bý”±˜:4!”D(Ç ƒDQZy%ÒZ!5ìïïn?Øÿž¿H™{€Q·{ä-¤RM§K‘¢­S µª ¯ˆTÆŠëÕûê—áäF᡽KíàãˆÓ]ÉRòÂÆœúM"NwOÄi™ˆêºpEnòº#´w+b6_Rfpyª:2B8+Ew vÀƒU/ÁJ³¡S·æ�v§îSw…‡a…‡^W`Ytÿ94 _ÚP µFhÊE%ˆC °XB;…7 ù$-ä9ý†Ñ`Ó¥&ä¸a¾«ÑðÕüÕ"ý5e7nëŠÍ �_77€¯Zű/ ‹mºæ — 8 <­p ÝÆw¸;ß ™o6ŒÍÈÊ ÈÕI !òòÅ™ºÁ3£îî醋¯”˜i”–b½_ŠÚ÷VhÇí"âjÿîï•¶Ï÷k­Áùmž‚æm¡Eê×Î<.rç sa Å®ô¢*hAOu«”Õ¢ç�æíÎ؉�3 \Úãu²I»óò¨äå7u´A�tìz^-fuK+Áp¥\Žbj"u” ”�ÉCKælµÿV/•Èo™2=l†&‰�Ö¤Ñó­p,]R°BI ¤]«õtp²{µž”Õz™ «¦ÌC�¦°È`É�‰n?ÜßÖȧËX¡‘bHz[?†©Ÿ�?·Fš2úïÕ‡rÊØŽaÔÚŽÍÿ}ÿ~Æ&n lùMÿN­ë«›Çs¦WÈâRž­X'‰Â`ìAõ_Bp1¼[“…C«fÀ(þ'Gymlf™y»T*ýºLú¡ñS‘ÖeEŸ®oo.¯?>\Óã]×>½TVâë k.0'¼{CÔÙ@®�Ô`½ó˜Ðxc|Ë^óTöH¬ÓýŽ“ó»úøÜpéq›�¿ò"þDâWü•¤Ñ 0´„V‡Y ·j‰ÏtË:–qP}s¸¨4Åå µ~-Ð 7c0�šuý%A™2*ðE‰-â\bäÂ^¾.…+:á¯!ˆÕþ l¶<¤<p�–B¨"úö�<‰–@i¯L§ÌšÂ8˜­—… ÂÙYxT­ß”qœÒƒÌQ=þ wóR?íý‹ÅÈ"wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/e1/0000755000175100017510000000000015222270712023661 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/e1/783a01c0f8085c1c7da84f242895fbfc55fe2cwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/e1/783a01c0f8085c1c7da84f242895fbfc55fe20000444000175100017510000000307715222270712030745 0ustar runnerrunnerxÕXÛnÛFí3¿bà…äÊ2/JÚXMPF7 q$C¦cø‰X‘+iaŠ+,—rˆ Ôßè—u–ÔÍ\WŽ;i‚˜ÃÙÙ3gÏÎ 5Žø^Ù¯^ÿt|î¥7ìu�‘ëuÚ5ha0ô Óî{Ðî�:-ïìÚ€C¸ ¦òæh!ØòxÔqÛ:õy.`ÎãŸs"�Ñ]­ðf,� ‹ð] "$ðI""GÉŒFÑʵÅ™`Ó™„J« ¶i¾†O GDL‚‡ßƒÜ0ýcó9­s1}«6Ù®üëO\h7¶ “@°…LŽÒú"ÛEµ|*È\›L,áyKmBÆSH ‚†,‘‚�S‰ø%�8<æBí8ç!›dÊ–Æ! g$óD¥§zƒK8£I‚ïz4¦‚DpžŽ#¨Õg, qB� %ʈ,„0Îò…]åbºãç|6�2 œï¾¤"AŠÁYï¶ X<ˆ ‘*|¡Î¡Š¨3ˆ¢ƒÕººÂ þ凳CÃ6Û�2‡3ã Ìl†11×[E0¦�&t’F5©�«¾w:¼ôÀ\Õ;¹ﺉÎrÆñ-]Ò"›/"†‰"Ç‚Ä2Cð*‡ΨuŠKÜwý³¾w­Rèö½AçâºÃ¸p~ëòÌÁùåè|xÑ©C®ÄÏ*ȽToxÞè3¤’°(Ùp�� Ê(„YR<ð€²%b$ ×ôþãa®wPHÄãiž5ò¥Ä^È« l1—5¸ e$ùÞcÎIíÇA½/-è"Q7Å…DY`Œ.›Èt#ÎE ÞñDò¸\Ó¶,óÈrL«—®ÊðØ0^° Ês½ö{ÿʽ>smß^¶Ný¶ë¹þù¨ÿÑ?5^  ‹Q³{½0XDiHá àó9�ë³Ã�Ù‚ªðš¤�¿Þ\‘,›âñ4˜µ‰$p�­iÇÇðq¥ã~Ìü™D¬¨&=ï=,�ºm×M8ÊÿjÔ&TÒXЈ’„†UcÏ–þÒñmÛ7�Ï@ÊbéØ>*8lâã4äxç(|Ú}ÈÔƒÂÉâ�ßÂámþ¿2nVK•��oco5#Q¾^½Æ»Â$üÜNÀj_Š »¨fô X¿"um€ÇQf¨5ÓðÆ¿-˜*Öùxω¯*«?¥Ò_ÝUŸ…•*|F ‰Ä:¨hë{Œïà án€×Y鬲]oÔ«b)&-oŠÐD•$é«çµïR±^©ª(ÃãñŽ9FVN ÊœÅ\¬ÝÍ!X¦iÂ/;�à[‡„Œ5 ˜A%¸†XZ‘ ¼y’ï¹c±4‹­YÍÒÐ,/5Ë+Íò«fùM³¼.[, ³¥a¶4Ì–†ÙÒ0[fKÃli˜- ³¥a¶5̶†ÙÖ0Ûf[Ãlk˜m ³­a¶5̶†Ù)av4µ ¥”ZJY ¥”EC‹ƒ–R´”â E�Sb}Jl ¥ÄZJl ¥ÄZJl4̲êТa.«}4ÌeÕ¡�†¹¬:ôÑ0—U×0ËC‹†°¬1ôÑ–5†>²ÆÐGCXÖúh¬–5Ö0 ³£)ÁÑ0;š ³£)ÁÑ0;šœ f€± äfSZ°ë’4’ëµSãqÀŠïTø9Éü1õÓ8I .$Ý”û/E뀼èú¿n yk1‹ž œVŽ‚ÊTÄ;^ØéòV~O�?9a¡alúçC {Ð=A°Ù$4š¬Zટ<j›D¥ºn;yÓ1OpÄË3¨T¾b|8¬æ{½-&‡-ç0õ©\TúqÞ>׌@¿}Pmâk2Fž‹¦úEQ´äÈè>Ü 6Ú½Ô¶£ ŸŽ�Ç�s²±Uã·�‚#à=G~ròÉ0ÖSÚá^ÒÔtòÉ_Hñ]¥³ÁVýùqìå#çs–=†°ìÿCX>–?aÅ|oJ~«‘ÿA¡k¾«ÒU¤¶ß,ßÂØWª©¨í~LáG±úªúQ5«ØëÖŠ ç­]Úgâ#Z ¶ö¿+6 j1*¬‡’gj�Ú®{Úé—Ú²%E ªR³=ss½ócö�üŒ}“‰j²wí!ýéˆýwsÚî1ßE‘_KL ¢ïø?[�›Cî€PÝÚž¦>¾ 1þDjÄÛû³âß͹—;wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/80/0000755000175100017510000000000015222270712023603 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/80/85b81bcad140151524f7fa8dfc141464e5119ewlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/80/85b81bcad140151524f7fa8dfc141464e51190000444000175100017510000000221715222270712030500 0ustar runnerrunnerx…UïoÛ6ÝçüìÀqÚûÒu[Ž…Ú’!ÉÉ‚m0h‰²¸Ê¢+RqƒaÿûÞQ¶Û 8t<Þ½w?7•Þл·oùéæŠÒR’µU�¤BU’ðY©LÖFæÔÖ¹lh¤t…�õþ¹QÛÒÒ»7ïÞP¢÷¥’ô j+›£ËR6;eŒÒ5G*e#7Ï´m\ò!�”¤ ÊJÑlå�¬&Q?Ó^6ôÆ U«zK‚2¤bOËøŒ.ìA¡¨sÆèL Ä£\gíà…å| ßPß–’zÉñFoà’äRT¤jD“@} vP¶Ô­¥FÛ¨Œc á”UmÎNǕکc¾îøFÞ0`œCÚé\ü_:ZûvS)S)WzÓZx6ºÂ™Ç�nÈȪâ ¸ׯèœgAm�ÿX"—÷PêÝK&(QÑ65R¢(¸“k”ÌeüGf–-Œ¼ÐU¥L-Óu®˜¯yl[Šs±ÑOÒêz\k ¼®æ® I×Úã‘)l04®jH޳éÄ Û�±è¾BöºqI¿ç::�˜ù”DÓôÁ‹} ZÆÑ}0ñ'Ôó|÷†ô¤³h•ÒÇ œ ÉÿcûIBQLÁb9|Ø‚p<_M‚ðŽnq/ŒRšhM#J‘ð*ðqoJ ?ÏÙ» æAú8¤i�†sŠ -½8 Æ«¹Ór/£ÄGú †A8�‘Å_øa:BVØÈ¿Ç%3o>w©¼ÐÇß8Z>ÆÁÝ,¥Y4Ÿø0Þú@æÝÎý.H�ç^°ÒÄ[xwŒ.¦€cçvD÷0ó� ù<üÆi…Lc…iŒÏ!XÆéùêC�øCòâ á‚LãṜ¸Á5 ù^èwQ¸Ôõ¹#pá‚­@úŒeâ{sÄB{ÂÎÜÒ›‹‹KU@B Jý$]�Ñ‘ÿ¢Å" ׳‹Kœ¨Z¾~ˆ«ÝLQok?]WâY6ט歹½o-–÷:Ó»�®_|À­ü�Êß¿zØæ0æ¯â¹Â^¾¼`l®4›.nn(åuåMâ¤d²Fí±\¥°Ð«F~nÙŠÕ:‰{nÕ“¬ÉìE&¯�Ü‹Æéðîu á2,’òË[*ósA0ÇhÂz�¹Cçû;iŒØÊûR[ôJ6Í�z�ã{úÙüUc;.Ož? §œP, -¬rªõ�ºh+ÈiPàÝϰÓXnÑZ½ƒÖòç3¼ I–"+‰-‘}êš‹‚hõQÔ¸^ƒ3½ñÌ\wØ=žØ¤ÿ 7çÅ+�Rt^˜Nlµ�ÝÛ�ëH¸šÏ¯ñøìTí Õµœô„zÉí9€ ³:ò8"ÿÜ*k!‡L1éØÃ§J§dçV»‡±5,�Ý$c£o½ñG¿�_�¿¦'­rê_¹ÑXŸÓÿù÷ Ï'ƒ_Ü Þã½AˤóRC^¿AKµ@;ÜÃÛUuj½Û‰sõ¾0�?JN¿Ñ¿ëõ½·öâ»d½&®Ú@tg?áíÎõኲF¢€k�me×x%`î3ìKôQÄãÿÚÿ䦰Ìwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/02/0000755000175100017510000000000015222270712023575 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/02/9d526562e57cd1a37a22e67be82511563d061awlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/02/9d526562e57cd1a37a22e67be82511563d0610000444000175100017510000000173615222270712030347 0ustar runnerrunnerx¥Tm�â6îg~ňª¬rûrU¥ª§JÍ‚kCBst?E!1ÄÝ£ÄY´­úßûØ„½Û»Þ§¢HÈöÌó23ö¶Ò[ºûùÇŸ¾»¹"Qª–dmT#i§*IXV*—u+ êêB6´äb@Wøh¢�/�Ú—†Þß¾¿¥DK%i£j#›>d%›ƒj[¥k‹TÊFn_hßd)<Ú5R’ÞQ^fÍ^zd4eõ eÓ"AoM¦jUï)£T6ÒX}­Þ™S…Y]PÖ¶:Wð¨Ðyw€øÌX>+¿¥‘)% “>c8v$…Ì*R5Ð$T÷`'eJÝjdk•[ AyÕVÃå¸RÕ3Øt翵ʻ¬N�ºP;û/�­c·­T[zT( ½í "[»é ëY7º¡VV•EPÐí¼~Rçb, jþ¾DŽ÷TêÃ['(Ñ®kjP¢(È)4Jæÿ”¹±;VùNW•>Yk¹® eý¶¿ôm8϶úY:Cç×Ú@¯«¹ë‚SrnmÔ– l14®j G�íÖÅ,vÛÖ û 8êÆ‘~éõú"bÁ(‰fbãÇŒxB«8úȧlJC?Ázèц‹E´„ˆØÅ#E3òÃGzàáÔ#öÇ*fIBQL|¹ 8Ã'ÁzÊÃ9Ý#/Œ P‘aÅòf´dñddÿž\¾‰àQhmL¢PÄXzp‹×Ô O˜G~Ì[�YÞ–¶f¡Í ÙÅ–Ú©~íBlÁÖ0ýªeÊüXhOø&ضôf0ø¾ Ú7Ï€íÿ;ƒ ÷.ׇƒ®¯Ëá`€Á¤ÑÜ<á))ôé wÏþx=zÖª “µÍò§ôv4ü= üÐqÈM—èj4úëT5i•½È&ÅU¨ªôùŽ®÷Ò\öºf—a¢OUŠ{ìÌoãæLG¿RÞH<,i!wYW™ô¼?ê£öæ©Â#eÒ�n.ç¸>ÌO‹”ý>ÚO)^€#¥VE�±2ºi¿:êGc¼Ww (†=ܼxZž³èªO‡Ð/¡û“oãztûFßh>}Hy’.1º˜‘Q0þÊk áô³OûÜ9ŸÅžTa¶¥>¥hÓh.Ò ŸÎ™õÅAð?ßèçÝÿìg­ªÏ¤ôýÿÙá:Î2KD:Á½¼÷'ÉÈ Á§ñòÞ®ï¼Áxð/€øTÀwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a0/0000755000175100017510000000000015222270712023654 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a0/d18c70126d024a13cce7f0bc401f6b9f0123b2wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a0/d18c70126d024a13cce7f0bc401f6b9f0123b0000444000175100017510000000527715222270712030675 0ustar runnerrunnerxµZëoÚXßÏüg²RIªîJ;3ÝŽDƒ“x†@Τýd|ÁÞÙ&�ò¿ïï܇_�„d·UªÆöy¿Ï½�FÉ”~þ埿üí¬CNf$âO¢(Ù°j³$öCÖ7ûU»ÍÁwoš<©�òqœä�WÚ\zAJ¢\«?e�¦i50‡�ù•Ñ *®§Yï‡pÀ*I%Ó¦®§Fˆ+‹&£ ç®7¶ÈžÐÍxô§Ý·útÔ›àù¨Kw¶s5ºuãÞÐùF£ ê ¿Ñö°ß%ëëÍØšLh4&ûúf`[xgÏ·}{xI_€794°Ð êŒÈCMʶ€wA×Öøü ”{_ì�í|ëÒ…í ™æˆöè¦7vìóÛAoL7·ã›ÑÄû>ÈíáÅ\¬kk蜂+Þ‘õ'hrÕ $«Þ-¤KùÎG7߯öå•CW£AßÂË/$ë}XŠ”:ôìë.õ{×½K–nL#<–`Zº»+K¾¿~Î{4d5ÎGCgŒÇ.´;ê�=±ºÔÛ6ÈÅxòlN`°Í†Œ7´6µ”ºð@Ø`·Pº�¥oõ ÷ kÀìÒ³Vëï:0èh‘ߟ¬ÒðáÄ›ÍD–�G��o!-ê¾û‹“U²Z¯N²u:÷f‚?W5ÒÉ, QäÇ|»¾˜£¦­—äÞ$™ ôk�\÷[µˆà5›íä^Ãv£¾k o¯[�T‡üÔ2t’ЧŽu9˜( š_.ýû;%:ê Ÿl¬ïæÂÎñ¦‘È�$‰ú^î}ªê³ðï]d(ȸár¹l±ºE*Ï~”èÚ<Ï“‘�³eõ)8¥ž»Jd¡wç©·®ÙD84^þµ6 +ü\ u J+~¯‹µ·@�n›@Û:£Çòñ± ¸j*epûJÏaè®~y|]ôzg¼­#â‘�š³H©u~é­pš]J™Nui†Fû‡ù”Œ÷Œ÷,±V;–gÜ?Œo-¨Z(‰”— ¨®ôê]h°oßÁÝOâ÷9ßjá²WQ¸ÁJbÜLÉ[),ó”®c\4É‹…ù:ŠpqˆÏEâVßíߊÃDemå`D–ÃkÒBofsÎGðftå�±CÖ1Ò†¬¬kŽ ÀÛkýÆÎÛ0 rü.E÷Xéý÷J�¢²5 ‘ø)óˆÉÜñ 3/u]ë#KËuS(Ök¿teËð^lõ‹ô‚Î5n´_Ÿs_Ïä\Úî•­lÑ4>kGŽùè¢ Û­u¨¾BÚxеÞ\HÌCZ%Ól¤!E+Z¦É×hNè*XáùjHë(›'�Á" â쉒)ñÝGñdHáŸË4\­(I)zX.¢gQ<^¦Ëd~‚°qOS „aœÝg~Å­æÁbá¡‚G°O=¿q²|J£Ù<£y²˜„8¼ Á,¸[„g(ˆ/‚èaH“à!˜1»”N½YÇn=ýð|ÆY”Ä,cœÄYŠÇ!T¦Ù«ë:Z…C ÒhÅ ™¦ Âs:áÁ9‹Ù/ÏQ8ÕžõkE` {„èW.“0X ÊgÌ%õzT‰RÒlr¿Y£FÉz3Æ÷›yïŽU#ßy§s7Ñï»ây„ïMõÇÛC‡C÷áu˜Ydž½Ñè¼ÔtSŸ¨ L²,ý ð(c1j�®u?[Êѯ²`¯ÈaŠ-/Äß`R }ÜT"Þì¤Û´õ3CýµyæAª±†LïE«Â»ŒzÙˆ<—Ön°ÈõgÅ3Ö#âÐ5„t1¿y–©tZŒ4XÕÂ"‚áA) &\ð‚1˜¨v”|Ášc’c¨ÁÀáU��6˜©C{a#¶¬´ÀÀåU¯E2>ßn}=k`‚LÚºM‡ÅWR8º¶ø}‡Ú彫`”¿Fï] ß(ýʉÿ›“•t焜³�eO{q8@l¿ûçë@ú�VŠÚb=µNïqpO¼¼dqª‘Œ×ªBnlkJ�}Ö |µKlx_DÄ>»D=Ã`yjto¯³6¬P5õ-.°Ê¹ƒý2íp�´Û›\ïGÇýñÈ]ù±'i>b×õ÷`+íè󯃮E^;™@Cq1.ê�¼ÿl”ÙIÙIpsæD}܉š-ôù6êùRÉ¿ù&C3saS£ \£“ š®ú7]îÜ7‡Þq*Q8´`æ^YU#î�·GIhÆ�õ?\¿Ç\././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/e5/5c4ea63f356a8cdacb3597ffeea984020b0cbbwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/e5/5c4ea63f356a8cdacb3597ffeea984020b0cb0000444000175100017510000000312215222270712031225 0ustar runnerrunnerx¥VkOãFíg~ÅU�ZÛÂÂj[ Ê®L‵ÁNg)ÂÔrâ qëxR?YÄï¹c'ÙN¯MÖ¯CÏ�ÈõȾl k¶ÓŒ{¶sF§°s\Ÿ6 N}—|6®l v}º°¼î9<›§öÀö¯ÚÔ·}‡}öáÔ¤¡éùvw<0=޽¡;²ßƒ[ÇvúP¬ Ëñ;@ÅYŸñƒFçæ`  Ì1Ø{Š_×^yöÙ¹Oçî gañÔ3ót`ÕPÕ˜öE›zæ…yÆì F€( °kœu‚÷L•!À n�ëbò´àA¥²^Ë‹�,„9Fö󆼚Y¬UV,!Jñ 0vvv“®¶]¸ÝOáÈò>[^x¾³‹¥$¯Vq¸žnÔâ‚ÜC‘.dÖ™·ž6~^Õ¼WˆüNä�ù‡§½Ö}|»‡Áœ¦ÛÝg–­Ušï¥ÑZäÍ™ŠÃ5IÅÞÝÁ³ó;âWl¶Iç* q³ Gkƒ¯þÏ1D5ô1nÜn8ò½q××ÊõR褩oCÇl–Óð‹È¥ˆµ"ù"ä¬>¡ë;œVƒ§÷Ë!Šõg.Âåa0‡žõËØÕ |ùá>�uÜ��¦!·"YyžÄBûžÉìî>e¾M»µ.ézk·Yзjë2´{¡é�iY´À=о÷:™#ä­†§2aTÂ1¶´…(Šˆ_ÍÉ“ú!ûA?¦ ý÷î‚Ø o�âZºédÇ[DÜ!îØëZ “yµ•ÁÖßC–ŒÜ˜4Ìö>°× 4�ßW‘�MÓºÈ5(²Ê§ÂØßL•ç{ús5òIÍØvüÿ½êߢW¡:X÷”šáÕÙw›ä÷«L½°ôoqGÏÖ yª¢zžioBf ºš¬V?ö ‚ÕëE‹D¢@òßVßkÏÛal PÐ]<—7øü>­£ÝÆ0TeüZ^\�¢ÏÁú£*E!bœý£Ð Û�Á-ÞäQâÃÃö™åë6ß|*Ž-÷2°Jˆ<¿ÃÅrT‡½ê”×!ù:Øì§n#d¸‰oRjj<"g„ëâåý N!"wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a2/0000755000175100017510000000000015222270712023656 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a2/713541702599f2355bc4c3fe027039a88757cbwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a2/713541702599f2355bc4c3fe027039a88757c0000444000175100017510000001722015222270712030300 0ustar runnerrunnerxí]m�Û8’¾Ïý+ˆY`ÑÉ%™¶;»;I° dÓyé›L$� ‡ƒ ¶e[[òJr'ÞÅü û÷Ë®H½‘,R"‹ît>ô3Ó–ŠO•ê©§HËz¹\ç—ì/§>}ø?ÞeO?^¼}ùüÍó÷O/žŸÝcgoÙ›·ìùÙù;;ÿüÙÅëߎØ]ö!Iزú|[¤W?¾þôì—ç6s¶È ¶É‹„¥ü¹‰«4ÏÀœ�¸X¥%[¤kØW²m\T,_ˆu¼OŠûå*Y¯Ógùv_¤ËUÅŽŸÝa“G�þtþóö.©’‚ýWUZÞc¶I6ƒÏ?§›Ëx½fq6gÿ•—+0˜�åY¼žs·=Öÿý/›žLöa—³"ÝVå�³|ž<Øîå8·E¾,â uQÀ¡–ù¢úɶÏwlg¬HæiYé宂#ª¸÷ó‚{Üäót±çÛvÙâ«V ƒ¸7%?`þáå›�ìuR–°ïe’%E¼fïv—ëtÆG¿NgIV&,†$ñ��—9»Ü‹�/x(šPØ‹ðE†Ÿ°$`áý*)JH:;m½5€÷PsWü –o93w ê=[Ç<«Í¸<þ¯ KJC´s W„³Ê·pd+À„cý’— Û•Éb·¾Ç! 5ìÓùÅ«·/ØÓ7¿±OOß¿úæâ·'`\­rØ›\%5TºÙ®S8PÈqgÕ‚ç¿<ÿì yú÷ó×ç¿ñCxq~ñæù‡ìÅÛ÷ì){÷ôýÅù³�¯Ÿ¾gï>¾÷öÃóLÔf“gbLu—ç®bçI§ë²KÀo@t Q®çl_%@ø,I¯ Ƙ͠>ÛôZÉl=ðâuž-ÅQC¾xù×åõ„¥ –åÕ=ö¥H¡Œª|�f‘ÔólöàûÓ„½€D}^*( Àx‘.ª{±Îóâû{^Vyv�ýò”±“édrrrz2¹Ç>~xÊ�ðÇ££?¤ (Ï{yösôéüÍÙÛOÑ»÷ç¿F¯Žþ›Ó êí�AÙl½›'ì‡Y¾ÙäÙƒÕGGÕ~›p$�ÃnV±èåüó§4›ç_X÷ד££d¿6µy~ÆNÄçx�Ö=ãåÅÏìêôÁtúà„Ýoþšüt„£«Óh:�NŽþ}ÄØË·—ÿHÀ!4“$«¢4+«:¾« á ‹Ýåå¥lfwëAÚÆ rZ¦ Ê¢Ù÷kZî@¡w¯ÄÿùÆå6O3®¨õ" Æb+lc_»¿öÍðçPâÕ/qùY{màOa´ûŸ &x¢"žBië<ÙV+és‘”é¿’hНÄf1¶Ê·kPÐ:Ò@^¾†ÎÄîB¯… Knßl™­ÒõrÕoj·DÐhª(ƒFØï»›As¹¯Ò¨Yœy´…œYN.ãÙçeÁ;Øðõoø“A—ä¶å®Xij„Û6rÃvo‘,¡ñ�õ_æ}‹õŽ·ÁhÈ&KÀàr�dªÙò2Ï× ôë6nWB&7Û¼ÍÕ;+`4ZŠRù�Ív…¨«m ÙævPT/_?Ë�ú¯ðr y€=€%¶p‹ú�ûƒÚm¡:’ê³.Î9k6òyEæTÏDö5ÝŠåëyTÛÌ…ŸÿžþOk­ÁD ¬@õñvâ¨÷uÛ—uIÆëíª÷'í| Se’dLm}™Â4°ÝUQž­÷ì1›p¯ôX–Qµ‚ªX®Ô=0>^G+žeDGÇ\ݾŠËH„õu¦Z̨¹|mù1›ö!ijHT<Û•ê± ‚!nÕ=å*†>¦mÛm·yªÝ­«tž\Áä¬ì3˜jjuyY¦Ù2â³Zýs—ìt¸d±€†ÅEÝ$½L×)LuJà¶í�m»¯ÒäK Õ¯oŬ‰#Sw¥YS5êæe’o’ªØGó´¡I´Ö‡Å©‚v$Ö ‡ X/A³Xç³ÏuJÐè©ËhWvu?nºVÏY[é ``VAsX ”‘®“%¯žø²Œú^Ë?‰~+ö}IçMóä–«„/$…þR!úm¾Õ7­“2‹PÝð2¯ª|ÓÇ�í6Q¾X4Áݲo³uº•š$äÙ®(aQsz üŸc¾|—« Áåݺà¢~§®~A,¥ï¨¥Ùínú�Wò™ŠÙâMÐ\ÏR(%¬¡‹×5Àç0a±Ü‰…Mg<ŸC^ÁJx ób*fÕb;—Èò‹™MòŒ×Ì[}!‰»©<»âëhb¯j/vÙ šP»µõÜ :+b˜—]¿ž×Ÿûvý;Z”Lø¢ä,],`ÑŸo`•‚Ï¡�4kãÇ|÷æ®|ƒÚ;fùJǰ²™_+îX–P£Éäv u»„â'Ÿú¯^·K¨Ã,¡ØˆðÍÿ§¼oÏ"u pì,Òíy!ñs�8O{^èö¼P}îè†Î á³iæµÓ 8qßDà‡øyNt2~&üˆ7YÎ?·¿ÉðßÇ£eREÍ9$8-v|‡ý¾ÌñÓî)?Uµç—`û+»/N¹‚ü6xÜ�bå»ê¡ÐWªÏ‘€†Sí œŸæŸ[[q>ìø_ÀÃ)ÛË�¹e“fyÑšC4wÙäääÎlõ°¤ë 8’+áwÛÙŠ À6ž.8øé0 3iËm™¢-§hËC´åOhËŸÑ–¿ -?¡-�ô-óÅçv -ÙVH½ åÜBgÎ_veÞ¶1©ò¡éôðµxÝ&n®îkÿæîd©Ýi$‘�Æ{Ö¾<žž{kõ[Ò+Õ?ðR]õ×~ êšƒ&+`ÈUC@t6T%(×TŽ B1¿9](aÐåaƒñT‰†N�U,Š”zU3ŠíõKGq R>?! I�‡u••;,�EUdõÅÉB]¿6×+§›ÚqF`»B¶Î'ÝZ³öO×ï)$y<=÷VYÒ+¤ókËKm–rÛ.�×r UÝ4Y%õø W] ÑÙP•Ð]“tÔ]§oZxuv7'†.ºLž’Ð èôPLJ·]B;ªož3“(Y^#‹mnüI1™ t�“¨3ãIÜ�ã)¬!¼CÑ&n®!LØ|7Tu·ã©e<©ÊéZ‰N�„t(b仜Gø‘M¿šä ") �4 x(îL÷kÕk´Ÿ,¿|˜F\'•°â†PÜz£)6 £ã°R�Á•‰ÃUz¥÷Pü6€%K}»…ñìbctsëo鑸UZOOJa‰ €Â„@O¾uEnK1œblˆi,®�Þ8‚ez{Ðä3ŒPÀƒí ¯‰KTtFÔE|{—÷°Z«›D�¹yY ZY1<%¡"аj¢u€ÒÜ‹¢5¹~U´ž Û?é_b[„A0WeŒ�Ñ™1k£¦Œ›Jzûk\4]ÒmÐf¬�̬#PiŽD•´5Šªô=ê¡XÕ4L©fŒ„&ÃÑ3Âýo`Z fú\Ú Hb̤ðd¢“dmŠšDAß5Ë0nls´üäæš›¶…Þ05 h×öéMgTm¦ýs�ŽŽê‡¼Hß2.Âûˆó0j=d×Ç@WœÃSl:�«Îz(Ý÷šÇòÈŒõæad¸è¬÷:è?ÐÕÕcŒºjÊ�Ιª$åqnšÇÁµ¢jŸ[1ŠJÜ>ÓF©‡¾”pÚc¡Km ÎSupt2­T¼Y©iµ(mÆ¡‹,·-y (e{{ t±*pþn\%LwC¯…!a×·ü·ºîà ëÚø{QuÍ¡D�Ђ4-£Ñit”´…–VÑõnþ˜,MAõŽo­çÚ+’s½ùPjötBÓ²»z )Y}ê+IÑ*Ä÷¢l5ªC)ÜŠ¤t*�nGÅ«Nm£ÊWÇë zG�@Ý}¨Ž ¢z;¥uºSz u ô�RÓ@(¨Ã*мÎG�ªu u 0�}Ç‚ü" G{‚#Ýÿ‹ ‰Yª™ `ŠkZK uM/­¡ÆÒ~käÑI�‡ ÚÇË›n1P¾µ_è" UfX�‘›‹åÕbÆáƒÍ <½&Û�Å;¢.žmëÅ©6ÂJÁ¿åXŽ©ßbw¨öc�§‡AkE‡ ƒ^‚Cm n�·Uxu¡zªÌ°J#7�:šCõ„ÔRd4:}ŽÄB‹µaÔöa¬ù÷‡Ú+Òa“)òÕ?J/|à鄦mw'têUåꯋ�Е×ÉÏOêƒnN·z$tÍ yêÕŒD'̪UÝ¢AÜà+½ˆé#®_©ºGP©¾‰>?ëHNà®Êô§¬*²}ß <‡Ëñ<�4•@Ã3¨]„fOý!:3VéI>PÊ ßá%ó0B\&GÉBúDš2é*/7H:uFQÉ/mjnå0NpMp²9¢9ŒHeÉÁ+ÌFS£Ó6¦8Ù¢Ö¥¶WÙ…±å!;9À^+òÖ`Ê`®. »:ªD«;*âTB=™Ò‘HaûÝ‘(%=ÎØ£*$uÄpò$ÄC'ÞgÓ|•±ÝÒ\¿ªøúAír³ïK÷|+¬ �D�Ž$4ޤ¨JB:1Úë©G(Ò¬¯Q]㙑ï‰Ñâ27HH‰6;&�@#桨ì_(>tΠ·Bkݰe¬ÇW�>3S–;Á”;k�3 EO:� ëWŒÞJw{ 7 #Àå{Dï –÷ýú÷†c¨±háÄHçIý^°…÷ÿDÕªÈwËÕ�fd;Dciª‘£ ëƂ⩌BçĪÙ J{«Ù(Œ [yÊ�ì êSþH×�Œ2 êª#WP:oª–6ù<^G+xðÊ�’z+Dhy:êc «Èˆá©!ƒÎ„UA½ ”îV?½I.êé}A™÷èÊé1F]UãHçIU ¼`h›—i•̇Ó[! ÃóPL]1F OÅèt&¬Šé] t·ŠéMÂpQLï ¼ÿ@WL�1èª@:OªbVqÕg ú[Ÿ‡´c²G´†‘è¡"S4t=� y*ËŽFçΪ1“3DK«6“qe.º3yÁ˜6ÓµhBsvâªOª:ïªfç ¼í-ßOr�*ƒ0ª=ÔÙ…@—¤ ÂS‡�«ø:(Õ­â:‹°Ü»È¬seßýMT1 ç*�q8:?ªHâÙ,ÙVÑ"ŸíÊ¡ M¶Cü…Ñå!9 ºZ,(ž‚Á(tN¬š‘� ´·²‘�¨pQŽì ª]þH×�Œ2 êª"WP:oª–„ˆ¢<‹6ñvHK²"5Œ@-ÉQеdAñÔF¡sbՒ쥽ՒlF…‹–doPöòGº–d”QPW-¹‚ÒySµT®âíðÊ­¶@D†‘桟Ú?]9h¼§fäñô¬[ÕbIo«“zwX²]Rû�2n—|³Ò(�«†€èíÀÒU>c/!!PXE‘Ç?t™xüx �eyÕ*:™³º*�žZÔ1èLX§âÞJw«»Þ$Œ[‘Êjë}AeöèÊê1F]UäHçIULó†‚ÞåЇŒ¡aôyè…B—Ñ”§š,Pt²¬¢Bž­¶�eG.C.Ah]pÊ ÞU~þðt~U1^¥É—ør=x¾­µA|‡Ñê!½6ºâ žBSèù·ê«u€ÒÜʪ5K»‹šZObv© „®'0W¥Œ�ÑyQuo·ë4�;Tù3C”bˆ¨ £ÊC!Jt™Ø`<µb€¡cŒâ¥¾U�bƇ‹tw å3]D Ì8¬«œÜaéü©ÂJ³¨~šÎ�¨:#Äj�‚êB ‹Éá)$ ‚N‚UD�”êV@�EXî]ÄÓ¹‚ ïþ¦‹¦ƒ†sË8�U$Ë$ß$U±�æi1| PµD†Qæ!5ºf¬8žÂ1áÐÙ±ªGuƒÒßJH5 #ÅEGª?¨~u]Q*ް«¶|€é<ª*«»ä÷UÀ çË4φ¦$dŒÈ£ÕCk(ºÜ† <g�¢“eò„¨hu‡,Ã8r‘r "AÛèDPnð®2ô‡§ó«ŠqQĸ®`�Ï>×”Ñ6†çÞ³g„*"ŒxqZC¢‹ÔÒS¬#�tR­¢µzDTµâµŽãÒEÄV× 6ë>º¨­�~î\EîŽ^ªèÓÍvÝ\'$=lÊòˆ8ÉÕLXIxÈ[ ‚.h3ˆ§„�«h%(åâaâõÓÕ€0É2Œ yJÎ@!Ò'º%�1HW™¹AÒYS¥Ô>?9›Gp;Õ,ÉæqV•‘üàÖ¡å­ÃpTaD{ˆÎ!8ºýÀ=Eê N/«x|#JÛ¹×alû.2w´ê`Eo àÔ\ÛÈõ„@¯7µíÄ—eô <ŽÆxÍ«Ø�Ê,¬v<:‡pOï úpOõKÃéù¶êÛœZP0çBì K³‹D…P@}¤ä»UF`\¥b‡¡§—;¿$s¨Ü÷7[îû°r—‡ʽNÏ÷`¹ãÔJå¾ÿ6å¾oÊ}OŸUx�Àø”»†ž~µÜ¿¤óŠ?°ÏVîbÿÍ•»pO/w}¸g¹KÃéù¶–»9µM¹‹�×_î ”{}¤äî>ãZîvzúÕr_%érÅ«g«÷Úàæ ¾öO¯x4Þ³äåñô¤[kÞ’Þ¦èë½×_õµ(ûæhÉu? äZùC@tÔÚ‡ �æù—¨Êù³lõßÝœúè:0bxjAÇ aÕC糧ÑDoqýºè}�6úôeP�1èª@:MF½¬“ÅЄÑDÄ­…aŒy| –‚– ? „¦™„ÎÆ˜hŒ)WUÃMÂHpùŠ,e¿/s‘òÄâé)×@”tºŒâ)F–[Íq ³—�ˆB*}Ïûåc‘Ph’b¡S2¦ sÚU ›o¦!á­/Ï: ¡*rõÔÑ((�6£’.óªÊ7ã+·ÚîÆµT‡!É€&&CS“ CçeLN–Ô«zª�¾™ jw½¢šL„JÊÖST°túTYe»M”/å¬H’,š­Òõ¼H¯4�¸9¥™ã¡KnÏS{Cxt­"4»Cô€ÅÓÌæ×¯K³_¨yý»—ÏÑ«vCÑë@Uól�n£"YŠK|õ÷'¶”F  «�ïmRtÝšA<ÅŠ@èÌX*ù@)7¼V2#Äå;œä $"}¢ Pƒt•š$�:MT»¢Ì qÙÞ3ñ§å²½™Ø‰( #ÍGE€€ôñ¾Ú‘ÆÓso—�9½â꼎—šƒ°”;éDÃë¹>hòB²?䬉�ˆèl¨J¨ŸXÕ1ƒ ^Ååê‚ßToQ„bsÂP ëÃã) �«Z/(õ ™;ÅøúÅ£¸ƒÒW>Ó'fÖUXî°tU™Á4~+¾ÓúMØ"~Ã8ô˜s„{º¤ôážR’†Óso•�9µ†š0 K¹Ëœ#Ü@]×GMžqF`\ea‡¡S¡Ê ~¨ðã(iÄÍIB ‚. 3ˆ§<�«H$(å©Hæ×/ÉÈFúDŸc$�1HW!¹AÒ©SEUÏfe”f%<€Öp¯Ó²\¾µ¶ˆÞ0=&5fº¨¬8žº2áÐù±JKuƒÒk¸†;Õ0Œ—‰Hõ×­´ÚB¡«ËØUc>Àt&MJknñuÿ²$nL.Ûa¬z‹­ ;Tk†$5†ÎψÒ/(õæ/K5Oa´¸‹­‰­ÓZ›ò*°V„3¬ŸÐ\`é4ª2+ó]1Kj�ð‚¦ò3¼îLùrkzž4„XcÖCp8ºê±<¥gâgÕv…èÐDˆG„ñå¢Dìäˆ7Ò§?ŒåèÀU�tºU�6 'žÏáaƒ«8›¯“"JÅC/Öy¶\‚Fú¨h[¨DÂ* UìrW‡sç�.5a9*³‚] ¥§Š�cKŠÇV&>ˆ}`ÊgÀö’@:÷½ÑŽ#<$ñƒèÁÔÛÐE¾ôì ˜�çŸ_ðÇÞ<ãO½±H]pMÒ>†÷ïf‹ºw­DW§ÑT~€¹„™J+†$\3ˆÄ˜DQ(¡c�w%(å0áj¤IÖa­Ö¥±JÎ`”>ѧW d ÒuBuƒ¤3§N¡iv¯Óy\%í*”U?šä¼Ûùªž\_ì²™Ejè;k'6K„n-Ù¬Ïo’„ÝIJ¦»SÄnsw¨Ê™1¼¤lÍò¬J¾Â}÷Y½íY½ÉR)Ú@Ô%ÂA»æ²ôC¹1k�˜É·âHÌÚ�$Z-@ gF :aÖ&­ùAˆF}¦“©� ãÉ¥ak¡Ãj[è�[r�vmà~ÐtvÕFžocx‘`w¡Þrèe‚Ší¡ß(½p— ë¯N·¾:°ßAφÓ/Ûê�[_)VÙ¦×ï)Ž½Þµ§åÐ.©)Þ¤¶æÙ� 0t~¬½Hñ2ôš?İ2ò{äÌŸ,èCÊQáçÚ…˜AX:§jê_/8×_q*ÎÉ u¤Á±ß®C齫Ì!Z”ý]„Çÿ ûÔ` ¤¾eê†öÒ³dœ²Èô^wKº$¬mnÐëPÛ««fpøá{ß »ï¸ ZåàÁ+™^'€gL¦ Æ;èÙÏѧó7go?EïÞŸÿ½:úÀ­Õwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/29/0000755000175100017510000000000015222270712023606 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/29/e8395acfda0996880204f3e6a61bca931ff072wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/29/e8395acfda0996880204f3e6a61bca931ff070000444000175100017510000000354715222270712030674 0ustar runnerrunnerx•XkoÛÆígýŠEpR2+Û¹ P¸u F¢bâÊ”@Ñõ $W¥¤•´ E\²‚Ñö¿÷Ìrù�l$­‘DÖræÌëÌÌ2«(Y±wïß}÷�a�{7`c±æ±ä�Î IŸ3±ÛçìíÕÛ+6KÒ½àìQÄ9Ï:�)ÏBJ‘ÄLH¶ç_=³]âéÆbÛŒs–lÙzf;n±)r–q™gbM„ÖQ±!ªÇ‘8m�ÔUè’sï§c×Á™ë ÆC×ûÈ>@Ï›€¾.H Ð`ÂÈ †rè�ؽãî€lpÇnðÉb#7ðsP›Mm?pcÛgÓ:™90?¬çz#Vœ{Ç ú°Š3æüŒ/lvg�ÇÊ”ýï}åß`2ýä»ïv7~pà™ýa씦Ô`l»÷Ú÷öGòÎg8ì+1íÝã�£Ž`ÏÆŸAàN< c0ñ_-Déµê£;s,fûîŒ2ò'€§tBƒr摞ç”(”jåu]ˆPÂtíËбÇÀBy¼á~‡¦EG¨Ü,Jv;°­úšavl3P7”9Ó"½N‡¤xÆn+ñþŽçcuf.—qxàËe·ÓÙð-úô–.×É�z^šëdû7†ŸŒçà?>ú �™æå¢×ïý´è]þyyI¿Ä]Ãbþ’æPîä-ć“uÒÒ0“|™?§¼�Mp�>úÔÍ©ÙU6Æ�ñx#iV˜FÏÐî´\šæY@€gØó›o¯Ÿº%�*(´K–k0ô(òô ‼Šùþ¦‚¬ßà+ÔþÜÍû Ny(äRæ›éiÅ:Ë7Ê/JK+4ª ³õ…ùÅ4‘Ãb�ÿÉãâÐ]È‹îO‹ãÅte^æoPÈ<9¼€•¼,Ó¼eW3k!4FE¬ÖzeEª¥ÎE”‹4â,â¿óH­^�…˜aWÒ=»+¥†µfœBšmIåà #Ž�Õæ‰G]bvn¦«ââ k”%!÷h¸µ#þoóý[>½ÅÐjrÞÒ:I5.SHó§ÚW²+h%×ù|×íkbZí)@�¤ÞÓÓBÏ©º7D«TkÕû°3JU?¢À¸¥@·ñ[Ïá©n-ƒÀ,Õ,�¥(RʇY>í%1çŠ~O/è§4šl�“Ð?ëf%ôÑ Ý¯C°É¸Š‚Pô9Eƒc­ êqÿ">ÒQ©Ž“ì€ÛÍyè¦ 4Óü—Åqa=Qw©.ëuÍ9N$Îz/›«Äj«}mµP)óJtÄ¢À‰ÀIÉkjT¢sú¥ QUQœ)%4+‰†[x@ÓLI�Í0EPýŒ�tLŒ<1ÐDJ�é'&ÍŠˆ_ãPS”RY«Ï±Wá2^è’€ŒYQŽëJLstŒ«¹â§²Ú ©]׊ïö¶=¤ äk$—˜ÙV3¤ÛÐíµõ5(êð0g»n�ØI4°I“vÚ—ö^ž|æñï'¹©~•xéÑ{W¥9Óƒä¸ÇÆÈªÞ¥pkC«H—æ¢Þ:5ÕÀ-DsŒ�Í×ïÏæZŠÐjEU„F¶¼2åû¹/çãµÙs U2þ8C˜9`<ÔôJà@yÛˆJ`T¼ö„ŽtÒÏq"¢˜ý¥T´pNž¾ü�þ©eÌp~V¬fêÊT²‚e\§âªtU�ÀY:ÿ:KÔ*ãáç:lÄÖåŠü>ÉbE±’<� D½…1½õ­¹Ijk÷q‹<$N?$ôå~=‰Œ¾46[µÒM‰šŸ7vëʇ7�Wn{¯¾W¨tè+}ô3žF!¢21½J`•A$o‹”ý�JRáé¦H/(óï,þz¢×‚/Áþc¯JÉš’ > «Òþ³ƒÀ”c:!”°Îß MÈwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/74/0000755000175100017510000000000015222270712023606 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/74/9e4a1f2066e29cbd87efc97ff73bb6f84e3febwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/74/9e4a1f2066e29cbd87efc97ff73bb6f84e3fe0000444000175100017510000000204615222270712031213 0ustar runnerrunnerx…UýnÛ6ßßyŠ«�r!8°5](6“%O¢ã( 胲8È¢!RKƒbÿ¯o±gë“ìH;v’n+ Àâéî÷qGÒy#s¸x÷Ã÷ß�½V ¼Õ¢ãP‰†.QðVñú¶äÌ);�·øÀDn;±®5\ž_žC"·µà°­æÝ>eÁ»�PJÈÖ Õ¼ãù#¬» SJªŽs�uÖ­¹ ZBÖ>–w d®3ÑŠv He2µÑ§d¥2T˜µ%dJÉBdˆ¥,ú ŠÏ´á3ò8ºæ0Höƒ‘%)yÖ€h�£ê=؃еì5t\éNÃŤ¢éK£áés#6bÏ`Ê­e”÷ �.ld)*óË­­mŸ7BÕ.”Â@ç½ÆLe‚¶±®ñq&;P¼i ‚@ÝÖëQ�Í1,Øäß·Èò>ÔróÒ ¶¨ê»)±)XSJl™eü�ÚDŒòJ6�|0Ö Ù–ÂøUWû±1üžåòn ífÜJ�zmÏí¬’Ýh÷ŸT�¡�7�í’c�MèÉZìs¥qú°•�%}íuü$bF ‰|¶òb4�EÝÑ)™ÂÀKp=paEÙ,Z2ÀŒØ Ù=D>xá=üBé ä·EL’¢è|P‚1N‚唆·pƒuaÄ  ¸¡”EÀ�pE Öù0'ñd†ÈÞ (»wÁ§,4˜>‚z°ðbF'ËÀ‹a±ŒQB�~а! ýYÈœ„lŒ¬r‡ Hf^X*o‰êc«o-îcz;c0‹‚)Áà AeÞM@vThjxtîÂÔ›{·F]  ŽmÚ^ÝjFlù<|&ŒF¡±1‰BãÒE—1;”®hB\ðbš˜†øq„ð¦�Xazšº�ìPL«­êÃD0Å4l‰¦Z¦Ä  ǾH6#=;9Š ¯� IX:‰æó(Lg'C ‰–¿ŠbònÁOJ—BŽëŸ_†‘¿Œõ¸ãuù2fÎ[»6±Í”øÞ2`):],Yº¢Stvñîòü¿2fÄNæâüÇó#Šï1/Hý9s*Ùm2íÂx<™£ö©Ú"§®û΅�ŸiÜîøŽg<Ópª®NíÑ8UÎè x  >´¸§ÓÔ§ISó†½Ü¿U}[˜Øp˜¦w^êÅ·IšŽÞÿ(´s1zÿ'<ÔæºvÎGV¢³áJek>‚£â}hpª�o08VxIBbæà•ÊíÑÜù8ožGð鈅¢ÁÜÁ»¨2TQ^¡G„â(ðßîøRò«“¹�ãÿ�mÄsV'Áõ58ùhô5-ÿ¸ÅûlGg²N•Sè>k®žuÞ\?[Ù>‘rˆœ†ùÿå%,~’x”†«Øl­îo)ûòù¯¯¤}ÀæÀ—ÏÃîõ[šN†/é ÎÎ^Ÿ“Fïf wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ef/0000755000175100017510000000000015222270712023746 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ef/d5d2983227352e1fcd9ff6492a5931b56d0a20wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/ef/d5d2983227352e1fcd9ff6492a5931b56d0a20000444000175100017510000000056115222270712030664 0ustar runnerrunnerx�SMOÃ0 å̯Ø� –˜Ä•_‚rS·³Hã�8Œñëq ¬mb=¥öóóó×eØlïî·Wœ”æ‚Ê’ž•ªÖÍÃæñzcßMÿ®P[ÎR”FÐÒèæö�—Þ3… 9:{(´RB.’©(Su~c�x {N£ì�¯rš#¦°“âÖ•Oz*é·’”#P…A$&OÒ¡kr—u>iíØjTPèµY;\Ž˜µ‚ T«—ÒŠôˆfNz±U~#ø�D&,3§‘{ÖÝÅXâ2®õÂF“êØ{q6ªÈ�û㯞#kvEÙC¢Ùæm%nv¡Ô KnÙ™¿†n³·ú8±2ÆxTŰ³-Ú4Qq!J¥ï•©­Lüö.T³y`¤ mSƒGH²9¤ó;èÑ¡U•å7�#î+òB‡AÐЯ©`°V°ÎÃ�õ_@ÒeýÀN’,býß¼®ð”}=R²ó~[útý ÒÛ[$wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/pack/0000755000175100017510000000000015222270711024271 5ustar runnerrunner././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/pack/pack-7963bfa55e62922dd2828ad10dfcc809e292ce1f.revwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/pack/pack-7963bfa55e62922dd2828ad10dfcc80000444000175100017510000000126015222270711031344 0ustar runnerrunnerRIDX9-Z]%€ž2z�ŽV—HM�qŒ>$G[rO* Wt 6^I‘›"™K:“ˆ�’i|aj˜)x–?T3nBfl@0v}š‚/&YSgs{Q‡†Fm'!\k <…#4=(‹wX_  A~5o+c•du‰NDœey”�h18JC„ŠL,�.7ƒUERpP` b;yc¿¥^b’-Ò‚ŠÑ üÈ â’Î>ò×Á¥pää÷Ú,÷øl…Êãæ././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/pack/pack-7963bfa55e62922dd2828ad10dfcc809e292ce1f.idxwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/pack/pack-7963bfa55e62922dd2828ad10dfcc80000444000175100017510000001262415222270711031352 0ustar runnerrunnerÿtOc !""#$%%()))))*+++,-./0011122444557899::;;==>?@@@@ABBBCCEFGHJKLLLLLOOOOPPPPQSSTTTUUVWWWWWXYYZZ[[[\\\]^^^`````abbcddddeffghhhiiijjjjjjklmmmmnnqssuvvvwxxzz{|||||}}}}~~€�ƒ„………†††‡ˆ‰‰‹ŽŽŽŽŽŽ�����’“–––––˜™››�ŸŸ7)f(Šø$d˜^Gdô߯�¥ C•1x·0f)�I½Óž–œ­Ó—žš¥ÐKÀ^îÇßu÷ ¢}|&tÓ&»®ÿ½|KNÉ<à� €½©Ðhõ¸î|ãR½$¡ ë4º�)µž(›œ0Õ=�P^yzÙ SÕ¶Õ[6·ùõ{ øezk».øu˜ëâ üÛ(å†�:¯º¹šNÒþÿÙù…Óº{ıÐÒ<«Ùƒ à:u['ø}až­½«ESú— »i¼9ç/Ë�‰$`ºr8h;o'jÛÍÙOÍz·¸P·ÒÒ¬fû{7 ŸÐ5û$•¹U!?ü»·œ7=>%��†ëâYú’˜@t<Ÿ¿•ëGüfãϤ¢.k­Ÿ�^" Z‹—O®ÃøŽ‚ó–p[/]$ËuLä ÚÕÆŒÉlËì íþ3ý%Ôº4š‡;Xß-i þª_ß�–(SßP©¡Æ€Ñìº ”°,vÛq*á»-o«ÂQàõÅf5ù4+î?Fû ãÁ{SëLÐTÀGì+hr6¬h挊=-Ç+àz­~¼+âzY¸úYÌþ&lxM¥ïð-,\,qm ·ž3–•m3ÅE¡•ž¼9-€Â2›ÚG½ÀË¡ÛgIÒÍÌ.¿Ô@«ÃlÎAÜeÔ)IQ¥0›.ÂÅMÎI‰ö]ÀàÊ@¼ˆ}'²1î�‚ˆ{ZcB#5'Êφö®2¼‹Àé;ž•!õÎÁôU\(°¦ 3w{{ÁTW­�tF ?áYåÐ3…ŽQ£:'HßûQ’ÉIî÷aÊž4À:mn~1 /WFïq¹å F®4ÚØ¢—ñìë–(Ip÷„P‘kó5ÄG‡!m¹+þ#E†¶T£ W7¯~ûë Lq†CÜ[Qò ù8åMûZØ+Q‹Iµ%»;7ql9ŽÄß@†B›ËgVE ^\c;€ß²ïã�׃ü‹Ïqô�;�–etÍ2*o0Õ¡¨t%Ó9e;ý»¤¼!§ä«lZê%Fì$*è¤3R`=†ƒß{c[¼ž®”G˽¨^Ê@Ú':• ƒª%H¹-LŸ¶Î6„"¥ñ °]ýð“I¦d«¨Ð¢rÉ5­ðÎ Ñ ]êÕKuQYáL÷ƒþaþou1s5NwWÅÀÐi(`ïJwa{$˜ÐPágd…ùíÛW ¥,+”^„¬¦kž§'Üoi‘Ö¦›H¸Å¥XŽFk<´=UrowUxµM?Ì+¨U�Àfl¸¨nÏ•;Û>‘iÅ…#wP´mu×™Žàn‰i9FÚ®uèC—Ÿn YäGîþ�*Üb3ÁP3=_Joi�a\@‰œ@½Ì}5p>@&�oú,o S+¶u—ûu¾d"{`ü·vp·ÀsõǦ7$�Õ¦ókØ{q¨¹bh× |]­É]|iœËÓóv c"¥täx!¡ª�+y:P(Ð3vÊnï ùŸþc'¥JW@¨Òzçvã�ËhsÄÍþMDJ«ÜÅúMÇ…"zt¡Qw€Ù ê~SöŸeõ¢]”�~ÄäîK†‚ú½xóºÛE£€†õ¬™ÿÒ²=åÚL\Qš/ÀXçD‰<ƒ¦�òt±€¹¼—l[Èks�Øw}l¦b#Çœ‡/!¡ƒ…q„‚êÍúŘbW9–¥#ÙŸzÓë†2bƒ$0ŸW»†ËÀñn(-ÿ‰«‡¾úø‡·7¾Ü&èǼ¯µðšŒra½ñ¶½µIÏ<<&Q|3¨­A�8$¡ Í„©�zèáyº¤ééT�Ò"2.ekUûÅêŠ `«h¾\‘A2•è`ÜTØñÆÕ_ìŠLä%Δ)ÍjŒK�v OŽ~ 4ÓÀ80f—„Ñӿ  AYóWœˆv8Ú˜O⹂C²º] 6b—ðê.‡ç›å­W½ñ¢þ)Úô™ˆÿŸÒ›ì2³SRþœø¤�ˆA‰»¡z5; hRØþ sB¯ÄXx¯?³:ód¡º«¨0ÑnŸ}ÐUj鲎>‘ìâ£%$¯îSYá…{©’û¾_Ƥ6ŒìÆIÒÅ'½?“Ë’0i†Ï�¨jƒ× Ù°nHóëcG¥BžŸ©°#ú ÕÞxÍ'Uòu_O©ú«O¾>." – HÎrŸo˜¬|×'¥r4*�tq-«�ìÓÈܯ‘Cðk3ñGéC¨‰Œ†~{ô±²ÏØ d½)W�æÏê©Ì#pý¸Æoÿî) XÀgçfë]Ó¹ý;söÚ¶[¦¨²ñôbJ‘ÀŸ˜rº»¸>Т´&—þØE€y¾¢)IÇQ¥¤:ÉI&–ÑŒ£m'¿ÀCÓ×ì{½ûóí�Ÿò“·œ1¥ÔÈŽ‹íþqOÜÎúʸ‰] Ó‚9ã„¿á…¡ØY`lÅÊ»S,ÈðÂtüî·Lܼ=Ð̬ŸtCLuPê™±ò�Ï€u€ÍÉ¡Ç-]3HPÕ ÑìnK7i‰ù¯ÒO¥SÍKVÕ¥©êª_~?GŸ™ÞÖ/Õ�Ñ‚G¥¸ s•Ã)p™£€ØmáÒKVD#q„.õ…Xdô�ÖØzŸ9.” A!Š#®ìu]ésÙù²ÒÃ^"›$iì}ÿñÖ)É™áµÚ0¹Ç–Ö(ÍêãR`Â/ȲàìÚ?8yÀ^¹­Þ=ú�¢NÒq“çÅEÛzÃpû¿9»åîvy!دÌ퉧ܤ0ˆÍ'¥ÆÛaoܲ’¾‚+%þ£ß‘͆.~n‡´‚pOZYL3±zâÁƒ €tz€{è@Î}ü§ão§tñbXçÚü¥¿w,ij– «×ê°hB¸¡í`çᢈö6V÷•Û…¾è%ëñ,Ÿ÷H·3&Ä:yL2Lƒ2u¶òˆp-/¡m<ß5±ZŸËÅR͈çòíaõ¶}þº¤U‰Ç¿€h°üóX„ �Tüû—ddä9üBî[+ô I{Ï|½Ià楹-á]BN÷ôM0ÞΈðöG­G~U Úôyð·Ô“8–‡ñU¢ø©j˜ŒùC�&"®£P)8Ê¡± á) [ù‚MFK¤8®!qo›š”ž¾JÛúHIï=U!�JΩLÙ¨±1û"8¬9çñ^i‚-üP6‰Ëmúû?ÁNdèZë)µ?Ý„ãJc[ýaùÁén£®�"±·/\ÓBŒVýq‘r41 ™Ì u6:þ´Z�X¾-¬\b‡Éo¹VÛˆüþb'×:µêñŒ=>‘I‚z> çˆÃYhjúz%D:ÓÅéÁÙ£�½Ù£t‡«v0½cRÖóG–ÎduÔ^4ÙŒûþ_±Æ–j;Øø!”s“º}±q¸ eWZ^É5‡'»­ô-_n;o*mŽ÷rÊšó~ ªW7éÖ×>“&œ2>)ñ—)¦6 Ð3Öc”,³¿¶y1jÐì£áÄ�!™4� ‚…w¾hÓ?ï9¶’@Õ “_#™y¯scI�Æ1ÃÿêœßÔ!uk—N04QÑ^¾Fg|ÙIøÀ7åÚÆœI~%îÂÓìˆþ9…µžScãD’pÚ¶p»…æZ,ê%:=ìîAS-#Ùé’gýYÑ·QÁ´Q2 èùGž‹;.Ep–u3ðÓAÑ®E…$¶Ý›"¥p\^‚Ÿ�¢9Öy(þìZ´{zÙ{p�È‚˜Û­ÍÛ»©ó‚ß :ýá'zؽ¿¢æqß,+LÎî¹™Ô¯.«º~‚p™Òn~—´sß¹ÒŠ)näã'–ȬLJË*LÌéj”ŠÑ¢ïçÄ®dP¬NñÖ.ƒßVGÒ ÑN *$G½|¸øsˆÊý±ÜBLeœƒ:o²}µçagé�„/¾§iˆÜ.G„%<q—ÚR!ô’SÕO¥ÚŠåÏ‚ã9{Å�F>Ëa�IÃÏßj€c4¸Ïç´aTø° Iîè;‘ Pm§~»Ôí—v.9…t_!­ȵ{2ÀFç;¶’`_‹ ¾-âø[¬ýhnÐð-K5õ»‹ËÌ{ð…ãìµ­ìÆ™6�“^ÀÂï5²Å^ù“X®Ú‘w3æå¿*}ÐÃG¾7¥<†1¯#¨dxÑfcç}´ãEå'G±T kóˆ/¤SjñlB: (–Š àº„Gˆ8®=\�`‡NvXr’~mFQô²… !T…˜åǧ4¡³¤DàoìâÑç&ýZœd´’=tB„ÄŸ…©Ïæ­t}q ¿ŒfE\s4 ùФ~u ·®È”;ÐODnipr¬½nR˜Œ€æxþ›x]€j°¹þ¶=ƒdkÕxv�#ÄÞŽÄ<åZ…€•>_Ê™h_NÚ¸ƒË6K°Ei&Æ£ VÈtÙuðz9˜èëi|¾ø!s9›©¤%‘Žs¾Ä”lÀ�Àôìžøvß®]úešz›ù¨¯ânèª:{¬Í}‹E�ØBíÕÓØwù‚]“m¯ÚNT‹l3X±ÏÜ(»üzÝu’gécgdm´4Åâ²âyÅ…O ±yc¿¥^b’-Ò‚ŠÑ üÈ â’ÎWœÈÀíŽÇÞ(;²¿Þ¾Ñï‚kÓ././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/pack/pack-7963bfa55e62922dd2828ad10dfcc809e292ce1f.packwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/pack/pack-7963bfa55e62922dd2828ad10dfcc80000444000175100017510000054462515222270711031365 0ustar runnerrunnerPACKŸ”Jxœm“É®£HE÷|EJ½´^“$³TÝ*&c<Æöó.�d0fJgzàëÛÕ­ÞU,¯î‰X„£„E—P–¦96T’aJ©^@˜c¤C]Æ¦Š¡!cŒTaÀ”t (HÇ’4¥HI*å’¦C#S2œa“¤&ÊQ�‰qñ?ƒJ ÙÈri¨0Pnz¡àÜülùœP$«:Bæ¬ê)ˆû¡ª 8Õ#ü(köóWðgKþ’nÈ’bšš¾ ¡�õm[³_E¿f ž‚]OÉp{ÿüpOÿü~ƒ•Cy¯Kðõkl϶`ïïAø[ë�DÞ¿¹ð¼Ï3Û²lDzB;\Žñú´t"G¡ιšT7˲ú2 -ÇÞÃça¼9Œ"šrnñ­aæ(ÐF‚Nžçé…àÓ*c/g_^VgrVnºÓÂWßg#ÁšT Åá•fâ ³J÷ôr> ÀY|7ù²ÙšÏ•ëÐháubt�àÛ; Ä�×þÕ‡ÛìÈ¡t—þÔ·á&4ªhÜö{iŒß‡KõB:íâÏÃ7•³Ú˜íÎÈ}^ûT÷CÔÜÛ7ëøëô­]òV9’s< Õª€ŒÄÓ.A©Z¬”NÁ뙉£ÏÎKûB`³™¯U¶<}{tk?”ÌR‹ËÓá¨z^#%<­NîÛÀ6‰ŽRä來)ò#ÅjåWOk?Y<ü@‰�fƒ�l k×¥¯ÞÆ¡:õxæûÒÓp †Ü¦3³œtVΖÍaB½õû£¶›©ï´¨¯Ä¡¥Œ×z€ohÜ-Ž6Uƒ9ߘ•µÙ,–ƒ&SËY,v²fÉt¶O‘†�äztÏØâò84Ͷ;ï²\±0‡w¿¿4áQ“HÀ5šqøÜ®W»Vš(Ïì‚lPQ@÷jÈq¢g——/;Y牢ž-q•ãóoZ3G¦p?ná<ýNîÊh„3ú×Uâ_ë¹­}fëÎó±½g”‹þh½èË�CÍöã”((½8²]®ñ»:°ø¦g…7Eîþj‹¶£´Ò7óCï>ÅdÅô%9ô–kí/Ø~oÕ—ñ¨‚µÞVÑV¾CG|œ B=ÇrðWi¡ðŸÞÖý½†В€�ßn€’‘“; ¤‚‚ö-hÈ­ëïbQ¿¾(áw’µ¤ã_C¯YÝwuW BôA>ŠŸ5Åóþ ÚþADJîõD@Ý�¡ø²¾+ê’SòÃ�u@¤-xœ340031QÐKMÉ,É/JÎÏKËLg˜èh4õEÂ���]�—ÓåóDõœ‰(è¥g–d”&1Ìð´³ÉyÛ®X9³¤é^éµ?7„T’™ž—_”Ê ´€O&ª{ºÿºÃl?úš>O+ˆÖ�…ªröpôswõñw×ËMa8³f~‰³O©HÀ«™?M<ßPÊÐUçãéìêìïà£WRQÂð©£@Wa®Í}Ó�QóO :ÛñM©L-‹PÃ^†•2¾2ïxWó8Hd¯ÊB4µ¾ž!`¥†ïz›:ª£ä“�$•MÕO�oú¶ª4ÈÕÑÅ×äÌ· Ï.êøÆbö}*ëíÖ}/T_×C%çç$–d&eæd–T‚Ô¶ïcúõ£}»¨ù¾;j/ŽKíY¿õÃ,H¦ä'3ülòuó^b±N±0¶Ü¬)óöyq܆ȦV$æä¤3<Ͻ~C㪠×ü36¡¥/ÎXø¥×ÄÊó‹²ÓròË‹TN—ú<á¼uõXÏÉœÓo¸Þþã7þ ->' ±xœEŽKnÃ0 D÷>ŬƒÀ‡(ºñ¢¹+Ó6It%:†o_Ê Ð�Hq潯‘éX©Ð\h]°‹-8t+ÈZE|ß4%ÎÖã]ñùuGá¤�–dxй}⇣î=C•´Æ‰)WÈÔÚÊ3� Œ�áŽ(�sõy’È׳É;¤ð¹hûißu—·ºr–<¿l·ýæwãjW O LZ�«£LÈD½ÁÔÅþq[¹`_Èøá�×¾‚œ,yl±&i§Q5*�ΡyUg=ëy¼tÙ-qý¡xœ340031QH*ÍÌI‰OÌK‰/I-.Ñ«ÌÍa¸ùsÓ¥ÃqJ³U2ßÔþÿxMóä̇[ !ª“3R“³ã ŠÀêþ%©_·Üúêc�­ÝDϦ*;îçPu)©9ù•ñ)ùÉÅ`¥ÓSZm¼O¯žçÜæôséÕ¥N¾Q8S¿3xœ}SMKÄ0½÷WÌIOéêê© ¸®(‚ž=ˆHÚ nÖtR3Éêþ{“´J«®Ph&ó>&¯)É+Ø? Ú(0ºvÒmA’<²çý¢°T]àUzD5+ä ¡•ìÑÁSóìð-DÚ?ÀbmkNý:™öÀèÆ"Ú@¨ù æóòà8w"©ã$ pÒ’�×–x•›Wüéf>ô)ŸfÑy�’óvV¯€ƒ² §­/Ʊ—Æ€ÂIñŸ4=`ZdK)ªw¹51)¡p“Êÿ*Žrñbë56^hòÎr‡yÜ Ñ;ËÚ[·=,2x#�l ‘•m„·Öðd´›d7¨÷ÏÙ�8ÇÙvùÄ»€±Ž"ßëüùr1ð‘6Õ°X^\/.o+èœuÓ|Ʀ¤i-A,{ß ö.ºì†æ;ôWÖ¿2þÁÓ#Ø$ ¸�‰Ápî±J£¾vy–RQ€”4–öö†ÜÊrŸÜþ}”þ/Ø¥ÿ=àˆý �b;»xœ=�ÁnÂ0DïùŠªÄ¥©Çœªr‚âZUÈI6ÄÅY§ö!þ½Nâp[�ߨ3˪£«ã ¥­T–Eiö0º"öuqD±¬2˾û`ÌÙÑ_ /?YökK_dXðó EÀöùè e` ¹Q}Ó•êýLåà)Å®¥êŠãi�àÉ8¶-m(óÞåK¼\U¢-:2¤"¶½}$Ó=²Eš�ä{¥âíñ˜…Í$<Ÿ/nì¾KoX °=±æ ¤Õci¤ÒïØÏKAcº�­u£+5ò‹’–uÔ\“ý�½oqHº‡rÍõh‹”’øÅ•('°Íth´‰Ü>=Oõ:û3›�p´ixœ…UQoÛ8 ~÷¯ º{™“ Û“�×­C;ì¶·îép(d›vÔÈ’N”’»ý÷#%'MŠ{3¥�üÈ�¤lÕ„ ¼¸@oÜz×Ñ‹ªr¶©žÁ_É8 >Ñ ¢ #FmGˆ+„•L„6(Û­/(ñƒù©�¿Ï&EÃÙ?UŽxnŒÛì\‚è $Ë¡4ÁÖ…õÀ70)›”1;‚›2Íyµ³BÞr„=ð¶×äU옰zß1x “&Ê`7dßËO7W?ÞßÞ\þøUø”°sâRë„6ÊÙ¥ŽW©…ojDªŽbH%�³‘a\G@ÕK‰‚j`tD6u_G·F»?©æY3.ÁY”] A¸h_­µ÷"d…ÿM˜°‡ãÑæÌEmkÜ�”íÁ¨ˆgô‚¹®Ü7^rÛàëõ t¬:š¿_±À[U ."0 !0®OYà£ôHP�›¼Áˆ‹êPB·MÆà’oà,Kq&"eÖúˆµ�Ab9î\Ke’Þ'mz`›­V¾Ë Hz5O¤6Ù˜êRb¾â©ñTP5Ø<¥VØ­]*÷‰¤ªÌDz›/ÿؼ9qâÙH~nï“~ÎzLë\Ñc÷s!ù^å~Ï)7@‰%W§W{�O–"k,‚¢íéI7=c&$–Þèv«v†{\÷¸sŒëúM6F×ÞaYÞy,͈èéèÂîõâUo”Qˆ3/2�¦3t’Ú¡;N¨ðç†@}�÷JšNïrûø@žƒw1$ù–Æn¸Õ8è{x~uýåã Eiô¬Á1•ÕöNAýafÓGˆCznƒ0h΀¯y=Ws"û³0m ÷’V*àr.y¹Š“ɆQ; 5¿Z,òbyKúÐ?X¿?z ­/Ùþ3ÛßÅ^H¼XÛïóÑIÖ?xoT*D=ðP=9d)cÊ„Õ{¤ŒZ^Ž‹‡©lHÙÅ2üh7:8+·Í»ðŽ:®R[bîIƒiàùÏŸey+½àÅð)ÒBз ƒ_¿~·€±çr»oäCõó�ãñCº×Z÷ÍÑËr8>U§öû÷¶úzU)�¸xœK*ÍÌIÑçJ/ÉÖ-(Ê,ÓO‚ðµôãã *““3Rãã¹´ô€l.½ÜÊ‚Êx°˜>%£é°¿xœÅYÛrÛ8}ÏW`’‡‘j#Y7ßT5©õÆNÖ³™Ä;›­r¥(ˆ„$ŒH‚€R”‡ùö= å±7R^¶*YÑ�îÓ§OÃ/ØëÏç"UógÏ^¼`÷Ÿr-RÁ�H¾ø/zÝ>þ|a6d…fƒÞàäY‡½‘_ǬÌ3^°õBäÌ”zÆcÁ¤a2ËD"¹é†iñG)Œ ³ŠÅ©2‚ñ™š©Bä2Ÿ¿d÷ƒþÙ—ÖÂÚÂŒ�ŽæÒ.Êi7VÙÑ:[¯�ævÙIùFèŽYˆ4=*Jüƒ7ÚÁ+3ÁTi«mû̈Xå S3¶æÒ›)ÍðÝLÎK-Èà w¨ÁA¯6(’¹`1dת„©LåVjeXL¡$‹ë…L› úŒ"qfû›í“YzVh¹#š9Çq¹±ÝbÃ:¯XUŸ]¾R2anA¬ÒT©r¶†flÂR9ÅV¯UVp;fÓ2+�²¢Pšróöî_´ÃjØŒºÇƒFâ{”øÁ9û[Êü1ö¸¸¹3ž$lÇ"çtd„�´0…ˆmä²ÜjOŽÏç�<•ó\iÁ&]÷ ™0±¹eÓ KÄŒ—©¥ØõÎ÷��4˜;Â;uÒ ­¬BT˜Ðh@` U”ËD^âä*µªpfNÀÌ)™¹ÊÍ8Äð¶?O©Êœ�ŽbIÐót®ež„Ÿw.Ä}öß„;Ÿ•ÿlg[ùºÆ[jÍæBeÂê�Om®�ËÙ sky¼ðHëðú[ˆSáìVMȉ˜QªRa]al°o†Õ¼rÃYŠóÞ B�¬ÇÖíB ¶â%ûÌ7)G¡a—BIÏJ … Bk,½÷’MÖ9ØÇ2™î¹ð¢+¡]5àý'FM/`²Ppà7aT^—Ô0⾬w@•ôºÇ£nË/8,q»µˆ›ó³ÐfØ«_úÝþI·GïMK™6Òî(õ$Pj�uPH’Ò¹s:'8ìÁédŒ"�H³ý"£\ÄJkTUºÙ³ºGç[\qŸ³[Q� #ìp£ÕïØm úœÉX:ü)ñ:Å­ðO)Œ—¹å1ùš€V�7‰°\âÜX¡O2ñ_¿ ‹Èã§A¾§¸š”…!wðw{V‡eJ'µ™ü ¾Ñ?= ƒTxÇ;í1Ÿ|MæÑ:‹¦ ÷ñ¸@ˆ'/«*±”OSD“¸H\¦r#Wbß° £èý‡»«q¹5Õ‚bºˆ_"Ðb%Uiì\Äáa­ô2À�WåHÄÍp 0:Ç—9(ƒJÙP—hT)*ägÃB—h8ó<4b¿ò< aŸƒ Ï¿w°Þÿû`®ÒúƒPj”‰(ºžáäˆ9º† "›ø)Šž1¼p�Œ#¿w4\p Iã¨ÂQÐfÌîÉ—Ö#èÛYÑv{¯ú]´Và΀RE^ûüÒ�Îðʈðæˆ•Çã%Ÿ#ötb�€…Üp;UÇÖµ–ˆ`LŠ$/ÃÛ®Xñï/¢5«È•ÈÜáìÌJ‹@nI0㔀ŠÐLÌóœzŒqDë8Ý•8yLìs©bp!¾JZ7~ ë÷o¥ýg9e7ðÞ|iQLºUŒ¤zX›í}1wºM®(à PØ-–o;ñ\ÀsîúÕŠ¤‘ *? ­ _¾$"ü‘ Κ~( ÄMç^"¹®B2èØ–‰¯E zko�øÝ³û£�ÂÑO«£‡Šl“ÞFuP³˜¦ä‡K™sˆ*j®¦Dã€0"M×ö }?Ø?©ìC·¸²êc‡wvKáÙ& Ö^!£Ðj±”w‘ô—b3U\£Ð]4d€\I»qzíÉ]€84ªÍ‚4líl†VC%=ƒ\ÖÄ>O½D]Í·¥Ç ÚàRT¹äWïELPU�=íöûP1…±  QœñT~OeŠ)¸“$“»�Ÿ®&h‹lòæâÝíUÝLo]ߢ¤xõèjÒO$¬¥~"¸€V'o\Ûe1ØšPáøÈ·YSN«ÏøÇ¾r6ÁÑ¢Ï×ï/?|Žn>Ü|º™Tƶ zxxY ÏÚTVW_9Ñ2ˆå£Ÿ}&ÛõÂ?›Ð 41’>‡ï:ñdçåLhLm]äÎר0TŠiµÊLPH¤:@;{"ˆjXµ�µ¨´©Ô¬¯3�«uúÅË&ÒÂ×?½ºåÎö_¶½ÙØ‚_m¼­k7`:Âo¼îüÿ['põ°ÛóqßÒOÛ÷ rêÚM?ˆ©ˆ©·â»²ðœ45æu Õÿ[g2W©š»Aó9áp®=@háóÆûXEªyQÚæ¾BÞ.¸%rò’ó³ ãúË�Œ\Ü·~FÛúñÝ�½W«P>•I€�=ôÔ]Ðm�Êí£ z~À�^Aõ¼¨ÖþxV< ©×$°bi\¦0…ÐVÂríÀñÉÁŸ¸ZyŒ§¤‰êóÕfΆ›9þ/3þƒ¯ü–ÿÏñ¡—ný­Ä¸0fTu¨—‹Y™Çþ'C �D‘ƒ0†…Ôðÿ€!3ø?h×’ŸÇ±(,Š1ïô™Õ�J�NW<-)Àj¾„¥¿óT`"°úQ{áa»½ <´w‘?›º#†X®§”úú §‡'âtØ4çL„+jïšOÉ}Ì ‰Ø%]ÄAFƒâI:!î¨Ñ�]µIR€ �ÒÞºwr8uŸœmëaTÕC¿Y—`Ytÿ4 m¨†à@½È±Ã‰%´3^Z�Ó’Æ/ö £AÝ¥9ßÄ£eu2:ü£-Þ¡Øèîòiw›«(öî¶'ÝPÐ}ö³M¸&­ýÎ7£~3ýTYî–Ä wv<ä¿f@¿#:é9×’Ïs@KÆîîƒD†æ˜‡Ñ.h®g?ý®«v¾´ÆÃ]З5mó¤p¦’:§› §ºßYV€æì> X¦$Œ, 4ûnšªéQ†"úhg£n–4 áp^>ñ¼¼WG†ŠúŠ¡yµœïVÌ¥4Ì4S?»@¶ý¥c˜+íÿpºvàà³ð¦j’hðhM9z¾æ†¤KêG&8jQd�µÐœGë±Gëë”k9Ûx  J]3$F¢wooÞÕ™’Œå9RÌéB{ÎOXl6HSÆþsù¶*Àz,pˆÙŽÍ?Bß^¸±‰Zƒ1îðß­ã?Yëêòúnìo)*ñ´&YÇêÌ:ÚR.¸ (­2·iÕ Èè?�̓;ðãß ¾›ÔBã×2 0‰¢�Wï®__½¿½bwª¸£(x¹Ns•ît{D½j—,UXo,h–^t‰õ½æÞ÷j9æpݶ/A�Úün!ûul*ìZìò�©Z]õ›¶i"KuPÝ9\Tšâõ5ký^»ƒ©_üyeJU‘C±T;˜XKÈú�UC¸ÒÕ Ý¸OÕûÉÎ ,a´š%=@:¯h¨ôÈ‘9ÀËݯ¦ÀšÂÈ•Ö.pÛ?€ÜðÛšŠ8N« Ý>ÝÌ]ïê§gÿ^ƒzL½”xœÅ}[sG’î{ýŠ ¼ˆŒ€hËž±w¬ G@dá ErIʽ�º@ôªÑ�í iì¯?ùef]ºÐŽ}9ŽÝ‘ì®ÊÊÊû­­=üï·ëÏö·ùõünveo?¿»Z\ZúÿùõýÜyÿýîš¶¨+ûãÔþðûúÊÙ¾ÿþgcìe½Û7Å㦳g—çüKû¡qÎÞ×ëî9kœýP÷UžuôöÔ.ªÕ…ýç¦ëví/ß}·n×uóøÝ¯ÆÎŸ\³¯iÕ¢µ;×l‹®s¹íj»¢åmVå6/Ú®)–}ç,=»¤·øcáZcëµí6ôfY¬\Õ:›×«~ëªnjéy»ÚdÕcQ=Ú¢ÃòUÝÙ¬,ëg—_˜Sçåÿn—m—¥ÃS'hs•k²ÒÞöKÚÍ^鎴nf×tî)C\ºu Y×�i=6p”ºÛ¸Æ~+ª¼èÏuó­½ð›è[-^³Ûºíì‘wwM¶êŠÁÁ/[ü5wmñX¹ÜÖºì=þœíí¾î,¯·Àg»ñ+1Z!Î)Ö¾ÛôU×dm75ÝŸž¸¨:WårO�}Ödô³ïhv$äã AO|þŒŽS?6ÙöõkZh ÐÛž^¡ëjÜ6+è),qÌ`‘¢kmßÒJúÂ< >M{ô$ž0/œ) œ Â)üŽoK¶Û•Dm´s[ã\Yµ×ÛúÔÒe-�Jê—{†0ë»MÍ0~­{»Ê*^ Ã*Œ-=KG¨k¦„/WÙgBÄÎeß0à™âO8_ãÖ®i@Û„9Åùnv �‰ö¼¡å�ŸvH5v€ún“u¸H³Éž„DJ8Qð>{¦×Ý< ùÓ [[¬±¤}.ÚÍù4lAgX¹â /÷Í KæÎÒ=Q�®c¦åÍ3Ñý˜¼Šg2ÛÓë¸m‚m%Ða‘ÊVîÙ0œ߀3,÷­ªŸÃºy�5™f¿ÊŸ5^íܪ*g¹×òmTNp¸kÜI¡ .á,wÕw„CÈšò"àÌÚoú'æÎ¾iÀR ŸGžº`¹@7]ãâñ .Ŭ\ÓoúÚ1R±,Ê¢Ãe(š�ÞRŠ¥)¶/Ö @"ú¼Xƒ$9\�ÀÂïpè”À"|FÆÌZËý‘mw%­ûm¿ÚDŽ'ÔmV1ôSW0F˜»íÚéa·=ñã.kéo`aĸUA V„B>Q¶uFáj+WÎã…F$Noï™é¦þi“�ž`+P%­3#r @µ"¦l%Ò[­mĽab¢žLOï‰:ÊzÒ`T(‰ ½½:F^Ä0ÝÆvÏD�#½iÎÞœÙ�ÜëX׈òr— Ê>ûáœpN"Bè ‚I™ß<OžîJ÷HÂ�µnËJ^Õî4½AZî;–ŽJ(áÖ±kNõŠ÷U‘÷ʇå/“ޏ"9Ù�üsìJwão¢q¢žIx6Ð%{&†z ].dã%‘ŸˆÞÔ„M[ºã¸]ãþ»/§øfø bÕ €–Žˆ¢ùF¿ÊZ#Â$ŸÊ- XËg2¶¸ R;0 è­¬SÜä–d0Ù.ußÒ± �;ÄAAû1Þîë-#­X‘Â�r.›­èæ@ÂS•GçnúÊcÄÜx¡È™¶ˆÉ²’ê7üÈ6«ú5™Ä�QI×Ö,e Ó ÙЙ0ŸhCºêjUowÄœ„%EZ$+ Œ¿_¢$½‰DO‘Ì"Ël»'RÞÒš+C+ßTQ4,ÁõjÕ700d3²Ã™uÞ¯:1�Èz"Î{RÇÀ9½aRÐ0!!!ZGà? ]±åǪ½¯€Õ]—Ñy†¢õÙ‰º‹—„(†=‚AR,æë ‰H‘j�9¥×šØËƒJ;,ôd�ˆ²†@£ß-éJ«®P,«-A¸S04ø{Nœå +b˜[-öTxfå-ý›XjÝãvq˜;ÑB˺Êk+7#Á.FsxQ¾©u0нØ2» FÑBd3òQJ¶y îðgȽûŽþÕB^öeíoÿ€jâ&‚DE È�E SÃ:Ú~5D3›3¯w}³ÃÁAŸÄ†M+æ>Mݪ„ÏkVÏ0:˜5Ÿê"’$�F¸·9ˆ´‘‡=@bê1†Äª_ᆵ]¾#ÙJ7ÿB£'Èær]Öì/ÔRK÷Å îÞK#ã÷#.T¡Ò·²mbø­«ºz X‚§ D®z‡-®š˜¢cyR„'ÖLä8²RäÇŠ-cz·qÚz˜ß}º·³ë÷öòæúýâaqs}�‡¿¿ u¶.*ّߟ<$:f"æ)߯ç¢�´Âe¡àVNÄàÞºŒNôÝë² +(³g•ëbRÓFCßʰg3UÑB$è¶�D¬ eFÆW€Û‘»ÇˆNÁ†�öÌXÂÖW�¨So©h�‡ÞÚyF›é#âæ9]yË*ÆNHåN詉¾àÚ _É$5‚ljHeÁK¾rVÿ“E|™MD%Ó"› Ê{Îl ʳ³~ØeMçïïb¢ó¬ÝàŠDaB¤Gë"SÅ0a½RuÂ,ü¸Ê� ¸«D%=�»/Y?0ph½,� (¼_ÅÖ ÿk²dÿ bãô)FÆÌNV5­EÏàwE…+`â®*ì©—�,Ï«µ£ôÏÉàîì‘ØõÏ9“ { ¢Y+dä§à§º'6O°÷Ì2�ˆØÈtíœGë@š¤èDz`jƒïTTk܆c‘(ÇòiÅOÄ;"f 1ýtý�[õ�Æ<ÀóÆ IŒ8ü†xñ”‰…Ž;»Õs‚Hó—=)à GÌ@Žœñai9=¦M… 9xJÙSV” ªïfÇÜ.*^ [ˆÄjE+ÃEàË‚>}÷ƒXæÙ•e¸ ÂÑ““;ø<¯VB8ËWa{]Ú€øÕ­á[€E¥¨x)„…Ol3T„®Œ�W¹×Œõ"l)“ÅC µ0rã°`;²_[ØÝÞcZCO?aFh¶Šû<9Ù€AšƒÜ&yOBc¶ý‚«ºb{ýº†ÉÑNŒúDl剻«——ÉŽUá5*-´-*Çê6\kRåÁ)‚v–pEØ;’[Åû?ÃttIâŽ)–^6êU’ž)Á*m¤‡Ð 5‘Ùâ!³Å‡¼€mYX Vy ^7 f¯VRvÙHîY/&£_¬9øï�4m+†m+á!>$YÓ¹ç‹å¨k2CÛOÅÙŒÓÅ ""†WÚ:ÇN,cCmh &Œ7ä%°ëy ×ÓëüIâ�NÔUNÅ‘˜ ‘Œ£?oržã[–)³Š‡ÑA)Ý,ÿ˱Çò‘·`{ÈÎÆ/š ï=LÕ¬ÉíÂ#-¾ž RøQrÁ#÷´€&Æ.VÈaSôdJf`Íú‘týì —­Î÷ˆ^L=*W™X‰a£VÌ;–ô‚$fûU_f!Ú¶J²þúìÁ�JÀ3ˆÞ¡•{1ƲmMÏE—�Í’UÅ‹_"ÞÑ=»7DPË&ƒP›ˆvT©ÍåÑ >T·š [ù)�¹1ué”òϲs‰¾òÛ¹GBECrAï‡äÜê[ö(BþSö_„„KWuÂâÁY‚TŠ&mÀ�›äqæñå9i¨æ ‚´[K«šè`u •û‚õ‰Ô& ß_˜GExVuR{ P¬(Ñ%1„< bc&#(&J6`¹š6ýƒàRR%æÀ£°ÕØ1c Ï/™³o䥺"¾ÊIˆˆ +¨!ó”4^pÁ…òVä’±–‡ÍY2ØŸC#ËEp©‚üûv*v ¶/J×x·@ÝÊ´—爋"Û ·‘èâ{XÓÇl˜B/ëFâ|9ÀA3'ÅpM&*ERYšQ`,q(Å7딈…ÏZàŒge1}΀aµt3ŽÉÖ>Ô�*Ïø$Y¹cÇÑp¾¢.Õå‹v]ýÇú^ëê0¯�ºç9¿ì«ÖŒÙ•‘:v2»º#\ÿ@ ‘Ó>¨ìi·Qo.1: «D@&‡ìG ƒ+M|yÈ–íÅ(ÒzìMPý‰!ü4».J¶ªÚz�ž »êmÊ5b¯h—¹3—d’rRcŠ6ÉÒí«l‹ÄT¹7eQ!¬ÖöË€ooÀ3 #4�‚iØnj¼:E6…˜r +$Ï:fŽm_y'–Ý]!…5b K2ÉœÓP€IaH²h„Ýv€^Ï Çð*!þ”†‚ÙïC¹M˵Æy6@$´æ@P¼±Ã½ÛÙîeX†¬:–{Ÿ!ŒÈx<Ùö]Ö’dº ‰¸‘3ò 5ØüÈY¼üˆÅDéÿì�8Ä mÑ·>ÀÏñ`X�tЧZœoË ]!:”›$v�Ç·®ó!I¿?Æd+ÀnÍÈj@ЃÃä}UÛk cØ^¶z}꜒ÓBö»Ü =\±²4©É«þ¼ÜÑÁZ°� ”•¦ö‘ŒxHÚ–å«<Ž]ß©-Ÿ�vU?“süèädƧ‰Ö䜒ӂ¥ÉþxÊJÑÏmDér?ô ù‚9ÿAfò–Cã@ŒzâÔÀJ2(äÚ"—(ÆuðgÓ0©¾öQ¦wásÞ ã3¢SšÂEŒ�ˆ†s’5ÚG›×!w¦4ÖÖ ‘Ãen²'a:Úì mYò(ʾ• – ¸X¢+†$•™G’Ñç¸ÖW¯¢XÖÀQB©>çH:qeZÁxhÇN˜28zv�Üj ±ÏTC†�:…,ºÂÍ1mH³oCŒ%rtiF�*©)é0A,Á´t›¬\O•¿ùWƒ Ü�!”)32ŸMB£IÀ{+,ã|‰‘I~OòÙá.�'Êñ) äÄ\)÷µ)v¢‚èM¦ÕË€7 v„<ûªhVý~,üA¥h;Þ0‚œH£,`èäˆrZ{Ïæ"Ýñƒz�·ˆÁ°:yó=y[Ø„rä‘[Dvà��#>ïñYòâ”ß Ã~zf¤­^_2ȈcÕ+eÇëzpyP¥D"Kèi²uó öa1ù3]ÆjSÕeýeB¾eÆï£$(Dlo×}IÚ¼dº¡?*wèóp†È{óÆ« /‹Û›DptîÓš9¹µs³?|oß¶KzýÍ?þñxÊ´$xáRq Ö“ˆ'U és$q€Íõø3´±âAŒ¥ÂPVJ.ø9"pXÍYÒ¥±GAÄ¿,H‡Œ·àÌúýì0dÂÆàUø€‚x¨d¶6«‚ FEòõÈD2åµ³¨¨BMŒ¯JdÐp.¢éTe±"óŽ[5ÃP}êf±_(69ýÚU�®ìD’H‡ñ�š¸l›L…Ý%§Ú•×¾RdêÉ6.ÍÇ&ßÞß.¾ýÝ×g]J@-Õ@z»£.0ÕϯÚ�I#ÊÅø0J@±&ä³ýö¸˜®Ú9ü’”åüp c!])Ðn@Ùñz­3{1ØõÖ|sn‡C”;“T0çp!b‚!84š`þT{ƒŠ7OžBÎ&Wÿ=[­êÆ›â*‚~ŽI !¥üÙ’<וÙ±1·· Æ#3¹oIùÄñ=Uûhö8.Rª{° ×�®ªZÿ eÑš^ ãëH}BÛïv5„^…±x Ö„0O‰í“·íÔ2þ=M´�¨.�ôªjmŒcÁç.ÔR¼¤±K©Ö‹g‚‰à¯öoÇ(VÓ\NÓ4|¡mªÈ~‘ ]vÎÆ«Dý ìW„°}dl‹KùŽÄá%`l…¡ %ô#˜÷˜Ó,\îv(8¨:Ÿ0†¡Øõ…Õ^Išˆ §AÝÑÀÐaù>\�[rTßgH}XGÌ�-2+Ð'!:?…Ãg©é§ºì·¢ÕHÒÔ !þ6HGzS I1Wf’=>‚ ‘·-<¤E|ø®M²ÔQå+䯇PÅ4c%+UYÀÀpªÖ¥åÉféH$%ýŠy}uzÅ‘Aê©b—íØõq–žþÏŸ(Æ4W™”&, 9”Z1÷m¿ÓÎO©N½&cEÕ麜ºt(90P„‘‰°%É äÿý¤"Lz[bL¢�×(„b™w4"6ÚllÒ=U.*V>‰J½ û�‚él�ÛCÚ†m5Nèmö-ÛÀZæÅ‹œÅøtòÄ=Ÿ²½·ÝeUáãJ"%އúŠ?ÄZÉlÞ7?ó«Ë‚¢ÁHrÕ[©`šåm,$¬HA^Tíÿ_Ïœ‰Pkà¿WbN-K}±öH“é~AýÕÞe�„n“GDs&ñ'oLîD[5Rb-˜IŒL ,IP#…Ì ¤w�ÃP'ÓkqUÝji¤˜ÒL&äÊ%cú¥¸­høôr(DjG� >N�Óƒ„þ×éaê3¤l¹«ßÖR  Q#b½¶®´àDà~OøRiNCí™ý f1SJ’cY«º/Q?LîÌWXH’©£ªÕ‰œÒs"]‡ÞezuZÑ‘\Ø!=j…ä“F­Ž˜ÚpY‰R֌͋¢õA% ׫UÖ²e&î(RêÈ` ° –ðQ±Š�+§%ìÇÁ˜'ø‘rybé ÄŸ–Ñ.:ÁøKõƘ�åŽý’™á8=Si‰¤ÒÙ¸f_îã\LKÁ`ŒR'·þâ…«G%™‹ @6¡t™)›3ð*뾑è Pƒ(ª`'©c0hø+t7ò€4II/'žõ0ü’CQÚÐîô$) ãI埰wrˆJÈþL"C"XÞí1š³?ç5Xx¨°kÓ+ÐB®$ò�è_qÈá"âuq¡~Œ£nø�.É)ÛŒW•Rkt…¤ Ob—Px7p3Ø2ÒCnHÀ´/¾>UÞ´>¸)FÉÆPƽÐ$QËj#¨Œ˜·nAÉ’mnÞd«\ãNrMÏqÁ�sÍë®~�ÿ•ò¯Pòç1Ìëò¢’x�$•îŽd‡¹A,¡:ˆÒËK'ÒvÍ C¯I³Õ¾F"r�†oÔ×NÄD®®„x¬]ˆŒ’àc ü$)Ò°G¡8ÄK޳˜c�|')wÙù0›r “2$ãá‡A‡N”DCsí`ÛoÅÉàG¼£*�L‡^Q>5] ;ÒðÌñVZ0ƒJ›T¯ú‡I—f[Ò¸S´mjú;ùݹO^µQúÌqHy³r.smm )NXÏ$]qzŽ*H” ÂI ÷`å•ò2 ÖC¡eƒÃNM^÷ËnÝ—\/ÕÆ¬]M]> ž×ÙSÍe‹lyd�¾Û&­ òÝ Q=q­VRb·gj'D êªM·ß±­XK‘W(#""]•YÛ&-ÓQXÂç�ûÐÛ0ÚÜÊ!˜A2n¯ˆ7£G za<”rEîñY³19ï$@€s›‰TÙ1`¨0 fäQ´� ÷—•¬Áƒ¤ÄD»J=ïaM ªE¸}ÅK³-€ßÐ~Z®È¶&@cÔ”°™ÓF�!®„_HÝŽ8È –Tüo_”²XR!¸¥cÕy;m¬\ŽÄÀTûÀ´bÝ~s{A¯¾"®ínž´:qAê…Ü‘¶­Ã膯Ç d²ƒ÷¥'´=mѹxˆ ™¶G©¢«M6vEÕCôËQ5|c@,ÎBËx)‰†ÔZJµUDÄ€„Šä\RšÃ©Í¥c7˜å,Qæ²Í´Ht±$ѪQ™†b½ÐW�ÛIZ/­ÊYk7­¸�)vcmPbíKçùf!‰)ê0ó[%œ¨#ë4:›~ØÜ&ÊZ´²:ÑqÁ´Óúª�ëú¢Û»ÔˆÍ¥*gGÛC[VŽôYÂÿ£ÇÎUarîa|Û#•C‰K—ú½F|}{ŠÇЂßk)�h‡HÇt 9•*6ÜuUK8±é펛Á$)coŸòÖˆ&µéZ,ïƹp/”›¥ÁTÃt§ Šî¸»ùtÊ–Rø?êÔÑ+ô23ZÂsYºœwéa;r9ºÏ1A÷;„�¥6Bs?̳‘mšä(zK�®¦JJæ=�š‹?[Š"8@™ñ>�šû¹ã°ÈóÆUI(*W®C!…Ogæ�eNŠ¡X[±¸�©c‘>~#‚婨KnÄãÃõ¥”ìqg½BuãZ•q¬ªËVMݶéBZ¢ñ/ˆT8yÏÞæ€\š÷<Ê<Ò™Ä/‡˜ˆØ²Ä~ÌaŽh~ÄŽj†O ›qáœú®¼»÷IHû^A´¡?`Bi3¦‰¾BZ„ïPjñƒzZŒ­Ÿ/ì,æeœ¨N’߯ÚÁ—–ހƵ^ú ¼éÛÎ@³Z�#ÒÈõ†•“¦ŸÆyµSnæ8²s¦(Í5ù² ɉùt›‘$ ¤fDšÜb»1™ÍÒL“«§�¬A-Fè—„“ÄúzžPÕÆš.; »‘È·¯ROkhCÞVÛ=›Îs ›ò1Ÿd¼@‡{“¬-éª#Xð“Ja’Hû‚9(Añœ( ìã'8Y#Áªc¥18F¦s¤…Äç¶Ör™ãÛø|vÖi‹Ä|�Ô´NKœ� Ež�šÅº]ÍÕÏ ½'NgVˆÿñì8ªô¾8�ɱ˜àCN¨PœjîXã"ì1 sRú;NúQï=Z÷wÓº­×È�(¾ôÍäñýØã¼ƒ •ñEp¨c!H�n‘»ÔpŽÊ(Â�Òñtá¿öù‹‚bX¦ÄŠÉ�÷Z�ÄÞ¤/¿@~ 9/n“)¼bR¾œÙjÆE­}ów¦o~Ãð6¦OBÜ…vSv[š§ ¾b O~–”[({‘Ô¨ +Ldàݽ;ë[<ȶò"šqõ9YA½¤ç`ydâl]„~uö5oD)Á÷è`ºÉÇ¢ Îm¤Y?vÜž˜Qá‡#„³Ä¡«K0ôÌm{m= a$ ã—âQòsº½l<Ùë4$Q•¸xÃ0U¶•HzŸg]¤7|tpÜÈ�£Œ°d"ÙbUÕ5äÌàxRǨî§¶JkxB ˜ÆÇŹTK�ñÓÆ}×)¥!á½­euñ$Ée™õÈßk< �ÛÁqí™ï²]£VÞœ ʬ-Ž>ð\€­ªm'±ÚGÆèÚãºÚ§Ï©æ”’¡£ë†fc2�j®`×p1~8Š€Ð RnTÄ6®6a��ðÙw�k Í›P Êæ ήœˆø�Ï…ÊÛ`÷ÊuX˜sí’:=^»Rj/^O¦âXYR¢ í©š·LœEopföÈA¢ÀV=+à¸ë ¶ÉÁ4ºŸ ÚdA¶,¤Þ_¬Q–Ö–AMtjú%úÿ˜b‰D97Á•„²F†9 FÎJyÔŽtUU¹YËÀŸˆÄacOìÕf2F`k«tq£‹¯Éófö­5g)ÏFtðÌ ­KmŽ+c†¡4 ÿô½ÍÙªYwzÜ�Hôù¶5c}Єô—�h$&g:8’ƒOR¸69‹ùó³LåÆ ±ÖEƒÊ–bëâ<¿ ÜTÖÐÒ')Æ÷ÓŠ}zý8376¬zM0ÆU~Lñk´âƒÀÙÇY€’à^”øë� 9!¬¹ L†à¯Nä€5Å~Y@…/ßðAqšCn¾ðz%<ÌkqD.ì�kéE—^uBÓ¤íÍþ7ÙOì—ÖaBH垇Z}U‚ ZvP½ k8ûÇGÿvܺOC�QM~”޶Q»„¯�L“#2»lH \(™K¹Š”&wù *’b÷ã¬Z‘Ṳ̈”;LK9,9äh>›Ìš…È|Š‹`ò�’7 X †7±�ÔáÃYÀRÒÀ ó‚³¥ƒÑCi2$µpä°üø˜‘šóQ¦ÓNlñeàNÂû~¢¥t비á$9ãÂŒi=$4˜Nµ €í UX|/ㆴÜ–òÌk>}D�é÷õ3Q4Æ¡ùÂ~‰‡SÉs¢×j˜UhW/§ÚÄÀ=ô/ƒ31ÕFÜi°$⬷"ƒ^x϶—TÛ_ÄyA§“–lŶ'éË,kË4Â’%Ø�錱Ye'ˆâÁyŠùŸ‰XüiF(äœdiÕ”�WéH.1Ácý ÷1ªSªnÉeôÏp…š‡kl]ó(”“ÎûbùvŠ]�Î F³¯Úªìáé´Ì]’D� ¹4éY!„“+NŇTš 87<€º°h”ç¾ß@r-’l߿≌9wQJ†“œäE��ÎÅAÀ\=ŽÄEs+ Ï ™Ïe¸´KqÜWq2Q—!�ë ˜`Θñß¹¨¿ &Ö–¿ êÝz�’«³YýmHž#.Të3oÚfrŸ£–|¨|î{?eHFC¨ShÒý#ÇbºnSï³R3euRB'Ý[–1§f+íÓcÚ8efB¯fP,̉¥×Ò)÷Ï©ü3'}ÐRÚ#T‚ôÙ£wâMb¨ëÃQ`ç1 2­DbEªf¦±²‘DZg¥Î?Þru“F½Ò¡pØ'>iWÉ›7öÖ�µô#ç*‰:ÖÍÄÞŒLFðTˆèrOÀ7~¤¤“Átƒi1·q'·±‰â1Êo}gÆF_¢ `7¦P‡ñ{¡‡dðd†“¢]³T�oƒ_R<.O¦q$ùÔdái,Z*e¾i¶R#‡nŠTL}ÿ۩מÇiÁäÆÙà&c®‚¹ZÂÍaÉôzL.”iM‹�‘25ˆÊ¨Aè3ÓrÔ“ q≀� %ÏûÇZz�ì-mÒÀ+(s™êEÖå$|‹…>¼ªsF¡w‚œf¤IÈ®åGBÁë TÀ©†‘öœK{g„:1Â2Žk„ñ˜zØ”9¦j©óZfæ \îDô‰ð ¸02Ò‚ë²p—ÊèRíÎ\.,ǾÈTŠLÙ]~Š0$�å»?Qé-B„‡ÅzuÅѧÉð�"$ª½�zÔitJÒïE'ñ7í/Cq@­îËT\©ZíÇÞ-'NÏÂØ¹Ê¯|` ë”bÿŽì÷äªL9ùc ½Æýå‰tö乌µ�ð=O ÷á rqƒXa@¦Î\—Zõ§=8—'�´™�×=Vå42_1F… æ®ÀRlðêTéR;Yœš ~HİŒ˜S&ÌJç1Á¨›ô}ÑùŸ¶$Y_Ûž_Ó�l2jyJš« ð$—“1Ä`º�PEp4)}õ b'ÎJg@ÌÓ‘uóX¨Šdà£8³BÅGá‚EÑÒU$�BluDa„{… “ËÎ~ ;LS‰dþ‚D:,#ó±ýèiS¦nSðˆb+&þkL,~paˆÌh&% ç±2ÝŠÁ‡F¤eu JR²lÂÛbó4bù˜‹&�ƒÇz+ÍðMÑ>ÁaMK= ´s’š‘n4­IQ£ ØÐs§qX²Beáž\,ÂP®›" Øö™d‰ÙLǬÜ`L*”k9,ª#=¦-²-™�:È컡‚´÷¾=¡žðôÀuæöuΓCl¤ÕÁ®UçõØ�` …ÑB¾Ú7Àæ† œÕOûK=¥oº:B%üá ¿hG!l!e ù`8Wì^n!†Â9SF¨Á:#†¶š`þHS°J©›=wÆ‘'y:öG§Kª‡¤2|&¾´c÷Elë6õŠóÄ2ˆŽÎ¨<)X/±iXŽzÚ ¹:]cå ¨ÒH¯Ñ †bŠä’€IA¥æ�æš–° µˆ4¶;rœÌ€CŒ%'¬wÙ~ËuNuL(胩:šÆÇWuHà^ óU¬Œfô¥û�×ÛlêGšQ¯"I|œî€;|àuÊmI)ùŒ>O'=” ÃN¼�H E´Z¼s&õs�öÍCxIFýã×ç¢<�„ 8¸ÅQJ<«üØÖ�EÃ÷#ÄôðmÚ­—‰œ�=ÂÀšHlŽc¹ÌtP�bÍ„ÆÒ!Nž³à=OcÔý‡ÿ°Ÿ²†n MóõE›Â�–MÂ~¡Sƒ‡É5}Èñ©;�”ê°ƒŒHT„ilÞv ×!„isŵ0…d[0‘QºœVN†°{šéôÕÁVo~¸Àp«ûð#ºï¬Ø¾â/såõÖÛo£y¢ÈuN™=óþ!�³ëy2Œ¤3û1{nµŠ …y± eù~‹c)·½ŸoGˆ„ºÅ¾!6túÝ‹h~Êg¼ ªø¶Öñ¾µ¬-¶}Ùeþ;1R©w0™kð#R|§"|ôøšª—ƒ¸|þQñ 3~2y™Ôr/æÄ}w�|» ¶.yô¡âý86�B f°xž¥·HÀl•oF¥˜Ú¥¢Ÿ«“X`@æGW º`)¸àœhX7`b©Îô5jÃæ±tšÑ›/PÑ­L|—b²~éóÿ«BÀ`PŽG¤TßT"aDÆA~Âk£Á—'´>õèg5^ßj;™Øh&Î刳^Óá £8hoÌñdNŧEúƒ \¨Z脬ñuµ¾ÖúÐÜÿ §›š�xû‘ë�V®‘²½d˜𺂋%E ´Š­—î*¡—¿]Ø;G7LpÿîÒo/�Â#@Ó©oJe« kt5ýÀ2ŒiÁ˜?ö©/2Ÿ!�ÀéyÀ!áx°· m?®’vES„n^­Z Q/vn¥â…%%CG>gÂ[„�‰M t'y&Ož„™ŒÊƨ©§£ã^üU�Á‚¡òË„âr­õÖ`(!–†]#\™®&ÖEDŒz†ï:ò üÈ¥ †TRsì.‰ñœøäŽ:ܾ ÊCh„ò9ˆ1žb t@7&ÒÍaa\°ÙO°v‡õ¦ÞiŠåc«Mí³~Ž?øÌ1øºöª=…ðà‰bþØËé/p3X&È'«Ì“~ÏìÒÇZ¸ïëù±ô«Vƒ)ò¯aR5¢)-ùHbô^Ý Jxuÿ†÷ùS5rÅêÍ­p`õQc&iµdÝÄê\“Öý'õCU=x#1F準µº>Rá–�HñàNð±´W›¬—QаûûE( Rú¢Åá"â>Îïævqo¯oì—ÙÝÝìúá«ýps‡?ØÛ»›ßîfŸ¦öᆞÿûa~ý`oçwŸó÷öÝW3»½½Z\ÎÞ]ÍíÕì ¾œôïËùíƒýòq~mo°ü—ÅýÜÞ?ÌðÂâÚ~¹[<,®ã/on¿Þ-~ûø`>Þ\½Ÿßñª¾£ÝùE{;»{XÌïÇï‹÷ó&;™ÝØûeñðñæóCÞÜ| E¾Ú-®ßOí|Á Íÿ}{7¿¿'híÅ'‚xN\\_^}~O°Lí;ZáúæÁ^-èdôØÃÍÔ`7}Ö¯`hýOó»Ë�ôãìÝâjAøÂgµ>,®i ÆÝL ¿ü|5»3·ŸïnoîçVPH‹Âï÷ÿ²tEì~ž……»´Æ§Ùõå{%g6tM8®ýzó*‚Î}õ~€ jnßÏ?Ì/¿Ï§x’¶¹ÿüi®ø¾ EÍìêÊ^Ï/ ÞÙÝW{?¿û}qÉx¸›ßÎwÀÒåÍÝV¹¹2úéBŠËCÂãÊW-‹Ä¸Í}|¾¾&îæÿù™Î *±C*Áú³ßîæŒè„&Ì—†Û „a…0¦ü ý!ÆW"±ûéæýâ®E çòæú÷ù×{“b…ðIvöîˆyG€,‚X½½Ÿ}šý6¿O({ýÊöÔÞßÎ/øý�è‘àJPu}OgÅÕÒ/t;£;Æ N¹Gó™xí ‡öÆïR`ÏâÞ‡Di¯nîA�æýìafbúßws<}7¿&D1�Í./?ß¿á ¼AÐÜ&\\Ëmà¼Ìâ‹»÷Æ3Óí‡Ùâêóݘð°ó ¡K2&7!OÜŸO .ß.>ÐV—õÚ쀕¿Ú�tïæôØìýï fG݇€\(Nèt¼‚âQ¨ïç ù¶>‰(ðþ I%U^ù@è…Ž¸ä¢óþ ¦¤oèxFñE+9kþ…6üœ¶á§Þ�9-Ä: i?:¶›žz6ÇϾ]µAzÁ7é°7.–=]§L¥…?êÍ5ho²½ }µ˜ ÁkùàgVÒv5¶¼¹§ / ¯{'ž VxÉ÷) q“ýÔ(ä#Rº•ÍÃÊºÓ ë\¹dYÄ¥¸ƒDì¨|pöŸ›®Ûµ¿|÷ÝóóóÅcÕ_ÔÍãw¾Þã»_ ¢j÷Ðu“Î6Áž—o�óÐ{úšºÂØ(|,$Û¡t…—jÊ]êˆj™u™F[¦^Ìù¯­d@HÓeRþŽ+ŸŠÛ�1¶ãÁ�2í4�Ø‹É5Ú³úOÝ÷׿̊„(³™©³w÷7WŸæW_SWæ-_ªÞ§íöD¡ÿ—?ùþüê".7fè¨;X˜»ûHdrÀß¼‚°s芡„·év«W) „|„–6ûâ�œ/´á3„>†!¼­è?WŸ¶:'žxZ{³fK$d¶£Ðô[›-_FYxÇö­ª÷ß>/âøcýŽÔs°ÁNÈb"ºXÖLBᤂÌŦ¨µä]1v½GIƒ¬ãgü'ý\sÎE]ppIrÈ÷Ö8í…H2Ì“K4ò&1�æºcÄJøèLJ�T2Ž|Ú9ù®¤˜hø…ºÕ�»ñémâRóç\*±ÃdMúH­[:.ŒÄDzA2;l¿LíÿÐ&݉dÎP"ÖÔÈd:ý°×^Ûídð/wv‚I" ¹¶H A([:¹ãŽš,ê4©_ÍÑÅ}DKéÙW#Þí+W€�+‚5§â{¨rY~$_ƒ¡:ÜsƒúanÈBÍŸÜÅófÿš°üº|Ü•›n[Òåü?÷yì´Þxœ½Ymoܸþ®_AøËÙÀÞæµWôPp}NšÂ—3âK‹~äJÜ]6’¸%%o¶¿¾ÏÌ�¥Ýu|8 ‚Ø9œ×gž¡”:úyÿñ³º»}x¸ý¤Þß~¼ýt}§î?ÿõîÃ�¿Û�·Åñþù‡ñÁºV½Y¨×Rï[£^¿|ùÇ¢P7nwðv³íÔåÍ?Tï¼1êÁ­»½öF½s}[é»êC[.ÕŸ·]· ?¾x±ë¥ó›)Ôí£ñ©6¨�ñ�í:S©Î©â•n+UÙÐy»ê;£°v� ½´&Ê­U·ÅÎÚ–¦ FU®ìÓv …õªÜêvcÛ�²‰o]§t]»½©– P¿ÒÖÇh ‹2â(‚ñê½i�×µºïW�¯îâ¶-�ß9¯;h@[:¨XÕÒµ•%ƒ‰K’ßä²§B‹(t¡B¿ÛÕ†t‡ù«¯×•HÃrvM,º†Ch‘�)K²ãåR]�K2kÛŠüö:¨>`ýÖxcŠ öX<øBy³†žäñ“úŠ/ŠÓ¾X°Õ´ô‚m»¿û–¼3‚XÑ‹_±æÎ®¼ö‡\ކ_! &ì�ÿ¢6ôG›¼4š²(ñx¦[襮áP[rþ)ç!åÆ5+KÿIbtPyjâÉëV]dû.(mt{�ƒ!¸S�þbØ¡dN±˜_ëÒ¨�w�¶2U£-‘TÜom¹MIÈzî-d¬4E f–…Dy«‘«²Ö�ó ~à_µ'ç�ðÊ ƒ*,l\e lé‹9¥æl»˜¯.&NûÅzì«úRö•¼ˆDñµm¿È)Eîò½í¶“¨Þp¼ö�-ûZûyåÅuoÑãiÔö[£+† ]À*Úļƒ&ð„¬‹¥Ô8^ü e$Ý�óÞ„U)~p½/QëÙÑ�JƒÔâÔÞr¤ÜB™¯eÝW⌃ ²¶¤XÐ`Æ€ Ç–Qv-A¢CÉnµàê Ôb’-™¯¥ 9³R&‘#v�~˜Ú’GëJ~ËnõoSvlø‹3ŸLXfA7ÐéÚw¶¶Ý�’jã5г5†r’dx#©F›ŽÝ´ö®am&ÇP‰�Χ]€d“ ½â¤ÓÙ3¯–êökivìÀÍŒ<†íû;^ü/×#ÿ¨ÚGs˜Cú(ˆ€ dëÛ¡KE â wÐxeHÛµIªªðÔÁ¯—P�Τ-?»Ê®íaúku õ襨†:-.Ί ­ô²4:1à]„�Àäè»îÛ2¡(E�ž®Œt,+€0…Û‚‘Éy£¡—9@°"«7ܯ �r{¿5œÁ£"p›mÒùjAoZ±q‚ÑÎ&z¦ˆàò#9F髚I¿¡�õ¹_�Ûh1¹¨ ˆÌ9ÙÁd’ƒm=ˆ�”ª•Z3�¤ÿ¬ßTÎÔ³“hiÿÜ“‹©ª¢V½ž¸…T5þ€M í{œ‹ã<ƒ(Yl;�¦d­Ä›F[$Õ+rdÝ× Ãæ¯Fó‡¤Z@·D½¢ïÎÐ ·&¹ç”SWµ¡\`ïQ8 ß,Õ/„)Dü"[âÄ…Þâ.ãÔ¹þf4i÷ÎÖ& `•£ y öÛÜË”Ãß™ôBƒñ°Üµ­%fÌ1£ë¦�iVÖ¡Gó V º�‰ž[Kñ”[w̲i!²3½ªbP,€Ú‚ç Ím‘ÿÕ¤™n "ò\f ½}Ù!áŠZãÄN8¦.KP2G«€ÑÖj¨^z(%ÑŒÍ�f²0¸«™¬^v†Somö†û¶¡âRµi7ÝåEP¡¶Q¦ÉEkG„¡ é½}dÑ`;æÀ ’LFÃmy5æ1äÄ:È( SSju]ä’²jXB)͉qQÄî¹3þ·L¹~]‚Ÿì¨ßÌu`-§€Ë!>Ž$+M,ùírÚ;Âé~0íVçóF²¥è8Ôu6† qApC]àÑ4 JtlÌ?0!Gë¨~1çÉmЧӬG„©ù…µž †š‘ ’õIý·2«~Ãc—Á¬GXi2̽  ‡ñûså˜ ì˜ÿk¶ÌØæïÈU^a>æS¬3fZÔä!°h¢Å9àð=E‚»ÍWSö9�2¬Á|Ë@¼'4Nà!ü/¨åî ЂZ¯4xÓb{e=¥\äM=M¾ êÑ{»Œ<�3~fyu¥~r*ë,³Ì Ÿ—W‘Ïð‚§zÖ»†B¢Ã“¤£Yôi–K飹£$¡·÷2<)yÙr¼äfb‘{(í†ó$¾Š¹+MCsœò(‘“lòIRŽY;!Î\ùÖÙÄ!Õ�¤4ºm‰€îL)«3VùÃ<ˆ”6e¢”I©,‡ ½ºRŸ©²F—…­¦âªcê5†î\lhXzš Ó(8F,õÝëQÒ¸yE¹ÃŽ¿$êFD¿ûÆÚfs’á™<Ú>�Hþv‰(jßQ½5»�bÏc‚Dýh°Ï¹¿‰ÏÄ/ ™i�¨Æ0sÏÓY23‹yX�^³‰«Í•ºRú:txI£-¥m6ò¸ÊEDÞ»¾®Æ«F7b…ÿé­ª‰ŠÀºeMÇ–!QŠxmÈd‘üç 4›:r.›z$Oj³5DTØ!¯Ù–#¸fND:y šùåC*¸xý %á‹v>¯ðNÍÉêò$¹“oxd&|[½”Ê? Õô¡#b&�$õ­'A�q¨Ä•Y¦™n¢Í+aj|hŠé\=’�kYÀoXrŒ8 W”²ÈÈÑ0rOÚ¬N8Gk"¾Òuq¼gÊ/9ŠTRt'ºÚàt±U�ƒ’ˆËœß§#b•9�HЯÐSS—h`‚£±îé6e¨¸çòË­kNŽÃ"”Îø.ˆ1¥Bãíøãµ¤ÛdM³U ñXMšñQ÷ÝÖyû_A ê´d}ùó9igœñŠákÍÿ¤¡X»¿ExœeR_oÚ0÷§8ñÔJQ×U{Ú›IL±âÈ1e<†ÄW!F±ê·ß] ëºIHÈw÷ûwÓYXI¹kì,Üáãž±ÔŸÞFwè"Ü5÷ðôøõdv\€yï�G¢u¯ÃcO�PùSç,lÜíÈXiÇ£ ÁùÜÙÑîÞà0ÖØmØ�Ö‚ßCÓÕãÁ&=ÔÜìàw±vƒPCƒJ 'c‡4Áïã¥-·P‡àW#´¾9íëHz{×ÛwΪbv?‰´¶î™€zï-¸¸Øùs„Ó�®!ŽÜÐôç–<¼·{wt7‚OñCÒsÀä3�£oÝžþíëtÞõ.t ´Ž¨wçˆÅ@Åié åøâG¶ï28ô=eýp7Í�õ-4ÞV¨réüñsØþ<âµ:;aZ�+›_m©Bã{ß÷þBÑ?´Ž…ïŒlÕ;ÿËNY®×|D«W t€ÓÇUo­ÐÕ};{[êâzë¿âŒ$"ÞÕ=œü8éýóõ—*µ0®È J­^d&2˜ñ ß³6Ò,ÕÚNh^˜-¨ðb ?d‘% ~–ZT(ÍäªÌ¥Àš,Ò|�Éâæˆ+~ï?t$5 HðF%EEd+¡Ó%>ù\æÒl¶�¦ Î…ÒÀ¡äÚÈt�s åZ—ª(Ÿ!m!‹…F±…y@U¬�xÁTKžç$ÅøÝkò©*·Z>/ ,Už ,Î:ãó\\¥0Tšs¹J ã+þ,&”BÍhìê6KA%ÒãøK�TÅHUa4>L©ÍèFV"®eE YhµJ­j"A\!®,´jøt¡÷º!-_’Ó=¤d;É‹M3}›Ó§»G¯Äéݯâ\®T%n*ËÄ^¼�ª²Úý^o<¾[h+ÊÊü®'ð31y™)§„,R¡ ‘K]8UÈ"Q"7©ŠÇcqæÄRCZb §‹Z gD¥¥•˜é'eys]¦Òá7>j)ºš­˜™J¸…¢ÿ•UJN3ü®]]©�˜ÖNäºÐ¹ÌDªUfÊ\NLÕÊ@®[H‹Kˆœ)I;¬·ÆÙ kD®ª¹‚í3R°²R"©´Ó Ä‘Zõ¤-Ìž Y–ÞÒÔß;¬® OÂ:9Õ™v+¯%Dz$¸irX“B«­"ÇÞ"RÖ)™Æ½Þw£í §*7Â&•R…]÷ÛιÒ ‡:Öù¼®bˆ¦gõçäýE\sœË¡Èô´’ÕŠ”,a½$v½u¹\Æs÷›j>ì“'üsZŠ6ê±Þ9×nQOY§]ÊÕ2.³*‚“Î$&³Ãif¦Ã\Â�jX‹Lñ’Œ¤E–¤E͇èq?~ʳ~¼á0`$Å'¹Ê(¸�dŽ}ªìƒ3¥`)Œ5Sàhí@Ø:Y (ÊBex.ŒÓ³Ö'´”YVÊŠÅgS‹Dì¦v$t+SW€C‘š%#OBIUÀ2:øt®„aXS»²vzm]¥”ãu.dRky ìÒU³2&ÐÛº,MEH€ñ]‡[ ê"Éê”ÀUš²ðÂOCÀçìk>üµ¶N|2Õÿÿù/„�Óð<œyHCøNi±Ä�‰£�8úá'‹•@d組;|Ø­]§WSŸÇ…ƒ%~"zHÉ ¦í\¯ÜÂñQfr à2‚®BHæ’à ä¦È¸eP›¤¦ÔãSè€/'ôyi3»à¿#NŠ�í/b�dÐηݭØùꕸQä Yõ¾žÛÞ‘ ½¡Ã¬¤]С\¶¡¼õ�kœ0%2Y2󌴵 )Dâ”õÕž Ox£¥ãñ™è�Üèù©Îɲʤuc@8%°Ãg¶£S�¬ˆA�°w÷øJ\^݉›“ë«›;ÏˇŸOnÄí_NÎÏÅûûÓ[qw%î¯oïnN/hÁî.@7#D3Ô[M|V�ç ´<'ÓÅÄðÚ-¯Úíî;°rw—– .&Q~ 1÷ÙEöŠ£ BìUÌguë¡6<ö©�óÚ8’·Ì-Å?pÂ`­jýÒ³�ÕÎTvÖìC7ÛFiHÇ{5C­mÌA•2ÅA/`.c�SiÕ¦Ž�†rÆã[áxÜÇòñø×ãq�I›K–ÚaóøÛ…®Æãè%Q&Gá‘+¢-—³pLžü¦+pßLÖ™cß+õ÷šÈeE2M£ #RO¨?”2â°{̇6f÷Æ�ûdÇ3‡Ißs§O Ø™"úÔÖøÛþ~ÃÀ|x÷mêæÁËW8ÈÜô&kÐPùš'Mk ósõ$©IðÑ{›AZq†�‹òÙòb`Ý€ þÚ}o‰Àgµ¯ è8鳎� �F�x{] ºÎŒ)µµoWF�mC2{øÛÎæsŸQûJ Á¦@*Ì—VC13�|ÅÎ%¡ýYû¤mSôÉ›ÉVbE{Q>Ì#´E3� “Pt >›A6rqòì É”‹Ðuv¥&ñÝѬ®˜ãÈ�ªf&‡ãkÿ¬¦ÿ£ð'W·?õ}Enzªu‹W–Ü\PÓCD‚ã EÊÕ—u•«>·…?aù]�Çç4P êÈÄc„†¢He†~ #}ëKGöÚbÂÍzËoú6_åFfôwiþñCPïñÅIœ§ýЭ@WfƒÑ7¨Ø½ÑŸKª‡è�¬~|±�t¾÷}÷Ìöެœ)–!¦hO`°ýÖöírÍuÕçÔ!gF¨…H`Á¿ÖÒÚá×ó{CÚ<ÞÈ¢±ûÿcÛ�”’ËÏ46šúm,§«Þ(·K=WÕ‹ºÂ·�Ò®¯G/ìVXmÑË…&Æi•õ4ÓxËM<Ë=UyG$íŸã ÷×ôƒ<êò±wDc”¨Œ¸ö©n{£ïFçxE|—ò'û‡fH d�(¸™™¯XçT¢Y¢�â¹%™ÒçgGÛÇyÿ "þŸ�E3ƒùfë=ѥχÊäâ •¨Þ~,Ž8‘¸¥"©½× ]júá�g°T•4‰¦æ’rÌ÷�½7±˜üS˜EÎ[åêRDÇÍù¿•)<Ë5¿‰÷ÃKžôÞ²„B¿K5oä·¶NÍÖ'¦H6éý´^’¥žP'pì&JÛÚæÞ®]�‘ëð±Í,’ÁÔ;¿¼Û‹ßþï÷i5ªW(ÔØËä4žù¡²rìúæ–´ïïÅ{,©©÷íÔõ¿ \ïlDÿDÓ˜ý¥1+ßį_7+_ž2§V‚÷Ÿ^^]œ ç†wE»†�j�‰áeí„Bœ:YÁ�_è€xNçš×é%XqxG”~Ð[®ÃL>¤U/‰|”¥¦Tk%ÒpBô‰¾``õ~Z®ûŒ÷Þ2Ëf Ïê 7™Lz )YºV„‡÷è’é‘Ü|Ã/Š—hNeêdVûñž_ì£9ƒ#T–ÖËØe”Š«’[„f²Î§‰ØiÚÊÉLfVMú44´� ²~±8â�ŸÅfkGQ ^ùÐmtwÜóý,¨Sâ¢ÚÈ¢ÖS�IBSÈ3ª/±žýBóKðÓÅÚ¹Â�ŽxóFZëw17üWNòJ¿i#âëÍbB$³½¹Iƒ³�sJ¥“b§¶ž¤Û¢CÛšÙÛ†!³Jáʾ×OØû¦Ú�‡×gA "Ï·ݹ‰ÖÑl®¸«e”S#É·Ïœdöœ4¥µ(š¶þ® ÊaR°&xU øî®¦Q™„Ñ• õ´° ¶eô·!bâñÏ+ÿDÿ2W¿ˆ($¦hÿ;´½+î¼ôˆ\™Clʯ6ðÆùíøãáùéÍÙåñ»}v#IB³P1ï½PA3þˆàx¡dâéå½8WÖâñ”Á”‰kªòI�¤šëñ&Þ£ÆÛߣ Å›(éf _:òG�Öd¹,†‹J‹-ýgw13LY!)³ö"ÀßiRÛ:«IÇ€ç"êb+=­ýåVN½XhlÃ¥(!‚1¼¸¾!±Ý�Ø}ï/7Ú�²†Dâ>Ã@œaxAí`Ôæ+º²í\£yTm¿Ä¬Í©Þ{É{lÃ*é!†¨ÚØøHœñ*ÝõØkÏLܰF™©ÞF+D„4¬¸¥`ü„Ü^ nœïÂ,Ù¾¾EÔÍ¥p)é’ &hçËÅ“«dB6Ψy�ºHC÷J·Êƒ.ј/á©ñÔµ�Ñ·±³s~z}Þ_Ch«È5—¥&‚Ãl^fp«"^¸½çK€CêÓUçE/§ö˜Ètx†ô¿¥9æüìèäòödLöÄîÉý¶³ýÆ�íÂgëÚeÝGf9aŸÐŒ(L5´œòô6hŽfðR‘ë¨Ào*Ù¥Jµô ž†X2EÑnM¤ôÙ01¼èÃéC�>º4†]J¢�'ð »05˜Œ $˜ë<»†Åè�Üí·Q¤+rÿ×íbq�að§rFsދ⇄KM=uaNùD¯ùBëBI$Є쳩7ï¸ÂôD·nj}wࣂñXg’.7ªÜ†£„Ve3 ¦&˜Çã+;Dˆ`Ÿš >Îc:™Ò8º‘4‘3tòÁ_äŒõ¼C\|1BÔÊìñÜЦª”جi˜ShžÚÓ4|­ ê2°§ùëGÜûkT>ɽŽxœ�TïkÛHýî¿bà>\R,Ù²�¤)¤�Ó–¦iIRŽ£y%�­ÅÒ®nweEà?¾³+Ûª¨p…­wæÍ¼yó¤¿àýó'ˆe^0Ã#žqS÷ÂðáËóô ˜¡bJp± 33�q±ÄjY‚‘tÅ5l�`ŸJF,Êj` (%E¥æ»ê÷¬FO)f¤LSJÊT†ç¯ð¥ÈxÌMV¿]…TÆöC§°B¥¹ ç¶N¢Ò€À J±É¤N›ªšåRzT¬ÖPIµ.ˆ‹ �ÑÃÞn0ºßÁšÚ{ßÞÀÐí› · Xæ¢S*A�å|Q ÎY™>ˆ™ ÀCAbP%h4n¢µ ïoÿ�>†O¦÷÷á·‡§o_¿~y|žÞ…6öÏíãÃLJ÷7Á¬áúçR!È]ªÍµ.éB4ÄX$Iб¢€X1�R.á=`”LÛ%Ú¶a º %%EbZŠ>ɲBmø‚$<ŸÛEÿM hó7P$}ˆ3)ðP2J�SŒ—`ɨކ5]ìy»Ek› ©^©-‰3|�Ù‚›i�«¡égçT-*y–ÐS•fþÀýà Ë‹ õ`a–^f»{ ærÖ�ue™–Íœ¨ûNº�n½Þ‰G¾ÍxÎ ¹½Ý~ÿÈ 4ÑVk2›±Ët9\T‚ºø0µU˜‘xè즭J)QÈŽÜõöfì�Fä(¶‘ÇZŸbxãìyIÎjÚ”EBKÐ�³d3[ŒêÖÑ®–¶DîÃ"|·RН�ÓÛ»ÏS?O~œß�»eäÖN Ɖ¯÷ìöd'‹0“èTV4CÊi®C=v¹÷ÊÍÙ&nƒôØ5Øsû–眔[Òttp+çb.�ˆX'¤lEÎõƃÿ•ÖzT¤E#é÷½íý8K�)ô›Á€,“–‘OŸ¬A•WÕ/fЖêÀ¥ësp}4Ʀ»Ï ´ß›¶†|A®1î×RÈJX³¸y×{c¶Ag4X÷ÖžçíýSë0$;þ0 ¾¢õiýž;Mü Ø\Nè�Žì £Ȩ2:!“.ȸrñ'�àÿCF§!“r¹¹ì"v¹ƒŒƒ}ÈUdÜŠ<> öº 2ù äôøã«ru9=þøº…\ïC®;ˆMZ‘ÝqršØ¤í29êršØ/�‹‘ƒ¡ev ¹~ NBZ'»ã’3Mßã“ÄNA~3Cµtªxœ340031QH/ÉÖÍI¬L-Ò-ÎHÍÉÑMÉO.Ö+NÏÍa`2×dHÓèú¡’2#Î=åËýõ½K¹ !šrS‹óóô’J3sRò3'$Æ8tÎqØ{¦–É”­ÀÎA­×Ä*€†”�Ê{Ïõsþ¿dõ¥õ^á+.U=<^-¯´êxœ•WïnÛ8ÿž§˜ê°·-`[MÛ/×:^(¶’h×¶ Ii,Z¢mndQ ©:þ~Or�¶O²3”-Y¹— "r8¿ùÃáo¦Ã_ž¶9|ãJ Y\8çƒ÷Î/£³á›I8N>,¥|„Åíå4ƒÓwÝЋƒØu'É&2½$éýl _? >º®?wΠûãlŒ)?»în·H¦…îË’©Ön&SBwÑ—Ôë§Af2çìwD¾ñçI�<ÀO�Ë”åƒTn·²0c”X‚ƒš…þü$Æ/ñþqÜûøùÄèî£5öáýû�î}P¤y•ñŸ�Q}mÑ:/Œ0‚kˆâÄŸY|·+à‡Uþ©»ÿåìÌšM–È.Qdü©6B{¢XÉ‘ÍÌ�ç|ôÏ’¥�lÍÿ]°-ÿ_qÅ‹”ÃŒˇn}¬VQ<çLó÷J¦Ø¨Éu²Лj‹þ~› o€j€WRÙßg–›å¡¾ ŽÜSÏì+ØË V&°b�u…iFëË=(­û¸SJ…Þ¨´(Ö Ø°ÊEñf_ò [-T*¿p×–ÕºÖýâŒ(0Y Ü>`")1mjôR C·“Æz/çk–Òˆ”w#ÅŒâ] LQT óoqñ®<žMe¹Wb½1mj†{ÎÔèÃûóOC×~¶’�Ì3®F^˜¤Ëk™kÃÅŸÅÐ=ÈÇO�_cì_ß3Ër#8܉Âpõ*3Ý›^pµÅÛÅÊ ²Úà•.÷°V ᲬÇëXAºaj�7m¤-‡k äÒ0QÐå0 C (jØ:ÑrevLqTÂ:ÒZ¦‚!nSÉÌ�ݕȱ¶ÞÒÍ;ñAÃyg�eœå ª­>Ç#°f#+ƒ5¢‘-RÂêá!"òé(ÎÅV,‘ºÍ†n@ÑH¥12ò¿[™‰ýå6ܲZæBoz� 2±¬ njÚ´EÓ£¸\|sšç­›ˆDcsÐzkÏ’µ’n)Ô´³ÛÈm72Ñú·ªÓ†[ÝLbJ­òÔЩ­džË…œÊ"©þüšWNo€-å7M™@ýXjgé ˶>"½ayK~H5z†Ã^H€"GµÁR,ûŽ)âg‰y%7>ÄáUrçE>1,¢ðk0ÁFàx1®�ÜÉMx›žˆ¼9r~xÞü~ æ“ø÷‹È�c£–âf‹ià£,˜�§·“`~ —¨?‘$OBkøø1�Îüh|ƒKï2˜boé5€WA2'Wa,¼( Æ·S/Âæ-ÂØGw&?æWZógØœh÷ÀÿŠ ˆo¼é”L6˜Þ-F‘ß0Qp}“ÀM8�ø¸y飧ÞåÔ¯Mb°ã©Ìz0ñfÞµoµBDkc¦ãµ×pw㓈ì{øoœáœÂ‡ó$Âe£�’â.ˆýxQS¢®¢pÖNiGÍЂ¡þܯÑèJº7‡Gh}û 0L|oŠ˜1)Ÿ†~Tz™ëŸûÐm»ðY]=Æöilâýœí¹êãÊs§Ó¤©�5�¹Û’ )U}¯!_wq‰;2ÍR1µ·oZ ÃO;tZ}êÃü‚.ÚÆ]'¿;\;e±²DºaõÛ1FÝ•ÿoOÕÏ�×È~Õ’æ)WïØ~·uw¹ê—J™Ê\»Ë\.Ý-Ãö¤ÜªÀWºÌ¹=rUÿ(è;à¨äŒ¦$„˜„Ï{1•W+­rÇö91ÉѬP2®�,¡N:XÊ{�n y#Ý`¿€’<×=Ë<«& „¸CÂ)‘èpRIYaó"LÝ£Ò�T-Î2 ûA‘É�e\jWª@_Ñž­ù‘“°]”•éYê2Šô··ÀR%uËÉö, †ê¨ò:#>=ÖÎ8{زG$Jm$ØcºlN´0Rn\W¥­l‚ÛB ¬ûÊN`*1K°“ê‘ îÏÏ‘[MMݹŸg¦ÂÎyÒ]��³”eUÖiÆOIƒ#¼ÅÚ< ~­´�;4ô×þûn‹zú£©ÿ0'¶Í IóNrÚ”?dóåwýìÝ ŸÄçC›� Nœljý´Lmiº5 �~MvB·ÀJÑ·«þªz‰’uŸÿ‹f»8õƒ “+ÌíGYpGC·Õ¬² Ï=Êx©xJ£P¿u@ÉœŸÊºžZ/©PÛ4‡ÿ�ß­ê�{ÿ¦ßÿ~�•gßf�ãÍbyÿxè÷ñÿS–¶GgcyŠ8±7xœ¥RÁŽÂ ½÷+¸A“¶1»7ÿcc…‘¢ »õïw¨ÕmV=É…xïÍc†8¶ÊF§ÞÒ²(´5-ÍDvDÁN�“FóMýÑlhÙhH|8kéÝÑhþ-‚­F¯<”™U”—ƒHÊœ¼q<Ç‘­ô+Bc'P t:׈Ïa—f'ÏÙXØÉxǨI sZ>¥Úù¼ÀsV\3ÊŠ „:v`-­æã^ <¾~ûÈ’±‰º¿Ac�KnÉלæµ2×Cô®‰~ xð>±lÍ8iGhó5§�U/)‡ëvŸ…�ˆš­È�¸Xá—Ö€Ky”Â9Õ�Œíž°é\×öi]ç†ÕøªÝ~³§Íч^$¶te‘0¥  ÿ‡»PóT0¥å›¾=�LoËÿ¶D:Þ5–¶“°öú)^ü¯³%)ŒP”Å/oFH¨xœ340031QH/ÉNÉONÍ+É,ÉL-Ö2ô2ó>êÌÿîÁ²ÝXíˆU¥�‘O³‘Té6Cˆ†ÜÔâü<½¤ÒÌœ†ƒýšOŸò•LªŸ®À`Õ0uݾ‡{s%!Þ¼xœuÌÑ Â Æñûžbí9FÌBD*'g.èJ,dÈÊÉ™ëù»LÆéúû}ÿfÏ•‘æ^%÷œÜ諺Õìtf‚·õa×lVûXGôiÆüsö8àºC>¢{Q«Ø•./C Ù�J�6;Üd ƒ¿å_…àBª�Ç%̱�7½ì©] ăiY¾¾n/iG³'xœ•’Ánƒ0 †ïÚA¼lØÞkê-ûbW:ÔÆù¼*þ#¾=4'9B(Õl1ý>ÀQwŽ,Q‚a.aR9޶èkpÕ_ß”’›±(¾‘÷ÔY© xœ31…”ÔÜ|†„¯6õ2§=1ÛSyGÜ·Å;²ž(˜™˜(ä¦ççé%•fæ¤0Hü®6çdšÁô·ÊÔ�¡ŠövoŸUVœ™[�“ª›Z‘¢õ’B'°½Ømœ÷÷>Û»°%aO»Ý^F®Ã®¶ ’AKòá¡Ýºù«Å1>øz4Íô§‰ˆ Øye‰9‰ºÅ%‰y)‰9ùy© ³Ÿ2¬ ßûqÑ?Í[_fvüŸ‰,rSù­xœ340031QHÌKÎÈ/ÒMÎÏ+)ÊÏÑKfÈãŠdxâþî_£Ö�$ãƒÆÒ¶ñ^†µé%Ùº9‰•©Eº)©¹ù@µ–}GîKK:´9Í>�æÊ­“,‹]mÃͳY¬=Þýe<þ}u\&—X¤AÕf§V&å'¥è§æ¤&—dæçÍ>¸õÏ6Þ{«š§ìøÜ:#8ïßÎMÍ&Põs‘W•, ,o¸É-üª.øÛüÔ¯‹b§ôBç&¥gæ!yЉUvÁ¤¾þ™\œ?òn©Ï­rÏô€©MÍ+ÕMJ,ª’6·µS�À"ÐßöúQä¯I3JlæÃU-M*ÍÌIa09`•›Wg(Å¥îö^¼pçS.·u0eùy™%ù¨îäß­÷C®tÆëG þÜÖxÚÖhµ^`LyqrŽnI~zzNj1PeOQâÞ�ÛXönõåxYôßör^¿¨ªÝ"ü³/zz´~¹èxœÕkSã8ò;¿B“«�$Œ˜­ºý0ìlU |bÊ Ãr·[.ÇV/Ž•µv—ÿ~Ý’lËŽ�˜Ç°w) b©Õ/µZÝ-™ý]2žû1¡aâG”Lý€x |—†1õÈ*ôhDÎ�ñÙ…rÌ–÷‘?›'äûƒïȈ-ç>%W~˜ÐH‚\ÐháDZÏBÄ4§�Ü“Y䈧‘iD)aSâÎ�hF5’0â„÷dI£°Iâø¡ΈC\ …� ò³irç‡Nè'Ž™ë;€�xÌ]-€y'AzÈ~L:Éœ’ÖHŽhu9�:ñC‚}i¹ó“9[%$¢qù.âÐÈ Vò�vþ—p8—?F¤«$@>5²`ž?Å¿”‹µ\M?žkÄóõd•@cŒ�\±ʱÏ"Ó @ >ðÍe͹ã0He‰ M¤Š8Ý»9[%MWQ$)ã1P§øulAð) v‡¢¹,ô|”(þ(§m ý΄ÝR.�˜ã�%À¯àga™O­ìŠç0¡Rk@tì(2EÈCœÀìû0Kq¢eYß§LœéddžŒ¯z–NŒ¹°Ì/F_ï“VoÏ-�\ã3órLÂê Ç×Ä¿ªÐFô/ð@Fg½Á€“ê]÷çïØ¼¸¶ŒÓ³193}�tà¬w4Ð)êxÐ3Î5Òï�÷Nu>Ê,“Ü]�é¼ èõàçxl˜CãØŽ-xÔ@Jkœ ½2FºFz–1B…œX& Gu“#�qC]`AUg@ðùr¤ç¼ôõÞp�p° ŒSº¿³ói¤5KnöçžF{]°÷óVÞ÷cœx�?y?ÿig'Æ•æ’Óäf€À£9¬þ –ðÔY‰Í‘�OätüÙô®uË�éƒ�ü>6/3,³ cuÂl¬ºsÅÿùÆÿyÒ 6*þ<¦À³eƒà²üP>ЈüyØDo•0›~1cÿ–Ú°�Â`Nã�ìïÃR¹sîÁ½Ñ„€—tÙb�‹-ðŒ-q©Têã3½Ÿ0'òÎ(/%u#í¶~Ú!ð)«è³~}dö¬¾}nöuŒö‘\¬É7õ¿RÏŽý?ž(V†0db^í|&²xÎîì[ð—Ð SîÁ €}Îp‘qÂ÷)²»ŒØÌ�JY2äÖá:d¼|Ñ=÷pˆ³\‚ç—›CS^à˜ZÀs×¼ ùþã~rŸºGa÷|ÊÉ•s �@ a. ªèz¶Ï Fº#ØfçI²Œ?îïÏ`[MÞƒ”ûw‹»»ý\¡lähÁp áëÂû Ü}"ŠN›LV³HhŠÅdSoFc4?¤«¶e,â"Ê-žó½dÀ?Ô@Ì)ß>‘"ÈN6±ÂDÄ Ù.ì3ǽ!�c&¸’½8[¸9+=·N°‚¶[æ{d×sG#§z�ô»»ÿvsRR�W¡•-ÝW¡&ÜØ7¥•®H1ðÔäÈô0�5!K'Ê�”ø�Ÿ÷ óu&pKËûaaGI оm+]ÓÀ™¡åŸÚæn_öÉ wÊ]œ’« =« ÂF¡ìm¥Q`=»‘ÏåáKÀ‰ç«Á[eÜ…1ÃËÁ@0|!Aû»¸MZ5 2&X®�ÁµB®•ÂoPã¼{˜uF4YE¡,³î(ícëš�Ð(É~e—€Áqë>úõCøóãš½èýSÝÖ‡cëÚ^žé€½{WeqiÁÑÿ5«?IÅdþç —­ùÍnöµÂ°6Û=Æ ¦_нB™‹©Vs\`�ã:„ï‡Ä-ʃŒ"O6ÕŽ*a+Õ…3*!ç„s“¯y‰>zzŠ7ÁŸ< ?xË&Ø'OÃ.Ü`-�"ÆÆn?�\Ñ:àfw„ŸÌ%É\SLpLn›HÄsNáªÒTð*`hŠ÷ÀϺáZP ·Ö™4¬¢ý †Xáªaìïg§‹iñ#L˜ûŽ;X¾}ý¼÷¿ÁT'¾�BQˆ.e=MÖÔ$YË3dîoØ7ŒbÔÿ/£î¼v×¼×£,iÝP^r²„Ð�­fsqЧTñRÇ‹.QŸ|Ä~ƒ1A4ôòN,ˆ_²<ø‰À‚NX�­ç·¯‘ ­ò2ÅÃaAÛ0]Áÿ²Ê/EÍo¦8x—§iVDº°ç©ºå�”ì�`Jkw 8yÏ»wå­jÝ@ÿF77f 4Þ?Ú›ºy´†+~g1—Ʊ Èb±Üwá««I»ÝŧÛÔyÈc¸c~÷+@ºòË'PWê»D“Ò;Òi¥Ü­u;žgcʸä>›4,9Ù3Pº•ÝÈu· ÎQzTž#ROÐ7�T«S¥Ñò|û ˆ®l˜�o)åÃå館_u¥/M}±°'Q ;̃»5übYäHÅ”àT oàþ¼h¬Ëˆï…`«­ïâ_BŒ9ÔÞO ˜\g¦nN3aÕ6¿G×IÏ[óÞ+L>)€üBDçCw-­<óì–J¤’cÒ)¬à�À\”oÜ~ç½ç?¿„¥8»UQlFK¥B´§…󈛫vºaýð°nÄØ’r:EÝlÕ,Àñ%)(ˆB´½Z–LmV–ÞQìœ&7W¢L½«Â¦«tÌ–½¥AK¿»² ÔùE>Î9oë€S:ú—~<&��”oe¤« =þ‡NÿÀ‰hö~â`ö�"÷.ÑÕ&MȈĺ Ì‘¡BSQ¬ªäOäÊöÍ+Û´ pzÜ{ç)ºPÃÛ·˜�!§™wª½i$V·°Î·2xÿ6ái°–[p9³‚ïFÜrÍ6b–ËõHn¿èx2ã5S¬:ðM:­Jc!É)w)åIul±? �À¦ ?±'÷Üm,Ó¨|äl€áŒz-MÅ—-éÒ6 ËZ�²åØÂRM‰l´;­bõf»or³>ÊN$|Ê&l|Ád @f~›Ì%ÇPœÏ‡�ïüÄ�&K�×»Ûh¡ ®õñÌ–FV3]7hQçæp+·©Ì�åµhÙ�8­Ò�OtL¯¢Ï­\ŠUŒ¥4 ôy°UÜÁr´x[Y:¢”Y{¾vµ²é.ô$ÔS?Š“"^ðY(cŠWà7á”[xDùíÖNê>ÀO—ß!…0Þ¥üFˆ ßr×Uµejä@­¼*ráyb¾pÓ3†ºUZ¤jE3óÌä›~‘ ¾±Á³GÒc™?CzZ ®0ƒÔ4^SÊ^|I®ïæÍuôáûJ%¡LøµIy÷¿¬±ÊâÃ�q.F­£ª‘³œ…dAÆÄÝÊe¥€%ºå7‘Ôúq±¬ØOLÊ_‰ÇúÝb¸‘û"‘.¾å®ì‰ *{¯—¡¡’Ô`¯HøYÈË2Ô¸ågÁ`³ah¼rະ[q¼&¿©Ã­á¶ìîŸÃüßϘԭteü&#ˆz9ä–ûµà§ÖåY8ž¤§O_E[µPM­‰s·úT7ÀXc|[!Rh|mS&íMù\÷*[Ù|‚ÅUKöt ÷À²’00b^YÌ.ª‹Áù;¤S]õ•®¸  Ô{xùÇ•K)äÎ/Òò— Ô*N€ðGðVNaëyÑz.#O„kê¼Ä‰çÖ}D Z8V�„Å9ù2šñ×:Ù�œxcÊM:oŠ·|°è,ž=—8( ÿ«„äÿG…ˆüýæû%]¯¦§ãF|jqüù%¸b÷ä¢}ïÍ@œYWN{…Á¡òÆ×º¬SU‚Ø#ãß².‡ö‰a�ÆUPÅ÷*f¶ë,^E½ þà û‹iômÛV"<|ñ·A°Ú(Ÿº"—ÅÙñcV–«(äÖ,—âXž�óäú2)(»Ùéxj#ié ÏŽ^™SL³H&«¦•IY”ü/ó_Üð鎟$xœ½TÏoE–¥bõGB’Ú$02ÚµÖ¨iªšÄnDì«ÔÄÝBÕ ›Ýñz•Ínº;N¡ÈâÈ�š3jœ®ü þŽ\y;3»×ñ©ª¶gÞ÷ÞûÞ{ß›ï(ü¶öëÙ…‘FO�ˆKú(¡ñСè[ Á§Cºö)‰ H´]� _õÌö•ºÑ ª)ŸðS];C;¡3ˆâæ�Ò(¼cS»®i µ©ï ãÈwµ(Ä6G`y^@\¤� šÅ Uöù¯9 U\ø64AÐÛ�¢€Ø!ò¢ÝF=�±±ˆƒ=B±íPÿ˜ ]\¢ +: ÈñUˆà”I€Ù cíbšªºÍËcÜ(}-}<:ŒCì÷qßö¤çnœœ@MÜU·ÓÎ%OÆÿ÷€§ÂºÎÎZÎ+ e?YïÖâ†!}Úl¢qDS�ÇþY/.É6§Æ¬!9aO�"û÷viéüñ²——‹À߉„Ž; â°‡eOß]f�â\a/Å-ä8g`ǨâÃ�}§/°çËo�^²QDý£lt\?�Žf³>wn@éYÛÁ*¯§hc½cÝÃ͇–ÕÛÍ'krèˆÒ¸G‡8å†Cû|ò¿&JÝï¶z»øÁÝÇmÈPžpŽ<¡,SòŒf¹L”•%\¹Ž½¦µ¥L®#ýeA›h]úfÈ)ˆ!×1ý|”Ž@|àã[U`M�—s�pØ{~ce¤wp¯ùY»e)Í*;�ï'¾ÚB¯e!,ú{‡À›�îFÔïŸ4“�|¤ ô3$ŽÒ•,&ÄéàÖN·ÛÜi݃=ŸÚ}C$R:>)€DYÜtVV¯Óé¶§'.å™<á»%É5E/“ö¾¦ÈN“L€<d«ñ–)dËþ¼Pb_–Sµ¯î�#ÛhHß%ø˜]ݬ®\7ê³|À~©­_Ë]ö£g@z øûãµ9Å3ó˜+ÜdŸlêWr ðKñ%öóæj�çfßl½Ãjµ÷楙Gl؋ͥíYëvþó€ôɆ­WkÝö§VÞd9ÂÆìEÕ¾'æ\²EéÓr–þ÷-c^›IÃúâ!C�¥7„(«�k 7‡„ró² vYXQ–Y_y}VïþŒòàWŠKOJmì¯Ú›—ðD�ìþb·—_«F³*ÿ|±ý|nòbL¯X&¡+_ÈÞ#¤«íSéøTý½Y•6f”ØŠŠ}oð?$õÓÊêuÑÒÅÿ6úc·�xœ�UQoÚH~çWŒÈK¹!êã¥w’ X16²M(=�,ƒ×x/Æ‹¼ëzê¿™µ)$\zi‘ìÎÌ÷}3³ží]B” ¼Ô¢â�‰‚. ±â¥â)ÔeÊ+˜8Q.ñ�¾Üî+±Î5|¼þx ¡Üæ‚Ã\”šW­Ë”W¡”�%!å¼âË=¬«]R ²Šs�¬ò¤Zs ´„¤ÜÖW äR'¢åX!yjÒ§d¦w *LÊ¥äJ$ˆ©\ÕŸhâ#ù ÞéœC7l#ºï IÊ“D d;˜`'t.k Wº+°ÐiUÔ)i8˜ ±-…›ü�Ö 3 �ld*2úå&­m½,„Ê-HA/k�›Š6Ma-Ê£'+P¼(A n“ëQ�ñ!–-T·%2¼»\nžg‚%ÊêªDJnbR‰%3Œó•¦rÏdQÈ¥¶’e*(#õ[Û¶íÉR>r“PÓãRjÔÛè .l�­mM*O0�%o«†äXãä$§Š4(�ÝØ€­¬ éË\¯"Æ BÍí€�Â4ðï�@×qݵ`îDcz¶-À‚í-àÎñ°ÏÓ€…!ø8“©ë0Üs¼¾;8Þn1Îó#p<Ðù†°…rXH`ôǸ´o׉ �È#Ì!‚Ú0µƒÈéÏ\;€é,˜ú!CúÂzŽ7 �…M˜]!+î»Ç„cÛu •=Cõ�Ñ×÷§‹À�#ûî€áæ-Ceö­Ë*LªïÚÎÄ‚�=±GÌDùˆ·VÝ|ÌÌòÙøô#Ç÷(�¾ïE.-Ì2ˆ¾‡Î��Y`NH>ÂS91Â7 ç±…Jý¼#èBëYÈŽZÌv+¤àSgji¯Ó¹Ž� FÑ]ìÚ Ä6ñãqçwEÉÏ Òœ%è®õÇ"Ùóêžë¢¸Ê»Gã'4öð{•ÿÑéèý– /ë üÓüÌñ@øóØl�M ÅØ$¿ÃµõšöÆù‚…³ÝW]îõŸ¾ÑØKåίÄaÝ•à[_ã‹×hY/¥,xRO×\ýyL93×�Ù`Äbd±7›Ü²à¯›†þ%>¾N'\ä2Òs� .µÜü‘ñR>�3Q)ýŸÅi4¦oµ ƒD'˜ ¹^Ò´PÚŒl¸LÊU.«Ød?ðýÍs'¼ p ?ñ4Vâ+�w"Õù�]rN³É¥H;¦ÝF™Çõ6Å)Ÿd ïŒt²Âå©ï{8ɪÅÁñÊÍX�K¾{5Ö¢z¸´Ò13ÿðÂÈ’ºÐ±q|‰¾‘¥Ðò­ø/£Ûb5t%‹ÿ‘ÖÔìû :Èj0Þr–δãÅ+ÊŸb§Žˆ›ð_!~Ä ŒJ%ÊL6´gÒÔªˆµ\¯ñÿ±0sŠŸÎKTkó'J<òø«,ù›âŽgóLΗx™T?Õm|G–2©Ò_;ŒwmôDâÌ;(|XÉÇ;<ƒ^ï|†þ ‡ =à»çxœ¥VmoÚHþίùT ¨óÒ~¸—k%ÜÙ¦i>Y/°ãEö�úßof½CÒK_"ðzç™g晙ݫ.Dk^Ë%/,yÆ3¾`yÉRØå)+`âD-èâúbûTðÕZÂûë÷׊íš3¸å¹d…Þ2eņ—%9!­YÁæO°*Ü’š°,±„Å:)VÌ) ÉŸ`ËŠ Ä\&<çù X +Ú)‰_)–rŸ Ã$O!)K±à âA*» ’O$ù#ú%´åš�j ££œ¤,É€ç@ïêW°çr-v VÊ‚/ÃÄM‹l—‡úuÆ7\{ sI »# ž&lDÊ—ôÍTXÛÝ<ãåÚ„”ô|'q±¤E•X“⸔,Ë�#oë‘�ÚC^¶”P©S¤üî×bs ¦h¹+rtÉ”M*0eÊã?l!i…¶/E–‰=…¶yÊ)¢ò/-[„x`* Jã\Hä[ñ ¶Giõ«r�`s¦³†Î1ÇI#¦‚8”Õç(ÀVÊéy¬—5‰± ¡?Œn­À'„iàqö +ÄgÄ['û³pG`yÑøC°¼;øìxì¯ÓÀCðp&S×±qÍñúîlàx#è¡�çGà:XÐùÊ¡†rì�À&vÐã£Õs\'º3aèDaÔ‚©DNæZLgÁÔmt?@XÏñ†z±'¶]¢W\û >@8¶\W¹²fÈ>Püúþô.pFãƾ;°q±g#3«çÚ•+ ªïZÎÄ„�5±F¶²ò%PÛ4»Û±­–П…Ÿ~äø…Ñ÷½(ÀG£ ¢ƒé­Ú&X�RB†��ð”N´ðÚyv…B©>U·Ðó,´�\¶å"VHÆÍÍ$éU«õ‡. 0Vòþ"KžXq‘²�¸\­Va)Õ(€îbWØÈñFä\Š"¾gOðŒzYYÖ/�›V«¤Ž\ÀƒàiKä3l(,x¬ÃöHÞ÷Åf.zâÑéW<�&à:ΫTì¡[�îÕS§õo ðGÔ`ø‚=" ¤â“q‚Ã[¥}XíÀ¼»�«+x‡!! šJÔ–)[&»ì€©|ŒÒûIõ]½Ž^¼™ëÞT–Ð>åðñ\w b¨¼ÜbÐMõä™ÞÇúI±¬}·;7ËU\0‰£"æËx™ðìÜÓßÏPÉÅѪ×; Àcç†õ›Úʯ“®F$¹*iË&‹¦ÚM“o­ÖY•1Öd¾RUô<Ñæ‰~µ�*¬ ®®«6ùèÔ»±€k¦ÇÂ’ìQ¢:Â1ÍÐö(ú÷ýIÏ�{þ×8²¿žä¢íF²Ý² ˆ]e�´üÀÁCÞ¢#3þbÓíÀrMøS3jÔÐê@ÿÙÙ¢”QÐgâ합ʉ"“|«•kœl†®QãG„]sjÈçãá·[ów�¯’¯ò$CÿyNWÛc³Zâ‹$Å‹(vÉ(F)ÜžÕÿ í†; ©¿ŸóP é_÷ñâQspS%þ»©ü_®ÍrÐÈßey¸,!Ë“â?Uš4Þ&‹{l÷¤Ð³BQzГþèrh¹t¿Ô_Ça¬¾ª{�ºÿzk°çHŠxœµRÏnAŽÔÒ#’ØÃˆ­îRª\¼ˆhYÑð/�V­ Nfw‡í„eÆìPl�‹`2WŸÀ“àCèø&=7&Î.PÀ¶zr»“ßo¾/¿ß÷}§kŸo}yûÂëY¨ŽÅ™/ uˆ=˜f¸Kraµ$:<$^ó�¸n™ MŽ=»ÊmûØí©G£)».ê˜È'BPæø-˜Ÿ§jœ‘T–v˨RØ7¨ų̀Tиlìo× �"ªÖ‹ªÕkÆ(3…G–Ûóiÿ"Ü2ÌxµSÙk>1ÇÖ´I3ûØ»ª×PѨjEåðÔ‚}NmÀ™ZH~‹$¯ ¨í!O"w)=vx×äÛü¦­à†LuÍÃÉEÌàLdìLõ’ãX¨.–P+É+`C~½~clrîÌ Òm€ý@;Eô´PiñÛ܃Z�ùÔaĆT5³9õ{}úŽð¶ö‡æ:|xIç ÛÒrsSŸz:nCÍA*V÷mj³i3缤­A"t˜W,R„»ŠrƒˆïÑŒuƒŒh“Æ€2›Îñ’€8Œ‘ž[¢\Òc·±g,·M�àμ4ó¯ƒÚ!ó]±üM¬ÏUe€²ñehLƒ3—NÀý{™¿$Þ&mÜsE�Šdìͺ‹Mâª'¨;bdµÔ ó8¥ç¤sdz¤L/ÌC(”ZØü¹-?€¤ü‹ÊÓ•­×—Y»¬0UɸàeèU‚¹úNþXIÈO@»¹À5’ ~[~�o\UšýŠ—¢¡N²ºš”k«è7pplé@Žtxœ­RÍnÓ@R tCŽD Z‚*ÙI€¨×�CRL@5%.¨ª"kc¯]+Ž·²× P¹~¤½òœ8ñˆ7à ø9—ëµilÒÞØƒ=šùæÛ�ï›ÓÛ?®<©$! "“Âcù1‰Rh¢6}4Ç]‘Ò™ŠÞâ`|ˆ=ODp�¼ˆWã¼yžá¥…ð`{9ÙqC[à€§m8Ôw µ¿¯ŒŒñSEUóX{¥$#Æí¼A'G‚uíån@(%ó ¡M×µç+42gN@"ßZïhýméïìGÚދǼ-îRD].ˆkâ‹éØ—ÊÍËK×r0e¿*w·¹.;d>%ò6Í42¦<ìÁ ÃIÒû�LN!KªuÀK2©»ÀìØbŸ¯\M¦„xùP€Œ% �SNô¤¯Ž•Ì›PŠüÐu|lA—;]þ{C÷&¶´²@†ד�‰Ìñ­–œ»“׆’c„4 ÌùQJÚ.ºéN¤{ ÿ¯Ø*&£³ —¾6‹rcéúYþK%¶Fî–HÒê³âÌúhO)C¦F³U*«¯cØÆù­œ™}ߨß+©È“1ܪ×ÂØÂÊŸ@J—a}³-l£ÈËÇa7ªŸöE¾Qxöÿ²‚�€;ì}uƒ�nÞŸ#zY…Ô¬säL�)½Y>ëbß6ëì�®˜bj·Ø×ÚVEÈð»–üJB’é—£-xœ­VÝoÛT—¦ÒµNÓ¨êÖÒÅÚÀÉÜ-��VM�´aŠ–Åmbè$6]9ñmbâÚÆ¾I˜hHðŽv_xà�€÷IððÀ#ÿ Ç÷Ú±óQ©0üpí{ï9¿óõ;'yyã‡üO_-�jP«�®eJ®ƒ]ß"Nxßí®átˆ‰”‡´wb9¦;D<ä*Z"ŽRª*â*f‡PTh¹_ä¥/%�šÉGûœ©C‹¶»HIéç‘�Ÿ¶tR­j' g®ÞŸÜ„O‡öbŸ[6Á¡éÀuýÖÕJ]/•k¤„Ψˆ=ÍìŒóûSH-Ÿ½ýEfc½ªÕñ'•†^=(Õ^Ù�1ø‘:žÿo®Õµzåòni§çß»5’Fì|©.=6üŽå4=Ëa,½9>ãÛ)"…wå>¥`½Ðâo%ZâæÐ *˜°ÆÄ±Š†Ý'è*Y,Ô1� ‹;EœDnvèfÛÆsâóÀ…/ìÛeù5.Í~^ÞÎD†pì¯eÊ~¿š½¢J웫[;�g× (7m’S£oO0~•î´Kægý€žA.QÁH¾…{ÉvÈ)ÓúÁ§:ã™ŠŠ·‹*Ú+ùû._ïñ‚ÝÞ¸þîl´/ÁeïÁÂÎWî§³ÿrå�ôöï•·RÛ«_gxvyeØw«Ú^jOL8€¡àµréàt¦ƒ§+  «ÃŽ¥í¹â³­vo29ÄÀqµSgžë¹âÏŒ!Èǃ p$¾ûž í‘v¤-‘‚b HØH’RàRT­siwsaÕݤÂ�g´-§õÛìI‰…_dvÇ‘XÌ0Xǰ1à8¤M‘"èÏH95.Ní‚‘ ù�Œ† )Œ…)bÁÚRâ+æn^K ªh/}Tª5ÙŸÙ-Ö[Ûf6V§h~¼Qd8»ü¶\#§‰pj†¢!ºšË³_²9ÖËí\Iìûõ-öcîõ°÷e¹auº—FZECÄÄ>_+ýÝÔµ#È·îz¯QñVYÓuí1à•]`âÙe!ÙõÍì‡)ÞðVdŽæBÒÃÀÚ…éÔ"6Rä&™äùÆ8¥ ^4!=QÙÄDÎ<ú¾ÛwLôýS¸•ós¬�ú"²·)øÉ¬LD2†]Ó°gZ-î4é��+ 5)æ-å�Npø"ýÔ1>ÐêM½QªÖu,~Óòû"{n`%¿¦¢6±(_Xœãõwž$÷s-Zòtá ë±aCÈóRs]ÎóÇ~[•ßKO;°ÛŠ>¡}þ%ÙGÔ?ù÷æ|Ë,xœ­UAOÛ0U7¤M‚m°!Û¾fš”Ðrš T[a])Œ‚q´ÒÆ‹�d�[˜¦Šã´ÃNþwÜOÚ_˜vŸf;Iã¤DNì|ï}ÏÏŸíoß3¿^üì�œkµ(iB×#¶æ¹¨éxæ-icŒ*=ÞÂngƒâ˜C„¿òÀLjk{§0çXŸq�Êž©}Ñ€?† 2elQ“#-zÅ„ô`ôqY,j=M“¬v S˜ÓNxJÔ°ÚÈŧ쓙U~vÙÃÂ<û³ôÈPc¼•ÉT<l¥`ŒwÞ™™‡øÛYžeK+O߈¾�ª"E_@¹@z„«�0ôÏïø’@gÖò[\|>ÓÅÌɧ#&Í. OÎŇ"3è4D˜šK*Œ ê“`Šb°QÝÛD[•í}´±WÙ#Iœ‡(&æ»é`ÛS÷kŠÛ ÿÿ'¿-à t³ÈV§—û�à;²|»¶"¯¾^©ÕÀˆ5åa0mÈÃ~ÆØß™Qój�W˜Å~ÏLÞ KiSJB:61=yo¡$—3e_ˆ+=xY­Ó6q[¢Ô…\ äH(FfdJÏrýaŸ·ôP”8�σ.¶H“_ÙzHÿárU ·ò5ÿŽ' ‰fû‰Û÷ÓîíîW€a—Iµ¤_Hñ2qG2=Õ—õ›ê�+ÉjYÄÕoQ"ç‰ÄM^£’2¥ìÚð•”J9XEœ*À.`díÆ­×rdÉè-§c_Æ_SÏs(ñÅg4­†Zƒ(N�ß’ÅY^Üè°xË·ïlïÒÙ•ŒÄ‡OI1#¼a†³¢,b£óF8�K;�Pñùשç=Â`ã/ÂðÝAZÜÔô\71V“’®E1ߘUT^­ÕÞ¯–7Á¼¹©›NrŸ_ûüKR_qà§mLzáWض1í´]wYQ»ÓÓþï�ȹxœmŽK Ã0 D÷9…wI¡Ê r–âÈŠkbK ¹…ܾ†,œÒÎò͇ TäaŠæ·¥L6 ®iŒu‡ìR-1ãx?¹g|Š W•ÜyñÿòsÃ(Ö$|)§*ÿ-à Ublw:}“Z‹Aâíò§¿`õÚÉNÇ*^Ã×òmø�¦F‰±âxœÍW[sÚF~çWœ¡/àÈ—ö¡uó @M0b$9Ž›Éh„´ m„–J+c'ãÿÞoWâ" ®'Oe0xwÏùÎw®+.ÏÈOxA,“*’ÌX5GŒÃ=ŸrÅ¡�È>GV"×F}½Ø�Yä9CÿÞt-²=šºÎG{` ¨mzX· º·ý‘sç$\sâ?�3$sò@ìÉÀ ëÓÔµ<�—ìÛ騶°gOúã»�=¹¡ô&ŽOc PßÑk(ÛòØ­åöGXš={lû m¢0‡5ijº¾Ý¿›.MïÜ©ãY0?ìÄž ]X±n­‰«Ø#ë#ä�ÌñX›2ïÀÞÕüúÎôÁµoF>�œñÀÂfÏ3³7¶*Spª?6í[ƒæ­yci-(®«ÙÝ�,½{&Þ}ßv&Ê�¾3ñ], xéú[Õ{Û³ 2]ÛSºàU8¡áhèM¬ E…º™ˆ¨õ�gí¸ ,s ,O)ï «”^¶Z¿Ô…Aí…üzž†Ï,?�ÙR\$íVk1"eaÖYÀž WðG| P2ŸLRçF~õÅb‘²^)%Jï,�zÌôÚ  i%ƒ €‰‹5�iƒÁZ¯º­ï-«ó(xÜm‚\·ôŸSGƒt©’U/*œ°”â�(ˆf§aéZ+¿K v Ž5 ºÚ€èÏœI´7 ͱg]·^š¡›ó'ÿö? ØšÇ ø¨üÔÄröO‰) rþ‡àÞÜXþAÌp7ìü€ºL; S#éµÈþŒÉó_Õ_#Ô”:‚:Ì T6GiÁ°/1o+b²0¯n::ËÂ%»>ڕȃä«ê`›•ÎY„: £¯ÝÉüÉ\‚^mzYDi�Ìâóz_³ýÞ¶6‰9Ûîç x˜ý´­SÒuºNF|%"g1$_éÝ£Fª<’ ¦‚öP¤a•[ªØQg¾È(.ëËô,6¦ŠneªYë0ð‚JÔ>«äãÙ÷.ÈØZGðTDŒmñì^Û`Æl–©<ÕñoÒÛÑÜ4ÑÇÇ™xBØU¡á¿š$jÔqm\¦¾ÁGKÝ3æØ ßëZ›ã.í”Y� ©kW×øúSÇ÷igßó.]žÜÿ|õ¥ ­wïöûu�Zòj¸Áì¿p½ì“Û4ŽRZ¡pU äuëõœO!8A¿)|£ê—í×ÕÆ÷Fp÷ˆ¥áŒ¥53ýÅ­áÿr¡Úlî?i%-�ø¯—«Œ×Ì <2FIEí�½ Uw~ ÙøT ºSKBVÛc˜–¬)ôÍT¡÷ï_ëÚîQ�7ìÀÕWº£ÉBOé7PivõÛ ìôŽí�,üp`¹ ðìŠÑFêYùTšê,ª4…øQ€Á§ ÆÃQ´Iª¦Áê'P�¦fn¦Á·9në@Ÿí&@��{fÿÃ�c¤ëóèábŸð¶¼15�‹{cø•ê>~ÒPýªn´l ~à!Ý xœëìeÜÉ¿¡–%—˽$;<3%=µDA‹«,µ¨83?/>3/-?>/µ\AC“«šKÒ“3‹´` ŠKŠlÒAtJiA|AQf^Iš†’{ˆ·‚O°B™jŠÅä�Dà\¥ÉÅÅ' ؉L~£Á?y±†ìä‘É”9�|�É4d�ør“‹l”@læô’ìÉs5”ar`þI¿È�:?>73¹(?êB Ë5­Á.Gò²÷@ÎjÌILJÍyUÉkP�éñiE©©h¸ÎâÔú’ø¬Òâ’Ì´J  gã}�\}4�mÒÔQIx…‡xºEÆ;»ú…¸Á¬@2¨$¾"1'3=·1z¦˜ú\�+ j4‚é,J-)-ÊS@V`ÍUË­S²|±xœµOInÃ0 ¼ëz�X}@¾‚,Ñ©Rš4L9Ëï«ØÉ¥h�å�à2Ãê:ä²t6ã$Ö™7¸DŠ^käI¡(”jäÊ0¡ üÈSíA²Kݯ¸5o˜ÖÂÎ@ {ª_žâ¿©ôÛôQ]’îí°Êa¸‡Œc\©à„5È\‹pgñ§™P­{‘g䌜 êŽM ‡–¦ô‰>vdáö Ñ_çÜ/vµ<–þ òéix,�Ñýؾ§—¥`Œ¤èÌ7ã�WçWá xœmTOoGW$�`+$êJ@�EÛZ;&%n%ª�•D(K#€¶ãݱwðffÙ™�Pä3‡žæÜCï !> ƒK¿BßìŸÄ ®´«�÷÷÷~ï½y÷×Ê¿W?fç2¢qÜŽlë6ãAœ…úS5[÷=´,©ˆb Z$Pìˆ( õ5s“$f*oI'·�¦EÖ[ ð©Y#õ¬\²¶ƒ”š ¸Ho�vF»0g<sˆÄœÑŽE1›Q½ºÞŠÉY*¿°ó9�Cå�ÆYÜM:)Ñ*“0a©T€cö††Hª€)ÈPÆELŽ1¡ÌÒ ¨¥ÿ¹ïè�ƒó«p˜†¨ÓC¬µ,õ“îýeãŠþ£öãÏ›ÃÑhøx È~&#ˆ#­ìAd ĤÀFŽ-ý¡Ûo¡å3 ¡àwpJCƒhŠðfôx,HãI¦Ê úr·©?ݼ±Øîm5z&�ù3%†tB²X�¦a’C’N—@JB¦$1‘ñp‰ »h8¥²ò|\y¡áA$R á†'ÛiÈtg²Ýnç¼ßøAßkÖ.x[Û#îwô°[+…×Î}#¨] ÷Xï SeMoÜ6ŠEÁ �¢°ÎÿU…)A¤9†B%wKœ¦R6ù¦û ã¼ –’ Ê‹­Ú€©Øí—C޳2 S¬ºdàùKxoG¿?Ýr øn»ÞAy8ééï¯îêŸ6î,*sö²,ðäûÍ'Ž 2DP$Åz–Ÿ œ1L‚;ççÿÅxÛvèLŽ]ð±µ3 VÇ­ñ=ws˃ºQ®ÄúuõºÝ— bŸ®ü�ÊàÁ {½Óî¼°Úzå·›×l³l^>òfó�þI“˜Þ2êëíþšñGã'¿Ö´ûË­óybu{ÝÉ—»ìNú$óB;lØMºç·½Îˆ oGÃÁ8‚ú ˆíwû—#h‰n¢þ¤ ­XcÑïxaãëN¯gTunaýÈØw>~u¯®'ìzлˆ°ø)‚e�O½Èª‚Sç½N÷¦Å.:7�«Èp edÈœuŸ¯#³}üw>éúäÆù ?áµ/G“ŠõswµXgÔS@.Gˆ§p‚c`„€¯Y)jÖ8�Ðûí8ÚÚruz�5&æ:qÛ ‰÷š0Ñs%òµÎç?�¸«¸`ü'W�ÈÍ�š—(c«*WÒ¤ê*ÖÈ:çXÈUy Bž'Ö”E,•Ë%’¸|U¼|’Ú[(”†ÔÌ­äq±*w‘��9çZ×8=d;+¥¶—¼è™µ`:Íâ5ŸNC¯Ü›ÅZÌÏe¶Ë …+éY¹ç‘ W¡ç27[ÓD( ¹_}W ~‹ùZÍégYüxç~Ò3ˆ×àÐô\ˆüožXfRñ)Œ¢fzjâtf·�\4ÞY¢vñP àHíiôGÙ!Îà ÿ‚á_0üÿ ý«aït 0k“¯„e]d�rÃæÀ_Å·%Ã�ö¶P<[_´`BJ!`Ժʧ«þ-ëb±wÅ3®�rCSo^ÏÖ`£,Kä*0‰¸.ɈªŠ.%„›BÿÈ8ŠS%º†IúÓRO¯¬`äw€¾»“91…ÊR“bŽ�²{Çï­{I H+™sÛ¥àß½°epYlÒ–Ê*ÿ)=ÊøXá@ßÊAwIÄÂÂ[‚Oo'³Éì—r»Íؘó2ªÞÁ¨–q¦(€‚—p`wªáñ¥†ei‚¦ TQ|ÎÅOØUõgN¬”íÅ©t0ì@ÈFñ# B¡»WYâÐí©³lyÝlÞn±œ€$Î~¤ˆú¸1\Š„_¦Rªû$uAà¦ÃØñû““ãw'§Ç'-d~Çåu”¤2C.NqŒ�?x /¿øióå=z}çßewúîÍ7ê¯ø«CË�6?u3ÇTIYM48´ÑÁiö_ú=`F n‡!„Ü�¾�ŠëËŒWêv¶K�ËTΨ+6wͼ+ñGÌ�`Ë^3ú ˆ=›¾~3”1 Jý¯~{‰&dè(Hôv$²…<âåàrh"ù¶ÕŸCì~@Ù[æùøÿ¢"„C&~B“„ ·2è�ÆÚ¤<ð£‡r M¦’°œÄ23çQº¹Vn‡L?lˆ#U|?Eª™9õ�9\T~HG˜a[£V "¨Î£aqžm*øúçëoo ý&DT”wB?N„Rò Ü·‰`_þ�jcôåY‰O5?+TMj� ¥Æ‚J®Ua-Y3#‰;SÓgÃøDV™|&öé:.@¯ŒÅfEp,0Ç©‹¥c²!äzç<¨Hö)l�ïîîÞ˜è¶éÇœÝ=�¹pª;Ì8/Œq’xAI:4ºH¯ÑÇÊŠi¤ýÐÝö‘'¶©=å_ƒ­kTÙÙå»ËlŠøæÁVÿADw¢bÀŠÍ—³Ã¥½¯° 8Á9Q C;ˆÒ³Kl×7›dì_ìä‰4¡;8“©/ŠcÁýJ`xp³>ÒoMŸ´1Ý¡’w23Úv»Eå˜Ã£t™§§»‹HF¾»¶ºÜÛ€Pw7‰ÅF&×nÚ¶'¨·á¶ »‹ªéÔJ«V¨¼‚¹<¬ �Ø;®wñº/ÐZªhÖtÅrÏw¿&H20÷¤.ºéú“Âkçñ?‰·«H¡[›§v@¤iá–ûŸ»ÜÛ<Øa,a²z¶mÁÇ�c¬®´O@güó45?÷©2Y¢4(·Fì–î•ät9¾ãrX¶§ZRÓH“j¯eá´\sWgU!•ŸEÉ_Dó°¼~õv´9Ü}êãqœ<Óà†„(/ðÓÅ•ÿXÁÎΘ?�òN§¾•haØû+?pËä=’jxœ�SAoÓ0>l§\8öú89Åë´ÛX‘6QÊ´¢V#Mª°ÜÆM Žl§]¸ä7 ~ ?„ÿà 'T4Ò"EÊó{ß÷½÷>ç׋ßOŽBßÑ��ÇÇ5Ï ¥-¤ áÀ¬ ØñZ&jã&�?ð&£@ÐÝá¬ùýÉõ €·Y·ò§Îó�'À4IZ€ëç@sÉ-Ù(MÚʰÓ�ze´´Š¸ $J÷Œ|U’&éJ°°öÎN’»¸e´½Iñ$IŽç‹!œŽÀõ£ÑWó8ž¿}@Ðá©\gJ? e2u±#în‡ûÝ­•”lmC”0cµª�çÂ9å’¸Ïzm:ÌB~E5¡xœ340031QrutñuÕËMaÈÿ¥“¿ X{[éôߥûR”ªþl/3„(ÊMÌÌÓ+KÌIdøô6ñë¶Ú»–0… tßß�!´áLQjq~ž^RifN ÃßÄŸ_æ-^׫´q»~Ìe6§ž0ðÖ/a´xœ]PÁJÃ@½ç+riÁlj{ô` V¼ˆÐM2&[7»qwbèß;“"JaagçÍ›÷Þ¦ðX>ÁNŸ0À¾CkáU[ {Ò®ÑÖ;„—à�XSRv&ŸAÿqÉ»‚j$Á�'.�%˜ u@BÀ8S¤¶¦ :œÄý¯Ñ0ø€ Á¸HÚZl¸Ö ?/mf«œôŸ�m¡’$Mát ºÚ`L2øæ45ßìv‹»Û�Z¯ÕjÉ�·‹ï‹Žhˆ7yÞ²ï±Rµï󩟦¼¥ÏÌÊ`ep){VêZÖˆâ½ø2®å�‡£wg§y;㎲íoKæw¦F…0i«l8ÿ±³3ÊÑG'¹Ÿ‹2ù1ë‰Öé†xœU�ßJÃ0Æ©ƒ"â+JV¤Û…w�×»°wŠ^ˆŽ¸Æ5˜¥˜¦–"²g}†=Àð1d¯à“š¦)s!¿ï|çßïñê¨7 `Õ;ó1æÃæ|ã¡ëooéðóñ`ë-!b\ä\Îa¢_¢Æ´bê:eBDs©aA¹$¹VÆ¿ªæùÞ1˜gºC.¹&Š=·FdëoTAÉe’•p ’•pgùgÇ‚>1áì¸aÒŸÜ\�½ö¼Y¡S¸¥‚žôÝd»4¤IBì¼+×ktŠ~¡Q‡?h¼Ã‹záv²òÇ\«‚í¯M˜I™Uá,“’Í4iâ5᧯×û­yš•S*D©kí´bºPFþÀÎow-½xœe�= Ew~Ûkc!5êb¢»ƒ››1 âUQJ›R�þ{‘6~2î{÷¼º©NÐmBWe•8´gaÕ�ðGX+pSemAãá­cˆ2Nš6º¢ñ¦rSN¹ʼZ£á<].V=+á+W¼óóY.Ç9¤”±PËg|‡n§ï 2£°0}~Ñ©ˆNÿÁ/ãçà úÒª­E»»ã>b½ÓÞXø„JeœŒ§¥ø)_óP’ñ_�MÊ”²a^ªxœ340031QrutñuÕËMaø­d±ÆòùǸL�&Ý?f�§er™(d0®Ø™”q�§&víÉØšÌ92§¥/6„�›ZœŸ§—Tš™“ÂðËÃó½m¨â\A/ésâ+}n®Ø(j1¢8¹(³ ¤˜ÁqöÛÈYO‹™›¥8ûvè¤=lþ8I5—½:xœmSÉnÜ0 ½û+äÐHìCn¹uCP4h t€‚`±iK�,©5îü}IÍ’¤íÑ4ùøê nWŸ!&»E¦¦Y›!—MLá'õ è\X2`ßSÎ`=‡c/  øÌ¸qTA2§Òsnae á@IGÀ&i/fdÛ äúà÷í4À˜Â sql£ meÊÊ_£¢^+œprÄJ† ÉìH×.–�rÊ1†¤h:s¹„�r´Â¶dë'@É£ËÀ¹òò�!bbÝÇBü èí·OíÁŽ…L²“õ•ú@[r!Ê*©£öû]ðGÓŠéÌãL0ñÓõ•Ò'jáÖ2.‡$> WIc�¸7”èáÜ0Ç|Óu“ˆ+›Väv˼,Ýkœ ¡wvïŠuƒJãgÓ›f½þòuõñv¡ÀüOBXÒK´Ñ‰—íšQH•r€ý_�ówRúnȹõºýß"­#{¼“èW±IÖ,†|…0¨�ÉG‰j8!UÇ4ø"ÞË$ êôaõ%¤âá±írŸläÜÕjw�Çã¬uIÞ¹æ îîµ81T#<�çý]ÝqäâF; ÐÃù?¥ Az_#T¨0޶·èª A„Å�­æ%}ˆãÅÑýu´ g¨‚P¸¾úÍ eJ\À=/ÂÞǤýGÒ#}eë(fÕ®“¤æÔÊM¼§%xœ340031QHÎÏÍÍÏÓË`ˆŽL;Ä=möñoe;êÏjVžË”1„¨IOÉŽ/O¬ÌIÌK‰/ÈÏÌ+I-ŠOI,IŒ/(Ê,ÓË`x¢s©å_ ›÷Ýä#Ékí7º¾âÑšV”˜›ŠbÀó[–î/×É̚Ƴúú« N;Öc1 85±¦å —gõùš½lžøŸ-Ý©û0ÖÉï;-%‰I9©%(–}Žháœò‡ÿ·ìô””'–œÞEkcÓ™_šœ�¢‘MJøÐÚËRÓçÍZzÁû@Ü»ã÷KQ¬ÌÌKÉ/�ÏÌ-ȉOÎI,.†éã\T[_£VrYm÷ºÿ{k¼ýNÚ<˜‹CTÇ%ÿ¥Áge½Ã®.]ùjU|�½ûü™÷pè€9ÅÆ…»V¯0¸˜7¿öBhÖKæM}vß<" ªsDZüÿïØ5Ù$"¤?M{{Y S'Tñ¦ó7¤°ÈîÕ ï}vþÕÊ3Êõ+‚ù5¸�xœÅU[oâ8~çWœ¥Z F¨èÛÌv¥ x'$( íö)ÊÅ!ž9 Úéßã4…ÂNÕÇ�"E>—ï|çâ“á' ^š+9/à±ä)Ë S° a>á c¹=(¾.4Œ®G×ÈmÁF†±È ®*™òñ “i½Aò±6ñ ý zº`Ð Z�n¿ ’±¸.Àè^U°çº�µÅ*­xj0,4JË:3^Õ%ßð6‚qoò¯ h]a†§™ñÜ|Y“Ö¶NJ^dÜ@'µFae„Ma-“ÇP*¨XY޼›\Oìek ªÛ5q÷…Üœg‚%Êk%0$k|2‰%k"~g©6cžË²”{“Z*EÆMFÕ—¶m!êãDîX“ÐK�…ÔÈ÷…‡éÂöÔÚVU1&�°¶jk¿ÉI•ÆîslÀVª&èe®ƒWs�7 lŸ `é{÷tB&е ð| ‹¥C ʨ;vVêÎàý\/‡â@#hè5[(J¶ þxŽGûŽ:4|´`JC×`NÔ†¥í‡t¼rl–+éÃOÖ¥îÔÇ(dAÜp€QQäÌmÇiBÙ+dï7üÆÞòѧ³ysÏ™Þdfß9ä%&5vlº°`b/ìi¼Ð€X`û40™ú›r¢‡×€ ŸK^PL©Ï;‚&æ¼ È‰Ë„ØbÆù­±ié°Ó¹â9®�fá·héÓûhì-žÍ;W(æ‚ýBsReO!×%Kb5Ã+Z›Aú?ê·ºÎNò Öú)Ú*¾‹pU0¥#sÞáýÁÙ‹vqɳ^þé><‡žÑ®™Ž6ñw©^íÐâ·[¸�Ÿ?á¨çâLÿŒF¯8æYGL)©zG�yºèý¹ŒL}ÆË‰7CŠò€—bkîBe²¿�?oaw3�×Ð;ÈŠ¯áî÷lмý®u†øÝwŒÎ9¿g”*y2ê}“î¥{­è¹ó|Ya¼¾â¬¾›ø%,ªE›#;»2[4…u"eÉb�ÛCîÜÂÔvòõØ�FÜ?rP »+NjdÌÄ®×5ƒãØ�Ä�‚9qœhå«å§�L"£Ã)ÄK9ëöÏ{dã ü KÇêæc¶ì¥zÂæ™Ž�´èžãÐLÙÜ•œ \‘©Š²ÂEÁÒ§KûBëmõe8\㿦N©Ü ÷›ý~xÁg˜”2nâ ÿ³C4Úb!^r}l²‹ù¸¬ËÂþËó?0¡îÇ&cßûñùexÚa ýÎ ÎáÃXÃá/VÈ¿pµô³ˆ%xœí�ënÛº–Ç¿û)ˆ½�AšqÛØV{vÚ鯸$i4½ IwÑO‚bÉŽY2$999û�ÎkÌ“ u³(‘¢¸HJÑì1P ±Hþ×µø[º�ÒËC4ÿ~ýõÃÙ—³ËùõÙé�~E_¾^£³Óóktz~yvr}ñs„Ñ•ã U|÷|º÷//Ïæ§ŸÏ^¬m´ B´B¹>þsmÅnàãêI‹ë[7BK×ÃeÚXaŒ‚e*áY�Nø<ºu Û�âÐ½ÙÆØÿY¾ý2�u`»ËÇdÛÖ·�Å·Š�p%»—üøðå;ºp¢—}p|'´<ôm{㹋¤õ…»püÈAv"Ùˆ{ÁF7�iÃ÷‰+W¹+è}€õÓþ|‹—§Öï�0›Ь°– Ž>Vœì@ˆ‚MÒîöúyö®h÷"Ñ`uC¹·6>”©òm°Á{v‹5ñ¾>¸ž‡n´�œåÖ'¸2úq~ýñë÷k4ÿòý˜_^ο\ÿ|‹+Ç·.uî�LÊ]o<+ã -?~ÄÎ' ŸÏ.O>â&ó¿Ÿ_œ_ÿLváýùõ—³«+ôþë%š£oóËëó“ïóKôíûå·¯Wg/P‰y?§=Êêê]?ïâÓvbËõ¢]üÄ:Â^z6ºµî|ÀŽ{�}´ÐÇTëÁ,,$Z–ø«t¯q›²_ß"w‰ü £‡ÐÅaÜÜv깿x1F¯&¸–åßyøP\Ÿ>Öxï.±þ{/Â1ú{Ÿúœ~2Ì^Ì¿œšWgókóÛåùæÇѯ¸Ðõ�Ær,à/¼­í _Áz�Cçö—Ñ(~Ü8‰*–í"Fæûî‡õèáÑqåà@©þ|;½|‰þÈ#öü¥¿-Ï͸ñáúºŸ½˜N_¡çù_¿�ØÒæýÌœNͣѿF(1“šÃPqüØtý(¶ü…ó­¶®Ϧȵ“_¹ÖƒgFIýÃü�jÑ&ÀMðñ=,ÿ®V¸so+´ÓÅ�j•8Ø.nÓòô/¢ðŸ›BÕ\9Q¼ 3zp7Žy?Á  [6øØÜ6 ¤…5�غñœ8Ýyó~š6#6½ÍúôÔ�6˜ÍèÐÎþ(6;÷8Ô?[¾µJzÉNšëìw¥:\[Qâ Ñ�t!ÙƒD)»Ííãàƒ¶†U­G0ö¡±xø±ëoƒmÔX¥Á™ô(š™Ã¬¢|ÛÉ6Œp .Òÿó�ŸœÇµµI¥ñÿéÆ�Vt{�ôwÞ܉ÒͺxKz è-8”�±ycÙqx‰�ò-ëÂS+¶Šñ‚ŠèHò³x«lÿ‹6¸îç$ë¹Nx�€ðž™ë|C”WøáúvðPv¨¹ Û¢pk´q|qž³HóZµ%Fæ�ù�þHJ²�lÆ(Ý»ël|ßàNqp‚N �¥BœØÌÐÙäQ¼kT)1Cœ9Ŷ“G{9 –çÝX‹;t˜×)6��É‹ßBÆvÜŒ­ üÔ×ÞüÚ@;,Çgêæ‡+'Æ�»ŠˆîŒòM•ÝØ•ÝC—$Ç ï­kyÄâs$Ÿ#­­ð±<¦]ŒÄ¼ÄN""ÛXí¢ó Z+)¡µ:ÁCÏùZ; 6æ"ûU;à88î Ûõm⸧݅ ˜ùf6ÆÓý^ÙÁ6D;TâƒâÔ†b-Åxü“JA“ä÷©»\â¿$&Cì8æCq~ô&)þO”|Ì|Ü}ØtCÚ:Þ¥­É+~Þ:Þç­}ÞÚç­}ÞúKç­*4÷y¬Ã<6ÈcÏEóØäužÈŒÓ?‘M^ï3Ù>“í3Ù>“ý¥3Ù>su–¹f‚™K¾£òK8!²”#’¦€‘4 �\Ö�uqª5vY×àÞö4ð¿}ÖÝgÝ}ÖÝgÝ}Öí.ë6æ=ià/’¿ Xþ SPSr¬V³5§¡aÇ´Q@ÛšUVb7Žw‰ýÕ lý‡Š9ö3~–7öw‰÷Y~ŸåE³O(Ÿ'”ÏÊç åó„òyBù<¥|žR>O)Ÿ§”ÏSÊç)åó”òyJù<¥|žR>Ïj>Ϩh™QÑ2£¢eFE‹A锎Aé,ƒÚòŠÚòšÚò7jËoÔ–ãú–zÔTÔTÔTÔTÔTÔTÔTÔTÔTŒTŒTŒTŒTŒTŒTŒTŒTŒTŒTŒá-”Ï3ÊçåóŒòyFù<£|žQ>Ï(Ÿg”Ïå³AùlP>”Ïå³AùlP>”Ïå³AùüŠòùåó«�ÏÝ„Žu·K"ÙÎÒÚzqYa—ͬЯ䲵õhÞ8æÖ�¶›MÆÎ.±ýYè%i2Ëp¿¿Ëü-3S%±yCäxØÇz³©ÑÐlÆi–0½¡Ù”Ûì¨ÉÉI¥³ÊQÙy'„>}ñ‰ZøL%=Q©^@¿yS»X�Š«èCΙK­‘¹‰Ãƒª0nŽÏÿ–ùyM~’ v*TtC7GoŠ]9Ø9öì?ø“�Ÿ¥ÆŸÿθ� Od„�„§2“×Ê3¸ròD@Ù�Q6Zz£Óhe:a„¿œûé‰èn-Æùé/ÏÞâbë�ãlÿÙ¦®=×K¼Ètí~‚qç ³›Otü‰k³µ¦2ZDÈUÄfp±J”UÄ 1ƒÞM­±”ßî�ê÷9q•×é>°Dƒ‰¸gIG”hÑ"SQ"|h•™˜J%nhCTÅhØ#ù¨¹p%ö�ŒÚ¢aLÝ_.þÒ'ÐÁgEŸ”g]eÄ@C…'7m•£ƒ†§7kÑc…OÏhÕ£©AO7�ò»±$’vÏøTÊ« LÄ={%6Ñ:’x¢…$ E IBª"Ô%§¸ñ1f=ï"~ô ¬Â‡w„ÊØ…ÂKLŠ01U(ÈZUuã¬xÈ@ò¬|\ÊZQoPD#Ÿ*!�!$É4†’$ÔJ’T«*u‰5~”Œ™�éÉ_}“mçÅ;Ò e¶‰ÊBé&ª 囨.”pºº—> '—OöàÓ-­4(´í&.(q­®" µºŒ$Ñê2’8#dºd'&Æôd¢ÝŸ}#,³ÿng_^í‚Plµ+B�Õ®EWQ/¤f‘í ÅŸ„ÆYC»��3ƒNtíªRàk—•a»¬¹²Ý�sc±‰–¨©´_¸68‘P¢©³Õà«Ã Î:,Âà­Ã" †:˜ ÿr1 þi»AÃ7ûY+ü몚à_—Õÿº¬&ø²}Ÿs\ø1ÞTúÔðO�`S#ëlÝð‡T…?Ü¢*üáUᲨþÄ:Ž òëK>ø 'j ïµU*JPgkI¢œ-& p¶˜$¶)±.aÝ7cÞ2$TÝÖ7Ž ÓÙè%»N½0q(faêP¤ÂÔ¡øV׊Ê|Q[:ïq·Ò�żþphH¬Ê“Ç -"Ã?ZE|´Š ñ**�¡Ž ãÊêI”×í•f¹M<žŠþPâ—€ˆXz F è�¨Ä×ÓË¡ÊúÙ GÕ¶<(UˆMÔ¢`D5iI‘ªIL XMbRÜbˆu‡/�¸3{£jÃ~‘V1� Íj�©*ÃT?¨: …u­`¬¾;€#—ˆÕVÃ!"ý&y"6jɱQL†ˆ�b2Dd‰uFD‘¸“ï}@Õ½¢°j�ÉZW)¡,B!X„B°:…õ.PXή±px+o~Q¦¡¦ –Íj <Ô4Ñ’©Ö5Û&\2�ø$s-k¶ËÑ©gÎ¥„¼ !ú2d„è˰QP_+�gˆƒ;5ÔrN¨çdPÏY žÓ¿~Îû'|Oq¦Wžkh9·�QK@D)=•øzz½VßL'J£Z³áP‰ñ¦=…§­�bR�[Õ¤ž·6ªI=pe©u÷ÄU$zª«5é÷kÕvò´¯Ö]jOYáò°ç¬p}Ø“V¸>ìY+H_+ëoæÅc½ÝpøÈzר< 9j2„äÈÉ ’#'ÃH¶\g�‹¡*%ëmzÅdÝ8¨T—)�RÊ•R@°”²Â%Ô‚V`²ÞU, MVÛဳé-ÌòðlQ”h‹¤ D[$e@Ú,ÙLÅc« TV»^¡Êr�hf*ÁUɲJ–@°U²‚®¬%­ð…>hÞ=�V4=SÑô0EÓS”žŸ@ž›<ÉâV½žG$B‚ À )‚@&¤V›¢V0‘ß¡…Ùf8€ªQCR J2 j�’�Uƒ” °h©Î Õ+Up‘õ{…i�¼J)A $ ‚H5�2n¢Êú!¢ÛÀ°¦�g:@¦ƒ`:ÐÕ³„aÕ?¥Šq£�KÍRp5kÁÙÓ¬§ SKï­µô >)`Š/ûðn¦¥U†C˜òÛb 7ÌêR·Èê"R7Åê"R·Á‘în|qâ`L~% e5û½¹•Ù~—ÛV¼�Õ&»IÕ¦»Õ¦»ÙÄQÓ}CimmRäŸäßNÂU†ƒœòË…J·’ª’7’ª"’·‘ª"’7‘v"]ÞBjŒƒ1ùiI”ÕìûöQbû]n[ùÖ_ zÛˆ¯½eÄWƒÞ.jTÓåD˜9Ä×FÛ.¤œh8Ô!¾Œªx1U‘¾œª¨H_PUT¤/© •n/ªš¢a\ù‚-ÊëöeåDÅÅîõ«+¾ü ‹¯¿ÊâëÁ¯´õôr(ûœ1æPöÕb‚²ªBPù)fQ"R¢T¤D©H!ˆTéA¼hÁFyµ~é“ÙL†OÞjôi—ƒÑ§]FŸv=}¸zÐ'ûtº8‚²úCãPùxUQJòD¢¤ä±DIɳ‰”êP¼X©S*«û¨Ê ï\Þ= %(,�/Ae � *K ­]¹ ¸m,¶¤úàЖ8¥…l5!°Õ”¸VSRÀZ©Ô5Õš£„‚ZRõ)˜–Ø-ÇZÚ5:ˆ&"+Ã3]š‰èʰ¬E·‹%ܦë/ƒô.y^ú-Û~jÅ–Èšî´}o¥fùùD5á3}²6¨õ¯uSuô"ຑ™ªÖÊàºCݽ\˜0ÒÁì=Ù/[ÿ_‰ðÒã®â›iAot3Mè�m¦ ½‘]7¡û馹lwéâ¶ihÎ]?nZgΗM{‹ëª{€ˆ®¸ÛÔ@ùãVù©Š|õÁS&¯_TÆÔ7Tô nÿt²îÃ\‹mÉ?\ß„Vd­†siUõKùñ=[Ke=%¦²*„SYBŠu¾B„7écþ] V[<Éj‘Ì4±Ø!ï*-+G„Å¥V‘«K­(V—Z]"¢®÷ŠÌñm×_áPòœEŒ§4œÇÁš!VoÖ[NÏ\ƒ\Õ]mÌç‚ÒÇBÒSiòj‹£=ƒkW/²8Ú†Œ¶ÑÚ'ZƒvZ7æCJKÑäM4Næ&œRKÛl!™œÍV’IØl%™lM)u–ª[£¤š§‰ê½&iÂ.Nd÷(¥g€,(1tA)  JÆbºú‰»kg4ÚâKÿÙÔŒÛ`–ÔÊ—4€¬&#�±šŽ4Äj:Ò+uºXsdŒQ]EÍþÉ•X-VÚ'êÔj—„«]N«vM8©¸šZ)ukÝ;8*BÜÔ ��“|zpužcù\Ñ͆Ã-Ú75€qõdHÆ”AWP†mM‚�AN4žÆhœt“^±G›Ç–ÑoJ ”6B£´,¥­€ð)cE+P+fÌЊÅÎÿèfÃ*í›P¹z2@å Ê•+(Ô&Á΀*OÄé#ݤW ÒæñÈeô›P¥�€€*mTi+  ÊX騶“~ùLÔ´ÝP‘š:§“©uAu¨ÖÕ©ZWTÇ*¡ØW9QÕÖ´Í’5µO�á¬ï4²dF�® ; |ÙQ ¬¨­ŒÍ­,,Ï»±w£Qîûƒ·ÛÆ}Sk?ÎÖScl³˜ _›ÕdØÚ¬&ÃU¦ZgLŠž1bÆd­m¯\­Ùƃ¶ÞoJ<•�qTBÄO }7aú]ð2¹5ŽF«,Ó‹2mÑÏÄŠ�W€‰¤—lVŠ«Rª®:•Q¥ã·.;ƒË²Â¶.kÈÈÒÑJÈv‘wÎ#(“i™C�Fì£öX¬jjŠÄª¨¦8¬ŠjŠÂ�h'g‘ÁÖ�AQ˜¶|¦^j�ĺª¦X¬ËjŠÆº¬¦x$d»ˆHÛ±lÏõ”ØÇ׆HLmz ËÜ/xT~6¦¨.uHºS ]:2Â3°0+6†„0�Uán–-àë ØõWÉÊ…«üO±• EÃá\sS®©]uóä”–00ô”V10ô”2Tõº_ËÀ�¤1"“jô4+ ëäìú]·éY×3!·ºfCn�̆ÜJaÝ0Tx²e¥ÅÁ©>é²IJ ˜&_6i)�²‡I˜C<ð©Ô~2æ3«}¤‡ˆbÒr$Ó–# ˜¶ùZµ»!^rû&rB×ò`Ü+Û �~¥sšÈT"!SQ‰‡LE%*Ö»gc[T±Y¶yN–öÉáKô�fÂÍÈñnGŽ¥p;r\ÙÑ»Š6t×VøhÚVl™vú!¡™ñŒvf´Ýl°AG¬>°^_~?c²V0m*(yõ~~quÆäQ"ß»ùQ_Åw»Þ‰v‹ŒíöE±º4s3ƒÉ_ /Õò_P&ñgÄ¡ Èb´‹l„�|ëFèÃõ'* ʰêÌ@gÙ |»™ -ÏPî5²ºüË+µ¨`”ìü?�|FÖ«d»r é�ÔD½á€Y�õ�¸Àà ¶’Ì…¥ÔR[£„œëU ½§be*ÚØ�±  :ÿè‚Î÷Åtõ’. 6fòmzçqöÅÒÐZ�d¿ùŒ#r„WŠ”c+IaŽ-%Å9¶”è(©îH×+ÙwN+¡G6êr„ád’ý¤†9ˆ0 teê Ê0Ø *kÅÝ2w…¯>pèÀ^³D·õhßÔØÇÕ“! WP†ƒ\A6 vÆDÑxª¾—‰nÕ+ióxä2ºN “ÒF@È”¶§´Je¬è}É“áñ显¹týUúåt$ïMOõ&ÃÁ)åšMyrRo€âèI½ Š£'õF(¶^wo†‹¤1ÊC²^½ß×DÕ­'ï7¢:LíµQr&`¯‘’³{­”œ Øk¦À6:áf´°¼ä¹šlÛ¾r[iÒÓ¬ìÂ-À´ìŠ›l~ŠËÒÇ«.;•‘eÄs]w×eÆp]×�ÑeÄ-¡«ÿ­�Ù7åÓ ¤“ì;ó­ï¡Íš (•—N)&q¦�ô )%éW8RJÒ/q$•º}�#/JÒë�2ÜÊêý¿Í1³[¼€0ïõ7:ŠÉÂßê(¦ ³£˜.ü펭ºZ‰æß9�‘ÐÔ•´f7“Uê3Lf«Ô›ô6]eÕ¾n1ë)|’Lð·^÷Ó(¿|¿¸h8›€/=LÝgvzÍ̬©À`èÍÃÄÜ è¬†¶}Gv½D$´k£Á`<Ý<¬¦~í~.Vã䉗à34&}™öaDꄊm\õ䊧Ú݉–T”ŠNÚjPßyïA®ÝÉ XC„*žä)ÙÓšFÿù°a†û‰P&mn¾O¦Y0�=TÇî&n>åôî¦Ôf¯ôG§stÚ‘É­ÍöÓk›pgV:nwI¶}Ô4ÛØÿÓ¿Ž¨Çi�¹j™W‡Uùaókò�´%JÒðé'óÇüçÅüË©yu6¿6¿]žÿa~ý/ë¥4Õãý®\xœÕX]lUÎ@kaZÚÝnÛu»ËvZ…¶+P(5�©@ªµ•€µ-ÅÖ2.»³eÒÝîvf·t# Câ“Rnâ&Úx1$‚)òÀÒ@MðEL6$¾™È£‰üDF£÷Þ¹3�Ÿ;³[^Œo»ç~çœï|çþÍý°X±ò)³ôp}Ïð[‡Æ^úûÆú„áуo o°/ÄÅ„4#rƒý‡„ñ¾w÷ õ F¨¯Ø†«x—Á±Šnp6à‡ßiö}–ããÓãÒL<}’‹$Ò±œÒ mñtîxR䔜œˆÆDažbË÷ª¾o¦ãRBå±|FäŽç²ÙôŒ�"6+'Ídww Y.+¥D“a‹8“eAe)š4�ddQQÈø§±§MÉʹX–;™H~.’IKª»j�7NO¡\,'+iY@ùÒ¹¬ ÅQlÓ�”ŠN‰¬Zœw‹‹ÉhÞ4&C¶Œ—�!•X4‰k'Á s»¸ˆÑÐ ÒMàHkìžçt�г÷?ì`'²”ÝÁ±ÒDþkÓãÿ×󪎮­ì8³ýM׆;æªÀôÎ \ª®âyÜÃB]%Ó4B…Œ,Í SbVÀàYz(Z¬áúÐÊ®�t*b2ѹ}¿Š]˜ìDS¦pg#S·.–g.�Rb+¦Øõ˾Jõ_ñD³9ËÅeñš³pû8�¾î™œ_Oƪv4v}qUx€çõYɲdNNŧ…“Ñ|2:èEë>®…×ë”Vñ¤xp§X»A7ΦGZè ˜ù9n›m99j¶€õ¯ú1t2 TÀíýc•òÏ RÞU%¯�$87±Š”/C¤¼‹HyG‘6Ûòç €Ö{«FÆñ¼¦ÑžW·+࿉бîgB&+;i³¬\mGüêt~$>óõ bäC[®uìƒkãˆÌ:ž÷ 6Ói }ɵ%5zX³¨ $>ª]'¥ E/A#^JpÅÜGú`ܯÁ®»Aj<Æ„"Z»ž‡MÚÚE‰YÕŒƒÁáæ„7|ðãÃp½Pã�P楆üòÑN”9`’Ø ÎFJR…’4dÂ(=+�p(�JpÇo˜`«#Ay÷÷w‰ZøtQËtJ–§–’ª–)š³ZÖ¤6µL€ÒjáP• U-*’¨>¿ÑnNtç69jÚoׂ¿¿ñW2a®,žïÆœo� "5Äó¶;.˪7ÕÒíµ¹–*¡®Ú&W¨°i5ÛcÂ=§�Þsjþ0éºÃ ÞqëAô ì¤ÕÏú´µÜíΚâ×f°.ºáëaͪ|KÊ^cBÝ[hqPøWá-ZhÊ1nÒ·‘«êÝÉ-·§oº³ƒöøëìµÇ¾%µß¨£ÁÇ“Ûìç«1$ø¹8^ŽäzfÉUŒ›äA#Ò¢Ýë*!6Í�d%b�a±mŸ»k’Ûæ]JðšjƒK!åaBàÂQª%h­ÅüÞË  ã…rãW\˜pÖXQŽWeDµÝSÕÓj&'Í—ÜÜõ3è˜íd'0…eµˆµëZŒ’Ç»Çæ¡}EÃ#´ÎúâqæÒèö²ÎzcþvÒ½'¬ã€<;­hû©®‘,^×OTÄ<ÉÃÅÏãGØ€øô¸*û°ê¡?÷”ÕÅýCô³+ÞJÜ¿BÅnZ}k�fø�èÉËf ~q©]-è6D>Að{Yáf€ ;¼&©€§f›•ÆöÂ)?óbá»f¦ï5—¹rž%pŽ‘ tºÑo-\3>ç¯BÌ^°8ÖÞ*°õÌffÂáíÊœ¥Ër§t)[Àâ“ÖrY�Úç×Xù ÑÐ#ÅüÙ¯è|!ÚÁ45ÐíÿáìnãL‹vxœ;4�±ÁhÃ7fV· G_×É_™5¡,Y—Éó]¹&§±iÎÉÕ,&“mØ5Áìlõ\Õ\ î)Ù®e©y% Z© Ê(”ž’_š”“ª�’šS’_�)T ÊÌ+16Š/QHÉ,N.J-I…(Ä«M [(dÇósrâ‹Kò ¬U€©y¥¹ å9ñù@©Eñ‰ Eù¥EÉ© `jr§kZQbnêæ�œì“Aî¶²;—‹ áþɌڢ’`¥ñ)‰@We–ŧ§–ăUNîu•�\f 1l²£¬/,&ÏUàb+¹(‰a@1²â Å©9i:H�¦‘3ÔP‚¬=ùƒ½4²á<`¥¶P¥õ£A’"VVÐðåâ‚….Ø òؼU_PR„ácv¨þÉ‚nòHör²CuM.A±²’x++ɲ²f¥8ÐJxúàâ‚¥°­JXm…«Æa1Ôt‹¹±Ù]I’ݸUð]Äj¶°¯ÃKcŸg'‚à½£Ýø+Šõƒë“À¥L÷Â< uËâ܉îðPe`°�ë³Ä&»Ç–H·mTIV]•f‚†p/=5` ·Žy¼‚T¤Å!|,ñD7º›˜HáZ¡ùOÓúìw×Á=KÒnÝl*ÁAG`á^ˆ!ÔI#"Á¶:±„ b©žbÁèXý~¢^¶1CÂÈÉîòâgPPÑÚÌZmwæ!ÂüfyPÿM#RÍ:»LŒ*è÷Ç�º0ÓÀ0 +C)ƒl¨N­B6÷,›9Ý8]ä--GÝTÔÍ ÂØ© ¸Æž$jf°E3Õa�™u0³€™…ÿ@i\]k;ÝøTán²UÑ3H¶M¸lp77oÂD‘àÇš,-4”h‹¥8o/~ëw\[È”ˆ¼¬ì­(„ï¡$…:�9À2 t^ÕǼ�d00TDZ¾—±‚EÉI©FÚH´‘úAFM¶`(]Ì�$Á²ü84lÒÐ±Ø i€ÕDHÍ#Òª6eu˜›¬ÌAV�¬úp«”Õàæ[÷¢Ëj•ÊÂ�dê&²­¥*rÙØz"•û‰*q÷Õ„Iï6ú�$¬y;1×ÇBÊZqM§Jk�Zö5Ôö¡òˆqM š00³eC]ϸZDc¨¨ÕÖJ•d«j:i¨À";íÊ‚{7€ jÒ ¨·¦F{¥Ô¸¾u;þ«¦ðüÂ’K¯]„•L•.ˆö·›U4WsÐhã›j¶–ÚÆuå�aǹXvr;1å7"sð`×û©ñêäí«;hÇ<÷€�®…qÈ R 8{ípáh˜[6|º°Ðª~ZÎÔ�Ÿ–Ã~B €]nw® ›šFA†E»DòÒ4 r�šFAFÓd³Ór?[XÊÁëW&ìšE›@«³Eç¬FB8î:Ídƒ¤þíu¾8–²*6lí( =¢Šƒÿ@ôòÄYR…FWwꪶ¹âs×÷䔞npf_«K%ßPö>¿² e�Šª€ãÉÒ†Õà„cYw#²ÛáF”1 år! ·TÀÕê«ÙR·º÷zM€ÙöîÁÖ&f[ØÍà¿Î¥itÃò2+p3NR<6M+?÷úÐ`i¹Å|û�/Ü{q¥… RDCÂ0›âRXwÒn$C®03°t…çæž4æGÍKùVÛKfÄÚÔ;õ'#Xï{HBlCŽM$AnG—…m¼Õ;² y5¤UÞ’!8 Q ¥/ÝoÉ0øØ/!Çò‡Aaù3túÚ�€ý=ÐFn?Ôï©&EÂÅWT°(áz•|Úó>§¹äryŽ?žË{>í,^l§ü„à‹l'uÐæ} nÚíæfÈx˜GX$ú(¹{ñ¬eÛÓ“jÖÿ¾9üú] äÊ)¬‘‰^>X仨f?á·íЦRîS�mxœ;4�ñ¹Ñ†w̬!þ¡Î“¿2+BY²,¶’!ù¥É.‰%‰ î)Ùቕ9‰y)`±Éå,“mØ•êá*âËŒã�Œâ ¸ª¹J3óJŒ�âK2S¬�Üô”üÒ¤œT… dN%ˆ263/%¿\A«L[#ë.Í‹/N-ÊLÌëI+dæe–¸âÁj¬ 'Ws곂y›7rf°Oôâ…»ÎÊ*3e²€²¸ÔH�ƒ Š2ËâÓSKâ3S4нW`  1k²£¬+Dl¢‚èäF“` #бa¯-Æ´ÔPC n’öäö"ÓØ3Sl�A4ù ~ˆ/’S+¸¸`�¤ ®ìørˆ¹ñØ__PR„×ýÆìPÓ& ºI lg¬˜\(î‹lo%)öV’mo%È^n${'û¨)ha³’$ˆ�6ˆòÉ…1L+Æmš¿°ädm \‘Ä ÑŒ¨ÉòšQ¢HÎF¤ÎÉœÊr¦Ø<€‘€‰õ ’á*†e80 b¨Â—"å0Tý�ä”ûªÑÈy ’ !.™ì`,bˆ5{!«"6xQÍÆ)†ÙÅÍn´F3�ßeQT‚2&ª ¼b…Àc¼‹cˆc|@�gX¼‹ƒæÜ¶Ž2xœí]ënã8–þŸ§ z€EÊ“º$qÍLwaÈTR©l§.H¥¦Ð¿E¦mmdÉ#ÉIÜ‹y }�}²=¤(‰u!m2Œg*¶Ìóñð~<¼ëõ�|¿þr~öùìêäúìô�~AŸ¿\£³Ó‹ktzquöþúò·=4Bß0F³üöå2 ï^_��œ~:{µ˜ i’¢E’bÆðqáçaCr"q=34 #ø-CK?ÍQ2¥‘¿ÆéËlŽ£ˆ%}Ÿ,×i8›çhÿý tøóÏo_ÂF_qŽSôÉÏó0;@ß–8àû¯áâÆ�"äÇôŸI6‡ÁiûÑDÄúßÿAGoŽþT«�i¸Ì³×A2Á¯–k^ÏešÌRAT�¦PÔ,™æ÷~Šß¡u²B�£OÂ,OÛU%ÊI”,’I8]“g«xúåsŒ@ïEF L¾œþŽ.q–Áoç8Æ©¡¯«›( ˆôeà8ÃÈ%ÈC°Ëݬ©à¢Ê7¦ ú�>µð;„Cø�æ~‡Ó ¡ã27x€À5û~N �¢dIä^€ÖkùĪLîÁh3C]Ú 8—"Ï“%”l˜PÖû\pƒÑ*ÃÓUt@ 1úqqýñË÷ktòù7ôãäêêäóõoï q>OàW|‡ ¨p±ŒB@†‚¥~œ¯Ay‚ðéìêýG9ùûÅåÅõo¤.®?Ÿ}û†>|¹B'èëÉÕõÅûï—'Wèë÷«¯_¾�½B´n2;S‹¶™º²sUc'8÷Ã(« ð8:-£ šûwàðtôQujЙeË�’xFK 2µ]ß¡pŠâ$?@÷iÕ(OzÝL�z¯ÐÛCHåÇ·¸â[éãC8üQ’¤èïI–ƒút‚Л£ÃÃ7/�ß ïßNH _ïíý!œBõœ¢óÓ_½ŸO¿üð.>}½ôÞ_ž|ûæ}½ºø‡÷qï�"Œq"€Šƒh5Áè§ Y, ÍÚÛË×KLð¡Ú¬‚yç“Ûab¼Ó¦0Ã~È{; �Ž•¤ tÑÒzÑ™èî¨UÞ—Ó7ý ÀKR²nšW}S>i‡>õ éƒ~ÿŽ�´‰‚W¼ºÚ½½ðeøp³šÊZPé,­àqšL2ðs8mm™m­jÊ,ƒÛÁT«X-Ý€!^û0 ¨œtºŠ¢ �¸;dÈI©ïÀyh,-Sð0&Œ HÕHÐGCMR n!²yÅPj ‘ÅyA¶àGƒ£]€KWõ‘a,2c³¾”BãyNZâÁIJgoº›–ŸN«Tˆhï!kŽšˆBÛ°Šƒ!¬, ªó0Eÿ¹{“ÔŸ ˜êl2ÃÃí}yR?oVy^õ…±C1têà*ÑQs¯BV2DZ/[ÃP8¢ýŽ'Ð1ާál•ªJéMØNþ*ÕÑdé0”ï •‰šÂÐm‡Îs˜·çÅQŠKØÖÃ�“¼¯é%o|M“< ’¨ªÐðÌ[²‡ƒ4Ag¤“;3=Ðw›´O.ð¹¿OâB}ÑЬieQ|Q®Ð¢œy ãârÐK¿ Béœe^1ðȼb¡8Zaƒ)ÁiP· ™#-œÊˆ" +2Ñígß´?r'µAò;?’ÇÒ ö%«gPèób… ºU(¦£EÉIZ�*þXâ´‹.dþ+O¨LU?!€D%:›_ H«¯ŽgÕôÉr‚#œãZräù Æ; �Nƒ°Gå¯ð°B[=¢Tq° ÏãÞ¶($ÏRø ¸¹SÃâªSÆt *0‘؃ò-hYlH©5u*<Ä�#³À‡ös –hïÄHx襑‰×=Km[¥ãfÒë‡msêEËúÏÕÀ”†c‹) H^dÞSù#<-k]·dŸódY¨Ȱ�é5§àÉ׬ú§;шr¤lÏ/…†‘­[Ì"¹Èã¡ñGžûĪƒ9%,8KS2]9ÂäoËÔª‘ÎÃPv"6KÚŠ^ONÝ‘¥#R¨å™š¥£½Í扊©gmÆ"+#ýú·O}µÔ–2 C¶¨T¦·÷/i5í�|? §S4M)!ª%«¬\üý…üüG$z‡ŽþÉ,Ë ´êèÂPE®YÂó"*Ó¿k_¿:~[­á½=Fû«8…öÃÏ w1´ 7öŽß>¯è=¯è=¯è=¯èu†¸ç½ç½ç½ç½ç½ç½ç½ç½&ÈóŠÞóŠ^gºç½ç½ç½ç½ç½ç½ç½ç½ç½ç½ç½ç½¶1Îã­è_™cK„ÀÚPqû9Á‰’8Z9›Ü–U�œÑ,×zóKY¶}QÑÿ±¿?|KÀèUååßš[‰üáðdÏR~Y}ÐÌ£½ÞýŸ.Š®nu£ÇÅéO/ÞÁÏþ T™¢Âü«ÇmÒF§½½ÖM¦–`z\Ø:¶3íW7JÊ·ûRËu˜›û�Žz “éy¡õl´ï͘·¶s4Z²™ë·öà–®ÌÇ8+ûönFÐ>DM®jnVtÉì¾R ù0¼]¾oæÔê[Þ³ê¦Íè+к×�Û·$¦r7Þ¾�9CVņ›’X»‰èÚ|ì²5 1ÀúÆ.»ûí~°Åp7\$zB­§æÛž[]hƹB¶È+s…$Þ¥®/Ñ×%j»”è7oM‰Îc nj>Qê*µÒö5¿ ÍxÍ/Ï}(×þR`—Pêl€"”]& ›ºfCïù7Œ(U‚z\Ym{f ¡šŸ=!ž”éASï7¨Â&&CjË ý®)Ñ}RÍÑÜчÌ3PK˜ÃèÄ3ÎzœO™4õ.q€*l€Ž] X¸æ@÷9L7 ú@�-,µ=ºñ,Ì¢s‡UÕã/¥=Âî8ýêv"�+�‘It Ïöº’G¸8²ñ)eWs蜾t^›·°‰¹sU|k,�zkÓ°Ôgbý¨ëøøS`eY>ƒÄ!‡›Ãn’é¹õñ·l-ÕçUYÞgUs1Î\î>eÒr2›ÏMop=ƒK"se6Àa Í.}•V3×ênØÌ (Æ›{«aÛa.» D�ºLHéùÉ]xâ¼E`¦4Õ$p�Ð& Ö„F£°c÷á8lk˜¥Ê¡t¬¡ÖFÝüºµp¡�ú ¶ ¶áv—–+Šœ.f e2±¬Ýhy�[Ñ-Âqª�/”r´ê-¨LV¬E;X ×ÈÁ8#ë¹ööΛ·uõ›¸Ý¥ùàZkœk‚Ù囊Á£ ß¹æ†UµRälWmÅíÙ¤‚l%®•,Òˆi±§ûê;×áÌ­š`öÃØ0­¶½—Ð]ôªØ�™ä™ ²…9ââHÅÞžp5ÀÐüp!´Í¼pÏå�n§† ¥ŒL óP¶§‚‡»ÕœxàâE î˜ÏŽÑRð�îëäbœýÜ[)´ˆÏÉíÒj §¶²Jhvyªdó­—ޏá§TxÞŠÛ3J Û8�ª7¶(÷+‰]âO¥´ö4°ìrGÁÚÜÕÚ}¯ßqÙJ'¨Õµå¶çË0®•'}OyµLã…DÃ]N*½K¼©”6ÔEä°ì÷ ¬-_h;ü2*w}:ªëfV4Ó‹ëǵÂ!î•X3‰ÃØ5>qªb•„hŸ[JöïfØÐ‹ÛÜñŒÓŒ±‚·®ΩçaáF#ùEt7ÉÂîLßâwn¯F’-`b²ÖöÕI:å¯Rz´×ººƒI. ½E©ÅO&îhÚ,7ã­C÷Ë*•Ûˆnˆÿ_-E· ´Càv[�M\|À½sâ‘_ˆê¦é.&0»ÇÛ·&&r6Þ²4ßD«Üž47<•ÒþÖ— D³`š…vH»��ºê&`û×»átSoà“dòíù«›‹q®²×ë(S”¥ßü|¹Ê+£]R•é`€¡’]bzå ù>%^rÃK–;¥´ïö,ìǴŹò-èºÜ+åvi.¬¡º9v‰ˆ�²aûKlë}á½S•šÕu¿²¨1^)æaaß^jnÀË]bÑ×È�ÆöÞ€>ó67àö7R»Ú €—t©,ebý¿Í8è;ì•ã M­ß³“Þí’´ 8ÁáØ%Å€ÑëPBªºÀ K¨ŠP± ãmÏ“n<+§±È{€é]&Z'±*©ÍÏB’�„é]Lt?‡Ë5¾@&;d<ûç­<"žµª´üäîdU¥/;íT[ØÌ‰*5|+û8ÊfWÛ.°)!�o0I¼<û”ˆ×îÖ!:Ü%À³af˜mêøV"Þ"™ø‘~È«Å6Ÿ¤Îc]]CÁ® h?Ú©8C wµ„’�ÜŹZQˆ8뚉tª9Xa_v.½ÜÏnÉ›èµI(IoÎE••XÎ9)Ì5;pí3TÃS"Q%A-?ºã­¤7#—ì3,Þ,?{œ^ú3¼)£kÙmùL‘ž›ëB™ärõ‘˜¬â¡×bÞsÌáZgžQœÝ òW+/+Ü]¥3k}âò‚›³–¢¸Ÿþá cˆ­2¤}ªªùDä)/£æ,wüäue„Ìl†™:¹Xády?HqÇ´+EÑÍoU?/o,•ˆÜ$Fq6ùáiÜ)ÚÛ@í3XÕ"‡E)“®uÇu±LŒ‡ §˜á»nNvÖaÂT;ó;¹Ÿæ«%ØDoj§ÛŽS5ŽsfÕª˜šÓi>ÂtŽ‚W39œýuœåp§R¢œV©ÍlhöF1;}ÅÔ�³�Ü?Õ¼AY�Üæóòm×]G¾<¦º�2æ#t%ÕÓèRòBª.sسäÕ-{‚‚± õ4µò±²£gšú ìá‡\ëU’ä& m¼ðÌù¡@†6ù´`Úßè£èñ¦UAHÙgîöýú²½9¢µÍìÿÑËÇJM@6 'äxð$Ôzqe«ôæk ”ó°*id(´vàÚ¯ÞCl‹o4}é.ÞJº°X({ÁLÜÝ,?+Üöƒ/Ù9-Zó‚›3šGqNf^C<–!íSXÍ3"{E?¨»Ìgy5}c›aªN.VøI³ñÀh ©ÅO^ps~ò(ÎùÉ+cˆŸ2¤}~ªyFä§èu—¹ã'¯cŽ`l3üÔÉÅ ?ÃrŠÂL¯O\Ié�[/A �–áÃÍjê>dV1ÄÇž}2*xBdb% äwü«ôd´¨-k†yjøö8ÃXŸsDj»5‚ð4hG41I»ï‘h×ïŒÚ¶Ws‘cæxfPãdÞ ¾ù!!›pª~K¿KÇ,™ÊH% Y¾ƒqÈÎÜ…‹EÒ't‚’iD®#d3pëa/¦…ûšt™QIì7*¥ °£�eû^& †T‰ŸG*�è Iæx2Œk¾÷–‡Á­z¯�¤Þ%ŽP…MtÉjË]±~ s]0’ð q‚êCú?ÔRúU�xÆ9°ŠõXÀÒï˜Ê˜ Ùå �k6°¤OˆL#¨Á¥Å¶çD?¦q^,ü‡pþ®>¨/v‰¥Î¨!BÙ寰©kr”iŸ;J• *WVÛžC¨"‡6Gj‘]bI­µ‘ò˜LQ18Hž [j¥hÓo�1*Èæo¹\EQ¤«_¥\‹ìkj­ °¦ fùšKƒsw]V©Ÿkj¥È•”µõ Üs©€l‘5t)5‰Ã\c}«´þ.r¬œ ?fqÅ1J2 ÷±ø¦äŸ6êq‚ ÞsÍIN[�D¼ÝM2U7?ãüõ—ËhÍûy‘LÔ»�­Ò» [ `€±=¸v«é‘š±­‚O(n¶êœi·ôöÝ�VˆÃrÎ3NCl“ísNÉ9"óè:Í9-oxs›a¥zVz‹C5^„:3ÌÒÑS¡éÌ8M[íw0i*ø|ªŽÜsu&ñ¨is3}Rõ<ìܰŠýXZImÆÑLU8Σh¥‰¡ÚÀ{„ÓÿÃ.iý¯m¯ã(‡'ÿKÊù•‰ �ùW·ð¾ðY{)ÎÂß±7Iý™2%ÉÍG�g“Fþ£ÏNñ]`Ò “¿lËÅÍ*Ï“˜}I“$÷ø/ë§öNfÉ:˜Ý�iûæÊn®. ¹©nZ ©ðôµçM×™x£ºn>–Z�Er·QÛQÉmÒrüµ•Œµ ÄÇh!œÙl*ûNvÙTŬøY»ÇT+ š‡ñÇþM„½ló4‰¡Ù™xAOÃÙ*U߲Ћ²K 8½1@o|»dßÐS5õ{žÐJP¯žÀ²~OlÏksùç|•�7 ã0›cõ÷BÈ¢»ÄnY{”îµËcGÔä•¥žceå€&-ÖÝž››ädç륄¹ú-œŒþ®¤I²‚1yçsfLC3fÚ#\H=䄯5Ô¥ÍUãðÆéBƒòÞgfRC·Kb[aX�,–Iæ1NÛ|÷_�áœkµ*†èÖ´Ï8�ˆ¤ãí¯ê(wÜ«•`áLl†�ª9X¸‹ TMÖ^œäz’ñb»ÔÏ57@¹6@Û÷“©Ÿ¿¤Œ—xBýJQ1z­˜`Mw–©ç`gÏDêÏÀrIžIDwڞ“¯ì�êæ d›-„£ÜOÐý¦ ¾@¦¶MȘ�°qBÍ3Å”kÓñMye÷9Ü9Áë[îk oh÷„V>Æ™›âY˜å8õ&±zï”Ú¥˜Èëm€‹2œ]ª™½Ž†|ú' yµ Â VÜžSªèæ{˜„´tݤ ~ï“8ǹÒY“ZÜÄJ&»l¿hE3¶´õà‘Å-öeM¿¸í¢VE6Ñ=m€Yîš*x« …B5¨%Í;ÓQß¶*éuÖn0ЧU@6Îcˆ»mVˉŸã ZÝ`•fêë�òúïVðÃ4¡m>8)'ou$Ÿœ¶£hØÛ‹l—ÊÚþªl‡¨–?Ýp¶Cs Y—'¶góvyšŸ™]ÇäOpÆêÛŽD±]êýŠš›˜„m´< «h|nV�xB½`Q12*ZÓÀ|«FæÙ.V�Ù»Åk­#4¼àf÷â•;¹ÊM\� ,´kõ)™„Ó§×ë%F ö%£?�Ýá8§Ï1ùäåðÑ-_9K˜`«g™«JŽ�Úð2�äaGìç J˜ÉûÆóÑí±¾Ø_¹ñ YCÜ/7zî*÷ ýMÒŸG|¤`È£�@!öHÞvÜšóde~2Ø(åaem:øKœæz=^p“9±“š&éÂÏ+´Uñö½Œn|ç+R¥E -F‰pöס†k„|z·”Ùµªânõ«´[�ªœlfÍKÝüŽã¹Ï0שn7åÌ·D¤”•~ú é>‘›WÉ5«Õû;Y½ Õ‚‰ÄÔ¹Ä/·G�æ1±™¹ ÑòNfUsÛ˜Eg¾w´OZ,=ÙºÜðš�ÒZyXØ1Fštýö¢!·u{áx™P#ÛÈZmï#Sô¿‘LQô”«½e‚®të—hc»Ëtò°2†È?ÂÞ:Läu´TCð‚»4_ßÔÝP‡]†´ßiWs n¬ÓÎË<¡yû¦j¬+XÕL/Y'+Œ[Å4¿‰—é¼XM’ÔŽ€t`TA܇22Îq8›»ßÝ)”Ó?[0íTÑau€”„¶u¥;6 Å`D�`†ÏzùØ:ë÷Ïf›tOüÕ’ÿ{Y¤B™˜coÇ|”£€*Þ‘ÖB;°gEÒ¹>ÇÇYÜØIA�|¬p5›ûĤ´ÍÔ¢*/¸Ù*Z„§åeJgö9O–åõ)IçüåKjˆ¾2¤}öª9L$//cÀ“îÍ„Mð�>ëäbžÍsšµçÇ+½eñ†ð&óItÁ�èj§[Ò6 c‚´­�–I«ì“ÆÊvCNÍaޏÙЕ°¦ijÜÔÌÅüª`çØ›Eä6²KŸÞ8¿Ô9º!al~^ßÏs?˜ã ­œÐX¥dyñŒ�F´ðn×}š…6±òÓŽiyíGÝ{M¯Ðu ¦¸Q;ZÝi–‰¬½H¾1°Â£��…ó� ý»ØhDgY|“øÌU†ò¤¨.Ò×»@m¡T÷—»eÉmÜéêäe³ ô„¤ä§0õs2Îí°'ÍÉ4I½ß{ÓÔ_¨Oewl=ÿUtñ Åw»¬«€øÝm—åú~«×]²úŽuÃñ.ý��.ÙžïÛæj«_?Iý{¡g ÂXëdv ”~@wò<Áið–™ëÈ˨�Ò•WóTy<[ª-(8ÕiÏ�×½îS î0Ö{×ÌËÚÍ^"Õ5ï÷ÚŠÜ-õ¨ª7N÷nÉe3²�« öqnSewóN°a>÷øÑí­a еzÀÜ bº¹gtæ¾ñS0f–Ó+à«�؀囒^¶Z.!O ž0LÅ'—ßÎZét}õýl[ËhLAHe†NV·A;vÍÀÎ ,Ä0�¯2„ŠßQœä¨ò såó0Cç׿JFÛ²MkæüíÙ~g$šÚNÝ!¯nçNhÅ$›iÁþ€ãI8E¤!;ýÕûqñùôËïâÓ×KïýåÉ·oÞ׫‹x÷þÉ�B¿¼ˆ$xœí]ënܸ’þï§ f€ÛÛIÜíÎL.ð||‹Ïd’Àq& AîV»µVK=’ÚvïÁ<Ðy�}²-êJ‘”DR¤ä &n©øU©ª>’¢xyµ�Ž¿]¾8ûtvu|}v:B§ŸÑ§Ï×èìôò�^^��\ü±ƒöÑWÇA·ñÝ‹uèÞ¿º:;>ýýìåjŽAˆVAè ׇ?Wvì>ˆã×K7B ׃{ZÛaŒ‚EáÙ['|-ÏËDO‚õ6to—1Ú=ÙCã·o_¿€ÿýо8±¢ßí8v£úºvüüþÍ]ÝØž‡lŽþDK˜�¾íÍ«Xÿûo49˜üRšÍBwG¯fÁÜy¹Þ’v®Ãà6´WØÔE�‹øÁ�÷hlÐÌöQèÌÝ(Ý›M Ocí¯‚¬‚¹»ØâköÅK�Ý«?0þqñéúèDÜ»p|'´=ôesã¹3\ú£;süÈA6�/‚_æèf›<Ǧ|ÍLAçà'~�î'Úï�0‚Kè0×–Ž„f׎ñ„(Xãr{`õy6öjVî%Æà¹¡|Ú97A^kx²%`³>¸‚m"g±ñF„Ñ÷Ë럿]£ãO?Ð÷ã««ãO×?Þƒp¼ à®sï¤Pîjí¹€ Ú~¼ã1ÂïgW' Èñ?.?^^ÿÀ�p~yýéìëWtþù £/ÇW×—'ß>_¡/ß®¾|þzö%¹™ù9ñ(ÏÕ…Ÿ‹Œ�;±ízQá€è¬ôæhiß;ð™ãÞƒ�6šANµ3×€±l/ðo“§†2¥_ß#w�ü ¡‡Ð…4ŠƒÆ0'N½ôg/Gèõ¤lÿ΃P|�A0ÎÝàŸ{AŽÐ?‚(†è÷c„&ãñÁ‹ñáÁx„¾}=ÆOøjgçgwé¹@§¿Yß/?�~þn}¹ºüÃú°ó3\v}‡s ù3o3wÐO³`µ‚tYþ´³o×F‚ÙÌbd]Ìï¾»þAK¡À›g2óDè?'ÿUIê¯1ÜJ¤"üç^q=K6Û[/mâ7Tç4•dÉ*K�ôA\½‰­À÷¶è—××vYñ²âvY½åmÏZâ?+׋p̫חvd%æXežU%æä\°M NÊËölÏ6Qµ@r Œ"®«w¢¥½¦ ˆ6ëuk7^ìÎ�{¨Ò)�­UvÊÎ0ˆ"׿µR¢ÿ¹q64œ³X8iX¡6qo\Ï�·UÃë®g\¬/æ<Ø�ýUuö:iU­äɪ·\?Ëšêå['X9q¸µænSaM ‡ ª£¤¯:4p‚ÌîRDÀàSᢞÅõqVk1¼€Ž”Q…¶sncš'IíjßDÖcå×¶øõàγÊ3I!wþŠŸàŠ¤ª Öô%ÏY0b!¯ìMÇÁŠÍfe‹EF¶¶,É:óÜ5Q;�CN6aÍßþ,ù7¹øÁŽ–×I,÷Ó„³Ê›4`XÞ�”šÅí¬BOñ"ÜRºYMÏjKcGߌ ×˜ßLÚ°(u@Ò±ÉJÚó9$ÛèA»èÎY‰ܼedÀç8yNpî@m_&Rµ)÷ïqräCZü|ãÏ ‘ó«9jVè4´!Ãn‹úzžþ.«ë¿˜NÉÿ>u Hæ`½4°:€ $ë¿Ã·ÿƒÓökØ^D¶¦g3~›um¦/ǵ]›ñÛç¾Ísßæ¹oóÜ·yîÛ<÷mžû6Ï}•¾Mk…íüL;?tÝ0§+üà û‚#ÛT�p{MÐU½¦×‡hwㇴf@½½š.ÔÔ�Ÿ»PÏ]¨ç.”‰.TñŸ{Z*=­ç¾Ósßé¹ïôÿ¬ïtJzÚHh„ë°�„-ó»¼ÑÅš­['¶²n<Úî^ÒâzÕÅvƨ¼‡ŽÐ‹„ðŸ»@»ä|k/k§ZdÓÄfI%j\Wb†ÉÕ L”]…x*ÌæïÌҕᵨ�˜ÎC5Îø–°µ2¿Z¾ï ª�áåQM5BG¥j5„ºR35³�‘R¥Á <•z£f·¦îUGp§Ú£ØxÒÂÖ:„�è»a `øÅzWSe¢EµZ•ÒUµÑÑz´��b4áAã©T.�[½iTh€×1ÈÀ‡ïkС5´£rÏk¨·Ú„ÚpµY‹‰ª6qä áu‰ôC~PT.ð B/Ý]\ÿÆÊL¨Þ¢flA衚lóñQ«‘*PÊŸv2Qg®-N:QÿÞ�Ü×sãÆ)f<ùáêž5êõE šd_¨Í@E –œóÙVÉLÌ”8ò¼PóbÄa„?¤C�š Ôêè�Tí¡È¹ÅHövXG%�Y×t=°C^òÐ x}Í—ó`ßx�ãm¹Ì�MWjA—†‹A�n¶H#�V�›Ë&+è£ÁJ5%�AöØ+0ñ†ªLÛD�õÚsñ˜õnœ)@ 8U€4£Ã\�ÙÉ,Œ‰Ù�®/¦ �R=Ì ÕáÏðW¨Ï�ƒž3 «oñ\¶½yí\&4äÒ¹Ìu2ñ ¤ÎU Œ¬›«su¹l.“ècÕ\¦*Y’–?z‡5sBpâ+æÚà´íÂè+'·ÖÜ ›ß�ª’n¼X±C�3µ8²›.rpLl¹ØìþœBU±vY¬èÃ;VÝ¡¾Ã¢,°ðîŠÀÚ†Û“ñ~¼”"t"\®q¬�p �6¥Ã({”ì;ÊÄøzk(ŠÁuZ²‡‘uZ%Œf\£>¦®/<š. ¯m6ih¯kæ³»4À‘µ¶7Qó¤¸ÚBÎ3­3I�¤"�²3P›!MLGU17µ®DUëTã¹ µ®SŸÂªG�ðäÖÎê´½ ®Ö^øŽÇMÆ©“Q\qïñVpƒ{’Ë„”Ú1¹©l—7AlÞäâÎ�0Avct½&hûNrY�P±Ô~Á÷ü,‚ÕwøB—ý R7ñùƒëZ`pòÕßìákVƒ¿$Oªþu£FøkF-ŒÎtß¶¤û€£°‰únéÞaÌ•(n(ݹ#¬yº÷0®š¨É¬Ã(j ŒLºóat¥ûƒ;�— éžÜ.ÝõêéN—Lw¢¸�tç»6K÷ä¦ùtOÔ@‚¥Oªœî-0¢é^£mÛ#ǽ]Æ ùž ¸³Q¢_=ã™ò²»åMìWÄwo–ôéݶ$Jôà­{Ò§Ußv¨ Hxk¡ �+бî8X7ä)4èBôÌup1ä¤W0Ì,J¯swƉR¢—µé™®t5xþô]Ö¨‹J¬UoÔÌÏY45„ÔàŒÁFt¦ ¢Æ™Äi¸.¯²‹ôF¬¬ÌÊÄ]‰#)I�HÍä [º[¤Øàô »u½jPÔí†5º½J¡°Ÿ.©­Lϰ[÷L T’G­ š™tÄq°j§R*78—R3:“‰�Qc cŽN5®¯ò)ê�P©º2O3Ot¥”(¬$©`µ­Qܬ¬`±ÈŽ�™-]o:�óù%\­ÈµG�r­x²ëðL,^ O±‚‘+ÞÃ2F®^¼�ï.õ�] /mì HÛž»ÎNL¶îábÀ¿U:[î©…�µPgàÑ;‡¾ÝÀ67ɧ³süûÿ¬‰:QàIlj£Þ óAÔ7®1Öî6¹<ù®V !ÝëÆ4ÕÍZ´l>Ó©°ÁL¤¾Ѥ^þž±–ØK沸ù!½w¾ñgµã¤4�ÁšµÍB±—5™ÏÏ.Ú*ã| ê&:ÔQc€|uÚZâÐ~ÀÿÌ?vã$mNÓk'饺¦¸ZpÀa‰ª!&j�d‡&x@&'ZB�TÔL0©R=ŒSTâwÊAêcòÐÂãRкè¬í?7N1Q¯ñ0ÁЬs).ÕXÞP÷†ÐǼêƒ:1�wbZE±Ôñh*õQE›zmÄ�1P Æ„óá¯Rò)ÆL>XPYT½®\ ‰Ãê?átN èŠoÊ)Û_ EßÑ[E‰nÊ{üë©FC”8ЃÙAè&íæÆ£ÕBÌžnÊ)þœrCÖ�j�š}­©êüÙñçîáôô7ëûå§ÓÏß­/W—XvþD ê ²0xœë9ÈòIzÃfOߟÉÿ˜¥! s!ÏÜ‚÷”ìð̼”üror$‹Ñä9bÊ,`ÎBéÉ*òz,™@Îæ@ùdöÉJêžž )+«‚Ä¢Ô¼..wÿ¤¬Ôä-…ô”ìør°Iñ ñE™eñé©%ñ¥ñ%E¨–)ö(€Ížü0Hor”†ÄÖÓ:²“5{­¦(0Šò€ÜäïÁý|Fvíõ/xœ{¶�­AqÂDÉC+Œ <óJRsœó‹ ò‹K2óó6ž7efwŒôqôs™ÌÌ¢gk°8*†'Væ$æ¥8ç$+¸§d‡gæ¥ä—{æä@e&×°J"«Š/3Ž72Š7àªæRP˜¬øJ‰W¡ ±(5¯$>$=ùŒ¼‹®DJ EÊšK__A$µ±HMW¨‚§†-&ñƦk05¼·6f/‡hجhó–�æ^>±É—ŽÁÁñAžañ\i¦Ž±¬¡xœí}Û’ÛF–í{}¢;¢Ãö)Ëâ¥nvx"4-YV´,;$y~B H�…‹ä�`IôDÐù�óe'Á+€ÌDf®½“dIùb«`­M0sïµ6ÀwßDÏ~ÿëËo^¼}öþÅóóèù¯Ñ›_ßG/ž¿z=õöÅ?ß¿þó,ú&z—¦Ñ0ÿðít–=|÷öų翼xrß�“Yt?™¥Q6ÿ¼Oòl2»G¼¿ËæÑ ‰móhšÌòh2XAŒ’e:ûv~—ŽF›]ÿ9™.gÙð.�þßÿ�ÚO[O£Wã<‰ÏgÓÉl Zß­}¹�hÞ›eÓ|þ]oÒOŸL—妳Ép–ÜQ fâ[Ì'ƒüc2Kˆ–“EÔKÆÑ,ígó|–Ý.rl%ãþw“Yp?égƒeñÙbÜOgQ~—Fy:»Ÿߥøãå›ß£×é|.¶½LÇé,E¿-nGY¯8úuÖKÇó4JDŇâ+÷£ÛåêÀŸŠPÞmB‰~šüÕ÷ü!J3±}Åþ�Îæâ£¨³eÛžG⬕äŘE“iqÜ×"êe4JDtÛ㞪Ӱÿ¶}ñ»­�ï&SñÍî¦ø®³Ñ(ºM£Å<,Fç„Ø9úãÕûŸýý}ôìÍŸÑÏÞ¾}öæýŸ?ˆ�󻉨š>¤k¨ì~:ʲøb³dœ/EðÂ//Þþógqȳÿ|õúÕû?‹¯ðÓ«÷o^¼{ýôëÛèYôÛ³·ï_ýó÷×ÏÞF¿ýþö·_ß½x­†Ýæ<¯Î¨êTïÎón0öÓ<ÉFóÝ øSüÐså¨Ý%©øÁ{iö bL¢žSÆsËP`%£Éx¸úÖâ˜ýyý!ÊÑx’ŸGg™Fù¤ñg^�ÔWãÞ“óè¢%öJÆFâ§x—‹ýÆOÙ@àÿ4šLfçÑNæ¹8"úåY=m·ZO¿muž¶Î£ßß=+¾áwggÏbx¢—ÏÿÿñêÍó_ÿˆ_ýòÛëø�g¾~öæyüÛÛWÿÿ|öw±O6NM» ¸qo´è§Ñßz“û{1�îþvv–/§iÁ!†Î¢—GñËþ‡?²qòñ•ø½ÿH–#1k"Õ‡?œ�}÷]ô_›±üêyôtõw2ÊÖéãåûE�'íö“§Ñ·›µ.Κxâ‡NÜnÇOÏþ÷,R´Ý£H<é8�³ñ½Mfò†õüˆ'·ÿ�örys™L·ÏbœýÏ"_r�ŘXO Ò�›Ÿúe:¿Þl 7ÿˆ‹12¯ „bèÌ÷;Ü'ó›íïR¡ ¾UîVäQ‘ÅiIVçEüæùewwªWÃR à…o«A6\ˆ:íhÞKF»ßí>™‰ãâQ:Èk­´]í3‘ kŸÜNr1ïVðÛŸj³¥ŸÍòåvïíd f$ÔašŠ<Ÿ­‡ÿzDÍÒaqr‹D?MŠó¸þ ‚]Ù²§¨¬OŒâøòýáÅsb1ò �—pН2š¥/²iýãï?ºK·gl¸XÉ’MµÛΡýd»Us¤*týoÅÀJÆÃÑJm†{1dfÉC&Ô[ña,JçÝúÄU¶mpmë³ÕŸëá´Þ´më1S�ûŸt–ëá¼^÷“‘г¶³òEŠƒwghýçþì”Fù»\œ`q¨øo]uf6jc}à‹M�?ÝhTd¦MÔÓIV ÷¢$ˆ�“<Ù>OÜdùf’¢¿þKŒÆqoÍ[®yåâ(äó¾8îþ¨åïK¢ªUüý< „É™Ü Uš>d“Å|ë¾/6Y~±Ý¾ªo·júŠb¯ÿ³Úk�§Öã¯ôqñ­OÁ:a”6½\k¯Þh"Æq:/mYå“(Hñ\X1‹$¬Q“­Ë½œ¼²‘“­Ë 'ƒž z2èÉ 'ƒž z2èIN=YÕAj ¤T?záãAF¶-dd¡Å^þœÌïÞ'·âWþf.NnÞ²FÌæ»ì¶˜s½*»Þ©²v×J•]UTYPeA•UTYPeA•}îªÌF]ÉÊ­c©ÜvMfÃøC?�?ˆßwS…ã~ÚÛ¬·Äõ�´š®}±×t76š®}4Ýç§é(#+( ƒ" Š0( ƒ"äQ„]ëKÂVâG¹wƒRAueZ¢•c�öÜ2*ø¥}W„�ÜÒ9¨} 9¢rêŸhŽÙŸ‹ÊŸZ™ÝÙ®�ìRÿ[þ¶ÆŸÕéuü1­Ç ò>Èû ò>È{^yaÙð­U¸U>ŸoŠ•Rivž´vB³mš�¸tfЙAg>N�Ù”‚ *4¨Ð Bƒ *T£B/-Uèj\íDF‘IÕ–ZQÚÙˆÒ�(íQDi¥A”QªÊ¾ªÄÄk¯A¼ñúň×+¤…º­´Å‰_k/}+µû¤»S­­K³líÆÝ [ƒl ²ÕnÎqÄm·AÜqÄm·5q{íЙ]Œw…x7ât›·ç·´}�}æÙ_©âàÒÆÒ¡*­ÜºÚ‰åvËF,·®‚Zj9¨å –ƒZj9¨å –Õ²Né4ˆ�¾ÑJ› Éƒ&×kòKM¾›ë!¼aâ,Œ‡©îy§B2·÷êÙâÉZ]¡Žƒzê9¨ç žƒzê9¨ç ž›{ÍMš$ˆë ®�-®[O-Õõ^þl_y H¶C©ü„ú“n7Ùg³ËvøévÛÝ?­û¤cñ`´î ?-Hò Ƀ$’0Ò®ÒÞ= Ûù¤W/W4÷Ðr:´]>´­8Ti�öw²^t¢¯ãY*õ<ímå‡Â}­Áñø¡Õ,t›€Á@ T0PÁ@ T0P‡0P²&Çäøñœ×OBÿgE²'£B7D“ñhyVœ�aÿÃF·_Ch¸�֟β‡x( ÎÆ�‰S÷Õ×ÑZø‹„Û[ýûmÑ�Ñ·«b^L›AôUyK±éëmÑu�Ì…þÊãâïí¾…UùêëýÏ,&ÔíD ;¡Ügc1P7»‹h¾‰ZOŸ>îh¿CO$›Ý›xDȳ¼w}µ܆Rä&q&Úm�ñ}ý“–ôI[ú¤#}Ò•>¹�>¹”>¹’>¹–>¹©Ò’bnI1·¤˜[RÌ-)æ–sKй%ÅÜ’bnI1·¥˜ÛRÌm)æ¶s[й-ÅÜ–bnK1·¥˜ÛRÌ�ZÌi´t¤ÑÒ‘FKG-] §+át%œ® §+}r!}r)}r%}r-}rSÿ¤>êºÒ¨ëJ£®+�º®4êºÒ¨ëJ£®+�º®4êºÒëJc¬+�±®4ƺÒëJc¬+�±®4ƺÒëJc¬+�1ñ‰sGй#ÅÜ‘bîH1w¤˜;RÌ)æŽsWй+ÅÜ•bîJ1w¥˜»RÌ])æ®sWй+Å|!Å|!Å|±‹9ŠngiòaWD¢¨Ÿ’Å(ßï°«f“Ù¸RËî“e|›ÆÂø.¦k=°-lÿÞâer]áþãÇõ™ØW¦JammÚB¶ŒD�ÒqOuǵ�k_èŽ{Ú|\[sÜMãa­+Ía×�‡=Õ�•«†ÃŠt«9ì²ù°–æ°‹†ÃŠ"¡9¬Ûx˜öè4ÖºÖÖ4NŠ*®û½+‡)wyº»›ñ;KóÅl\ÚKhÍ•ÔTuŽ¿ÿ¾Ö>;«ì&”œQ‡Öâi>ûJÅ%°æéh°Ñªáç¤r·§i•ž~¿ýª_ÕBþú_}el—·ã§ß|½ŠçÛÿPôÄ7$-*IëÒ‚¥Mf¹¶`éPYÚ,]*KÇæw¹ ±tâ–É%•¤cArE#éÆ] ’k*IëÊ‚å†ÊÒnÛLIâÄïÚ�ãqêËmhˆs¿¸ê×L³ÕFÑ0Ng³Éì«¿½¯ ý¶DD¯žÿíëÄæäV衵úwcÍØ\%,Õ ›:±9êÀµÁ­”®ÊÀ1ßËXmG¬ë¬ŽViÌËX]7¬NÓùºpÁ*çaêÒ ªÓuåUΩ2ÔµT)sÊX7nX¥ü¨¨Nƒ¾Û<"ZNþ’ë`Nã¾’Ñ*`x&{˜ˆ�ÓÉÜ.A�—¯nF›³–®„Øž¨½5Þç/0q5¡¶mQ¥Ö„Ú±D•‡nj×UNkM¨V¨Š×zi *¥º&Ð++PEÒk½¶•Ó_ê�%ªœ§€ÝÄR¥ÄFX»©¥JŽ�°vsK•&5°`u©Î“Ͳ0!7ëÄÌj°ŠoNZª¿4‹V4BãÒÑ�+I#4.,�аÎ4"òӈ «P#2.J�иF5Ï\²š±qkÆÆm¶o}ëžrÏw #õÁÇ“¾ê)* :PaLæd3™ÕdN@r“9Ý9™Ò]®“)ÝÅ<™�údNÀÐ`褀‰ “ƒBê߀ìï;9ÛÞ)S¾-ÅÙŽì~Lޤ~÷ ¯)Q¢³ù%:›5Q¢³¹%:—AQ‚sy%8—MQ‚³9%:›YQO%6¿¢†g³,jx6×R‡?°q±KÎçªûëÕj¿ídœL)\©¸–N¼?ƒ2Ó] ÊL÷6(3Ýá ÌdŸƒ“ÝJLö<(1Ýù Ìtÿ§º ‚©é^¦¦;"€Ú¿/*Ýx¾3FÕ[Ó�­QéðÇä�¤[ðyÍ‘žÍ©áÙ쑞ͩṠ’�Ë!©Ñ¹,’�Í#©áÙL’fN±¹$ >›MÒà³ù$ ÿÀFÉ2UŸ«ZR/b¥­'c–J1ÉU·|ú=Ø%œ›n˜pnºeÂ¹é¦ ç&Û&œšlœpj²uÂ©éæ ç¦Û'Bb¡(9ÝBÈé& "?Ðå¥í|ªW˜öOïÂ.2ÝZèSóRŠç”y¸Ô¤ à½Ú¤ à½à¤ à½æ¤ `½ì¤Àg½ò¤Àg½ø¤Àç½þ¤ ཥše¼W¡T ¼¢T ¼×¢ª Ǹe“ÆÏ5�}Tv ·›OÆjUƒV·8oõÚ€çêÌÏt� ægºRó3]¯‚ùy®ZÁô<×®`zž+X0=Óu,˜Ÿéjž|˜®iá0]ÙÂ`º¾…àßžíq\öfåG!;{³ýÁ�ɘ՟õÌëÊ”èl–L‰ÎæÇ”èlfL‰ÎåÄ”à\6L ÎåÁ”àlL‰Îæ¾ÔS‰Íz©áÙ|—žÍtÕáì¸ì’󹿱øõʵßv2^«®ThKçÞƒËB™éþ e¦;+”™î©Pf²›B‰É> %&;(”˜î�Pfºk‚ÓÝ/ÁÔt§SÓ=@íßÕ_Ãæ‘êÀ�É)©ß�Ãë—8Ø\S›wjà`sP \>ª�‚ËM5Ppyª 6gÕÀÁ毚& ›Ëj"aóZM$lŽKMr`ßå’ðmÝWódW—@ÏÕ!ÐÀsu4ðl� >[W@7¹Ø::¶n€Ž ]ʺÎe9OŠwojj$u~—Í‹ÇKéúàMç„~®y]•w8™Æ@凑Ä\å÷÷Р°Óvz€ÂN7þv²á§�“�>…œlð)ätcOa§zRª¡y=ÝÀ“èC½Õ:ô¢°»ÛðÅË×ïìï1,z„7ñ•cu~ _)tµ±ÐK…T ߦÁ_à;$øÒÔTÃwIðÓ¹¿ À—‹™ý’„Þ1 _ÐËåH�~MB/Õ5ü ¾TP4sŠ2e»æaÙ¢LÚJIÐàSfm¥Y+áûè·îú»`N—[f—¾€�üÞD–å›HØr} [Æo"áÊûM\Ù¿‰ƒ«4q°U‚&¶zÐ8ÙªB# [mhda«u"gy–ŒâÞd<Ȇ‹YÏÒ^š=—åãy6 /’�ssÁÐ#�ô•}Ø,Z¬àñë,Vðøe+xü*‹<|‘Å ¾Æb…_b±Bǯ°XÁãXìæ~}Å¿¼b‡�¯³4áû¾š‚¥êó¨Rô Ç»j¢�)ú±!`–k&lÜÀ6nàz 7pµ„�ÛýZ µû•6j÷ë$lÔÀU6nà _b®�ð‘×GøÈ�e�äLÓ}ñ¾cgƒ´>ê¤ÍÐ:Dã#Aá&G‚ �…› 6*lJ$$Ø€HH¸Ù� pc!�OÜDÈX¸a�±psPÆòmÌ)©&ú×Oà¯ùEòßœ%ánĹÚFL@@1Ý…±Ò]ð!Ý…¬¨FL@xš< (Í €P4ƒ° Ôƒ°[ÌÓ¸·˜ç“ûýeS7‘'#œ´à“Ãe�°¸l„ÅEa#,.aa±Øˆ ÇFTXD6¢â‚²—ÍsšÍ¸¸èlÆÅ¨×·uK�5a*|<‘*Ç" ˜â¬rˆW˜ µ0 va.@Ã\îâ¦rÍ0•»˜†©‘ sâŸÈ€(Çɱޓ"!ó î§é¸Ÿ�‡ñíb0Hgq’çIïν�«�9i™¯‰™Eë›±qÁoÆÆU¿—þflXÿ›¡a`††�€·flÜXLÜX€ãîÀ·�à¾}�€kfAƒp<Ç  H”IÝ™æð ¬€‹``ü+à,XÝ=©»Û` u÷ ¤€a`¼GŠ\ -àO8h§B£õèYz“ûû,G­ÊúèGáPÖ¡² ’îG$Hº ‘ éîC‚$› ‘ì5$D²Å�éÎB‚¤ yœÓ}„ŒI·2&Ý5”1eÌ)PãÖ߬ã(U¢ÍYä4ÖÙoÍAùÖIoÍ� xk \®[SàâÜš‚ Å­9ÂÛ~d¶= ATÛ“$´ ‰Áœ|L²¼ Ì’{çÕ;Õ£OZ0WCeÌZH\0k!qÁ¬…ijÌZDX0kaÁ¬EijÌúqŽ f=&.˜õ˜¸`VaúÌö)°&˜«O0Wã¨v9³3 ˜�9ÁìÌfgwÁìLá.˜�)ܳ3 ˜�9Áì>ÁìNfw@0»�xÌw"wŸ�í¶½_NÓŸ‹tnñˆ°âÐ#<L«óÃÁŠÐÕšG/Õ‡|›þZßá€/Í­|—¾£=÷tør-¨¡_r wtèWtôr¨¡_s —’ þ†¾”÷ësŠaÊv†e‹aÒV²}ŸaÖVýßCÏg‰ã©Hȃɬ”Ïmòx娓nzT"eéyèñ–‡ïxèñ†‡îwèáv‡îvèñf‡ïuh‡7ÞêÐBâ�-$ÞèP@úîsX'½ó¨œL+‡¯ÉQ Cx†ê ähq82 G ½áÈ47Ü[Žî� G÷¶†#ÐÔpdZ®Ó hh¸Rí W  ™aOác±Üdº˜ÆÓd–VZ6¸|èI+àr <«äÔ€„5rj@ 95 a}œ_§ÆÃׯ©ñð•qj<º85 aUœfTÖÄi +â4ˆ„õp¢÷Õp–i®ªyËGq!\)ŠbYIùä±,‚sÁGÀ¹à#‹ß\ð‘…o.øÀ¢7x`Á› <°ØÍYèæ‚�,rsš\È7'dq›²°Í’À‹°�gy‘FïÓünÒ?;ûmóÁ/«¿ínâ—íê1;_³«}µúY*u@GÓ¦Ó\[ÐtÈ4•y§£é’i:6¿Í‘¦Z#t,—d–ŽË‘¥Z1t,×d–JáÐÑÜ�i*õC;7©) k7œ[Ô$P«&Zj¨�‡ 2Ï“ájv’Í&ûgVþŒ­}(‘Nºe¢Œ˜¥wbBÆ›(&d¼›bBÆÛ*&d¸¿b†-&`¸ãbÆ[/&d¼cœ&x3Æ�weŒÐx{¦ÚwŸÆ9ÉžGr*W‚¯£ G)õiæèè�‘èú�>‘ѽ3D$tï Ý»GDB ŸDd:LÔéôœ¨”@ŠJ ô¥pJ>c}“gžöœ†뤽†&f·aÆÆý†wflÜs˜±a×a††}‡vfhÜ{˜±q÷a1qpÿaŽ; p܃4‚ûv!@Vù Ìñœˆ& Q3u§›Ã�0°Ž„�p% ¬€3a`uw' ¤î…�ÔÝ¥0�N…�p+)p,´€ká œ �Öƒ{¹MzÙhÄ`^ÔP'í]Ô!³X#4î\ŒÐ¸q1Bã¾Å Û#2ìZŒÈ°i1"ãžÅ�[ó|Á‹7,flܯ4aû¶+î)WåVÔ(Ç3+êxD�Ôœj«BæŒ ™°)dNÀ¤�9Ý- ™ÒÝ �)Ýí ™0'dNÀšÐ`L褀-¡“¦„Bêÿ¥U“Á (uŸÎÎ�wUm�>i¢‰™Å‡˜±ÙÞU¥Àf{W•›í]U l®wU) ¹ÞU¥€æzW•ší]U l¶wU©&Û»ªTàlïªR�³½«ª ~àwUÙ$àsÕ+ª¶ÿ9ôµ€äW£ìN°‡WT!¬ôWT!¬ôWT!¬ôWT!¬äWT!¤äWT!¤äWT!¤ôWT!¬ôWTA)‚þŠ*ˆ–þŠ*ˆ–þŠ*GÚƒ9”%É¡,¡CYzt(K�eéÑ¡,=:”¥?‡²ôçP–þÊÒ£CYzt(KŸeéÓ¡,}:”å1Jcnr(ËSs(Kme\zt(N¬lʼn•Í¡8±²9'V.‡âDÊåPœH¹Š)›Cqbes(n)‚Í¡¸Ñ²97Z6‡bKëãÙØY>JÏΆ½»dfõ<ìbÿ“v «YüF‰ðÜëáy×5$Âs®kHøó­k@øs­k@øó¬k@„çX×�ϯ®KÂs«ëP„çUסÏ©ÞCy>µ) �GÛ„¶ÚõˆÏ¢.è‹g½®N˳§›‘gM7#"Ï–nFDž%ÝŒ<;ºxVt3 ðlèf@äYÐ͈ȳŸ ÃyÖ³y¶³y–³ÒÇ{[§ÓQÖKŠg!=ù˜Ì‹(Ûíd2J“±Å‹[÷‡Ç›ÃOZÀ)¾.‹œkÆ%¼Çµ—ð2×F\Â]qñ׺6Ââïvm„Å_ðÚKxËk#.áU¯Í“‚ð¾×f`ÂK_›� o~Õ{ý«[""t›£Gñ%°òù+Þ¬(‡Èó:Xœ y1,Ά¼"gC^‹³¯�ÅÉ€ÈâdÀ«dq2䥲8òzYÂÔF^4K C^9K C^> ÑyÖÿeú¬ïи-Xy4f 6»'ÐÂóX-<�CÐÂó-<‹_Т³Ø-:‹{Тó˜-<�—ÐÏ)K¡Ççqz|ƒ¡Â?¤Ï°OÕûž·þøÓ0Õ˜j5µv¹-‰›hHHÜD{Bâ&š7ͺ�¨iF†DM³5$j¢É!q--± �œh‡häDs„’û·Jñ}:^ÄÓ$¿Ã�Òã1¥}Ô>|’�Í&)ÑÙ\’�Í$)ѹ<’œË")Á¹’œÍ )ÑÙü‘z*±Ù#5<›;Ró™£:ü�½‘]rÖZ£ýá'ãŒö!É…´t²=ø"”™îŠPfº'B™éŽe&û!”˜ì†Pb²B‰éNe¦û 8�Ð]LM÷@05ÝÔžýOAz›Ì(ö§ ñhÜO9hvó£çñ>pë£çq>pã£Áfñ=lÛ£Áæq=pÓ£›A<žG‡Îcytè<ŽGF?¤á±MÇj¿S>ú4ìN9¢ZÕ¬œhn³ó­ÌK4:0/ÑæÀ¼4“ÓÒ,LK380-ÑÞÀ¼Dsƒ' ¢µÁ‰‰Æ'&Ú„Ø÷ý/ë25¹ýï´—S¬� ôh Ž:ÿý1ML·Ê4Q0Ý5ÓDÁtMϽ4M <·Õ41ðÜaÓÄÀt³MÓ}7�óŽéœF¦»q9˜nÌÑpô§¯6M2ÆiX'9®úÚtù`¿�‡#êM>1Poýáˆ�zCG ÄÛ„8B Þ<Äñ–"ލ7qÄ@½ýˆ%AQoJb ‚z«KÔ˜ˆAð¶&º·Ó =ƒ§‰ßëO>­ž™‡ÿ(Ÿ¦ÏÌÃ~W”Gûg¦a¿Oʧ4óðß9åÕZñßKåÕ6ëî*’MÔ�†WÔ×°Pß§kdކñž,†hïÒbˆ†ñ¾-†høîäb†ïÞ.†`øîöb†ñþ/†hïãH}Œ÷ˆq„Ãx×G8Œ÷‘ÑÂñìVãìi|»˜ÇãäÞåñÉåàk(�Æ�Öâfw¥z|7ªÇçq¡z|÷©Çgq�zx·©‡gq™zxw©Ççq• “‹ÇM6ð¸È÷¨$8¤ktHÜj·X8 —X ªVuë'�ÛÙ‰.�ÈNt}Dv¢Ë#²Ó\‘œæâˆä4×F$'º4";Ñ•QS Ñ…Q鉮‹JOtY8½W5L'÷i>[ÆwÙ8ŸŸ�‰½^n>²±SÕÃãi>;°‹ªÄûõ?Ëo5|µ‘BJ5VKÑ&R\›):4ŠÒìÕRti‹ßâ‚BQ®{Z†KCÇÌpEa(W0-Ã5�¡T¦´74ŠR-ÒÏ;ÒÔîZ ÙirWªŠžƒ4»+¥CÅá³Ü'ó«:°Þéç‚Õ©Ç©•ˆñbP|m-@8Tå FÒ¦“( B�¤C&QͯI—L¢* 5’ "‰²,Ô8.ÉŠÂPã¸"r(KC�ãšÌ¡*5’2‰ª<Ôg"uº« D�…:áÕ%¢ÎB�ñê"±gñQ#fÉmœ�§‹<ž§I¾ªïÄ?¬ÊCõØ“¾ØR‹•å‹¿¨¢ÇÄ/¤è1ñ‹'zLø‚‰¾H¢‡„/Œè!ñ‹!zLüHÀÇ/z4€â:@ñ‹JPß4âyTJ´ÕÃŽwé¢Hôc=4–K p)`.9,À¥€Åý@â~© q¿$�­€hñ#Shå#4@Ë¡Zón4TõöÅóƒYrŸÆ½Ébœ§³³³¡ð—]i­8Bf±sçEùÔªÛ‘£TIL$mœäÚš¤“”柉¤ “tì“ �¤\EL—0GÇšã ä(×Ç5ÌQ*!&’˜¤TAŒ3�î]—ÜB'|¥~YÐ_) ,jż—ŒŠå® o§mSVG£lCt®«ˆ5Ùß´”þë¨mêµµƒ£–æGµ‹£vôçõE-§ð:è%ÚÑ‚^¡ å$]½ÆAKY¹Žzƒ£–Ò°4à‰ÕmW-xjU­ Ï­Jf-ÁzȤ÷ÉL$ýx”ò³3£9“–Ž8éîu)N–εïZ«ñð޵ïV«ñàNµîR«áàµïN«ñðδf0ã]i Þ‘ÖâÝh Ðw'Ú2±�GEš,í|¼Þs)ˆèÇrH,=gt ×ì€ô˜Ð�Þ²º{OÙܽ—ìîÞCvzÇè@ÏØe*½bx Gìô†íàý©ÓY6¼s”§«Cƒ>]Ê)Pë€d…Z$KÔ: Y£Ö©"µŽGU©u<ªL­ã‘uj�,T¥QMVª"YªJˆd­ZB<�X5¦¹ŠZ]í}t¹ºŠb_GÖ'�Q°Úáã’Õ­vø¸lµÃ‡…«<,]íàañj�ËW;|\ÀZN.\ÂZà"Ö’—±FB6ŸLÝd¬8à1ˆX&§„­Â‘lŽ,_«pdñZ…£J×*U¸VѨ²µŠF­U8²d­�a²`­á‘åj �,Vwx’ª†TVªbߣËTþH'‹Q¢š±qyjÆÆ¥©—¥flX’š¡a9j††¥¨—¡fl\‚ZL\~Z€ãÒÓ—��àþ$çí$Ï'÷nªs}ÌcžëH9µ§„H–Ÿ"Y�Jˆd*!Ru¨H•¢ U�J€dA*!’5©<¼É²T†$+S’,NË�Ò§æ¤W‘¨ëÝ�®R×aì ËæÄ1jU[\±Ú2àºÕ–W¯¶ °†µ%€•¬-¬gm pUkË€k[ëé†+\k \çZSàjׂŸæíg³|yv6¼�LFi2¶Ö½«ãƒì]Ê©zë€dÑ[$kÞ: YòÖ©Š·ŽG¼u<ªÞ­ã‘ån�¬v¥QM»"YëJˆd©[B<�Ò5¦¹óh—:ˇ]쮢Ø×”õ™c”ºvø¸ÐµÃÇe®>.ríða‰k \;xXÞÚÁãâÖ—¶–“ ¶–¸¬µ$ÀE­‘Àƒ¤ÍÆYž%£x°�æ½YšŽãû‰øl2³ìéêNZéêÃfѽV𸠶‚Ç5±<®�­àa½l…«g+tXK[¡ãÊÚ ×Ùvs WÝvø¸·Ãǹ ß·>ÇRõº-­?öx²]“(¢ '›CÒ³qrŸ�°lÜ€M`ãv·lÔîö‚�ÚÝz°Q¶„�°,|‰°3|ä€Õá#l¹‹4™&Å»bféP~vÖK²ÙdóWlõPæ ÀI»¢J¤,FH‡ˆ{"nwtˆ¸ÃÑ!¦Fû l]t€¸[Ñ!âE;¼qO¢…Ämˆw HßfÃ:é�GRF­{<Q C” êYäpŽ €Wpd�# ûÜÕ½#�»†w$pWꎀwdT·ët´µ+  ])�lOá[ »/…Qþx1ã˜f\&u̸L¦—I)ó-ši†åQÍ|Khša™4ã‚ä`RÓœËk ÀLÊú�‹miiÍ�âÈÑØ»+ળʮ·ÝبÚÛ��ªÃÝØ¨šÜ��¨ÏÝȈZÝ�Œ¨ÛÝȨÞ��ªç§6UÛ;ÒQu¾#Uó[ÓyY0T¼±o†—�?iå_”iM��² H HY÷£¤¬ôQÖö(ñ«y”x„õ;J<ÊŠ% e�ŽzTSVå¨)ëpÔˆ”•7uDÿkmìÒœ¢û]>ô˜‹köQ¬.¿–Î Ï|h‘Œ>´ÆZìâ€�,hq€G­8À# Sà¡Å'øÐ—É-"q!€Џ@‹Aì?§tÄi›ˆ}œ<ÖA‰G0 J<‚MPâÌ�·J8Ü(ápù¯„#ˆ~%Aê«3Aà« ²^ Hóu@ïÞ.±­/SÚùˆ }DQ?J'ŒE�Û£#ÊÛQÙö舢¶GÔ³=8 ”íÁUlŽ(`{tDí:L%DÙ:À#*ÖQ¬VðÞÔé]êð¾óò!�@Ÿ®e¨ U¡J€T‰*R5ªH©Q¥JxD™*áQuªHªò¨¦*U‘*UeDªV-#F¬šÓ\Y­®÷>¶\]G±«#›“Æ'X-ñaÉj‰‹VK|X¶Zâ£ÂÕ•®–ð¨xµ„‡å«%>,`m',am `kKËX3�!;MféXdíÅl�ôÒ¸7¹¿Ïò¼ ›Géììl¸MÆC›õ&¬xšúQæÛà¿þ‡cI0}µ0v¤+•¾6Îw�ðu`¾Ò$uàëÂ|è÷»ùÊ%Æ�î¦ë tW ]¹9Ð]Ãt¥’äÀwó•J”ËdG“Kœ -4½TJ˜ !š_*%ÍŽÐGAKÇýl<Œï'iœËXzù“â?gg‷âÉx8J­ê™jýŸÃ²jìîåLwVÔu b+W3 º6•îÚ‰®C¤+Ï^ º.‘®ãöÛ]�è*•Ì‚í’ÈÖqb»"±Uê˜Û5‘­\Æ,ènˆtå*f3Çi)¥ë: Z´¤R­a6|´¬R-aÍ|‡¬]q2îÝ/’ǽœ%Y¾¤°5ÞqêØî ðU±Í÷Ñ3WJ›R&s¶iœõLæì�8mf³ÌÙ%qÚT6™ó‚ÀiUÞdÊK¥E�“)¯”V…N¦¼&QÚT;™ó†ÄiSò �’„ìêž‚”’†ìŠŸ‚”’‡ì*`…ôP…pS{Kañ³(†•ot°r¨cõ[u¬~K¢ŽÕoQÔ±z-‹:R¯…QGêµ4êHýG«ßò¨M~ ¤–Öo‰ÔÒú-’ ÚC•ÉM5»ËÆù|U%Ÿ­>ø¹ø®”eÐãÊÊ·à)–å/¥­•¯©^jˆÛtbCÉÔwÈĦٯ!î’‰M…SC|A$6ÖN ï%™×P>5¼WD^cÕð^“yMETC|C&6ÕQ]ö ¦-s)Õ1S—¹šê˜©™Ë\Peæ#ô_ãþ§³³¡Õjocëµÿé¤�[œ –µán<ø’q7|%¹¾ÀÜ�^wîF/Gw£�W©»Ñà‹×Ýxð5펓_êîH„¯€w$ÂÆ[ù^/O,çѪY oy½Å¹Ž~´ù ,‹ñ=F,Ý÷ °Ðßc4Àm£q¿‰Àc0î·x Æý�Á·3xŒ¸ùÁgên•ðpc…Ïp€Û0ü„sŸºäó©§ýÐ}‹Sq ŸÊóX~7Ÿ>•çÁýn<}*Ë£ýÝh<úTž‡ÿ»ñøô©L¯p$òéS™^ `Mt|ŸjzÅ€�O=âû,εUýçy#�Çh¼øT8/>ŽÆ‹O…£ñáSá`|øT8>Æ‹O…£ñâSñÔçŧâáxñ©x8^|*Ž?ŸúÄå)®Û°Oÿ9®•oÇi2%D²�”ÉÆQB$[D ‘j%@ªí“©O$[9 ‘lÚäáM¶g2$ÙˆÉ�dËU†<”¹²|Æke÷£û¥'Ûç/VÂâô@¶ ¸¯±eÀ½Š-î?l`OaKû[XûÛàzÞ–×èÖÓ ×ÝÖ¸–¶¦Àõ±…GÍëôlØm„�àé°ÕïÇ*{Ÿ«…¤ _ƧÄj!ÉÒ—ïI±ZD²øe|Z¬’.9Ÿ«Ç¤ `Χƪ0%�mŸ[Ýÿø"x÷ôÆj`¬2Øšƒ „­9RØšƒ †­9p9lM� bk \[SD±5AÛO@‚0¶'!Hc{‚8¶!ñ(�çy’§«ÛS×;½+þv¹=upœ[Q+£·¢®Ï€Z?ƒŠŠQ'iÓIä’Q'é�I3¯NÒ%“(ŠF�ä‚H¢ªuŽK2‡\6êWDUݨs\“9…£NrC&QTi&R§»²tH,Ô ¯¬ uÆ+‹G‰ÅC�H?MÅžiÿI/�n“Þ‡³³áíd2J“±¹>lŽ·ÇóÅtýÑWüåàýÛß_(søOÏ^¿{¡L¼õ-í–®vË…vË¥vË•v˵vË�vKiŽH›ôg¡¥< ø*ÆÏúW[£›ßFHÈEˆ‰x2‹Ç‹Ñè¢Bý=œå…4y”ÃóÍï¯_+GgmCG·¡«Ûp¡Ûp©Ûp¥Ûp­Ûp£ÛP•õ-ÚïÞR}yŸYm1Ogq?É(­íŽyíóÍkÃé$çé "…­ñNù2‚<ªÃmwÊl'S4žäÑnJDâäæwÙ×Fh|­®_³k„Æ×î¡á5¼Fdx-¯^ÓkDÆ×ö¡ñ5¾æù‚¯õ5cãk~ÍØf¿¢Æ8©!ŸGú †=^çKóKü¨ ”eE1™XaLæV“9�ÈdN÷ÉdJ÷ÊdJ÷ËdJ`3™XÑLOÀ g:)°â™Nzâ…ÎÜœ[¿jË­õj^¥ŽSp¯�îu¿3½»dæÔ{ÛŒƒ[TÛ~ø:: ç{Õ5‹IÕcâîT�‰ÛR=&ìGõ�°ÕCÂT‰[O=&î9õ˜³ÙŠ»ÌPüÖR%èÁVJ8dÊóh“‡k?® JL�å%›ŒÃ ,€÷X·°¸û;€ÄÝÑ$î \Àø4€qf àÅàþS7ŸÎký�¸7YŒsÈ•‚ ûÌ\˜Õs{ÔCáD�X9ƣرr¼¦LƒÌ`Í4È MƒL·i`ºYÓÓ-›˜Á¸i�ì›™ÃÄé ¬œšÁÐÉгuÎÙwý!åñ�11³d䲫ü–¬Žc¤¸?Œ‘â1F‚+Ä #$¸EŒ�â1FŠ‹ÄIޤ¤¸K�’â4�)½>Ób4™/fé{¦Åæàà6?3·ùòu6ÏÝžZ± 'ê6·ñÅinÉy]¦•Áa*PÜ¥•î, tW©¥;J(ƒ›T 28I*‡‹TÁ28H,ƒ{¬ÂÌ9:eÑóh›¦¥ã[‚%gÖ²ŽÚýv¬NÑ�‰âݘ(ÎÐ�‰à݈NÐ�ˆà݈(ÎÏ�‰âøÜ˜HNÏ‘Šâð©(ÎΚʧ£ËV‹0'‹Y/ òuUˆàî>3w7\¸]M¬ ‡uxÕ(�âóª!ðº=-6ƒçÓb38?-6Ýÿi¡é.P M÷‚ZhG¨Åfð…Zlw¨gðˆzp§¨?˜_2óy´NÿŒÇ™¸™2vY·Õ~WV/‰³R|%ÎJñ˜8+Áoâ¤|(NJñ¤8+ÅŸâ¬$¯J ¥øV-ÅÃB´üìüNìØ[äó8ße·Y>™9]¤T$'«ZX°²Ô •?'ó»÷ÉíÈêe=Ê1qZvV"$‰ƒûZ?‹©5ãŽÖ ÛY.ìe ¸°‘5àâ.ÖŒ[X0Á¿š�qójBÆ�«Ù·m…ÓïyTÉó*œ�¡µš#5 ¨7•ÄXSŸ»)%ѹÛQ�»%єĘOb;i„€á¤V%ô`2ûÙ|:J–¢:ÍÆ“õÿâ~ڛ̒\ ºÎ*V,a�œùÔ?ª:¸O§çºNfÃøC?�?ˆ_QþÝmŒ©q윖I5MrˆŸÓü¬.~NÜךbañ¸$°ßuà€½¯ìƒ8pOì@‚ûc‚WvaÁ}³ î¡íX|ûi–ª±{4­±Š™èBýyõ‡»ðén,¶ÝKîvÞKî6ßKîößK@[ÀK@»ÀKHÁO @{ÁO @Û�;ÿíˆâYñóÅl�ôœÈ[û%”GԂп-+ô T=ˆÒ¯ 4Êcä¤[ å¯é_䑨œå‰ÍüªNN6¶C) ½5:WWA�ÎÕOP£³uÔðl=5<_÷@ƒÏÖ7Ðà³u $ü÷ \þ®KP­3zÔP!¾ø ÁWdi^ž?ü¦§&yœšlÞqjºaǹé&çf0ærº'�Ó 8D~Ó�O¦£ô!]÷&ØîÏÚvofÐwïFÉÉïÝ=Œ®Âé a…ÓAÊ §c5ßÛ0<¹o<§ýVÀsúo<«Wà³:p>¯W°zp« ¯Á…;¥ÿŠ /Õ�ÜP1BÅà1ã;~¥ZßM$?v%g1ä(9‹%GÉyL9ÊÎcËQv&cÓóXs˜žÇœô‡±çÓÉt1%zóF0柵1_ýÆ +_��“·äë¯xurAÒ ä‚tÈÅjÃW1xòàulN^ÇætßulVë]gõÝup^Ó-¡³:n �Õn—Ð�àµí“zÅhoëˆ1T€/¶p¥~µÆ^Ï?†Ú�–ÅJ»Ó²˜hwZûìÎËcœÝy™,3@Ìc–b›ìDìß ÿU^ÚõpI°É5¤`–?[³\û¥Ë\+'mœë_׿x"3:K(2£³�"3²êZ$lµž�Ë\ë¸,¶ž�Íhë)Øì¶ž‚Ït7p°Yï6®ä8° G ÅÎŒËuª=T˜Pax {=Iö×ç¿y§‡@6òôȦžÝàÓc ›}z ÆŸ!z€!zC€Ä�š»gôî@ *´>ïö@é§FûåÑrú ‚ò>�~#QbŽD‰)8%o“ Н.�š‚µM ¦`í¨)xjÞN�šƒ¹U !áíhHx›É1º®e£Ú.¨Ö-~¨8¡âxj”cP»‚òdóÔ6 ÁÓ8 ÁÓ: ÁÔ< FÁÔ> FÁÕ@ †ÁÔB †ÁÔDÀÃ8Pa½˜ŽÞCØâ„ÂçÝ@ØþÎh÷`7NN¿u°ûªRq8&áp>L¿á|¼í‚m¾z |ÖF�ŸµK Àçm(xû ææ€Š�·3 bàm TŽÑp*Õ†@©5!‡ *£ýß Öò»åÉøÃô<–¦ç1û0=“͇ù™ >ÌÏeíñ˜L=“�Gð`äól<Œ{ó¾‹kßôYXôú–/Ý£/Æól8Å0ç.cá´lø..Љn–Gp³"€w}‚öyÇÉá‘U`°!V�áîW…†[]Á×*áp«„Ãk η=uˈçQ%ßî™2dJ]¦DS¤Ðšû¹Ààì,�Ý=›%0àÆ,‘Ÿe‰Œ8([hÀÙB®Ç íÁÏÌ·ÛÅÔŸ\,Mù¸ÏÜÕÔ·|!¶ÆÊÍT†Áišrh>*5 ß\¬iøæzMÃ7—lþ…g|Øò”i9\�7>@Üûh öG‡ˆ; "n‚dDß>È9—ž¯Pù°�]Cv=‘ìJH«BÿVfƒAr‚l’>`–œðËäF'7À>Ùø5QKÐD-ƒ‰ &ª'k¢–žË<€ïTæ|§2à;•yß©Ìø&jÉl¢–Ê”A¨çKeª!ð¥:E*öR�Ú%z©N‰„š¼<¸‰²Ë¥’‰Z†ì²ë‰dWBZ­Jà%³‰2ÃÓL”Ÿf¢ÌøDeA@3Q4ÕHàÁDõ&ãA6\ÌŠ _Î/¨‘þbíT}Ëçå§ÜÖÚÉ£â´Ü•ŸÀ@bV $f9À@bÖ $faÀ@ryØ�IÜ,®¬ ·fM¨Ö‹›´FXÜ©i`}Û5,C×ÖJ !ƒ‡ 28èòdÒåÆâ÷@&Àù�LˆD©7ˆR¾Ð�ÊǽWã-eÚ½Ÿß9Ý„%<¢$óê[¥I´»K§eå½ÜoÀÀbqã‹Å ,·"0°˜õ‹Y`p°\„…p#˜ÄÎâa)·†5âîkÄ%Ü,¦Áõ~×–¿×—ûäƒC^y=äõ£äuzB/n˜’ÓËMj(tÛL†ÜÈ“!·¶d¾ ä]š ïrØA®2XÈÊx8a¹ŽÐ»Ø@iÕJã(7PG½�Ò8 ”ÆQq 4Ž’¥aò’kz~3)ár¹I˜ÉNÊÀL~² |PCé�ÍŽr}tHó!͇4Ô4Ï�ßëFe“’ØÍ¥Ù^:ÒQ ¦#ÕbZÓy0™ƒì“àšg¥î×(ëÇ{ùÛKi0œ–·¬‡çCqÐ9ÌrƒÎaÖt³Ð s˜U�Ã,1èf}AçÀ=d�›Å@6€Üc*n›PqߨFõm¡½vŒõCCæ™;dîƒdnjÊÆBJ7†âA¬ F˜@Œ°®D~�ŸûµEéà`ý‚õ+�†“õ~þÚÍ $NÂ_«™�ÄIEøk33�8é-fÈx± •Çr^Al„eñ�‡¼zˆelÉ úÊaHä!‘‡DN³„»‹Mrêa6….LD[èDE3†NT4khIåã¡ÉÖ°w—Œ‡©Ó ä䣃=t°‡õ-§ìIƒâ´\¢ —‡Ü1°X<êŽ�Åâ�w ,�½c`±xø‹Ybp°˜5ËõAXO(•Øyìc3.ái¥�¸„g–jp½?¹KççÑ®bÈ!ׇ\rýgœëéI¾xʧœñx‡ “!�F…É�Ǥd^Þ;±~6k/�n“Þ·wOT� †’ÅPJßôøŽr3´?Žv¿µ�îããļe-¹¥6ÆQ7w—Ÿ¾9 nÓ0‡Ám:å0¸M‹·é�Ãà6MqܦƒÛ´Áap›þ7>‰üNQʕΠ+×%Î œÿk›.é¶ö¾ÈÝÁ! ‡4ÒpHÃiο«‹€ÛäÉt™ÑíÁG,ÙxøòS›ƒƒ¯ú|eTœ–·’†¼�âÎ@b.ò $æbÏ@b.ú $æâÏ@b $f1À@b $fqÀ13åqÏ&±óx7 ¬o‡¥÷ÒýŽ@Hý!õ‡ÔRÿ)§~rΦNJ|<¾Ñ�ʃ�ìgóé(YŠ 1O>õô§õ)ŽZ.ö¡Š×�‚Ã<¢Ã”6y¹ãñ¯ÚOn³zÕ0hNË€6Ï’„›Ñ¬O¸Íb…›Ñ¬\¸Í2†›Ñ¬i¸Í‡›Ñ¬v¸ÍÒ‡=!é>ë¼rs(‡1Î 5jw›§¢F6Çêª[¨n¡º…êvÊÕ�·¬E?ŠÂQ›m¶¦@;4¾´¦@›Üh?¦¦@ûಠ`$Ê&€‘(›F¢l‰² `$Ê&€‘(›F¢l‰² ÉGH:‡Ï:¾šíh ØÕ(]S mj ´Cu Õ-T·PÝBu;åêÆ[ÖLM�¶ï¦€¨-ÛþCœÏ’ñ~á­¤ðæ”yÅFeœ½!.‡>ш0I~œŸ<¥yÁ…‚Kž¦”¥Û_ÏK.�­Ê� <‡k"$å Z …$� S„,æ"dQFdͤà qªßëjÄcÒT'‚_“H5KhÚ@WþÃé¬ë£á•¿ýóøS9ð?ƒ¡̦#xQ¦T!O=/¥jÎÝ”ñœ¸\¤Þàãø²ï¥jáj�‹ÙpÔ zé¡ÊÜkNN‰ Šzx 8WØiA4_ÑeŒ0žù“™ÿ�æý=¢L ¦ýÉø¿»Áš¤Oƒœí£Ã#Pq‹ Ú¶OÊ^ÿb6pˆI+;§ê‡&�qU·#ߨTrßÐRÌcâ"Â¥,áu •ÆTè3r±�¾�{ÝNÆ¥›‡ —TÜì[áUjgU`ø†ç-ØÜ²¦™Ì5Ͱ]cŒùÆ5ÔlÁM¸©YŠ]£RV–ݹCØTZ»fµÎÚ¯Uvupsª–Þ˜ÝÂÔä„ H[p )ƒ„­r"BE°-�‹¥õ£ Bÿ ¦-e cLµmzpIØ^àÀ×°Ón· Ðiß�Ágã¸TÛ–ŽþD¯¾ÀnF¢…Ɇ¯T•ÊÚØR‘©ëZÀœ`gQå\ƒâŠ}?ÙÚ⮊¸ºâ-³\©©%M6øvù« O4y¿íÛŸG/Ø-;2Wp¿äxœÝY_oã¸÷§ r’²‚sØ}ØÂ(´±²êØ�­lºÈ†,Ñ6ïdÑ¥¨dÓ»~÷ÎP¢DÙ²ãì^ûÐ`‹äpþÏ�C®eY½› $#Ó,§½Þ%ß¾¶ZKòþç÷?“ß®%÷,“Tôz·TlXž3ž–“5tñBV"‚ÕÄ%KA)áK¯#±¢.‘œDÙ ÙR‘þ�ËX¶"‰A RÊ5°ÉùR>G‚qB¢<ç1‹€Ix\lh&#‰ò–,¥9±åš’³YµãÌQB¥„e×ôyfrÍ IÍ¥`1òp�(N‹uÐË)Û°JnW¦çÈ´ÈÁÔÓ%ž°%þReÖ¶X¤,_»$aÈzQH˜ÌqRùÐE;.¸ 9MSäÀ@oek£�¢A)[t¨¬\¤ä>¯ù¦m ¸hYˆ DRµ'áà2%ñKœAò%OSþŒ¦ÅCHμŒÏ\r„ד»�ÅÔ‡_ÈäŠxã/äïÁxèÿ·S6#“) nnG�sÁørt7 ÆŸÈGØ7ž@ú�ÄÀ4œX± ü2»ñ§—×0ô>£ üâ’« #Ï+`ê‘[o—w#oJn醴“™â‡ÀvŒ¯¦ Å¿ñÇa¤Âñ?À̮½ÑH‰òî@û©Òïrrûe|ºÉõd4ôaò£šyG~) ŒºyÁ�K†Þ�÷ÉW»&ÀeªÈ*íî¯}5ò<øw“1šq9‡Sº`å4¬·Þ3ß%Þ4˜¡C®¦`�î„Åö�ý’ ºš´"$8¾›ù�.C߯n6‰û= ТÇ6n’òÕ ²M`ÇR@ê>A*+|(ç?—CMóq/d ™ôWTŽÔœ=ŸgцÎçN¯—Ð%°Ý@îÎc¾A$ÈmÜï|èøTBUÀORÑ–}ñõ¼þ·¯ç¿_\àGæX.±,W tJ£U>òá$„è�€ŸTG•e˜ë dÁ²*×·¸H¨pÉ3ÅŠƒÂ\£ñ¯�à‘$P³P8OQÊ’^.“¹|ÙBY  æ¥ý Ô³ž8K,·üøÔŸŽú;g+�§GEÖçkð˜¤<[éïeÊ£z!á€<´æ’þô~^¯® ɫ˜z¬œ§4Êêñ–+À‡ñc–ϵ�6F¨ Â�€¡Ãp®ŸoS&íj ÿØR9NQÔnj–�0^E)?ÆD( È”8…�\†°¯Ü†ºÌA1ŸÛ°KCXµgûH�Ò¥´_‡¼k¦Vó½šg³‚„.<}¢É>¬�FúÏJ—p€>Ñ¡�¥¦d0Pƒ’NÛ6“ Zg+ÓH8,eÍÜð9þÁN*0Y†(—Aq‘¿³O´º’[M¦<ƒ3�¿j¡Ö¡rB¥B,¿l�{Û}Ê߯y¯VǶÈ%ßü·\ ÄÑo�—1p‚¥NÌ�‘m:KUúÊHÈ;Û*I-§�¬†—þòáó‰mm§Es@¤Êçn𛓔BÂc*ýù$•*qû ™9ÐZИ> cží¬éP7–*š“rlÜW­m^›«ž¥éÞÖ’ðÀfÛß ŽýR?åü×b[Ñ«ÓÆ®­w 9=�Ök2jÙ¥[·óÊ�}Ê&(¾½¥þ´ÃvEb¨çÚ>©ól‹6ºRÂ6õkç�FÌJ”0ü¤)kveŽyä): ×œ?) ï�*ÇíçáUCV—–ïÆu8£$Óèw+Å)ÐDz¬u´ìcŸ¢pË“rþÔxOýžV˜Í>| Þ†÷UÐ ^;§ò^›Ð˜Ólrµ�œÎ¤À`�0fÏÍÙ–ëM#N;ówlh·Õ9÷ù˲Žc´Q•qWPë�˜‡ Ž?Á iyÓe´°¸Š¼€[)´êYúâ�4zçÀô¬ê_ϰIG¥‹ ´õ‹—òº‹` ü†eSÿ,¢­êøužá`ÃABå4¯¢{µ=%´¥-ê4€¾XÍS–Ë£¡níØ ø>µf韦¬»pMÐ.•Ž ƒªîýòeŒ7*Õ5Ù ûd }†Ž‰È¶"�aB­vBŠC:£6™Vhôç²Ö`ÛÎ]Ópà«®¦¸ûÅêGS_7ñ¦J¥·ìsë»n±U!c¼¦ö�”µp(e—ܳêÑQ"KcŽ'Dôr2æ@�Ù¿ŽwÐÝȳO‡Œº:í6<µ–p žðó?Ã-¶<ÐÚtƒŽb†mññfäàÖú®i½1'^ÓÓz¨1W¹$ЦêŽ‡× |Æ<é] >°³^ÇJ“;5k�Œ;ðñà• 8KŠo^{CµÜh4h´{C]Å Zï^Q9ר¯€§â³tɾí‡üÁ6xj2�žê6ôh>™x¼1ÛÅJ½E©ÛudÝÎí�¤Ö¬»“4Äït“í•ý¶~gÍк«µÚu¼7´ˆ>Ú;ö™Õyr�Ù_¿f;á²&G¬¬Ã×ë�üø Rtõ'¸  ‘àµþ$S/¶Z��’AX3ø{ZÊ›ò;ï(0õãõP%ñÃã›d7ÑRZìË«$½v¡9þ0jUM¦ÊqèÖìdo‹-Îi#r83·d,Ëê}-Ø8\ò8ç½Þy’>fáݺ`oŽÞ±y’®Cξ„qÁ³^ïŠg›0ÏÃ$faÎÖ<ã·�ì.ó¡7pÙ*ãœ%+¶\ûÙwY‘0?~d)Ïr�Ü~‡ñóÙ¨ d±4y²*üŒpÀü÷3âï|zõu6zÿaÁ>LÇCh|7ÎïÆCA &u>Œ>ºìbðqð~H£¦€eF`’»/†Ôôðßùb4�à4Χ“Å >]˜ålQý2š]6˜�æ(�ËÙУ8aÄ”�À¸ÉP`AQ3cE¿?͇/ÃÁpÍq°ÜïYà-zá—›EÉÝh[o•�¾`°E¸}�ê['¹€Kr‘úÅZõf%Üfã§r êÌ—B«dç4 Àá`Ì ¾à›ƒú¥L³dÉó¼ä3õ3ðk§çç…ÂöR´Ýƒå‘;­çÓ�ï¦Þ%ˆy:sÙgÑÙëádyÆÎÔ¬ûw¼S›íy±¿ážçôz °¨gÌbðß t!ØeÆîxÌ3p2dœï�ü,‰7|"ø]ñý0òyv6E äñû«ñÞ‡áô!@ð 2Õ,w»!Ÿ€ÞVùÎSö˜lÙÒ�A˜•[bàZ„3ê † ÛÄHf .}SÚìûÉ'6ÁAß{b7bWäÓz26€kV®¦~G]"¥§ºL99ÓSÆÁÁBÈPòýCÑ+/ r±ýùÎX’â ‡ÂEº“©uAR›w5½@9ýu’ÂlÖ€ æ÷ ×Ò_m#·�¥�AT~æ´Œüž < Qha2Ó0DõjÞÙÞç?úŒÍ9WRíµJUÉ�ôc“€ðñ1ÊaÆ_a)sà, ØÚÏ�ñ%ï�/=pîX±2šûQ"C�tôBЧ,\¡§wÙC‚–ÈÒµ–no/û.û×cñãïH}^0 ¸ W€ü2J’Ìeï’¼À(ûqÀØÑ›ãã£Ãã?ŽŽ]ð.é;ΧCoø×Åp24ÍÎya_[ýµå2ø;Méߥø[}-­°®€Ã´SøÇ+t`»,ÈölhåÐìœôüÉxÑ’YžÅ^1Ù%FƒÑzļTnÉ=Ê(ìœûÙríaVŽÏ·Q¬]ßÐ7.HiƒÊ–ä}Ðÿ€›ãðO Ãгõÿ–„±â§„ùX˜#ªTàqðê¬�ã´ÂÃ#…)Í-ø�z¯�o�ך¬ÛHôý4åq 0Jɉ>!²Œo wðÖܧ‘{è2q>¶hÐf/¶\¦ã2ùDHÈö'{SM>óCpM³mŒA|˜eIf›Ü[è?Àe-#ðä‡q†Ü<”.AØÒ¯&Û»59f àä ªóÀ±Z™xûL&J6 ±a�/ؼÁ� †Â¥`*¾@àÜ‹€É�K=\âhÁ(Ɖ®OKR“Ÿ5-ѽR˜Jëc~ís¨��zߟgì¨>’ûßËš]êz‡�B­– ¨È[ú9GŸù�dAnzjº:¨„ }Ž0ä�ãÚ‚ëÈl‡ Çíò/Øïý&¥²fõaƒ˜•~˜È»·ø®¥·ÚÆ”BÚ¢×ÃhàÒt`–Ýyèg\W] ’±Êkú …°<«Â¾ô£œ;5H‹TO©¨-!5†F{½lÅBú¬�Ô hצÀHEµ™ÐÎLÎå¦Î †Or“Š~ƒYIÖ'gD¾~~‹5�c¢ŠrCð0µᇰ'·�új!!™¿£§Â¨ñò«jk,&e*^±I)Úè¹%�ÈTíú�H°à˜ÌûÙã%‚BæËϬ�EÀ#p?g‹l+mÕ'´›lYEÛ|mJ%ÓH WÞ-/8x¬$ @þüA2Ÿ2Rcš&€lëG¦âU;˜~¶�!5BªÀ;è Õ%dôó� &ž“܃èšn šœËpBÚ<ÑÖìZ®¡4'·0¾cÑà¡ €ü¬È1›´­?­ZÎB$p_¿f‡–\çëã“}­ñ9ž·»ð½êÂ'ׇöT=ˆyÎf›,KãE²çÁÎðŽcŒ©ãsoS—³¦~ÔSÁ‡ÄhŽ×·@9Î@Šg‡L&ävƒ mj|æ? Yb!ìRs ‡mÄ9#¡Ñø°Ï~ŠbÐûÅ_uÍGhÒsêSÃ#Ý�λÿiéò.YìÚWÚF;þA’€J…éF¿m8rƒ«ò3S§bؽnнÒlÚ„ðëÔ”Ž‘*¨¥ sï%î­’L®[ÓgʱJ%ßžµ)*æ·êç?[”¶a <0Š;¬«¶›ñJ­08è/½ݳ'0GóvangÅÀÁÿ^Fš+u&kJÈ0Œ±Ä¿„<!\3p9]m›à‡ŠyWÒƒt†;åî–F–®ì# ò� ªÑ‚Cg¤Â¾€v ñ. �?—¡s¬€©¦�9–c*|]ˆ»‘$rØ 6Z•5;–>qÓ•d…¬*s¹.UstñŒÌDÈ#;ý`vÊ0Ü ¼Ú<ªHŽ_•Za­Jvä^Ém]SKÉÔÑšâEJž4<‰T8Ú Ôµ³^3Ú�Øg‰Ø9�`çm[}Ü7yV-à®â lå‰b¾]­ÂOžÜ�µí̵…뫚¦ÓŠ$ÍÂ{Ïpù‚Ÿ.‡*z…“iAH„uQU41Å.ÔÒmÌ>¯LµZo“ÿåZy¡¸ÒúÚÆå÷Óå+PB¸R¾TÊg§QV‹IáÃÕO=¨�‚ºš¹�®òx»¡ j×µ¶³z[îy-\ç2l‡Dô„YÆ0\p•�^›„2­ëð¦ÓÅë8iñäÚ¬H1ª‹Þ.1q5ãu‡¶`]X´•jÏq*™�ÂøÐxÂîµyˆY÷“á^J¯Q÷«)A¸#lij0/RëÃ5÷õÄvÿÒY% mØï[…ÅìÓðéB¼ŒçÃgKñú¦]~°+J»î½r•D7ùZËAÝÏÉàGnò¤¡ ”T¾™nî@rûªƒ†¥[Êy $‡ú¨û «6�½@³üòՃŠÿïMþ1íŠ~‹ÞüV»Î÷-kiÑ÷I¨›åÿúÒæ¿ei]¦‡$·%£û½«ÿ¬e§1¯Zx"�'Zÿwª¡FæMíØWÔyžßúë ŠÜÝìT¨�õ‘f¼zRª…ÇâÚ¾YWšóm^$Úã5—.°A–ù�µŸ?-©zFéG÷ÄOšT޼ޣ–êõøÎÊ„y½¤TNµØgå%£Û,jᄌҺÏΫsÙ ii;€Ú ä5"òn Ý7 –É&Å3©ÛǯŴÝ(¨—ÀÁýl‹Õá¿Yzq:J’ïÛT1ØY|k-4+aß(å5ñÔV6|b•\ùòvÂ-µ´üúðø¦oâ|Bz[´Zñ]yïöz„ÚaTÆhëêV?hÙÓ¶ãÝ_y()P�€J &-W’×n~3½þZ#±ñ—Y²·r£3â4K8}yMA+QAM7«S܆ ª{å9íKÔ`»Î*:ͳÙÜ߯È!"«õȹ¼`3CNÕŸøoÝÇw€ï�g„ðrÊ9Ýk\=ÖåˆçÈí~$ȘŒáeŽ Ë½ßo¹v¬¢ë hLX'¾orƒ¶q§¦�w²ÌúA£Y¤×‡¶W2t„j)ï�:Xƒ±·õ»9Æ%8º@Y}êƒÅ9Ô6Ž9žõûÙ#+ÑÊ;r†�œv†�ﺅ´q\YDðÓ²¾òFþƒ=^RY»{m×JÜ®P†öúX½8Öv{å¾åˆ±ÅÁ£JÉg¤W�FÙeÓ5Ý‹¸>"õ’š&2mµü _¥4�ÄÔK�]h×sº�K;¤�Ý1W:Ðä±í˜¬ÂT$åÐ¥*§tƒ»,¼‹ºv™D‘·œVð€G¬ÆË“¤Ý.FyбkZÇÕ€ ƒo¶Ã&ÏíÛ‚Õ®v@…}ÞÔÏù+u7=*¦Ø-TJ�E^C}¼Yš×µ´LáÅ É5Ö†l‘㘨Ê܇Ž}m¼i�c.H�såž”^ª #m'gÄŒSßó42‡öÛØ‡q±»ÒÕVb¯ï;µí”º¾håø o^ÚÁÀUݾµtÖÖGãðZe_ÁVeæ»â»Èu„{ø­�PÞIE¢'1P!Åy@>}›À!Šf‚u–©‡Ã^²ã££#€®0á)ž>ñŠ®:ý Ú-‡UØnæ/�~�©wÆ#©[c3ÚBÿ%{Ó8븗3°c‹DïdÇ ^²?Dž;}I^ì'S®:Ä» K¯@—¸¶øûb?ÂW«,‰£Çvp£:^Ëà;,ÖLÓ2¬¶]*“ökç#ϼÊa½€M èƒ~P²ÛН>ÊWk~;¬L‹Š´­=ˆt5IÍ/²Ç¶Kñ|Û�xÄÇç}üËÆ˜�ˆ%xÀv„w½°FsFýƒóóá|îax$Dy›zËWX°û#Öd±ÝÎ¥Wå?–<-˜ýÙ�¶âa>¤�ø$)¨hDMÍz§¸îVݧÄkâ�‡Oñ*R:"z^/t*0:?Vdø´“'$åy©’rpø['Q;��}ˆæ§yLv¢Üb«<›$û«Vo­ü¬ŽJkÞ‡®””< ¨_Òm{i©I¿Á¦ÉÆ#Ë9ÏÎc$³a-Ï…JŒÛ4P¾¾õ­Šö>¸-tH„z1©¶Ö3ô7 ÚñsÛù‡X'ãHI­�ÖÚ('…F+0ÐÐióÚÃi{©Ð�dœ(’öRMv�Ä5[i¨ÔXóœM_  _CÞÎUøTç·G‹T`Ó]akÙO®Ac-¯r!AÆßU Êš·–þ¨×¸T(³´Pƽ/QƇÀÇO&o�Ú¤Ç_¤¿ ¦3¡ü¤dBÐh² ë;¥êðú¨V�6_�5×âM5Õo¶7Žƒ*d}ãTÊÀ¯9�®sœß/W'kÔ2¼½ãtâ‚bc±8 @\ár•DBô0Î.š¬Âæ‘ ž7y¢ò ̘³ñÚ€÷L÷#µBù5ù%79 [à5D¨ŸTKÁxZý?a¹ôyµ“mÅ‹Syå™ÞlÖ~¿Aׯ\Ѐƒo—ؤ¶W¿O¡¢¢šG¬N·J}�™¡B)µ¡R–yqYÔ“G]ºËC�¿ÚËñ2§Ó@\vðpЖؑ�ä+_}##k-ôDÖéý7}Å3Ŷœxœ•Wm�âFþί(­N²Íx™™½Ë’¹È fÇ:�íÉÜ &Äà:kl«Û%ùï©j¿AvïF#ÀÕUO½W¹5Më=9>Lù–Å’õz£$}|ÈàÃ݇;ð’ôÀ¼ð8c¢×[0qäRò$.áÀÛ¼Á^xš°ŒA²ƒí!{fB–@¿AÊ„D�d“<æñØ¢âÌ#“]v Cæ)“-Âd›YœéÛñˆIгƒw^)ñÎPJBDÀc ³óœxvHò “™à[Â0‘iå!Ùp>Žø‘—H\¹. 4—èÙiÂ1 ù޾™r+Í7—BNЛñ_,×ǃ…;ÿÉÛcxgyøü΄Çœ?û€®5ó?Ã|Öì3üÇ™�M°ÿ»pmσ¹ ÎÓbêØHsf£éóØ™}‚�(7›cù:XÄêÏ�–PŽíØ“íŽñÑúèLÿ³ ÇŸæA-XX®ïŒž§– ‹gw1÷lT?FØ™3›¸¨Å~²gþµ" ìŸð¼Gk:Uª¬g´ÞUö�æ‹Ï®óéчÇùtl#ñ£�–Y§v¡ �M-çÉ„±õd}²•ÔQ\ÅVZ÷òh+ê³ðä;ó¹1šÏ|MôÒõ+ÑdzM°\Ç£€LÜ9ÂS8Qb®@Pnf(jheYèùÙ³k[ƶ5E,�„›Ìƒž†Ó¢Ç�”nˆ’ý«íü(pvì–îvÈ Jj¿×#>&àá,0سlªhúzG¶^½^ÈvqÄ:]o“#u½Ô·IÈŒaðO° ;¿XvºÐôÛUÐÿqÕ¿ýãö–~Ćf‚¦QdžØ]»(ØËdÏ}ÌT© „dëì-eMlú‰æÑ×€ú9Õ E绂ÆâPҴе¾VŠ4LZdÂ'ÀörøþþÕ(€Xt†Â†Y †]Šqº‚8¢ƒ«˜ß Ï�—¶�Âû¿à†ßµ,är-³ð"< (/ ýê´¤¬°@l˜˜Ÿuc˜o³?Xœ�•¼1~\�nþQfæŠy¹Ì’ã¬d5'®†µ4 "®Úsü%NN8®Q~Ñà¦Èà h¿hÍ<§"ÁÁ–½ý/¹Þðl½ã, ñp–ĬM\Ë4Â!]Ê õ¤kCôé¾JGÄb½#`À¿á¾v£ÔÝaZÞ½¶,éZƒ‹·‹»¼¯D ™]ŠŒí, úÆJ_ɾ¾êã§q£¯NH‘ý•úÐÕ¹QeåìÄ.½ˆzm .Š*4eQm£¨vé`/’<Õï�Ú—4Q¯ØÕyLüÓc hª­à=Üw5w$àνb¢?m’Çj±Ÿy¥ZëpÌ£Œ§äü†¥D[‰ãJ¬`ƇÛ+¥…‹MkCêMN¥zTcp½e‘A‡[W@³ ­v�†[ÓãÖGëâô‡Ö°eA)Õ 5¾NI¤-_+ éå´”+ôƒ²0Íæ8‹‚4Åi¡wzƒ7RµUiD=“Tõ#&ØT²ÆÅÐ+Ågèµ^ˆ™ÊpSÙ]ðBoW s©>_/ÊOIÔ¦“‘U…U+¦k…† ƒÊ‹¸Š©WÓMh c¶º¨áÉ(Zœˆ#¾ßt�ÀnºÁ>Zþ¼:­ÌWê.Õe}C_"E"­Ù\Öð2Ü�*x®xGÁ¥h1´R~Áº¤gUÁ¨Òš)8ÊŒaÛ'ðÜŠ¡£ìÑà‹ô¬²$1ãEtá�c ÈøÜþC/ƒ8;–âtåûºù„kK¼Ç¾Åõüójy»X|¹©>\¬Ò2�‚š•ÇÇåTÁA´ì�x* $Þÿ�µÁû¼ÅõÙ×1òÉ4“eR)ê[;ÀϾíòÀgå»÷åQ’…51ÂÛr �,aÍ¿¡ñÀµ¯xœÍVMo£H½ó+J™¶–8³sZ�”±qŒÖ ðd£(BÓ»˜FÝÍdóï·ªý‰3ÊŒrZ)Ð]õêU½êjÛ¶m-üæC,ÚŠ3xà�fÒ²–Ln¹R\4ÀTL²õ+ld†»…¥d D y•É s@ ÈšWh™Tè Ö:ã o6�AŽQÈRW£D©_2Éи€L)‘ó ñ y·e�Î4Å+yÍ tÅà*Þ{\ M�‚e5ðhï°/\W¢Ó ™Ò’ç„á Q^wq8l×|Ë÷Èݤ®´S˜ñt`+ ^ÒfÒj»uÍUå@Á zÝi\T´hjèP7B‚buMy›\OìŒ Ei© z_"÷¥Û~&X¢²“ †dƧX2ño–kZ!óRÔµx¡ÔrÑœ2R_-+Á­l-¾3“ËNÙFh¤º£@´'U÷[ªÊ�ûší †q±¼ÙY:’Â+�Âs¬}+¤‰w™æãÏ<ˆÃiòàFø1,£ð›?ñ&p寸}åÀƒŸÌÂUh¹AòáÜàþôƒ‰Þ_ËÈ‹c#ð˹ïᚌ竉ÜÃú!¶¯�MŒ Ipå{1�-¼h<ÃO÷ΟûÉ£S? sŠ .,Ý(ñÇ«¹Ár-ÃØÃð„ ü`aoáÉ£âxßðâ™;Ÿ›Pî ÙG†ß8\>Fþý,�Y8Ÿx¸xç!3÷nîíBaRã¹ë/˜¸ ÷Þ3^!¢DÆlÏîaæ™%Œçâß8ñÀÒ‡AᧃYFÉÑõÁ�=ÜÈ�© Ó(Dx*'z„ýo‡B¥†ž"hBß«Ø;q™xî±br>7Y6N ‹oInÒ¿•"gJVj±Ù`ÿY¥Ä öËm¦+Ë¢M&áö`5Ú0=7kƒ4m²-KÓ¡eå5žˆX+¾Z€OÁJHSœ:MxžJÏs+ÒNÖû·‚ËáΖ2Ö1ØáõhÀKjqÃiÄþÅó«=Ÿ3,zv¬G¼)ÅÀ×ÂŒ/~;²ÀWÏžAZíCõ�NåÉ®ôöèy²7\ÛØ9Faöyž=Ôç7žÃQ^±üŸT2�S"œB³Z±wRš2�W˜“ýA²%ùÛÏ΋ü¥¸íñþ5ÚGѱ;Ò5Þ+h³SèLœè]­QÝ÷h)îPèíZ^2ͳƒ³/‚¶Ó·‰ìX߀1¬Ï­ÝéòúûÂý2ÉáÇ÷”9dgºÔ+] ‘‘jk®kÞ`â'ëO@WMKVàçÍÍ�€žvï;ˆ�}cŸ®Æ«Aî­hŒìO�Ôo Ñá½`ºYËAÍšÁÁa84ÝŽu=bØçS’Ç�¾”:Û¼‘‡Id|þ À USrNÆ4i>¦7z±ëÿ“ؔү ý-èLSª7Ðê©þ†ÆÛÏOÜì‹0¦}¼ºñÇPvhlªã™Ÿ.f!9Ñ0$7"H°Tì@Ð6ä.U»,ûOEííÿpÊü¸PF/¶ïxœ¥Wm�›Fþί˜^+7¹—|I”«ÊÙøÅ6ær9%)°¶7Á¬³ w�ªþ÷Î,`À÷ÒJ=ù„Ù�™ç™×]÷z=cê0á1Ë3Œ¡Øý�|½Éáôøôb·á nx–3is&·\).2à 6L²åXËw“¬$c Vo"¹fÈDÙØ1©PA,óˆg<[C1¢�d¾A3J¬òûH2N RJÄà ,®ìÉDCÙ×ÈÞ×ü†ÞüÖw/¯¸ò&#/df_Lœ �Nlw:€‘=µ/­å¡_‹Uìn®½„x6~†�ëÍÈ�¡7 || —~°W½qÎlß]P@ƾ‡æ)œ¨ái#¨7sJ+jèdEèýzá4\FŽ=A[ Rn [F§…Á·”nHÅz�ÕV¿®Š,Î…HU½ q˜¬$Ö2–㊯¡Zžº³pq=Ÿ£ Î(¼ Þ`jì.gÎkkÍò‰^3Ã0‹¶, û8«¼é…Ž1&ÈýNŽ�� ã÷=+y”†B&LR_üLRF#¥ªåí.’\7�øZ¨ØwÝinÄ)#ø€­‰µýÖüKØ Â§X†&ö÷jküNŒp<ðLHzÄÛS²”EŠ%ýR“þHÁªå‘pýµ+ Íà®~n¡i½…ÏîV ‡»õW£E™}ߨ¡²ÅJ²§Kû¼ª^)møf—X´@ÒüI¾z ‘zH‡ßuðj9–*öœ&á¼kÃ4L±˜ÂxÃâoxèhÒO„dŸ¤½:W¡*vTÍ,9ÔÄñµ²"—iÞ•õCS\dx,áp-•©™ÍŽ?d~ӅІµ=ÌÊCéw(ýù_¥:‡e”„U½„gáéëðäM¿ãi]LÏ…èaÁé‹íR<§VæèWÝ·ð¢•ñvMá [UÇŠ*ÒK½wwfõH7—fc³�+½Ãu´Ûo—#žt%dÛÉþaEiœf‘í[úAâJÁ†wòÇJ#"B4ŠT�—ŽeJ§6ö �'8•øŠ3ùHY —á^®k/ ¢€£ sšGkÿ+Û(�7zBXŠE2Þ˜²÷ÇÙgËüœ¼èW�_zx§B £Š“Öi¼¨§^M½e­¥(væI¿ß’)ÇÔ¡ÌiK¦r­š®Äñp€²`¥|·Õ+ÍvSÛÓ%ÞBÑ¡òñœ¿ëüÛç—gŸ_î½­Tþ‡Ã¥fb?Ãøñn>oåëÁÙ7oêŽÒá!Ú7éíŽJïÌ:}m�¼éõñhÔAüP[D°"©Ï‘ƒrR!Î’*ÚLQâTB¦ª¸ïûóÓýNUŽ‚úŽŽòMdód_|U>Z#¬þnuFð£MkE»Ë³Ò(-nðú‹?º@ÿú{OïŽÈ•ª�¹Žü§»²ï¾€²æ�UjGŠn*f%9€—u•D$©Ãõ°Ê«åv0J¹*OTP]ßkÇRjáÀ8é(5I¨»±'ð‡Ï^Ñp)I6ç8 ïwÌ4©z6=ÿ’¢jK¡®Y®”wA‹g+aöÆ¢À2¨‡^Ê2³Tèëi{ÔšŠêÕfÛsú_Pž¥¤xœ340031QH/ÉÖÍI¬L-Ò-ÎHÍÉÑË`Xí¿ÏNOiÁ4N�sE,²óeógˆBç¦ççé%•fæ¤0ìÞ½ƒßî¢-LjÓÿÝpn�g®”ö<·’xœÕ[msâȵþ>¿¢ã©­µ'{önRï&µŒ-ÛÔ`p^g*•b©­…DÔ’ö&ÿ=Ï9Ý’�0žd?¤ÊžÁRŸÓçý­›³wb¼ðµPaâÇJÌü@ üø® µòDz*wÝññ?â2Zmb¾HÄwçß�‹Q´ZøJ<úa¢b»ä^ÅK_k? ÓBÅjºóXb‰×³X)Í„»�ñ\µD nÄJÅÑ4‘~è‡s!…‹­heBôéh–¬%(”¡'¤Ö‘ëKà^ä¦K/Ú�È×â8Y(q4²G'¼‰§d üPлì•XûÉ"J+�ľK8ZXä©G4d¯éÛœùׄ4Õà€èl‰eäù3ú_1[«tøzÑžO¨§i‚‡š²`[ÄÇY ­‚€0ø ›yÝRÇkh— 4±"â}׋hYæ"š¥qˆ-ÃxDÆ;þ¢Ü„žÐòYÑšXs£Ðó‰#}aÕ6Æ{9�ž3dtF è5t�V[ÕÚWz!ÁÀTY©asÈXxЉ�@û>°ŠbÞ´Êk;#âÖ£Áõø±3tDw$Ÿ»WΕ8êŒð÷QKvûW-áüõ~èŒFb0Ý»û^×Á³nÿ²÷pÕí߈€ëÆ¢×…AéxÀZT]gDÈîœáå-þì|èöºãO-qÝ÷ ç5�vÄ}g8î^>ô:Cqÿ0¼Œl´ýnÿzˆ]œ;§?ncW<ÎÏøCŒn;½oÕyõC¦ïrpÿiؽ¹‹ÛAïÊÁÃ(ë|è9f+0uÙëtïZâªs×¹qj,C^f©{¼uøöëàçrÜô‰�ËA<ÄŸ-p9ç �Ý‘Ó�awD¹€žÄ ˆ#\ß1XHÔe�` ýý0r¶´\9�p�¸¸˜TzöæÍ[†27ã�“^ç“3œŒn�^orûæ-û¡ªy cMâÇyòt†ßöâ/oÞœ½cò.ðø4�ŸÂ胀^ý”øI .ÄMò$zôJŒòWzÛ›xJ»±¿"#¼€6ËxÃ>û #:«ÜTCM5¹K™uãv2ÐaâoÏpOrŒ,be¨Ýh¹®i` íðýß�I²Òggs91Û>f$“&èðç!¡ogZ�ã\÷ ²D¸%��´ ¶å*ØßJ� Z™¬#á"\Cåibìsá#ŠûV,’Äb]úfòÁ¹éö'WÎeo”»%|޽„�„?±íüTuoóùCçòãÍpðпºàÌ3•îÓ<ŽPn³mǃ; % rá‹0ãÁ½H¢ÕË«?;C|2H‰1 ^†B¨~šôî>8à ‹( <Ê“äpl@ê‹«VœŒá-ÆŒÓå „mÀc$̶8F†r.Ä;¢F ç”ZiÓYš¤1C ¤†=A#KØB(¸8ò��OXKÉf¥(î*l!þÿ H�*ï$ioSžzb-7mчŒtº¢þÅV«8J"7 DVøý(¾?H\ÿcþµkDÍŽÖ`x@®N|wÐYæÿþ‹ŽWô®­n+6ªw—ò—(žXýg}ôPAh«y^‘[ÈvSzG­F¡¯È‹f„�Ìyû{VÔ<õQªïÙuc#™~ø™´â7#³¸ý^2Ý8z�L¬8C7å.~;b D4ëëI 2:ïàˆÓ rŸ86Hñ(7E.ia`+3ñcÔñ‰¿„»&ßj.½QÍWyýf<|p(HÌ 5>p.)\fXé7ûLý%õœ¢þD­o¸¡Ù$ÙCœ£Hþ¡\“Á�×l ó­ò¿Ÿ3.zhBlh2TÖvæh�€ëC NâÔMD-)âÝôÊ0 8B¹Tz¸™?ÑÚ‰YÌ\æ°ÛùÚ>W8Ê×å�Oye?‚;ðï) i4•MëÏEhТ)\µx,B³÷8š¢œÜªK-–BìsªØw ëñÑJ†*8j‰£H?á?•¸'mæ À’ #b“{fÔÏîG¶ gÇ/”¢5pë¢$؈ÐHú¡ vgOÂMcæ�BãÙ-¢zí#RÅ+óDgnm«FÖœÈ'ˆ³¬$“ÎÕvôuT™³‰ãõÂw{VaÁÇ´â ë›þC¯wR!J6Pc8->¢£ �¼[q5zÐáÅ犙µ;KC>ì^¤´íÈ¥Êâf«aMµÅUÄËøôŠÏ€” Ìvmpmjo3Oã€M+³90…,?|FÌñdÒˆ¤äÞ+xHënçž¹ü`’+'6†ícV†�tËbÏ‹�l(XÐÀŽjaª}4~qËÊlˆ†¬'£²w zyMt¶#ý‘Á¬3C~ 5ƒ4…6ú‘½ËXï†9*©*Œ8kIÅF•¤Âm*¹!Ì>K€sê´õsC´MÛN3GCO±ütl²cHë³Õ¢ÆòÌõkVT˜@„5ÛF›„…•L½‚(Â1# 3Û ëNÏCÁëâÀ7�c¿&_7ŒÔùùî|ÎpÝèé‡ÇNÃ�I8(Ô˜q -5{¿Æ’—QH :À–³•fÎlNv“[iÑ ¸lI☓‚B` »PÙzÊõ=uR5}‹ßœbT΃rÌ-²î}ÈÝEiCþ×'¡WZ‘ÓddV„õfæ=ÝÙ×ïìºæîó@¥m3ÉØLLÉÈ(†:t“é`Ã’E¢ä}½§‰çkˆ|SÜy"[DŸìóqËP# …êĶÃùž…¸Gâ>£2מ™³¨©b HÖø¸/ªeV+¡×x‚9Ò:ÀhB�•ûåág�?P*¥öf{Øf°˜“$š|� Å:Š9yóU>c«Á‰—LEÑkÖ> Ÿ…Ñbáœ/§ìk¾±±R¡ç»i€ìhÎÿÌåÖ*Ù‰9v+#åØîF¨âæÊ¡â.RKj2PY ¥K!Ò�#­ g& aÏ�•k§žU×»îôFÇ %aæ»úiòËì4ó¥èÏ:&fZ"o¯ÊzltÙ­«Ú –Ü…|´$v%æþ3„S²…ÛÀù×pÜœ@dŒ:ÿ`·¡äQã8¤4[ãD¦8ç�$¡¶Ù‡?ó‰½Ab0 £¶‘°Õ\RX)…^!áÌ|סŒ‘ÝmápT׿XÙÿ­ækQ’êÚY-³ž„Á©ò°=3ø•5s¤õÉTU^ª�fýÞIMò8ÿãµì½Æxi Thsž9L·;¥ óo+Ä‚ô2غÑñ+l´Õ\ÒÄ!6[`Ã+2WVmÆÑ­¤>ÍÖèp•ŲzG–V "§A[-—‹n‹²�K JIÏ¡ÙVëVÊeó4ç;ç©àšŒî‹¤C%4÷‚§œ~³ËI”�P¸Ô[˜)åR~ñ—<“2"Ômx ë .vj§èñ²ùÓç÷í/Ë€mÁS‰ô]çM†^VF­=�ñ”—5Úö+maÇÆÍÚow|ß(íóÀ�'xÑ–2´åUÌqa)ŸÚïÍÉ¡Žð¢j™¥½* ƒbuIXÇø".‡²VÍ«‡Ù¸Åv÷EÊÊ•ßó1à³Ì#S›jVD^>­Ù¿%~¡I°Í„¶B ÎYÖáÆzÊÌÿk}–AZ©ŽévÙ»m¥�›Ô°<µ˜4+Ä6Û¸¢Ú=Íœ‹éaŸæcæ©‚-ø †²y–�脯<Óà|Âþ-Ñ [ f¦Y‡]ç!ÍjþO Å׉÷¥‹*äÐ] ±6a;«©Ô¦[,ù–6÷«köÓÿšV »X€6Â;$Mð2sf³bÓί&ðestVþ³Ÿl¶}N¡F¡ÁÍÙš¼¬Rº7\sà*¤’Z�&û@ã±ð Awg`²{’^ÕÛâtIz/§í•Ò²_§–’=fÕ½,3 e¥`=³Ítï!ô« ¯DÞX^Ÿ7“…�-=öÛV!èçð_o•uvÆ=Zþ|lôÕñ…xÐÕ{¦Iž@opYª×,ÁZ+ÌCHiéËÖ÷:ÝTÃbƒn³i]¼{IT5FWUCx°¸jÅ’Ä› _ž@éK?9H—¼”Ž1s›Lè�Á¤Œ˜’EHS‰Ð3ßý ÕBÓX òóqº<Á–ÿ ©[Ë¥@ȧ¡2E5Ü>ÿ`Š4z~ £5²¦ê ”<Ÿ·ßŸ›±‰ Örƒ^*öçsnZ$_¹ùì¡ÐLÔ)oøÙÔ£óP-;€Xs˜³ÈKCã›{ ]Ñ2Æ(·_À€²=T|ÝÐPñ{3*¨”¶¤õ ß[ÈNTà2+*‡¨ä„•™ŠÓÜÛÏyÇ÷o¸X†�y�9¬û\köå0Ëaão]ΘN:Q,)žv7wvÂ�úXY‘œ|Ä—PÙïä§pLOÕ¦ß7vuå=÷ÆéÒÒÆ8ý:k¯«ZË$•—7<»ßˆ(ïôtV aø%!³7§•}�á-…ÇÝ ÜýFÒ¿µi ǰxœK/ÉŽÏI¬L-Š/ÎHÍɉÏÌKV°U’9¥)©ñ)™E©É%ùE™©Åêzêš\\™yÅ%‰@u©‰)©E@Ñô’l]°º`ô2ÔuŠK“€:­Ð%�ö'©¿]xœ�T=oÛ0Ýõ+è ±ì$N;p�N]2t(�¡š<Él(Rå‡]ýû)ÉQ�hÄñ»»wï¾Ø9ûEX”Mx­4ïÑUþ€Z—ËèçG)Ê—áß#:¯¬ÙBy·º§ßÑC+�Æ#ÁO_¿=7#Ü¢·†½Å|ÞÝ­>>®îF³ÄšG˜íÙý6%b>È]câ=QCyâÎ(Ó0�GÔ»MùrSZí™·+ìHJY\JÖ e0îšØ¢ ~1ʯž�­:”Ü%oõŒÎY·‹&z”U�H¦*57MäMªG””’ÚBy$vh$ÑçFÝV›T ÌËÛ¬¼)N¼'É„V$ä"x4Vƒñ‚â>õ5QÀ݃Ã_Q9”P[á€ÐÎàÑQø• ƒñß–gæ-Ô\{Ì)Ï´‚C¼ÊŸÝ–°�Nd‚Z оªp°w¯M%¬©US\R\‘6XÿSÛ êH .JßdyD8„ÐùízÝ�ž¸_ Û®Oíé´¦A=ÎWz­¼�è×�g�´6Á «ý¥gû{­ŸþR:J£V°¡D¨ÚÎ:º,Œ&ÛÛâÌ–¿§‰û¸—Ê-Je„Ž2A2i˜AéV;§Ž3È»i]Y.™å’sYBs‡lVÞx·æ•¥ná"ˆÑ™ Ç0Ša”�ŽË:…þ½79ÍôãoÞv}£jhpºñE)­Hp"ž¼ "„t©ú�{Þõ ÿ„¥Z‹Œ€/RO/…Fàš竚+ª®É×ÁÒ'u"�]¼KU“æ-î Ðð½F™™»žz—6=íHãxKƒèißÍf””C˜±��a´ôNu!‡Qù\‰è]:=[щÜÉÅ ÜB¹Nñù�¯ˆ¤IV]?ã_”ß³0r˜ÄÑÖuýè�K�æ5/SSÿó;õ‹´xœ�Í1‚0ÐÞSl%0ƒ°ÓƱó ùbœ�dÈÂèí �¡°�öïþ÷�ílžáÅ­7YIòö8RV9gÀ6›>&w6%)„ºÓ©¿Î½6Šæ6±÷F×<ÞBVìÜŒ+Wo„†Œ§ÓíJQé[XIüBÙÈOÕ/¥­t.xÔiâ7)]ÿCl\õŒõÃJ"Å‹™�½þS¿À¢c�ÝäQnRÐÀÒžÖ›ÓXg?o/©d¯xœ340031QÈM-ÎÏÓK*ÍÌIa8^õîñeßÕ2oU§0u.Ù¾ýûj%Cˆ²òœ"ÝœÄÊÔ"ÝâŒÔœÝÒ¼â’ĤœTÝ2C½ŠÜ†kúWå&\lr_ºƒ³xêaMé‚™‹ Z+RÒuS2sòÓaŠÛôKyõZs«”ýÝqæÇ^ÉѬã8c·]xœµSOoÛ ½ó)¸akÁÑ®‘*¹U{ˆ´UÓ¶ž¢È¢cVà4ýöc;uêD�Ör°øó~ï=ÿxˆ >“ITYXJ”b&«t«Ê$Ð�Æh^œà¬„_†3CvÉi=g®Ø#È£dIóÄ©V•à+ˆN€(M“–½[h À#`ªá4ÕÒú’MLJž¥Á’¼0ƒmͤĭ².8Âû¯Ùa'Ñ¢ƒýÑB qµMºuƒ‹‘ôüùyI)…÷² PTZJÅ�³N:ýdm._²ƒÃ–Y+´ÂRÓ§7C#|ñQÁ ¤çÏä˜u�è›ûå ¢ð+¥ Ró�`¸ÎTA¥`Ê5#ew~�â´Iæ.8ÚÖ­kZ·‚”ß\ÿº»¿þ~—ãH”ÕhAÄðvçwlÀaŠEŽZa#_ßÿxø�wÓ›‡õ·Ûâvý3_β¦Ñ¯efÏÌø�DýFÈ?ù=²ö~#öı‚ê’ý‡ÝŒ^ðÙkà ñ.›�Íûëï~ˆKa íÞãô]ž9•6ŒÐz \óÖœIÔ4e™ÇSŸëd¨‹©=vhÒ´sè9ï>ԢcÝvL3Áš†íŒ‘WµoÍL^›‰/Ðûv‚¿4›ñQ¾� xœ­\érÛH’þ裡ÕÛ}LGìD·Ý´DÙŒ•%¯D·£) ¢HÖ 8(@ýFûûd›W] (ÑÝI ++�/�ÊêwÿzXê^×ÖTåû£7Ã×GJ—³*7åâýÑ×éÙñ?�þõû³wëºjªYU¨2[é÷G›¢¾)²­®oìRÅM[Ú&»-ôÍý›£ßŸ)õnV­·µY,ü¤Ô‰û¨þïÕÛ×oþS�Öz£NõY[4Ïè™/º^‹|¨¦R­Õ…TjÜÌáß¹±MmnÛ~ÉÊ\YXY5Kcéu[Í›MVkúÉ4VåÕ¬]é²É$9¯jøe«Öm½®¬Vƪ¥®õíV-ê¬ltND6¦YVm£æÖ€=ß›\ç°FÖÀÿéÛê^+¿7UV�™Á×ëµÎjeJ¢‘WðˆÑ–X¡—o«fÉí¾LÏ;k¿"Ó¥­l»^WuŠI·6Ë �¨ U͉~ .«"E#eu«QÂ9ÒÍrPc,!­ÛÛÂÌL³% ÀT“™¥x  D«9Ñ÷rw²³k=3s3°DkÓ4ºa$î÷8TjÚËÝ*»ƒMTôr­×µ¶n£åßò.mkÀàL|2# @ωèš7Àú†å& *Ý+õ(³ðùȳ­p-‹"0«uaœEd5ÚÇvÈF:ý4V'—_þ¼š|ü4UŸ.ÏOÇW×êtr}r>š|V£ósõmtu5º˜NÆ×êÛdúI]�?Ž®NÕô^ž\•ë˳)<6¨ÉÅÉù×ÓÉÅGzuòùËùd|“¸œ�ÕÙå�ù“™ù2>™ŒÎ‘Ìéäj|2UðãÉåÅõø¿¿EøI�Ž>�>"3ô–ûHoû4š^_ÂÚW°Ñë¯çSÜËÙÕågu~yM콆}žŽ¦#|ýËÕ%° <û4&¯`Q"3ºP£“éäò߀ŧWðq .ÆÏ'Ç'c|ù’Þ˜^^Áƒ_¯å6³ÑÕäW¾ü:E —Hé\Œ™*i¥Üã+�ÁçQ>ÕN®½f†b¯"ÃÏp¢žg3-(ø½ ƒ€~K9bì{—k;«ÍšœÆ¶«UVoßÍj�5hÇDвÿ¢ñ=ë¼ Þ½kªµ� Ã,ÜfY‰̆øÍÀ½¥8 }Ë,ªº*ð!´)nÜÚCuÝΖ�Q¢‚ØÕQ8rL�»¬Ácv„X­Kð]x� žÏõÜàËß�s½†oÀ±sOê V@Ôõ1` .¿Ô´ ’ÌÊÙ²ªyy\Qç �ÓYU—"ŸL�\5` B�-ÈY[ÂW€–�³z>lfLfÁ÷‹á�¥`ôÀ5t © ¸…0"zQ¤w`jµ®JT ƒq¦në*ËUÙ®nAòÀ+mÁ°•ÁL¨‘Þ‰ŸlÖ°ü"½Ù‚^E&ô;Ñ»Zÿ»Õ¶S\èÆY"«Ñ[̣早ä%5¯«ʵCì.y^ùç i;!0`Œ�ïEÌlD–6DÖçe¨Òuˆºuf,*Yù\C×5¢1hºdù$\;ùÖsÖ* íF–vXæ-ûW›¥ƒ\f¸öm;Ÿ£¢šŒTçžðJ^a,ˉ5#OdEë`ð¢^­ÁA!±ðÏ Á˜˜‘Y·KU–å )6?75¨7‘ÓpV•s³hÁAg�fxr«žR © ر̜�¤2šƒ¡©YG6^Õíÿ€›JrÕÐ#¦QízÀ™í&]:ØÀ �²„Æd…ÈÉÇU”JGªCOƒSpkØùÆ€3AÐ/¶Iöm_ß+:,l–Å쮬6Âî€RB²ÃÓ³j#ÀFüxñ{jðÓ*[ljðϪ%ÀZƒÑ©‹¯lÑ ‘(Ò—ÌËo s'À§\Œ-Ø�­Åå£l[Æuj1·Ô!ǃ‡Á€*”Qël¾Øz(0Ž=" -3‚»¦Ø�Ô¹Ùe]ª,¨OªðÔ¼`eRˆ’/³z!ødò#Õl×ðW©77øÉcpG%dA$}ÕCÆ“Ðb{Mh¿î%Àü}÷ iò¸l‹âýQS·ý%22½aýÔ®üq…Ð@rIø�×5U/e¯Gsïr‘ ºs1+�:GõÝ+‰.t ƒ²!Dˆ` õV~B°…t&+Zøð:Z6‚NÍœ£�ĤL dL~#2šov¤C?( ÐôÂrÿ7 “�Ý}û£]JP=H ÅÓ#*fþñ°šÝg¦ ,BÒ8TŒK¬¢p xŽE,ãfsL \ ’7}&‡ðI–)9o ᱪùGˆ;ße]¬:›juŒ@ÁÑÞ«³Ü ÏÔœ»8î<5B°83#$ŠW¾ÕÍ.45&ε'$BÌu@ä3pIÏÒuˆÛ@ÞÓhó´©ÏmÑÈÅ ‚°%å© ëV'oŽÃ$ Œ]€XàЊžE3kKÉT÷ÀXŸñÝBlYÔ¼9G¿�²ˆ"{ï óû`¾ýÏTPVÛþ¹ô›ë«?¸Qÿ@¤1\8÷§�°Û¦®¶aè#øS…ÀeÊ-ô¸Å …€>\ I*‘˜<Ü­îLÞ†˜&᛬;\Ý$DCØty ÔP& Õ%}!Ô–Ô·hy3I…š%(l±tÕ’m ¾Ð‰ûàŠ€bÉ#1-‚RüàãÆÓõaˆk‡Vˆl¥+ÝdyÖd!Fy™ŽÊ¸òÃ�c^rU.Œq²;H‰Þz¤|øª/ö{Oæ‡ë.ÀÏ;ø^ª”ãÂÜ9qêòÞÔU¹¢ôL¾;Opx°î…8¬5ß©�‡õÞ ‰YÑZ¬¿CÂ3ðW½0åÀç6æÞ4Û—èÀyÕØ3®ëœýߥMTmydqõ–YéHCÉV%Ç�êkÕªÌ ?Bü0–2î¨ÄQá,s\¨`NK޶„t /Hä+~³îKÁW:+#¯ˆb€´í bÝ”¨Ö°§(Ñl¡Ì-0å†õÚ:yVØë5Jb‘IðHWõe|ÓÖ¸: ¬(ƒ•+nÒeT?D.9Ü)<�÷D)9Âu­�;95îAÊÄná ¤:a{ 6™¡â„ÊOÕ© È\€²zÛfÅÁÅ4Ô>7h—O@<Ɖ2>ëÛ¡;¥óõ#�q_—þ<.ªUO¾L‰ØHŠòdºeRnìŸZ ú¼ïÃH×ÕišßÙ×âªp2W[¨j¨¢yMBÕ†R: 6ƒn1CËK›ÉÄm Û¡j©ÉFù9„Ü+SJ‹8®¨°j�â«u‡UÜ@µ¦Õ]'HHåàÌ¥¥GK‚ùüFPžWÔ�¨¨T÷äÒÆÂP}À®¼$a¯Ñ$!#À3‰H<ר·>°±ºJ­ˆ6&o–IáÐW,5ú]ßc;Yý® ³øžê9u°‘ŠÈ× Àf¿M_ñZqøöá´¦C>%5Fzö�½!Zc³�× JT¥ûWÇ^èáb¨žCôyžXàóBÏ›ç/\ìïì(.©)œXìK†ÓŒ”/^ħ¥@YØL;¡/ãþÞÆHÎ/™æÕ·‘“âAŠפ˜szû^%VýX)ºmÇ­^ã§Û¥MOE+¿n>šß`4ÿ1; ™ÀBW�ÕÑ9Ïšã}er«ª¤°Ñl)c�$Åu» ½‘ç–òQÇÃ÷…M0—´cŒ½oL ÀêŽAGœbZدoäPB¼@".ë 1kZW~1ç©ò1†âñ5sUb ‡ôö8o ´Ÿ$ÒØ¤« ñÔ¨M&�\üÝ‘N,!<ã̬-²š}ƒý3¥›0.k :«aO ¼º‡¢z‘‰W½ /ޏðr†Í1íÁC�tó ùa€=äD<;>ØøÂ6‚¦�#|×uÕ/âïâ†r¸zà>²ƒŒ�8…mÏ‘IlYHªƒ}Ç�&9 “PYâ›TÕP¯=N‘)åÇŠFgþ ÄÓ[r#rëN9ÒcÛY�4‹!CY ¢ SE KR¹a¾F´±�²aÅ*k=/8ÞŽ—¹&¯tº·˜¡ã7ƒØê¢„£Ÿ”\/K3DÃ*ÇÌ–ú¶½îïŽÔ!ê‡FÓ„>³Á,¬ê œ†G6éú ³¬‡ ƒì5+Á®W”dã–q€¡£iXâÍë&5QÕ†ÅуYA†’÷õYP \0ÐTÅ B7ŽÌå,*Ðzbý°¼ì-Uu¨Ï=ÖR÷6€,¾éc‘ÙÃN @@ö@ü�)¯³5†\(<ªÙ�;ô ¼Â¦îám¦+-vpÿf¶ÃÀV=ZÁ½®«‘–¦tG aÚ×ÑãtÕŸ�)pJ,þ±ä�‹æCJ˜L ìiqþòh˜õ¥ˆ®€?À!X�ÌÐ7:dC:Î9feRƒ' ¢k9•¢Ø™B;•-°ð=�(@`¸¬”¦6OGÉë1%…\†|–NôÂg×OW65wt�£‰dЧØÓÝT‰éA¨Y­›¿Uy“ãÆÓCw>.üå`P¨õ‚z5z‘î‹:¤ŸÝá7t“LUÄQ6n*¨6Ü÷|ŽÂ5½ÆÜµà©<{ Ÿ!¤“4™R€!fµ…»†½4hÜ7VãŠÆªLMÊ�—÷7ã]ðÇVnb¤+=[f¥±«0ö‰ÿÀV²ÅŽÿ¸³ý¾8ææƒ]GäЧœ2 Øga]Zï‚Så~ê ,ÿÙ¥…_#ZèÁð h ¢èdˆ©âÑ& Ï`ó#j¼¨$0ºÕ�‘ñŸ½;e™lgŸÂ8éÀ†‰@²¦'…B… `ᣪ°„¢qsê:F¤Pô†Ï�1 ÒîQƒ}ùÃÀ²o0•7Öºö%ƒûO_V=T_­¯¸*öeLYé�XØÎmø,/eƒo>ð ä>ÀskÉWÜY_œø*–ã�T:±[ ÿ¥ºÅÃMoÓ£=j²íäýt–^`ûæCõÁŸ2 <©^c”àNÄãe~s-dÞ.È×Ss×;°ã°àAñ¤æMMÉ”K¨2d``¶4€gž L'ñ´w]­Ûu¨¢xõˆtrÚo=óTíã‰SA¸’Aã¤æ³O ‹y× ”s±˜¸�$ºÒO‡ÿáAâN �¡¢T8¡»•G#Æÿ«?wúéeþkì9qÙóäaM ¯«§fÖÜ…—núYîSù‚é`ÿ]—;3=nÊ;~ô¹•‡½ñ§3òjjJGwÇwîMF¤åí_õ[ ó²XC¬lœÏuÍÜÈUè´eÊûêŽæÚ:µ›ô}KÞITàó€3ÍEÄm{Í5Hzá Â{�"3N*av�™ÂÝdÚö· /.e\8 ¢;j`¬õý3®~™- æ<÷”5A„ÜɈLæÍ`ïÌlà]8ñïgGH'vÏY„âiëO˜;t£cà¾éuºk†Á,Þ ·ì´Ð¸�˜‡d^ÀNý”gSä+*±ÈÑXü`QgrÝ“�ã4Ù®!TzÝ7­ã&â‚çkhlIŽö(w’�ås�;É61Rìefäø�¶‹¤WHx<ǬV:7à@œÇ�¬.“ã�½=F®ðÀŠR¨ô‹™È n£8&{vôC]pwGòÈP`//v×çé„"aС£“˜7 \BÞôK¡}Å%¿_ÌcOÔL‡»dõ~Ü Ï‡sp¶­4b„~k0>ßyïúãa~¿:ô‡gBw[¹1Ë 6¼qÀý‚ÝF5I™Y?®l»X Ñ´5åKé¬ö½³±>)+¡ù.4ÕÝK#îî…„8ºSJIž\Å—©�f©7cÂ9dI6ý´‡ùp(`ýÜ6]º›ÖNMe>�é�…ÄÓζ¢? ?¯µ–[ÂxÉ—z·r_·w”Iï¯ øwˆÓ�GB͈õ‚b¬õWXº“upGŒše7\ôœ'¥‹Y™óÊÔÒ”|Å¢¤øÒ™SŽö Ùƒ®�,à‘º¶åóF,b„¡Øh1ÉE Çå^`ý ƒÅË8<®Gçq³×YA£×Ô8¸xø ä°€v"®b»Ë$3zé ÍZ¹é:™ ìNW©çÜ?“¹ª�@Ü?Ú„ =»ëX«`í;Jʼng0RÈ(¬ß{OtBB7ªÐãðYïgÿ˜ö—ýýy=2ú^°**ÈîžDªî�ÕÎkÎñË�UqÄÚî´�\Z”¨° m"ÌcÀzÜÀ17¾äºÆØ�¥ xóußLƒg<-]±wÒ¢0�Ÿ/§ù�¡:kkš7eì È©y &‡Ë‡1þˆXâÐ~5 Ö1”¦„.¶ÿoFà?’'Ò¢»ä¡�ŒQmq’|£²�‡¤TÇݳr—£Ü‚þÞ‹Wþ¿]�ŽÒ?y_ʯ€3Í}·¯òFÆMöܸ’Á�[ÓÌ�.òÃiýu—°Æž#ŽÝ¥öÞärplÊõ¾'/ç¼édYt� Gö%¾Zg­cšÕšuÍ'0 ÖjŒu„e®)Jc™´œ è”va…Šœd9 ¡ÞŰÊnЧ(�!YÁCžýš,âL¢$«Éž’â.{,à)Êó(-ž!»�(}†$éb ñ?òxµ‚,wæ’û‡e/¦�¤óåã"Ioáú±€4+`™Ü'E¼€"sNƒ¹$^‘Áû8ŸßEi]'ˤxö­½IŠ”lßd9DðåE2\F9<<æÙ*†(]@š¥Iz“'ém|§Å I!Í þ5N XÝEËå˜hôXÜe9 óìá9Onï ¸Ë–‹8_Áu Ë$º^ÆÞ_ú óe”ÜOaÝG·fYqûD騞îbzM~£¢y‘d)å4ÏÒ"�æÅŠ,/ÆëOÉ*žB”'«$½õyæÙý¨ÆÙ KRº›ÆÞÕÿ´MYîžWñiL‹8Z&éíŠ _˜']‘=siQoX‰G4¶k�˜ìˆ «\VhJÍ;G¦o[¦÷W“R#³þ¢�‘‹U lƒ¬ênQ˜`Å�Ç[wpˆ‡À×N$²µPk&@ùécaê‚-«@cÍ�E‚¾3`ïs>ï •@²7 �N|¤|‹`U0ÆäiÈaF|Ä¥j;e¸UZ¶�Ö²7C¤-7¢GY"4jçF8Ü;�`%‘yÅM'Ø+šX—9+©høèô‰ibô¿8ï£èø¬ý軈¸©he¯5J+öM[4öP©®agpÍÊßwLW.fùZ iŠ¬Ñ¸¼Ö¬"f±ªFW WÍ�>HZ´FÓçÉú¨m.°îÛîÈ —8*™W%…óY)‰°ÞƒC“Ó1�¸ƒ–ý¦ôh:;¾Z”ô‚€MÐ>Â¥7À%ʾ ÈF­•Áw‰Òê}øÄ„FVí_zƒÕ¶Lôx5ù< GÿéäûS’ ÁÔ[‚5¢$IsFÃ#ñ0Yaœ.(È!`�ÿêÑØXE‚ªö°ûnxìK{œÇ‡Ãî¹2y‡Ð2ÉjÔ!Øñ>ÀŸ5îð»yôÇgPÄ*…†d Øfƒ¥—¥AÉOr × Ô(Q{"h´êë¸õíz×0ÿ*äÿq9j´/?ߨÁXõP€w‰ÏÝ¢–�b‡�Ò.±šoQ¸VˆîýùÑàqt»U¡B˸0³³³æ#uh{cA3nh€c‚*ð ”‚£´˜µØv´qœý²j{ay'þ¬ìC솵§XýJí.™®C¥y5`Nâî…žÆtß®º\Ü FBíß�]¼k?=Œ§ÿ\ªþw�ú ÑÇàÆuG–\¦÷ÃkËý`ŸjË ;2|¯IºÜÏ¥c2{*¶a¤$;�FÈÙ K8ÇY= k<@i aÇe¥vŸŽeë�`Á¹Á–'´/#yø4ƒÄÓè •Ñ�{> }z7×ÁæKÕ¢.QÛ#uôîÔÈWÊ›ð8v²Òiµå•Ó·*3áêchOîB¡;Cפíšò?TÛQÒD¿RS“: .ñ³@‹½c0kY{F»5Ex:% Êÿ�Ÿ¿ÍËïIÙup,öIóF3kÜ ¹[a';îøô-.M£zQA/Y×Ñ1BBiÍ÷ó7Á¯Uß(OËôïC]˜�Ó+w1ã…ƒÔÿ‰? klØ–+=ƒ{z1n¥vßñ’ ±mQ€œê£hýØôBì œ QñÚ·¨7¨áœÏp¥ �ôÓPV�¦oËŒ[å3lLoáå‰Ò8 ÞI¤hI;ÒÔN£~”�#0šàôƒ·�@úéî¼Uó’ÙpA{0nÝɰú’=./î3× v'~޵Ší¡l�£û[ƒ¼tÛ¥ó ¾E7š_óD»PípÚívB AöÒí²7�0[R²ÓÚe”Jš¾u ù~0öò;áHôñ’ ­:?©Dg6i ¿ÎÕÈÒž§Ç wD펰Hü7 ¿‘ÏË‹á‡Ã—³?Ra/~±IxœeTMoÛ0 ½÷WØa ÐØèµÀi¤ÃÒnH?.A±È6ckÑD)©ÿýhÙI“íf‘ùÞã“¿Àâå,E‹žT <*„à¼-‘èê&ƒG±C èZ¿î9• ”HÊF˜ ¶ÞjЂúîÖÌ#çó²Árgc€Â S6°µB# ½Dº…M-»CÕ¤8"˜L³»M~™=Ou3~1�Ä@˜ ¢«x Ôa7q^î¹s–9•^º@y¥ª2×n`Dˆ°>¬æÓûÇy¦«÷Ñÿ±q‚]aRÑ8±³Z32aÚÿ`O³™!+–F²"-4‚UÓ¢ÂK9»ªNB'ˆà CoBÕ^šŠá3±åóï·ér±úþtÿíŒ4Lf�X¤«›Œ“ “ Nñ<ámäï԰äºO*Ô6©’®çø!´SHùe+SŠÀ›bn¼(S'˜"«E�åXK"¬’wQ;Ø£'i Hk�dM–†¼�ΩúµßÒºdÌܯ�J†6Éþoä¢|ö0}ZÌ—?©ôü4†Ñz!ÃC, uðø>jBpt›çlž&Çóƒ>ΘRgö|¸���  ·QAƒ/WÌ¢Fj 1¹† ˜)Û@Ùúh…îö‹¨�v<8²gçf³Á»Ü“cC6�°^Ö¬àyÕ*�Ðèk<½Íá)IÖû|p¯¦âW¼êk¨ÛD·¸A�Î^÷†cÔØÈJ¶ŠjÅLœCÖ¢b†J–;�!‰�¾çU�¿‹Ì Ï;A1B#ŒŽÛ7Qè¯ÁXØó·òcÜKd]{Dt’«W¡P’™ý®þ‰°’ -xœ340031QH,ÈÔKf°ž;-µä¬‘V~½ÁÕ…+JT/ X¦B$——äçêg¤æäè—¥%&§uô_R2ÒKÍý}ôU—¨BóêŒ}1øtd04Þ(¯ÍY–¤|\RlN»¾°âÂæÖB¨Žô’lÝ‚¢Ì2ÝÄääÔâb ñkj®«/-2Ñêg()Ô]Ýûæò Á;8g0fQ0�Îñ½ŒSñãB†ë¢«�Žg ).O¬ÌIÌKšzöäÂ㺱ÆW¹.¾Éó6Ïìü¹‹Â †z�53ÿ_Údûô–OLà,ýÏ]:¡ s+S‹�âqþò’�I6ÁéÏòÌÿÖæ{5 «Ò †GÙšyJªª_8œ“’ªýð\ ª47µ8?O/©43'…!·ôú̾y�™–nÌ·Ö•¾pž.6ª¬83· 'U79?¯,µ¨83?VÆåÕÕùCÂ×ö—¸É²ñÚ?Œ|z·ú †}‹4=�.]buÒ“EmÚŞŹêû¡ê+RÒu ò J �u:Ó%–»fì+×þ”¦þœüÉ?OŽŽ;wÿ$4Å] WÞ�oé÷†ã»×†eùýÛùØùÖâ‹ÅßãËîõ$ü;|õ;ó5æ<¢$.¹cÂË…ÜÁ¸Ë7žØŽÞ]õo|Óõïô{ 7ƒß7ݺ…#$ÎǸ@*›�NÈ2Jqöñ±š]ÊåR·\㣺+cæ„ûØ RŸMü˜ÒPÅýXP¶”²L•XüiÄÇXrŽŽáÝ9 o-«•¤EÂSðh—§gC÷áÉȰ],±b]EIÎã¿á¤ÞùÓ8ø¸…³;Þ–m~Õ�Š ´a«Á£âZȦ€%‡Ýlw�(ê±…|UÎ6…£«ôáN ÂIfT¬ÄÔϲñI$Y›†¸2M-oޱ§ †+“ÉcÂI–Z$§l›CVcüªUî»Ì\ÅàþÞÐ6õƒ»i ©în!`ÇÄ6‰GŠò@å…5_ΊpýKM c‚% ËW¯›gQZ”LÐòÆ7õo™ä�P6€›6Ñ_E¸+šÐ3î!W#(oì64ÉAwù”€Ï\°Ï s²Vò¢“!µ°Oà#¿]É[‰ò‘³°³‡ìvÞ»¥C}*×8uÕ¤±=DãAI��º—(ùÛ˜òl÷¥•“Íb‘RÊóU‰/\Ø< �ÚOЋ% ŒåáV™M(A¾ðÀ¶bh.i_ñä¡"¼/áÔË{åbÑ<´ÎÈ#ãIã·\A­0ûå°VöV+Õê^»\íé·/W;šva¥7t»<‚ìwµ>ÝlÕ¾ ûqíoDwf¹îÀÙI"‹‚Lñ#S”à6\Ëý¶Šåg¢}ÑZ©:ýt°~ t »hú¤-?ï‰ô¤¥¯L_‘£g¡§ºìj?‡]ÕÊ0g }FD]Sû¸{vŽ�sRÛ Ø¥†4 /  :Äêú¨…‘ÁO™Ðqeý¡IXx+cFnü¬Û(ã·/­sMU8%¨]è5Yµ±öš¥©þé.ûݳG/]¯ÚêÔœÇêd×(¦ða�Ÿävo_GÀ5%3-•�×?½·öjq9È6Ñ�8˜µi¦¼ �s¢¡|øZÝ ›‹ÇQ_¼}=\Ô䔡®�m{Œ3ý8}M¹eÓç °é~$áÀ§sø?ÿ¬°Áo;•Òè_¾1ôœ{x;¸0œê™½YÇ©â?Òç[K I¦˜ú÷¨™Å)dÓ|Á¾þT\pШâ2REbÓ³Öïäð5Õûá *7#ô€ré_A´ì‘ú_yÜüIC*µjööÅZ‡]mn‹br÷öÚ¬¤*Ð&)‘¥lP3ŸÆòiÍ�¥åä³jíƒXK¯nòÀDî$üÁ:z¾>ÝÒOwÁ·Õ¦ºs’Èç²)MH�²G–®kÔælX1-¿ï©úxwžskÈ’»3�UÖæg´•g—Ý"¨üF`M_Rø)Acõjþi`Ž5èV½.h2# Ñ6ÎC*_­ˆP¹Ç! ïjUÊÐ÷:ÝøÈií4úƒßwúÓ�qaëNߨ}lT_zÖ­k~6à×–C{hì=Ò>YÅRWŽ 7ùì�D§ žC‹ÿŒ€�M¢n£&Ã�Rzšõ†ž'iìi|Ë/­„@&x £,\g©ÆŸB­¦3ùøŒÉïáNĸ{‰9~_S˜¼]V¾ÛŸNØÁ]ŸE>tÜYã�‡õÅAKùÛ=[Õ§tÈjxýóvÚT•;�+BÚðÜ«LhÛnlYšù¬A`/×Ä*D4 9MÖòõ#®CÀçòk^íd½Þ›í\kj¼ñõO5ãú›Ÿe­…$T,h€9E\4o×Mo®˜¾éCàé§KA�ÂbÆ�•ÖV«°‡ˆ{ÍÍ”¿@õ›Q½àFj ˆ´ý ¸ª»´xœåYÝS*É÷¯8kV è½[IÖ\«PPÉ傸æ>M 3 ô:LOM²${ÿ÷üNwÏ8ˆî&UIÅÒºÏ÷éóÕ}~J“…Ô$âL¦‚f2„¯‘ D¬EH«8)}éMŽè¿t­’M*狌~øðëd!=Ê8©¹éRj-UÌ”"Ó ÍS a“f©¤f,üt.š”)òã %"Õ@PÓÌ—±ŒçäSV ™±|ZͲµ ý8$_kHô(TÁj áýŒù±øšN²… ã±Ã8n&¡ð#’1ñ^¾Ek™-Ô*£Tè,•Óh(ˆV!Ë�oGr)F7úk&ºÒЀålÒR…rÆÿ…Q+YM#©M %“ž®2,j^4†m²ç*%-¢ˆ)HÈmt-¥30Ì%aƒfÎD†ïz¡–ÛšÀD³Uƒ¥08¡‚É ÇŸE�ñ ƒÏT©5«¨8”¬‘þѹm‚}ªž…QÈú8Väµr°’ÒµnK/|(0Îj`û�R–Ag𾄕¦»ºžåBÜui<¼™<¶G]ê�é~4ü©×évè¸=Æ÷ã&=ö&wÇ bÔL¾Òð†Úƒ¯ô¹7è4©û÷ûQw<¦áˆz_îû½.ÖzƒëþC§7¸¥+à †ê÷p At24 ©^wÌľtG×wøÚ¾êõ{“¯MºéMLóDÛtßMz×ýöˆîF÷Ãqì; ;è nFàÒýÒLÎÀkÔý _h|×î÷ «ö¤ù®‡÷_G½Û» Ý û�.¯º�¬}ÕïZVPêºßî}iR§ý¥}Û5XCP0'Ýã]×,�_¿×“ÞpÀj\“¾6¡åhR >öÆÝ&µG½1äf4y6'0††ð]K…M½í€ð÷‡q·”¥Óm÷AkÌÈU`véùÑÑÜÁ ã`¥3µlá„FQK¯Ò™ˆ³Åqbž=µÖþ&Ây{¹‘¤ò¹å�К7ËÝ¿b÷g‹ËêbˆÅ°~±àqyt¤9°Ž�™¤D§VPÏê9A½'±¡OtìP½:˜ã &—®oÞµÙóöØîÞC~¤¬£~n³'$ÍP­é¢{kóùÂl½D¥ÓD%«ÄK)qf¡nûH,ùF°�Qˆ½‹£o…JÏJ†Gµº¨Ø1ôBN|jC'uL‘›f '/n]>Ë4[ùQër&c?’ÿtb€À”�äÌ~o]&VY|¨Þ K‹æÂÞ$ú“cÕœp´ð‘g‹,™¼^°CBŽB!OM…ˆ)ˆÀBB�YÅK?i7. .½&ž.~E�b¹ä°�žƒçþ¹¹‡:�y©X"µž¼Þ$gM¦üÍÚt½à’\oÖf-Äû¦­;0†î!SæÌ[—¡Ÿù%Á-ä=ÍÁC¿_qðkv­�ÏñÓS6 Ò¿åçu/mùuî™Nh[RÝÃÒ·w…t*œ?MŠ çYbmþ7k�V�ò9°3´žœy3_F 2ùì=ö:·ÝÉ Ã•y«AŸ>‘å‘§‚Û°ÌoaÇ1H éñŸ[#›Â<»R–,rþŒÅ$<[1<4•Ùœã—Ò�ý4ÄB_Wdxg§mÜ÷¹±óׅu`„òó§:?y¢=ö¯ç[‚ç|ÏÏ©£^¢«õ5ÏjªEúŒ¬¶^ Cø´äT•ø±ˆL‚ô1´,ý92 <ÍS…ù§A·�ϰ'R©Ÿe~°Öt5›a.ú$†Ù‡Ä]†´›³åî¶�]#šyA.“H”㠚ຸPë“zõC Æ¡H7ÎèV>sN¯Rõ)U‘�aÈÏÙ:Á¬ Ü�C%JR•©@Eô,UäÛ)GcŠ¥BñŠÛxƒ|fô…1âïÑ©%fž´˜µ£ŽPZúS)r¾9Üp¨;v±Tлi¿6%>C Ò²ª·X—‡*f…hÓʉ¶_W¡ØÙg¶8–Ñæ4©œ�¦©Mú€ßâšÃì¯}ɾò+ÝßLÎW)Å›vܽ¨kzLóZ“¶NZ‘HÊbRàè_¹­iêÎn3˜!“?Í?lŒ[Í#&2|«{¡˜ù«¼ŠÈÅØþ—?�‘Ÿf^&—Â[Ê U¦n2ÂRaÊS± Ìæ‰ÃÚêHö›²Ú””Ry&¤0Ôn©ÒTpYÇ"ÓåK9ÿH?˜Ÿ*/›Ê&hø�xŒw¢šÈrâÒY!oµ¹É+ˆK�¦ûÙΊ.½îàL‘žÊ¥o®à£Zì/¶2í+ÍúI¡¬¾F>Ërw¸³Ú¸à˜¯¦A„}ªvv8�6ÑÎdª³j`”­ÛÜ‹ÅúC�\®snÒÇÆÅKÔj�(ê‹�mö‹ì`^ÿn§Lå�UuHµ.„”¹ý0Éͦ7ã,wrë ¯þ†w éõÆoßHø:–qà«'·;� T&g›F=£óZ¡ –óØ]Ì×<…štì:Ácœ!ïºÝï_µ¯?ÓÉÛÚÇÆÖ,r€üñ>&îþ¸ÐZjþ±Z—CÇ[{éz v6Äy]•F±:¦®?ÜçlžïªË¹6TÜTtTY)ÿÜþ}ç~ïÙ¶mnE¬úüeÜUÆüÁ›„"¼ŸqžŠ·¹ŽÕpÖtjìM.¯gUîÿóÇÆn—$cø_â` /µ%,3ŠmÜL'Ö8àhóJõ?ïhúõת•æy/™ˆ˜Ÿ$ E+Gc�Ôÿî‘«”¶ÒªÖ³>ÔÞ¾êP Ülò$œÒÉÛâJ£ë«o{/ÊR³sÃ)3•ê­=3IØ)3ð¯ 3ñ 4µ�Z!ä6<è°è>9WÚF9ã0â7šûŽ‹“ìŒ*}/,iKŠQµzs`eOt}ÛYÛ;/W©åPå¡^ÈPâiõ%< K]cŠÓTÌOæ0› Hóˆ†zkžuoT|aÇÛ@êüBý—pnqOëë¨9Úû[éßpµ·÷.þ·«¨¢·ˆy•¥ßI8QÚ<ë!ûŸ–Ÿ«éÏ]‰Îà±Cw¡Ö�}×Ë÷Ö—ah‡|½uÛ¼DÈÉ„Ï?æ/�WßHDZTè½ñ—þ7Ë[�³²ÿ6™éÕô5GÖu(/ýE3çÿ!ö—‰6ï ö»'T¾Tߎ 3Zž4L>¬?Ùnø<”yÞ“1V×Âzü>¼ï"É]™l‹T «×<¿�eŸM*÷/ô¬{óØjî mEÌ;ñþÛž×è=ïk{ý]óÔô­î:й§x¤ùæ±¶ÿxœÅWÛnã6}÷W ²M u³ècS, ØJ,T¶ IÞ4O-QYHj½nÑï %ÇrÖNÒ¢@�\,’sæÌ•£ë!$…ÐÀ+#‡\”ð±)¯4Ï ©2®`ê'âŒd½Sb]øéÓOŸ –u!8<ˆÊpÕ™sµZ YRÁ_í`­ÉÈç sH ¦ÖÜ#�U;¨¹Ò( W†‰JTk`�¢*:iˆŸ–¹Ù2dȪ ˜Ö2 ñ “i³AòÌ�>¢¯áÒ.âNââÊ*É8+AT@{û-Ø SÈÆ€âÚ(‘†ƒ‡Ò²ÉˆÃ~»Ñi qk¿&ÐF£ÄÓ��ÌDNÿ¹5«nV¥Ð…™ èUcpQÓ¢u¬Cv\Kš—%!ämm=°³gHKM5�‹¬Þm!7Ç– ‹òFU¨’[™L¢Ë¬Æßyjh…Žç²,å–LKe• ²HÿÜ…-Á}¶’_¹5¨�q% òmyPêCh»-]04`Å;¯¡rô1ëÙ¤ˆƒ6}�¨¥²J_ÚúqObâAÞ%nä�Ã< ¿øco nŒÏ<øÉ$\$€'"w–&4‚&¡UØAù^L`S/Mðѽõ?ytàÎOf„y‡ .ÌÝ(ñG‹À�`¾ˆæaì¡ú1ÂÎüÙ]„Z¼©7K>¢V\ï >@²üFáü1òï' LÂ`ìáâ­‡ÌÜÛÀkU¡Q£Àõ§ŒÝ©{ïY©Q"{¬c÷0ñìêsñg”øáŒÌ…³$ÂG­Œ’gÑ?öp#?&‡ÜE!“;Q"´ (7óZrõqDð=/bïÀeì¹bÅ$Ü?L!½ >ˆ[H£Eœ„Óe<ñ‚`/¢;wä-'ƒ¸'*~nÅÛ¼‚_Öæé?Ÿû‹.fvq€uÖ`ªoË¥nTÎR~³_ú–­O®ÕRÛàêf00»šÑn9j´‘›¸À�[Yø~éæbs%¾b¯:!Ýí¼ä‹P¦Á:»sóì€óÒƒ?€Ÿëka‰R÷l5ªDì ™Üþ } Z¬+TÖ©fþÙKÇØË RÅÉ.+Ív%u iÛ�†Šó¬mDV· - ñUŠ .‡¸u—'ü<Ô v¾‹( ߯ÐâŽÏ!¨¥­U+žKÕòêÝ÷æéÁÚH½²©P9ú¼OÇ®½F¨§ñA`‹k4ú´ÜYµ-â ~¯ˆÉ³Ûò¦²× ˆ[&HÜPtÝÈF¥¼õy Eèb<âƒuÁJñ¥£¤®›†—Ã57íëN¶âo}¾¯#ZèeoéCÊ�Ðöß{^ޏÁ;M[o–r-ÐѰærÃ�Ú퓳Í[¸äßlKÐt#á‚¶—–nÒâÊ¢ÝgOf&«Ö8ാ闄ø†�ÿzµºº²:_´xÙb‚¿î+õôé}ŸÖû¶@çÖ+)KŽYŶ =T­—8Ñ]ºDÔ\¬Å[~è¯E/!©v]å­Ò ¡¥AFH¥éøXRJó²FaÇÍÀ&ajI.5±Ü‡Ùê?ã.^æN¯Ð†ØK–m€®Z†þQÐ †a‹¾×í`qPÐA˜à¥æûçÙ"�þãŒQãXFÓ%F™LÅñ2åyƒÆN;ðXRäO™—gy÷6N:„ ç]Óù NqFkhPM冻n¬¬ø7ƒ]€ºpÅÒ§~” êÔ¢-†#—á¬×Å1m”ÂY3 `8Ç1ÊúŽ9�²¸phŸ¯˜š’^väÞ²Æ5†oêvîDú©$6žQÆÈü/ÆàU€ßiÌ‚š¸íŠÛo=í…qÌö½-À[ éÖÐ5†??·ìö5'Ëô‘3»)_, µ?{R^öþáiBˆÖaŸ¯ÝwuðãÏ),«æß€ý‡÷ÌYàó·ŽM¶©µ}�i½Û½ûðÊÖd/Tö¾¿¼z%ì‰73`Ì[nÝë²aOV—²/ɬAQ|ñLm[ß�SÍöy¤ÉHîp†|àør—Û9ïôXý7³^Q»´Äxœ½ZmsÚHþίèõU%@Nå>ÜÖú¼Ud[»¼Èëä“J ÒEh(id›Úõßî‘„F/`àîB9‰Ñt?ý:Ý=£\·Áp½X ¼��ãù ð«ï­X1âÀf!Œu£mü�ßîBoí øüéó'Xð­ë1xòÁ”dÆÂ�EÉe![î`ZHbwÀ îÀʵÂ5ë€à`;ز0B¾–xÁ,X¡(¢¤_Äñb¡†V`ƒE|åYˆ6_ÅTÞ$�Ô� )\W‹”ãª%…ØÌòÁ €Ö²%xñ„Ëc!‹Dè­£ƒD+?¶I‡lÙ÷6^*�Ø¥ý�ÆZ@zv`ÃmÏ¡™4k/}/r;`{½Œ>Œè¡tl‡ì¸æ!DÌ÷ ÁC½¥­¹v’†¤lÉ¡"u‘”ûâòMÑt‘‡Šd’Çæè2)ñ?l%è ‘;Ü÷ù ™¶â�í‘EÑ/iØ \·–ü™Iƒ’\ ¾‰…mÚt)r-4`ÉR¯¡pô±¥Ø’‘Àè{€-¥Ð²­½L‰ Ó;ã©?×@_Àl>ýCjC¸ê/ðûUžtãaúhRÌûãLï ?ù¿ë“a´¯³¹¶XÀtúx6Ò5|¦O£Ç¡>¹‡/È7™0Ò1¡Ô˜J�)”®-l¬ÍøµÿE鯷Ü鯄0ï´³þÜÐ�£þf�óÙt¡¡ø!ÂNôÉÝ¥hcmbôP*>íü‹‡þh$EõQû¹Ôo0�}›ë÷îÀ°?îßk’kŠ(sI–j÷ô ÉG(¯�?CŸNÈŒÁtbÌñk­œ{Ö'}¡u ?×ä�»ùáÉ�È1• È7Ñru1"HBßZ®ËPë�kAÌ*1…ôºÑøGšpµß»ÛÐ{îZ«‹¢ž{UZ|±v>¦ZqáÕ^w·|o»Q:֊Ѳ²ž2uW¾‡e@.ŠÝ–ÙÌÁšoÀœñH&ú˜áf¶60j:YhŽÑwÓ¡©MÇ�7(RÞìqž¹gC[»- Ê+÷ö÷§D ¬ƒ6Ñ^)ÍY!§3¬¥Ï„Á¹?´„uSð’ýÝ|‘0¦·Ùú&y¬äªœâè¢dOÝsåÊDzzˆ.Cá²Ð›Nhm˜i£î'2œ@1K¼C"x¼rOÁÒ¿EÊœôßkß[v?÷>]Ó/=÷×FãúZÖ?ŠWVšœ8� Rè9EPºLG�}Œ°è?3ÔÊ ©ÆndÚ@IüJní¹¿üëçÖÇb&4Ûcd4øÙîPL š{dh'ÌÌÓã,á‘ •Æ ÖØ¹Û¤ÈûœÈsZÏžØ1˜V°ryx_š9gpö%éú2‚„ÍtéËûÌH¥êiÚ¯ðìZ¸Í"êâ+(z”� ÿÂŽu “ÇÑ(çUÕP×,I^Sà Q)2ì}j\sê–¬B• —¸o¡´×¥˜’ö¦ˆJIšœ½ÊIÄmÖµêäI–#b;Ûv‡µ¯£:SÚTˆçàÓ:ôÖ>B&pð�Zª„ù«�×øû¦ñÖhĘ9ÿülŠÚàú8sFKUHà 9q�U ÚT»² f�RݧÜa‰’ “Zø²�¨ë-ZÀÄ#Þ=Ö¸ÿµ™|é@mõ”Ê~g»%·B›~Ùk«’¶Z)p.t–”9jHmPëvÈZI¡8ÜÜŠ°$ææ4í+m"u9Ž {õUš\ý+re7Õ©D{eOÔ1È$hËæ‘¬¯MÙ’.az‰ú8`7?xÇœ*èxQv$A¾¸trjV‘öš"Sé IÖOÚ­²•çò»Ž*uÀ\/ó4Øû*ÅM�”¢D í�fÒ7Ë&Þ€�?Èèw%sZðç^ãòL“ú:ï¿ ›-Çœ*“’tÐ>’tå†~ ÷²ÖÍé^½$ýø-ñoZRJYDò^‘låÚb‚Gâeâ �yÔÁS¢wúUÅŸÔÇ¥â–v¬Ø RÁï|i³lc‘”½RÉG×�è³÷;k^™óždƒ|rvD*6S’Â_r¿R·q®Œ“óäŠo袂ÎÇÂT]Ñ*¬ ’"� ›\žå¥ºr‹4ïÍé—ßð°VP®#Gá®%„…åÊîJ7Y»Ú×ã ]�+t†²ÒXÊ×¼†ä+”:6ñ(»ü×z»*¶U @’dùö§Y_ÍâÂp)÷>C�$—Ìÿéçòc@ú¹ô4Pe?óP�\~6H?—±î²"²6ñDÌB¡æHõ„�fÍÑCDóô˜žº3CuvhÎ ÃùÞ¯Ï該 ÛÞ<·Jöô Žß,\æûé%N>¢53½cÚwŒ²F|ë³gæ•9¤ga2¬aUç¨zä’™Q=š2_‰�…Š+òá‡0Y•õwÐ�û-Ç‘*¯¤ãKTÍ­JÅ÷ˆ�bM^âû~sžëÒå°Ío�S”Rû™™4ÓsLÇò0\”êŠ�_íõŒ®4³ GéCÌ8 böÃï�ó“NÈ¢ØÙý.ký»÷+\{�é3§r¡@í΀Âùá:S¸ÕA$y¬Ú݃Iuº€$ïûà2IP± }þ_šPqÞK.%|� òxžå î¡õ’sŸYAíÓ–ô¿?Lº°y·5ý€¤Wm¨ÔΊ7ÊÚ¼ú×_§»5ZÆŽÃB3»O*"ʾþ7-KôεŸxœ�VÁnÛ8½û+Éa“À�‹ö²è.Pl%êX†¤Ô›“@K”Å�, "׿o(9q»i ,àÄ!9óÞ›Î0“+JJeHÖVµ’ UI²R™¬�Ì©«sÙÒ}�Œè šêæÐªmiéÃûï)ÖM©$­Ume;˜¬d»SÆ(]3R)[¹9ж0ÉÇT´R’.(+E»•c²šD} F¶zc…ªU½%A¨ØÒ²>£ »P(꜄1:Sx”ë¬ÛA¼°ÌÇò ]ØRÒY¹FÊã?2³¼Ãæ…®*½çÐ2]çŠ#2Ÿ†²%8ý,]@}�km¡·×ÁUh^K;™R €�²räXœÄÔ²cQ}…4ºu¤ßÇz}1÷)o“µùÄ´ŠÂ/ÁÌŸÑ™c}6¦u�ÌÇ„`yËä‘Â[ò–�ô9XÎÆäÿ½Šü8¦0¢à~µ|ìËéâa,ïè~Ë0¡E€ Ð$t„TàÇ vïGÓ9–ÞM°’Ç1ÝÉ’1oêÑÊ‹’`ú°ð"Z=D«0öA?ì2XÞF`ñïýer Vì‘ÿ ŠçÞbᨼ¨�œ¾i¸zŒ‚»yBóp1ó±yãC™w³ð{*5]xÁý˜fÞ½wç;¯(‘3Ô­ç¾ÛŸ‡Ï4 Â%‡1 —I„åQFÉ‹ë:ˆý1yQsBn£ðœNx„~K¿GáT[˜ðú!ö_µÌ|o¬˜�O�¹¤“Ñè\!ÝÍ>§kÔ(\§soú9��α­jùÆ œúÛDnó§ ~®Ë¿N7-6íw›Æ¢g-ï�&“~¨éº:P) .0É¢p�À] Zƒ¿Ô·Öþf(Ã}•9{ƒPlxˆ?E§äzŸ–"{J·Ò¦�6®gèâ’öº}âFªO¾g­rçÒ´ê9Y&�I1È,]ÜåOk‡CWdÀ¼ü錤EÓ§ªH-7JѢØÕ`ÉgŒ9vš"4\Çã¹@5ºéÃvc\«š.+G’ññCjé•´��²hcÓ�‹%ÆRXº2øý†´ã¹-ñ+{‹¾g½½EÊ&)3ü<'±´=k¿‹aO;Ñ4`»¾ù9�n¦¢2O�Õ;Œg®ã�ç�Ç Éx©¡Êejº¶Z——®ÚÅ‘ˆ˜gGÔžÃSã†ðôêÝ0a…B¶ °ÒÚÆ|šL¶x!ºÍu¦w“ýn¿ç[ù®پà ®ª †e'Íäãï—ÃyÍ‹9æåÝÏ’r÷]R¶Úl{  O�˜ â;5p¥’_]oôž˜ÔŒÇ5ŠÐ ¢Þâ©?¹å¯—Co9•)s@•}QµW9ŒEÓRfO†T�r…ää!üXí«—ló%9Q–ãÿŒÌV‡Ñv£u%Eýæ mdÍOp:Àý:9z%V¹-Íh‹&8…ïï!8z³ÔêæÇпrÞhk]†þ§%‹Ÿö+ïþ�þq^Î9�qzþ3_ÿ‘Ó`|º‹xœÕioÛÈõ»~Å« l%ƒ¶“ì·¦^€–h™�, Å(EŽ$Ö‡àaÕ»Èï{Ã!9<䣴(á æðÝ÷›äò ¬½Ÿ S?f°õøø. æAz,†;ÓêÁþÀˆGϱ¿Û§ðéç°âÑÞg°öÔÅdÁ⃟$>‰ÒžÅló »ØAOƒmÌð-¸{'Þ1 RNø ‹Dà›ÔñC?Ü�.²"È”äKø6=:(¡zà$ w}é�ÇÝì€Â;)ñ#ñ¤{ý•Äè�9ø!зâýtϳb–¤±ï �Ü óH†âsà|É�Ð…þ ÍÔ€äÔàÀ=K3¡V”m?ÙkàùDz“¥x˜Ð¡0¬Fz\òDÁG¹…®•t†¸DdÐTšHð=îù¡® šh›Å!²dÇãh2ÁñŸÌMé„À·<ø‘Tsyèù¤QòWé6 ¿;þÄ„B¹�Cž¢¼¹ä…¨r­ü”ìT`äÕ�9ÚØQtŠI†$Eïû耈ǂiS׋Bˆ[Vók­/ 0W°Xοšcc }}…ï} Ö¦u;¿·!–úÌz€ù è³øbÎÆßKcµ‚ùÌ»ÅÔ4ðÌœ�¦÷cs6�kÄ›Í-˜šÐHÔš †’”i¬ˆØ�±Ýâ«~mNMëAƒÓšÍ$ªÃB_Zæè~ª/aq¿\ÌW²#Ù™9»Y"ãΘYÈÏÀøŠ/°ºÕ§SÁJ¿Gé—B¾Ñ|ñ°4'·ÜΧc¯ ”L¿ž9+Tj4ÕÍ; Æú�>1Ö©,˜”n}kˆ#ä§ãÏÈ2ç3Rc4ŸYK|ÕPË¥U¢®Í•¡�¾4Wd�›åÉ“9c.ˆ ÞÌÈ©�©ëAz¿_•,cCŸ"­!«ÀäÒË^ïÏ20 ¿KÏ�Îs€Ñt±ï«_Ü,Iùác7Γ,Þ:.#� â_Þî<âQu&ÒQì?�;®Ë’¤Až�sÚnàc­¨ãƒø]ä÷=è©íÑâ³#*vñùÌöœÔÑ^SEƒ »ÙÏŸì‹sŒ‡½ß{€� 2D�1�¨ü"NŒjt$úçÞ÷†ßÛØØ,Rb«í<¼‚\„ ¡ÎœPTë}GÉ «õD`å±gïXj7­˜+$µ‹YŠý£ii)ûi/¶˜t¸uÐàÑQÙÔüÒ"ßsMòmÕ¤sO�wi¿‚˜*‚ꉧ+�Šß^D,£Ë÷^„S3]ŒWTöÞFù 'LúÓ�{y‰ 6inBƨmçú'⳿…êá"T¬»À.?_„Î� áê > e´Òƒ®pâÐö·öÖñѼ/Ó�‚Ã/Wð3æMA¤³T¨>Ü`•„Á›Ìßõ¼â‰®ç§y7¹;s6þ¾ÉHZéfi©ïÀÌ™ÎS³â½NëÆUœõIqVgWø;«S�?è¤�Òr=*†ãyUy´m¦Õ….@5Ñ! ‡¿¹¬áñ�&û7W7ùt9û=ž+K’]ušZ9R ºµCß«J–5ÏOõRUõÒ®�‡e/í ΚÙ4°°á ð$oÃ(ð˜ã„‹¤Î#.½GdŸìýˆ–Ÿb0«ùOmv 6³È·9[ s0˜¤�k1ªC£߼݂’¦´ÌYÁ£0üH[QEYåS4œåõEÕ¨›2ŒR»•üëPƒ HF¢¨9*ŒZM ñJžùÜZñìbR6{©z�z•SheS¬’1ûK‚‹gÌï™.M(˜Èͽ°nÓ–bÎÒæê€à¡T¿|�¨ê_MàFB×¾ÙY„aÍÊ™¼�ª•n¯—ìß_ ²£jð6 ÊJ޽¸Å%FuC‚6�‰õEj.n4èd\œª™Fmû‚“™<¶u²s‹“¥\Àã,„-îò¸Ï.TbÊ‘raÍ4u\y Áð74=;ä[×uè@šÏÛX@O¾:A†’Ç»ä¥û«>½7lsfZð=·ôÎ~"81ÛÛ#Ö§U1¤ŒøI øUƒVnììÄß…Xp”óÃQêí1Œ¬•õ‚.¢,­H¥n¯²‹Wð*3×£¼¡?ò|€Î·×æxbXµ¼-yqqADåFìùõß��—䇪^´h6ôì©y^–¡²‘t£ÝkŒÚ[­RdÊàWr¦~?©ò>¯G�qaÊ#SºÞ«ý•rûBÖ½Ïvm•Ú}¼ÐŸÝ8î#P–2/ki™…'*"Xkf©'iN�5ª †Žz`˜¸^Î C,1a7 3œ�È; £\£K¼un'ÈUFKÇ$¶@þœßù‰òÓÌcë)Ÿ õ‡þ¿ÙÚÊèì[R™f¿z¥,Õ\úÿU”0v[NÒ`û”�¡ÜÐɘ�†Êœy×­ªÜØO"\‚ò2çÉtcõ–™ƒRÒfûV0NÂ`G´Í•½Ö¦úll�ÍÕbª?Ôq 7V#g!Õ™ò{.aa UR¨C¦Î;¸ EP(§ðAõ¡½¾\´ ãrÑ5×®pŽyziŒƒk�méÒ?µô¡R&i™×p}œ4œ(bNœÀ3Ϫªâòƒ¨ŽXh¼bfÆ¡-É2%ú Ò¢˜§ÜåA¿&A{ƒú±|O$ÿüv¸-ÆW�¢ÇÙå{¢õÉ·€óGê+} Õ×h4³­‡…�-z6ž¯¥í'£€cé`t£”ÓsåQsåK>Nì‘>�^ë£/²x½:Z s?7Ò[ÊX¢‰ÊTò´kÉ®ACØšaD�{Å,æ FÉiý“Rÿ‘A u˜£&faŒöM¶µ¼7DUú_ýÚKEÊíª†£†]3`ã4üH´Â›§¯ú˜7MH >*So­¾�yhˆÛ%³�RÂfÄYØáœvϼ� #˜ÀcÛ ¼`Nlâ)„Œ‘¿ˆÍâµ Ú�Kì(bŽg>â2'YóvŒô�ýˆ´â%‡9ã°Í‰¸Ôö‰K‡ÈÚ‹,‰IH£8ôÄÑ ÇO\ä!ö½¥')àt¾þ‘&¬ùì’%s½þ¥|Y«äÑ÷¢E—¸¢~LbxáK.Ø.®ã5 ID}1xÀ7_k·A*+h,EÄé®l©¯D4KÂHR>Çe 2NñêÄøÁgÌ÷Ù—æ°ÀõpEÑ¿¥Ú¦0n?²gÊ$t°ø| V¹jåP´°a�TJ ˆƒŒmeM!òÅ }°b!'Z\k/eâÆ “ÑÕô¡?6ˆ9!wãÑóÒ¸$‡ý <vɃ9½ÝO @ŒûÃéG2º"ýáGòÞ^v‰ñëÝØ˜LÈhLÌÛ»�iÀ;sx1¸¿4‡×äæ GS20Á étÄ JT¦1Ad·ÆøâûçæÀœ~ì’+s:DœW€´Oîúã©yq?è�ÉÝýøn41€ü% šÃ«1P1n�á´Tá1>À™ÜôNªÜ�9£»�cóúfJnFƒK^žÀYÿ|`R°¨‹Aß¼í’ËþmÿÚà³F€eÌÁ$w7ôúðïbjކ¸Œ‹Ñp:†Ç.¬r<ͦ>˜£Kúcs‚¹�=ŠfŒ8˜74µ®Áçû‰‘óriô€k‚“U`Té냃Hà ‡¾ýBÃã( g¶C{‹Cul?ËñxŽæƒ‘·\ùôLö\ÍGw’(fK1Sů£_Û/>r�ðÚUÂÇI¶úÔžß;¾‘EÇôÉ�KÈ|4~ „^Ãÿz‹wêK^ºOï^skŸÐÀ�¸ô"[‘÷‚ ýoшxÂAœ$ � ºsD‚839@$•AÀ·žOŽ)uy Çt`*¸åzA òK1Z FpR;p �b,�ÐÌ©‹ˆÌ™Â†a˜ ÈA`ô!*@œ˜RÎ*[®XäÅ€ ($jҭͼyh�:çîõA„ÁÔ!ÏÌs¸ð-©5 gYÁÖ'bŒt VÎÚ_|®ã§1HÊ‹xK¥&¦�aT�¿+¯ôô€OÙ¶ZF®û?×Ó÷Ö ÿÑ[“c0°ŒËkÃWÓßÛäèˆÏÂÏ6³¸SÿÞn“/Ù<•¿ÞÚsãpyrÊÇ¿íÆÔtt·;Oç£éttÛÄÔ‚òˆ_ÅUó¯2Ûì8–Ôbðõk#™†ùBc´˜UqŸfðÈ´˜£Yâ$Êç38¿¥ÛßóÅú*Pt5›>�"Ú*)­|íߤö¾åA&1ö}—­ŠÈœ²%�ò€´ Ò¥$9ÃŒ…àf9¨ 6‰VÔñfà±H²½)À`8²2·²¬�0dOЋ Is<—³à…„­ÕlŸaBY¢ýÉk‘êÀ�‰�‹Æ(‹x¯p1)fÒ‚cêƒó—ö'žÕ,![áRhËÅ`fš²îjä?QNú…/ L°)ª%+’ÕmbÚƒ`·{ˆ%Y?#b³ø¶“ᜃ™*P­Ö‡š P†½ÍÑ˨7‡u’4ö¿9Í_f¾�oÿO2e,uº’ ¯IƒÓ.Lî/bf‚ªçR€h1ôÚ}ºÖ\NŒò¯=°@+]»²B9hj„™C*Ù|niXè<3ìÒp`Ép�o¢†÷ƒÁV€GÿV ×—ï­s8µnÍ¡513ÈWå]ÿWþ.µëׯE »ö ìH¢ÂÔ›4N$Pža•H ˆŒIuOpÕ¥yøh;Ml`ºœÂK£”Þ ¶{䆭y0â /V¦Qâ, |Ř Ć"j]éÅö ÊhT3Á…¥t) 0޵±6$¿|å!€)ŒCåÊc%•Ù¡Ë€…€Š`/Bp㤨퀬Àןx N©¢y�œ¯±`ÅÐéÓXÊyæ0ŽO�­dÅc5Š1 Šv¼QO(k)‡ &…’ø 0¬By÷2%ºÏ„a"Šà ÒNؼ– «Ý%’PSˆ-ØÀtÊøÄ�qSp¤~NÜãØSL!ÁUëã7ˆÊõiî餅@¤QÝÞlÏ éJðÊl6€m‹lë—Ÿ-Ð% ¡”ÞaÂ~œÁ.Òͨ¼MAÁK¯«NžlçISº8ɳLSDðŒ’ {öÚöpW¶¼ÀÃ�2ûŒ\õCÒ«�¸×Ò«5ÏË’íîtmEyÆ+†dBöMÒkØÁw1Ÿarµ½½4K–ÉnVs²ítÒé_‘dä©÷&ýªZš[k;äm¿ÖáE^FJéq9d)ß#�a@7Æ ¯Rh*Ù0(a×XœÀF±RðƒÉnžé!ƨü_ȰFâ3¶êâjÖ˜ì±UÝ<Ñ!Té%ÁÒ^­xû�×ÄØºãMN`õÙöÁÒ…ˆ„†ùqžG²˜%Æ}ŒÐÏH}%sÜÙbT²@"LÑ6°1P®12íµˆf¥Ëz¹À¹R]Ô/ç\’ 2Ÿ©’´Q ­† qÇ\²ud‰} h:¾7dÚC¨QÅ .Cgf%ôª@¾¡òA5 i§ÉTîp €(nôˇí ’°àÈÙë3I½§œæ°ß•„UÖAóÑîÆ‘-ú"mª ¶(fóH’�ϸLåîÔÔmÙah¿p¿VÅÆÇ1|Kn´üX±�úŠYŸ©×Ì [ºl’ÎÒ!6ÿ tê™NÑÿ`¡/¦ d %ÈžQŠ"jÏHeÕ³g¬¢JÜJõ<Uí2|;é¦>¿Îö(ÒÉ¿7n‹#js„iEdA9Ÿ„�åͬ™íù¤õªÊ¨ÒT¤w¤‘Žú<÷aGñ¥'Êî/w½  –O™“2pʇˆ‚x�F:�½¿YáÒŠ� )â0m©›u.LØÃV�iwäß3^Õ�ÛNì={ñKÍ$^fS–ÌM¹©'@?9~yÏÔúŒåkbTbÞ°åKOl€T÷§vÑë:ïïÎÈ 9:ÚÐ1æ`Ûí’{h†ï©¾Uü[SyêºyX­Ï£ê|Q(z×6ω7eϲÔmÜe¦^»;~÷ö÷ÝI”‹÷&XµÏ•J‡bÄÍ·˜ 9ÁÌ  nB#ÜëÒ¾²ê•rˆÓ�Åï­´Zóãá¦-ÖTŠm×îÓ¥­`P_ÞèʱCÊ'¾±¸tfc·*Ýù¡†ƒ™`=¿°�‡ÂHd«ŽDÞäI~Ä“¬:ÂQ˹Öã‹…=¨¡r’šýÎ-ìÔ…—CZGÅ­9W‡L>¹skÅVÉŠt ÊÁ¥ˆ‘†´­ÞÃüYBÇÑYÊ«íæó®�Çе;ù÷rŠÊ,Pë[8!Ðq0�;¬•K[="ËûÏYGCæMDªûØÌ}¡±hø:Ø)¦®ÚרæB%qpuå_ÏT)«ü•å-šúD‘bÓ^© ª © ‹d>{­ÚøÏ„ïÓ KóÙ¥Í;î[„ÑSü]’n©dÚ_NºDþûvZê2”‰|ðÂ8•!ßf� "Å\ÞÉ.‚2ÅqþVBda¯”H¸\üU™?Q 5 VMP6µ1d[ÔNbVÊ–škvéY3Û#OK´GÆøñäNÞgôÝm�'ÉÙYMüØþ¨òTçᙆñ–,`å_ËÁç�2ý bÐuQØ…“[!"È_!£jÀªœ[w[F;Ë,*nã�«¶â ºÿ:Û¶q²;©ÐöÃÄVjnr+@)sy¿˜•“Ý])º*ûY"ǵ; Ò;öÃ@Vº}T�O/q ƒ¯Î*XP•S9«jÒ/ýh))d"þÛ˜+—ϲù�vúIÇÚåh_Ý"I'7×Í3ë#¿<�ÊÙ( uî�jÍß¿+Ý2ÛÄŠóëo™•¯G60â';d,q¨ +?r,³’C¦'�øÙæèº¶ikV&͉蕩ÛÊ š—P ãVuRènAç•^"“1Àq*¿ËÙYýcδË] ~Q"­‚`Ýüæ©ᥠñó<ÃÃ%&ÎcV¨™¢^VZ±à'¼ç�IWø4*Ý#“Š÷_žßá¡­ ¯åêí.^eSndÈKâNÅ#`v/³kMY‘)O²µkIP½`þú•Ækº "«6ZYå YÔéVUòÜTt=‚„PoŠÌiÈZÏ�afí¿­úã¢(IÛ‚•åÜ98ÑSÏ.ySѵɓõ£Ê,¾¡;€}ž¦“ð.ÑŽ.Ò»†–ü¢pÕ­…“쾂B¡âŠlý]è|^õµŠmffæ¬Ô¢Êæï‹{½øy�Þ«:!ÆË ¢KbU¨(´�"3¥ÝþD­ ’YK.ÓºÇ+{o|<õÇ—ÖíèÒ°†£¡Q’G©ý%¼B¿NæR‡…ü×Mê]<ÁOÖÁ‘ýŸ´_¤A¢�ÓàB¶uÑ Îûï A¦”/´µFÑ÷w³*T›Q‡ÒnŽm×¥n�ÃÂÔ.ô®¶C á�=o�Zíð-ª"ØÕ^‘)œU¶%ÔIÚáµ:À“ZõÅñ»,RÕ…ªb½\nj©…±„©î%Áª+äãt“©*ìÕœ€tÒ6¢²Èìä°|ôƒ×:͉õÐÿ8èÑu†æt4Χ(ç)Ù�*$7¶6S@�-=�+F¶¹Ä¼+“Љy¦ÐÍuÂîûq!ÕSJ�m)ѰRaüÔ˜7¸:ê�¢3p´8t–«“V1¾vÕ¨�][¶ÊkB7§ëb3±rÆßGÄbƒ«v‡øI¼Fƒü »æÖ ÊPŒ—ÊÔtåw–Bv1Hþp®pέ·pS˜ ‡9)ùŠñßãt'¼_õ/ ëÃkbLÓ—æ_ã‰9yÄφóÿlà;﨧ÿê§¾^—º­½ÍT‡±ÒìðÊudÉ«€ÛY·=Ñi*Kë‡m;·ïí,½þ’ZÁÔ j2¼gÖÍ{¥b²…–;§¥B Ûp4ëìð‡·¥ ‹÷‰ŒátüÑÞßžc¹(�fÕ…7¯Î´ô í· ¡7¼ðíïe‡Ô†±’×Ðìì¡ÛߨžQu1y#ô¦¦À¾­y³…Õ_ô«·0ì…‹y–ø]ÚŸaY7l�Ý×ti–‚.RãâÊ)E^“lˆÏÞ�h#ÍRu†Z¡M+ ÖчPQw%ûð~\Ë•ôøº�_³ÒN#ö³O+UÊŠ Öº›×R/ûTJV=ï ^j~i�íswª%-êš5HømcÝ{geØÊš5"kw³T«ãHQÕµ L {§T€ÌZü—( ¿añJÕµÚph…jJÉé?û´ÍpžÏÕ›°¹7ÊÀ_çga½O­?ÒËÌ…G Æ�q~SË>hƒìK$`­Zkô4ÖÛw'F!¥Ó‡ÙK�„¥ô”L‰†á�.‚Ï}z‰f£éóŒ ‘“Ù+M(˜¼Ò×h2ìSø÷§$LSš&�Ÿâ(Ä»h2ˆŸ‡Ñä‘îqo2�Q¡ ¡t6µ½ª(LYÙ8L#<÷QÍ^ûôÍ&¬óJz ’Y4xŽƒ„žž“§iÂüj'Ñä!�•pNf·°Šwþ‚JGA[SÁ3¼O¬ƒéÓk=Žf4šÆÃ/ïCxÜÇ¡3… q�û4 ÆÁchoM¡%±bÞ»—Qh_Á^€ŸÁ,šN8ŒÁt2KðØG”Élwõ%JÃ>I”2 ÉêNܘZ%¸7 �†ú8#áçç4Üû2 ƒºR¾|(Ì)½ëõ>É -¤¢8x “,…qœ¥ÏÉC0³QïŽd#>8ÅeWUtQtÚ¨Õ Ê»®ot×Vy!n—›º½©ó­h½P× êæµ¸yû馨%zıüÂ|;”?:ü ïðÿvù·^O3õ fŒ6´˜+U‹¼A?©ò®6ZÇ ˼OYQ+‡ NÃ/½Þݪ&fC©A™PÎè¼õOÚ24§��2evÂ=³] F}¤•³#]‡ÎØC׎cf™Zup_ø&j5t-ÓVuuI;î’GÂ[°;«ü›°$­Õñ‹\KŒîz<˜Ò%ÛK�[P_Š¢—K×fmïÏKweÝŠ7dÀªkD*4ñk!´¦…0ºw.¾Þz„gpÑš"æcx1ðˆ Cš'Š‘+aOw)Ë›b©Zý�ÇÙ×ì°àÂác˜�§Ék6y߇É?¿°JnˆE×¶ì·»jÕaÔšv!›ÿK“¿ºSêNË7‘}W�øA|wL|L— ÀX ßÓDTÀÌ; Ä…Šœn¯NÇYvÆPTÙ²;¼És¹Y¸¼ŸØ¶%aŠ¥=Ã)Õ†´üî@~4ß\Þ8G_Åv®ò¶+pé›È0!}|(hï'd·‡Uû&Í«ÓÖ•ˆ+[_ ?ر¹Òý8û¶o‹m§s¾h…äüX1²€ÞP…LœcðÎü?ø¹á(–[O_íŠP!»�pá³Ô»;8ŸïœrsÛÁïB-¿�U# `¸^¹?¾”zÓÕµ•s�‰÷)ºnò•Èôš›Á‰¨Û¼JŽ»Žjˆuï›2–B¶¿Û8ØÀœæ�ÒW·"ìuëÎLJ¢J¯Ôv#z/¡ |P…­k6æ6ÍOûƒ>SïòsŸ>_‘´+ šë2×ÍŸ¥\f⬉¼À¢ö&tžXо|ÎBž!QöÚ�É[L“õᜩJ3ÔH%èü猡XƵX+yñö®Œ{¿¡Í°c�±¡KX}q=áƒ3sýáÊ�¤sÎN¹>laþ`6qʱM«°�òˆ³1ŸØqS?³ôܙü8†„.ÏÍÍ£KïŽøC4[3k7¬TëÇ€í½)éÛÂÎ"ãíë�.�=Äþ^õÏQõꔀ¶Ÿù­á##{úž·cInù~M{YgjŒq­�>›Ë~ÿ}ß°-£w<-÷Ÿ@Œðè#?«ÏCñAƒvN&leß�ÿ[þÀ´›ÄÀvX‚è¢ä�±“ ãiˆÅGɶCú3²ß ,Ç>V~<ˆÏùq38¯ïÌdÏÜü<§÷cŸŽ†ôˆûhÂóO°ÄÓÛ®U%x¦ [ª¾ª=˜úXÐ…¹º¥¡BÝâsÒ÷R›5°”ë[LÌ7;‹ö=ã–F ü•o9Å®Ýööðú$¶…'Õ‡œb¿? | W¶'žý ù%ÛÞ°rxœ•TÉnÛ0½ë+ô@±d§½ È!hƒtq iQA@ÐÔHf-‰IÙñßwH)²¼©‚ÄY8óÞ›±FXrErY‚EʵL�´?…[ž-y�íEc�ªb»„²Œmcr.`g-ùÌññsVÄZéFŸ69¥KXÉ|¾mä:æB€µ;ƒ•ÆÄBÕk0VªÚÛÆQÔý1n ßÛcëºýù�ͯÿÜܳ‡Ï7ó9û~ýõîþŠ’ R�Uu¢�ú ±.~4N¬.¥Ñ„ŽgO“³y¾üx{žË×ò|¼¿{kž÷˜ç)Šg� VʶŒoÃÍv°ëøðæ9‹¤Oˆ(%ÔŽá-N U²pì"@—’!�­EÖ¢l2`™4X™2Ðíñ°ôêºÌ@C�A-zÇ éäÄÚû'ÄG{~»�îÒô] ª÷Ãf™U//uZÇË2%Î4€’Ы‚¡NrY°šW€BÏh½#·w &pFY�Ÿ˜3Œi,dÄ)o‚ Q²‘nI”[‚!ØQÑðl$sR€cJûÈÝK„êôÕÒ`E­*H ¨Áp Û Ç´Nzã1¨O;£U�þÜÓ‰Rj'›&É´ãnzÐx²¤ã]¸GÇjl.%ôÖ­æÞïa �àc{ìé,¹X$Våd.±nm —ϯd±Ûj�ªëýú–>ð¬•A‰r±òئä€ÇÁÍms�peü!™Q2Àe <“ñ~ŽaÖ}…³}~׸.;Zqû¤zëŽUÿœí`�<C¿qúÈßÃEèpßã Ü€`�É¿,>äE‚¾Èïv¹�—­ÖÎ(nÚ.CcºtNÛt:-ÐØ,¡ªé¦Úlµ€¼þb4?»±xœ¥Wmo£Fþî_1rÕ*9¹÷’~»Ó}ؘµ½ kÁñùª°6{Á`Nš¶÷ß;»@â—S7²”}™yfžÙ™YöÝp#™ƒH ™ XÊXNcˆ$!l“Pd0fnÞàúéæ1“«¨€«÷WïÁI7‘0“I!²Jd"²µÌs™& )™X<Â*óQ$ìÁ2Ò%‘Ÿ­DŠüä6"ËQ!]¾Ld²4¥$ å_ž.‹=ô“ü%Ü Ð2ËÛãîVE•+¥æ'•º¹ü®¼MšëëNõŽÌ¿—ÅcgÞÕc� /‹a3 ï†Õ¸Z;l#Oˇ�ĸñ†œÜb£ö,ìÂ#oF÷©ž¾C/¦š-r«%KF»çð2X;œ&�’Öî<§ÛVRMlúXE”ŸBÀ–¾ІFƒÿ^€íŽ“¸‘‰FjÒèH6Ê‘&F.Á�”ömÙ²Ÿ A·ê:í�šõI{?^«Í²tO•fÝ¡w*— ³^ý_uùÜÀšBÞX’‡=ðŒrÜñ¢)[i Ñ\‰•þ™…x|½œQˆ{MÿdM·B9»÷}iäÕXŽGa=¯«\?UŒGÖ^­EüPǧ+>Ç Ï¿oóB½³ui–¦¼·ò—êóEM]®DkŽ”"hõºb[;;ý+ ¿èÀW$&›x_.wUðsâ øàqð¥Ä,×#ÆïSÇUï·Jõ4úü|ôy#ºcâ»÷Lç+Ýøç¹_é6âã3œ}=—@­ÜÆÂyjåýÇ'õñø/ïfýŒã/à%xœ…’AOƒ0†/ž8xñ4ÛeØ2=jL¦6l‘1Ód1æKG;¨°b Lç…¿ä?ñ×x·”ma‹ÂWÞïyß~-¿g?§ßü¤ðFãGÃÝÄyÆ®7š8 �6e .j�6~œS†ZŸ40³�űéÇœ Ù [5õ#N͘¬YºéÉE&ÉÙ¯Ÿ¨{O‰:B ô-ÏäRݼQX¤8•–5E5£eò@'Ki\…m&(_ ^¯ùÇý£zŒ±¸xœµ]sâ8ò=¿¢'W—…,�ìÜÃUm6¹b’PC d2óä2¶oŒÍú#„Ýšÿ~Ý’lK¶ Ìì•°ÔênõwK>;…éÒ‹�‰1˜{>|ô=‡1s! \ÁCz§ø×ázy‹eïÏߟÃ$\/=Ï^�°H‚<²håű„iÉ"6ÛÂ"²ÄmÁtïz|Õ±Œ9˜äîù¾Ç‡�^ÿ®§ýÑ�¶q=NÇøØÂ]ާùÒçþ¤×‚î¸?!�ÜŽGˆžÄ‰+F ®öµ®¡ç§I¯àå¦× ® -V�I¥gGGÿ’†Ço×麧ÑÜvXgy¬Î;iœ„«6Z°ï« Ä"yioì­�ö¨OÄÞjí³6õ+: Xuá:ò^Û¶ã°8.&ÆUÇ÷0–èÓ¿áâ3ü×Y^©ƒ.ºæÁœÅ«£#tþýÏúì.iﱯ£¿Ž?×|Ë¢-'Ð[ÐÉ.Žøt¶è1Œ¹¯¢ûˆrþÎ}£wÛÁ£¶c;èûú^èØ¨³`áJ®=;#��i]@þ;| ø„ä…bI%À©òpQâ* üçÅÑ× Ú7òàÀkè¹GÊjk‰‚ñ™…šš{‹Wƒ@àÔµ»ÅQ×LœYÊÓ¾õ)f©ÿ¼· º£¦TBI5pŠ1y—@,‰Ýr›–&>¡îÌv^Ôm’ö•Ê]FVJ�Ïsmwì��j ¦> “ žK¸í&=j.SœŒƒ0,L8 0]/Al·£"ߥš–Q‘\ÇC�U˱òß»WfÚ{; l{ØÆs“åa KFIñp³YàBpÒÈ4øO�£bVÒsÄ2+b ærË›[sÛó|9\]Â9œœH.øcó‚Üø1 “Ð }`QF…w3gxƃ­,¡°–ñ]JÜäïöz�ÁŽ ¼ÄóaC±!Á¢K1ˆÂPD I^°Ìsà œb4Ä=ñß— ¶ÅhnKˆÌR  C¤;m’Äšy˜Ê±» v�Äåøð_†ïnúÑzîßÜõ¦Ð(è4›™ó!¾*öþd*\ Nµœz‘Ì�_fß$øÿŠ„_±BCAÁ¿äb©ŸËüG±\Ž(ëÅH ròeöWT�i¬¿µ`+I)pXÁV6)™Óq{s*$üî’Gþ¦ ¹Ùb]Gh3I+,Ê£BªÄgóDEÙ„Ÿw�‹ÂR…¿0RΟW®¿�ò,LÐëH»šÈ yººä¥T‘’*¡LÈŰfOÒ#Ñ�Ô·)Òóâ× £ˆŠóWÛO±T¦fÉŽ°EÂfò'Œªä«û#ÅÖLtQøêa¥�«Wëhß‹³Ã.Ášm­À^•8?¦=¹í$lA¬¢IçÙÿ<ÁÈ/ÉðW%ݘ#¿xpÃà[2ø®ØŸ…Õš ¸?ÚþØø¤Ä¡4XÙë]!hGªbÓui¨6M›¯�xGQ‡º˜Ý•ÓUÙØ�³ #‹›gÃ(û–^Yžh¦CQ[‚@ ÷Â{e>‘`P“-©í$)6†èÊmIdc¸Æ¦ƒÁÝÍG�:’ìežÑ;µ›ä!¶ŽÓ”øx¹Ë’h›!M";ˆ©ÂoÏ‘¯t?CCjÁ/ÒìÄ` À,süRM Bv…”øÎ¥`!wV&.êZ’S}…æIûÐkÀŠ£’'• ÿBŒªkä¾;h6«ò)SÝ]j˜xÌ‚B©Rƒ†&úZ(ÍÜ”¦‘M],CóË Úd¸`»ü�Šaß'‡~ŠóQó3/¡áQ¶Êôƒmª¨v–tè§³N…¦>€…‚‘írá@IÒ˜VI¦9w:5¬ÁpÙÉVI—ÂÈßàg gåÑ-�*DÊö^"Êæs\†¢°¤ŒZúšWGß¾okcGüвq|�רs„+ÔÁO<=œ’hÿ½Ö4$9Ë'��™TfÚæ�½ÇüšYâ$�!ÖØ šUk³^=t(±‡1.sÎvxnûêÕ‹(9´¯H�’�@°ÓãÕÐùí² AçMÚÖm/êë ,aÞ˜a@ŠìÙ®DYím²\‰“fc,$\y1=V¬XT³Ãç«U,™î; ‰j£”¢æÔæQ²ÄŽpÃ�ã1aql*FÉp)þ’²]6·S³qÀTT™ê|ÎÙ}�ú>Ï6Ë®'Ο«ÒsÒ:Å‘)"fæ¦)m|�XÈ#{’âk|HÄh?“†¶ï²@$X³Lž¶”¾÷Âü­`¢tDT£ô[c_!`*Ä3÷‘ó+ž¼1ûŸ‹Ò^”þ+î¾èI«ì¤ûƒˆ]À—Ñ`”. [#Ê�’8+ukÊáÒ‹Õ«)Íóè–ªˆl¦ø½ïô£êMõD³š ß•öU›4ßUNÔjA¯Hš•ÞAFÿoo!¾™hmõ tuµÝ·ž‘T1ïeÂ_/T¼B±ÃúRš0Ë$YÇ¿ž�-¼d™Î°×Y�mV› �³·yñ-äϼ8ÆÖøìýù�Ùö9�±ƒq-Á¶�·•3æØiœ7˜È½Ðî×Ì=ÖŠŒJú[]$·ôú@ˬ¥Šz´”[´&´w"I�ð®rº¿YY3;–WòÁZøáLǤ0ÂkjÔn¡ \%Ÿ¹6ƧÊïK•7Kè^…Ý�›{©(BÊÇ„"¶keFÅÅúÊ] E>UͿοŠ,HW¥­Hñgý±È1Í_½d›‹±^0¤/9Õ¬'%ÑBö½—˜œ*S“bÚK�ŸxD6¦QËvG§¡Ëy0�U ñ$mr‰@±‰13Ž2Ÿ*–¦ê9ž8?.[Š]td ®Œh‡ytš;©Xi¤ó&¿·ò[âç¿÷açsü2±j²Ë%b;ç·ûv¡ÓÒômX“™•¶HÖ¯ªQ¥ŠÃ’ÕV•J 2ëÉsR5´”úfOQ­8´k?×-ïL·~'¦Û8¡¥,¦PÚh_í¼œŽŸzr÷?þ„¦T’Õ¥?ÚºØÕŽí»•­ûˆjô{Wôð�ˆ ›<ÀJÔ¶H¼u"Ëø“ê!pn&ré]¥Ps9²®;y^+öö ò¬»¶[ø›íÕ”eñ)ÅbÁO¾i³°u•gFK×Zq§“Ë|ºD0Ûº�¤ŒvV"»WÀs/°}ž�öÊØèƒRAYåYЫ޵ÔõK®o¾ÓÉÛEAäpïÛ[ðýffhµ#<'BZt�Õ8Þ)§&]ãQÓ±Y2ñ: ‚g— ‹aÅkwK[´[F/JUCR5V9®š¤ÂFUĆ˜§G¯Â6ÔJµÆ*ô2øø¿²Å¢ÒªÞ�U)‡‚†`'� ‹+4r�KcT”¯št„û˜`øŒ„Ê�Ô˜MJØBG&à|V�ÖD]·H:ä/ x‘›µ(_”kÙ€–õk¾¢å9lµÑ0Jw½‚cJÈüêäøÙC”qb±?HÕ�“š†¬Åoí¢¾�;-¿:GŸ³3„�¿¥—~]~±‡ƒÎ’"Ütí{\ÊâLÃü$5»äçÚÝÈKK:°X2çbº ä8˜G­?ö.»Ã÷÷ÁôQCSœŸeXù­«¡>¼ SÚ¯=/|Õ-ÛóXݲ^«[ªus<ùWÌßàªÛHï,k¼U}”GÅÓ<°ï‹¿ Â~^ Ãŵñ²ã˜E‰AÅÅTN­úêbsOjòE}ùB­Î.‹m�Æ–B>¿Y», …ï<¢Ù]ÄÕÕ•RFòÝÔKÀÛU{Ç‚ÛY>nÁ�uÝ >t¯?ŠžtwÌnê}€’-yž¬+Ù×®zV{+u¨ií7FÈ›0ø œã]èùü§ÆÚ�>Ý>SD¡�>šaôB¯$z‰ÙMj#]¡sU¢´Ë-ðœ¢õĘOÌw�ôº^½%—o�ŒõMµŸÖb»Vï}=ú?X°Oê7áxœ•S=OÃ0]PK~‰.¥*°ÀÔ (‚ˆª¢›eâKb5Ø‘}!TåGðG¿�'M£Òv!“ß}¼{>¿|ïÿ|©½rv5æÁC0 ødúxs9ºæ·¬+1RaWŽu•Ó\"…¹#órâLÓ—ÛH„xš1vvBÃLÆ�Éòl²Ì@B}Fp"Âtá‘#(É€€QÍ4©ˆÚjF‹ ½pdó�€oòmà!Ûè`ï ü·‹›¬ÐN¡&^FÆòúÜ­xª®±œ?)-M±^\ÕÆr΋:Ó>bHBÇ)‚õ§6<¶âUÑ¢r¡ÃÄØÀ²·ÁCV6Å—5¾Uš,s<©@Ë×¹¸²�v �qŠ”ÑÃzû#“)t�5ÁHã¡6$æ¦Ðh]¢26;ß|¤7󬊬öÁ5ÐS»�˜>“”ý–þøŸC_�’°='Ϥ ä«~èmÈa š+¾u·5L¼µjc üª(·ÚÁýôîT…5:†Ê"àüc²-ö¥�ÿš�ÇH¼• ½¿fºhd­wTBº¨¥Ÿèõìúƒ~™9Kìk�NxœuTÏOÓPþ€¹é†Ëœn†ò°u]÷ñ‚“_A@4AÔÀ©©ë[ÛXZl s Qc¨®oŸþº¸¹z¸¾»üíƒzóäÉji‚Q…üV !´:- gí»ÐßÂ=CW•ÒÊ™•².™EÐÉú lða¤Æ¾]+í”ùac²nqtHÍmm¿ŒÔuÓÖ¦1.e‡òLG˜Ž&X•Ÿ+ï £j<À–º®gº|Žƒ_GêlŠÊé]k8„7UÐÒùžÛ¡$í¼1Ñ»QYRúXÛ"›CU”KS"ÿ¶4.š8j×…jô3@Ä®áYP£bú…Žò1¥“8N¾¥ ñ2t> �Ó³ÚTb¹õ®^Kñì<×Oi­l”SgÈV…tµµ¶.n‚é�ðcÅïÙnè–†¯£Wx éCÖqü¨8Û0NNÉ8`j˜3CsmëHœÚ­wÕŠ¹š@€ˆ™÷>©™1Ž\01Š uÂÂë¸V Ž#òO%ßׯÅÁ­‡UÔYØç¸`u ê-" ùº�t°Ñ¨yð�tISHÒŠ`2 fA‡õ™¼ÌÄòÇoz]óÿÒ7­�|>SÑ«ÎÙ´+cjuH¶ìjP1”`ÞÕà©‹ÉhÔ©éÑnØšëø:ϱï”×Ñ*ݶƒ‰.—Ça÷ÊPÿv&ÃoÛœòŽd{• ôÀF%ÊÚ"sMíK�PöÁQ xÉ­Ó’m‰\4ô�uu²- &1- >Ú®®Å žÙæw³àìÿ0RS1Q¢*6çŠÝŒx\iømÛÃ’{4 rÐ03›çòˆá— Ž$“[SÚ¹-·í¢^°O—¾ƒ^Í`‘ŠRu�A žƒ)bokéf7žDcØw8�‰4÷¡ÑdvíW§#%ÅÊÄ–<ÿ¡ìGüÀDwYJÁ+<„|ÒUý3ÕIâœhôš/f˜ª úbß@öm ºÓèÊ&n…EG)�g >Z6§ô§%¸’á¢òpYYWA[ø6°c�Æs`Ýð÷Ý—RO í:ä r^½hVPÄŽ¡ª{£*†CÝ%_ð,ÁH 3#7©î=$Û¤�"Œ¤«ÄÀ`h¨!sûî™×å�ôšÜŸ|éëÃZÎØÀä}“ï÷“ñôévòøxq5¡¸—Á÷Þ{-wâj‰©;Œ­.ñi  ÒQû�àÙäHÝnN²Ìr¯ëg\æ’Ôv[õg³o­Šoß\Ü]>]N>}½úí„eò’%½Xˆ�úbÜr’rÓè,ÈMþW[‚®!ù¹¥dbå˜dÐ-05ÀË€]iÏM>dne>N·­,6` “êëÝ¡£“ÓžXbžÔÜA­™Õ(i]¿ÙBÄö‚:ý(M›dèæD±Z›Z€ �ï8ïLµíÀ<¨�M”@lÉ2³;Œöÿ9VÀÅôúËÝ£¹½¿™L'—à^ I&áÈñ§ýãÛ€Nϰm¡6‘ÜÌr̽¨ æÅú.vŸìI²!4Fz!Ó|ãò�îú­Qp #yG²Xmi©¡²�l0ƒ‡=�z%–Uµ×lgÂOf ˆ¬ûùNEåÝá©ÌU¬v[æþNÙ‡™ ñëÖ8(%È;÷’ýý6ÎSÓŠ|"§H¶ßâûåÕÓÃ×»éõíäéòúa£šm{Ùëõ(N˜ÔõvÈb472ý¤—¼GjÂ) =¶Ü1»–TóÐçÍüÿ8ãä>bÑqÇ« m¯å:yòÉSÝ72þfçrÏÚ t³¯ {â ÉDÓ‘ˆhÄU†D´ÇõZN?¢1²é¨b�פ²„iLÊz�×îuà´{á}<Âp@×=W2™²®ÉRz}aÀ'Ý÷Ín¦Ü-sÚçòA3élÕn3‹ŽöEÖQä•soîgje„ýyŽpÿô+wXÁý b ä¡a†ÄÙ‘/¡ÞD®»‘ ½ÙA{p—= Ø!´@#ë Õ¢‘0ÖÂŽœaª±¥4!{­—kQz£äX£Y¹ UÛI¥’Kð.‡<³ 3n÷íƒBôE¿„¡!5ÖWŽ€VfLèCö*LµhN'¹ÎñZ ·»TÌ�Fù*ÑF‰`¶ -Ï™�¨Y¤†3ž÷-%xŃ‘`q’®,ú¯»,Fƒÿ‚�¹Äè=…¼xœ�SÁjÜ0½¤J �––ZÙ$5äš’s §BrH)í¢µÆ¶¯d$m“žöŠÿ¢ôÐ?)…~L�É»²öP�mÉzófÞÓèï›?û?qç­³ 8 s.D?_ Õ*Ÿ/e#r¸ª¥[Ù:Xð¤ÅÒ ¸¿7˜'Ýï�W�]URUÝî£Ë•@.f.°ŸÃ‡�I"°„¢Æâf&¤I陵ÜÕÙY4|$š\ªR§, ˆC·�ÿ#—öQØEûõª¦´öYKQ'ÀFºØ6ÒR6˜Fž5ï­t5èÕh‡¸ Ë(Ê^D,€R¯ÊÜ�i^jžWñ‚T mÞHë¶á‡ÑÚ�Þ¹�Û6ÒÑ:Ýpd+Ëx,/ØÀñÖ¿,rSÔ3ë 1MÙ”ìôÜô¡E„®iF`ª0èèéºw/&þ÷1µÁfwí^÷coÒ]<}?‚(Jò6Coä¼%SEºí¤¢ØîûÞÑ$´Fô0ä©”6(X÷ëùt׳ùÆ"¡Ýé“—«�sh·�¤Ó“Êae¸“Z½Õ°,ûŸ¸¥’î~Ù7hò®}ê…¶†r¤ìªFj�¦Ñ·¾«ûKQó/|š#ª·îlä™oáýŒ9ºo‡ÏýdÀá�téi–ü®O4С xœ340031QÈÌ+IM/J,ÉÌÏÓ-I-.ÑMÎÏÍÍÏÓKfþrà…Ü%µÖÿ?&†ô³ê{<6į)ƒ!Þf•ÖŒ]ÕokϾûvô^X`8ó„-PM¹©Å@%I¥™9) ܯMvÍÕÜ:Ocöƒ«¶ýq•U7Mf;Z»‘xœ­X[SÛH~÷¯èñT&21`¨M†$UÂ Š°½’Æ”J–Ú¶YréñÎä¿ïwZ-«eC&K¥*V÷¹~çÒ§ûø€¹Ë(g<)¢Œ³ys†Ï8 x’ó�•IÈ3vmºv€l˜®7Y´Xìtp:`Nº^FœÝFIÁ3I2åÙ*Êó(MHÒ’g|¶a‹ÌIØgóŒs–ÎY°ô³ï³"e~²akžå`Hg…%Q²`>  Š( ²/OçÅ“ ý$d~ž§AäC Ó \Áx¿ }d~δbÉYבÝžPr?fQÂh¯ÞbOQ±LË‚e|\ǼhQd%*ò)öÐÖ±¿9P~ƒl|cY0•ú#q¯P|Þ<š§ÞÚ/–_Nß¾ý ¢¿ßÏ*´®5 Š¿@#íyL#ªÌ¨¹Öcuþà@^ˆ^|À(&°#ë'�Z÷Òýd9žk8®72ínïL0Es´XI\ ÂÈž'“ÉÒ¡–15a¾Îó\Û¡í³î«üxQ<Äy• rU¿U§‘6Þêw*:SK¿ñžX×¾1žáüctéÙ7(ükC¸×¨i±|g<ιâç3@튪Y·xý²eúûo$fóDÁðRLEòBwuË»¸vwC@ýtG[•!:0ÜjëÕǰ]mOÛ{œÝIY‡b/Óö‚!)öƒQ‹ØÏÄ=5I[È÷NGägÎa€T£©™)×ú­tåßÖ8ÙxèÕBkçÒž¼Àè‚PvInuæ 1¿³Wù}Òí×ß°à¸ö½éÕé/ºG½V‹«¢{ƒÁ»wïž×±ºªb»)5HÖ†)Dþ¥ëçÄ ¥{·ödlÝõzì#åVKÄS\‘ÓonFÜÿÌÜ`ž¨´Aœæ*Qíþ^hü'SbS· Î~xÛ0(¨qxü‰g�Ú@üòy”p †í9æ v28ýWgÛÂfå|γ/ Á×&,€gQ,«3�Öž–42k¢_(,Í”ÔïUmJ'’é i•NöFÊWšUý—{(ð *`øä.ý§"€RÎvÕ+LMpê–tPßy}Ÿ¼V §?ÚÁÆàõYky/z‹´ØÆ«r ×æ¨Ìð×öŒkÉË¡­ÿf0ø¡YÚë¿ôWÁôæ�B¦t¾*ÿ€(ît•™«� Æ³{^'ü[áÑà -Ö©¸Õ0/ô ¿†H£NÕK2§‡WÆð“‡ÁS“Ns“£Õé¾=(ë�"ÿÒ-¾ªÈïÁ{Ÿœ—æxL“æ3GŽe¯Âª{µ$)þX£¦¦_kK“,oM8»)²g¬=¹�\ÛœNa. ܉i3âxYŠHàN´ÖšÅê…Ûpõòü0Ô¶s\¿ O_ÌI½Ýð?kš�ù¼Áï>i¬ÛK­ŠÁÐ2ôñÍ´íߥ…;æ§“Þ#Ò\PN¾L©§;¢nŽ [«\ïõYeËsLäÃ,Í&JÊbù’„“SÉ.cUmœ)ÍV {ucñfeQ¤É¶ai ¾2ûûl§wZ½¸":k·fÚoz¹ì»&Ô 3+ÂÑ,�wÓ÷Íkÿ�3¿Îz�SòæW™/î£BÐÓÌÿ7áª4B¤=q�ƒ¦ÚH‰ù.EÊO‚ešIоL¶;:ø® Ëò 4ï|âºtÿWî:m!HY¸ósBš+E[†øõ¢ˆê÷ä³aã×n�ÔàV&hy„ª´´î°ŽÄa“í^-?¦ &ôü¥U¼h/Aœn1—ÞP·¬s}øI{){­Sæ§k¨’¢pÉ&ž/lˆV{¸d’ÄôºAïlO*:Mâ ÝëÈâ3 'Ä£�ÿ@#3½ŸÉÌ¢×?XE N3ê &jôñÃ`(¦Ìúx¬�˜j\ñ® ÇÑ/ ¥5³#²4ã z¶ÜÔƒ ìú\ÆhJâmU¶ØL+sž3tî4Ëå[ñg?^ §‡âÕpÁ�ìßÐ윥a€¼:b¾EŦº?Ö� Î'ÏFû7lƒîâÝÀ�²?D³ßŽTÊF0­jƒV´Zo*‹Ah¿ xägC¤‰º%}ÔêÐÔ×õÛy„3 Ó'êÐì2©†hôJ¼ Z±ôé:A/ÉÀ@fÌQ¸LJB­9•ÔªZ³ŒjÎ)ûí7*†" Vë-?~9ù hi¸ëî?³s:ŒRsBmŸ"‡{,ôáu$HÑ®m*@Jg“k¤Ì!þ.3ËVßé²ÔG-ÓU®8"ó¡/[Š{±ÕÒÔÕ¸Ò|;\…ú¹´ý•)ØÊ>kGŽÅ‹˜æ`,ª¯P€Z7ôÛX''sŸ’è&½óbŸ‚„Vqô)˜ù3x ΃1ÝédA8]¬gAxK×° £”NÓÈö®?agK?žÎqô®ƒE�Þ�é&HCöy§­¼8 ¦ë…Ój¯¢Äü nà ¼‰�â/ý0�2ò?á@ÉÜ[,”·ûØñ›F«û8¸�§4�3Âk̼ë…ßA!¨é –cšyKïÖwV¼ÄN­gw7÷�xþ¦i…Æ4 ÓÇ1¢ŒÓ'Ó» ñÇäÅA ¹‰#¸çtÂ"rN`ú�NõëŠ@…ÏëÄæ2ó½|%lüR™Kzuvv®vX!;Jý$ÝLQ‘Ñr…›ùÙ9nT%ß¾„i×S4ØÛÏ—¥x”Í%ú»,'ÅàÅ¥Åð^fúpÐÕ«‹`u…ÿIñÓKaaþ¶ð(Kôúë;cs¥YtvuE)�+7.ƒ’ÉUc¸ a±¯ù¥e)Fë´ÄXs¯dE¦™¼4²�Û[à[ë ‹Ëðèʯ5¦TæO A£›%ú•¤1b/G´«¬ÚݤdÓŒiÐ)~ ï̯¦ãü¤ùd�w¢Š¯˜ã¿Ã¯ÃÿLà¤ú¿(p¾pqÂ¥Þkû¶ÌAêÈ;e×–Øè%ˆÁÕ0ü•Dkõ˘��ÐÚi¬?)²‚X²Ùçî�°Wâk¿k˜Âè)ÄéÜŸ~ÜtÜ=n÷døFtN‹^ja@°X|¬D;rM®‹KË«±rµÅâ·î‰ëù˜.â£ê6eGÏüK«¬E¯°£¤‹:Éüô`¹ì¹·¹5ÜWݼ`©\{Ó� ~&¿¿¢­r^8ýÍü/¿�†|3úÞÑ ªºu¥x}ÐU&_²¥J î탆m„*»ãpß ì�Éd2ú7pú‘þØl>y/¾M6—µ?;F WItÝ“ÇA4ÈÙ<à‹ñ‰¶{�¥Áô÷ìeºiÐEgÞÀÓ¦÷3Ä ò8â‡ÆÅÉ7¿ÖÏÂSÿmNþ8C·ö3~Îäú£F¢ Ä,ÚÒnŽN)ˆÞfæ�>'X‰ìs~M•ª1òb�0ùÎÅСâ`™¾ƒDY€)p5`0–gd¸D›S±“ ÷wªLSÏžb9ŒÞ{©1‰1  %#‚x`R€XDmŸÓrÄg’Ð,ǹØl³/” Ô à@!¢`;JÆÌNjò*,ª2O¿ä}­Æ¶ú )Œf ŽÐådYÛ•â[î;÷¨Óâá‹L6¶M×§ŒÒP‰Q Y¡ÿœ‘É­;GÜÊyß„ZÑõX╹8uxš¡ƒ~-Ì !GiR]H“ eËÒR æé«ƒÀ[Ï‘n±í—½¯• ì =㫊ˆ¨«qÆ$Š¥ $„ñ²Ý­¶íÔ܈wOÜóTü}�UÖÖчc�é` „8*‚âƒKÐ N{޶žËÿ>z¯8Üïñª:Áº"¯öAaJµK+Ëš:Zƒ" ¯Gail6ÇÊSnÌè iCïí¯’³¥5}¾KWžj.ëõ-ßmg¬%!ê4¨U¿ 9ÀŒqu~oÂwæÕf¼�Ûüí–Æj GÀF…�Ix–ƒäÀÈàJ¾ß”™¦�%Ü�—šUÍSê7�Î+8ÄeYåLë=ZÇTUc?DüŒß²:«wpU…>ýÍ $()TjÏa´¯èNmt§íø^Ù¶^âMfúI)¡-n]/!¢I”[nžØàPÓvom@‰ù¬Š3­É'Xã?XÒiìòÔëÑ’1±‚¨ˆðîŸ�–§iÈphTq ¯�ò™rÃIJ"›:Â!àÉÊ1J<\xØ '$ xŒ†4O#±éö% ·ª4"ÙŒïÂ멉ÉC Äýg…RÇr T£(�@Qÿ×éÈdÃäK‡*™ß„/¼ž¤›Ç}èö@”tÃ)‰2ÚN¥5Eìóòcs…)õǬ»LƒošY‡ëë)ô…"éÖø–‘’Ô>û¿µ}É=‘ÑŸÆä§‡��³`Pœ§HœÆ #Õ•g°YêqûfHh8–´â× e™þû–†"ys†(ºÆ>óòm6Ÿ9xá+Çé}?èË!…n Ès4Ïa a(�j{`ìT‰]&÷‡ê£ô“qŽêùkê\ô©þ…É!…²�¶GÒ�Õåx`§¯o°›äÝk>ë—N Ùeíô|·¿´;®a¸¾ ©½ÄÞl•èì/TÀ�£ky¹åxÆí´ü~ì^¬Ü±5,;¢ÀÐ…Õ�µ7Uz¿ú&w=Þ¥™P{èàÌ‘Ý䤥úzV;×tg‰º‚!qZ,«»ŸNi�wú�<×#“ö,îß‚£úNniÙŸhl0è€z°¦¢õê°mv/ó¢ ÏSÈs…_¨åíÂ�f¸¬ ðˆÚÙ.ÙT�úã!cæÍ€ºŸ%¹bÕœ' =ÜUº¸!÷êï~ÆÚ�¼ð¬‡˜�J±êœ¯gµã qd‘ÌX­{<0 ÝÌÚÑR1®mQ¼&Ù9óFž¯ÿì YÝ\k0ZX‚ R½nè39»1Òÿf}úËÀ=•0wýíè—†ò—àè]#g*XûŠXxêÖέóùMö=ÛðÔìÂSË öðºtþÀ«ç8\"×v0/“ér±"[•v¾ÖÄmÈ.»Ì§Ss6¿êŒÿèvº�¶]xœ�TÁnÛ0 ½÷+rëe<ìö%Ã002í•DM¤š¦_?ÚI—ÉNÒ°ó=Rä#)ÎFSEcÉ¿�Ôt÷c÷óiçÏóü ¬ ­©FXmôüí?”Þ ƒ9vöPi ¥J¡jLÚá5â‰*9rì0å°˜‚ ¨+Iy‰Ñ3šƒôbS~%x—L^8$¬çO™G¶Ã—¹ÄuX”„WŒmÕÈY6¯"r`[ynx7¢¯†©;xOQŽ�iòIrÛ÷~(7(RZéÌÙ?®ëcÀ[�°€ÔaÕíó0žÕ¹6 Í0Üy߯qå¢(]FY[1lËΘH‹#0Ј>Á¾1Ùß»»Ñ³CS“t5lF÷…N{Aoû–W .$Ûé>-ɰÍÿ A¦Oƒ$q=e»\·ìËåA®çþ–¤AòÈS«³œ®Óý.?"JöÆJ³ÒÌW;É+ ^I—Í\úÛß\)ÑÀs~[ìRŒŸ§Vå4� �â üK¥£ŸÓ<ˆ_Xê”TD÷k⛵÷\ç›å£DWP?¦v"Idu®ó×Ó_S³ñû肆xœ[^ô™wC2K}^rNiJª‚Rf^IjzQbIf~žnIjq‰nr~nn~ž^†Wq P%5-±4§$"®¡iÍR6Ù^LJB"¦i Öèìï¨1¹ÀHE&©£àæè쪣 ¤š¢Q:YÔHU*¯£êŠÕˆ „ 5È&(¤—d]”’žZ_œ‘_ô‡†{ˆw|¸§‹»kL#Vc¥p r˜<Üa`wc5—çj¹¸B\ƒCâ�}|œ�½ƒ5ÀºÁ¬Ã¥É9}…Çæ�xœûÌû€gÃAu׈Wç�x_×à`GwW�ªòœ¢øœÄÊÔ¢øâÒ¢´ÄäÔø2C½âÉsÙ$  5­¹€€M¬ @M“÷°MŽ`µ˜|“UHëN®aã˜|ŽMë!+èà@gxœ{ÀÓ,¾á5 [qfUª‚Ád/ÖT­œÄÊÔ"�ò̼”ür÷ïxÇH× ø`W(ÛÉ?$ÄßwòVQ©¼ÄÜÔâ‚ÄäT¸¥´üü¤Ä"%�´Ab^rF>nã\]Ü]¡¦é(„…º‚4é£)Ä?¡C�>®n!-šÄh òt÷™|†ÕŒ±vòqFÃÉ;XŒùÁ�ef` `n` iÍ5™‘MØ�º9úïl°GáZô‰÷)izÀ^…hš|‘U$4µ$äÏø¢ÔÂÒÔâ �¦pO Ê¨9š: 0Ð�‚¤—dÇC䀺@šá6P$�@�È IqHAHF“à1�¸ŒANà†:æ²Ò*QO«p˜ ”�`N !Q²é:�=91'')19;ÞP‡ …o„Æ7Fã›èpirö8?mâ�Txœk¿+¼áK½¸F¸:‡Äûº;º»jè%¥&–¤Æ'•¦¥¥)Z(XhZ+èë+gV¥*ä–—(ä&–$g(¸¸º9†ú„Äû‡†„†Ä‡{º„xè£ z¸zº{„(dæ)$ççææçéepq�,.Ïœü—Ea³ ëÆÉQ©“°5¥­á"ì(3Ü„ÅúÉÙ]'/à”˜|�Ãrò.}sâ-ª�ìÈ*:ÙŸKer=»Àd9ng&M.Ì!e§¸ßxœåXKs㸾ëWti#ÏÒò¼²ÉÚ™G¢e–eREÒãõ‰‘-1pÐíÖü÷Tƒ¤dy=“MrÈ!'‹@¿ûë|ö²Š@i¹FXq�À ^ 4XB#KÔpfx=€×0QõNóueáÝ›w?AªêŠ#ÜqiQw$ Ôn W’$U¨q¹ƒµfÒbéÁJ#‚ZAQ1½F¬&wP£6J‚ZZÆ%—k`P¨zG”–ì3je·L#0Y3FœY,¡TE³Ai™%}d¾�‘­†iÇ1i²ÁX&-gj¥�Ò美{#®HãËìÎOSX$ñçpLa触CîÂì*¾ÍàÎO?Êî!¾?º‡ë0šzü²H‚4…8�ðf1ƒ©a4™ßNÃhŸn3ˆâ æáM˜SÈb§°) » ’É•eþ§pf÷\†YD2/ã|XøINnç~‹Ûd§øÑ¢8 £Ë$ŒfÁMec#ˆb>Qé•?Ÿ;Uþmv'ξI¼¸OÂÙUWñ|$)| `úŸæA«*º‡ÉÜo<˜ú7þŒ¬K ή‚Ä‘uÖÝ]î(ŒÀ�ÀŸda‘“8Ê’y�ÅI¶g½ ÓÀ? S Èeßx@áŒ/]Ì"â‹‚V …ú8#qâ¾oÓà`Ë4ðça4K‰ù)1¥ôl08;ƒ×[ÔX4VJ»äתnjØpsZ+ãÀHÈ\ñ/XÂr×’0íp£ñµ%Ôü¼z÷ó_ÿöÓ_HðÈ :²EÃ�Uafî¸,Õ6êÏ4þÁ³SW(¹âë†úÂxpvFbVT`ù�uDÜÀHãɆÕ5!Ú­FÍËTð¥\ç¦Ñ+VPM+ñählÐæ[gA¾FµA«w°iŒ¥*ÑxjPÒÏÙêj’kºkV+Ô¤¸P› ·K2ÏU¥’‚¢AÁ±³`5_¯Q0(]Ÿâ–̲k oO™ª`–X‘~E­£øˆ‚$³•Å6d¼£ïƒøˆÒŽ÷Ñ�MjkûÐ;ø4ÄÂ�ä•îºÓA\Åd)P·-m]ñç?J"” 5øµÁ�‘ÀcŸ¶ºöãRTL®ÑPÈY�§VNhlh‚Íìà ʦµ¼ÏÇ–9K%ÁŠÂh»G6o•†•�Û-4˜¾n ”ZÕÔÿj[Ô-XA­H0Ñ·.ö K°ÖªlŠ6‹”®5ãÒXGºQÅÃSÍm¸F �Ì:¦­‚Z«µm‡IÕ4ÕŒ¦HÅ[‹—Íú� Wz¦áÏ;“ÞŽ%[ Ì÷‰ÊKlw~0J*½aBì@c-Ho7I(Ê-R�¼`ÒŠ]_ m$§ÀÖZh šq¶­hãpu Ù†˜H«±\¨ùC3Fm_¹|€ÚJlGc¹®˜A¨Û"è¦%¹/pe%¶££21GùC‚ÌUÀQøÈË]W³ìú•ée�-LhdåV¼�³}jß�Áï€ ÐØÜÜ2ISX¢PÛ“ó}qš£Šœ•}·Â/ýÚa+ìL›¤Ó=Âö�úCUÑúâóý o™N¹\ ·&Ñý:ɬ#ñÀVÊPñmÙÎжÂÖû݃–4�p4;i+§©„Ùôž5‹>š¬¡\³gEëóR"È&�ªw“.;·^Q:HJ'Ùµ8«©NUíÁ†Ù¢ëŠÔ7N÷=a¿Û€ZÔÔ·\o8®•ñ`ðC·µÀ�صv;Þ)UÊ)�[Éq5 ~(qÅ%ÂÂ�‚y~¸¡ýáÍ``h),çuWóG7åíkŠ‹=Ë£âe‹�œ.F'ƒßäãÙDªoðm\�ß�Œ ªÔ £ZséæÑ–i73G|-•>ŒMW뺑õI/—Z«º�¿ƒ(u3¹Û²�Gè**§Iˆ¶=9±4Nõ|bj 8g4âȱÑ,»Îo‚èvD·'Ù—ß…ÓY��Ú`ÐÙô:Ÿ%þç0»ÏÓø6»:>Šâ„Ž¢Ûùüäbðõ8H4U–¬xÈßìƒü²&Y~¤©? F¿m…ÎÛ¡ÎM…Bä�oa¼FÛŸµcùäbà˜göabÌB«G^¢~MíÉý‚�Îǘ¼?Ê%nG'{ÿ�î„beNÍ5/™e£þ܃a‰…j‘¿ÃR}9m¡}RI¼€ Ók.ÏáÍ,•.Q÷_‡œ¾ÝÇ¡×jì®íп؜•åÁ†•Ò¹)4¢9rÇR>tg9Å Äk„�x{ ÊPšÝσ¼Û®“½õߥÊI'”0±˜‡Ÿ–Ä>¬m®á#¸y…½ÞnÎS÷µiá’[ç@GÑ�åàw—½>�A °¹$yzÌçÝï,^¼ÈËdQ©o3ÓY@¼dÉmðK˜—Ù)Â=ö2öBÚ*sR¨æ´ iËîy™xžö¨—äTj›3!^àï©]køx(øgEàΘµ¬¨r«þ�.ð×ô­957øH•Àáïðþ~ü‘Ÿ@[ä]­öí“[Ü<µŠ¾É´œÞè¹`K£¡3�n†�ž#£Ûæ@+½,÷·)èÍ&ÞŽõë7Cçh¿Ý§Þ~«O=y#´ý©](%�ÊïÑ“äýÀýô/WO§á·£ûî{Ñm:.é¿9j÷'¢t üÃ�+ÕV>ƒõwÌzÿt‚Ó›Mïß¼çÇï’v§r/×~iéf>–ÏW'·Uw’_zZnYû^zþ®”¸;èž°ýºyôØlÓøÿÕ?‡±ÿsŒeAšå>ÿäO®Óv¸¡vHγïwϾß?ûþà Nÿ‘@½î;‘ xœ­’ËNÂ@†ƒqUƒB”d¬—´P¥Æ°"šlQ.A„头#©ØBèÜñg¡�`\é3ø®}WÆÎ”rQ‰›^Ò9çüßÿ·ó‘xX¾=ðÉ/‡³äÓ¿µ±Š dq³fXz«â=öLyi…y ­9òKêµÞðZÄ4e9yvÌÿä݆«3¢ÿ é{<×ä‚Kxœ›!¶I`ÃtE-…ò̼”ürk®â’Ä’Ìd÷’ìðÌ”ôÔ-…äŒÌœ”ÉóY”&ÏääW¯*Ï)ŠÏI¬L-Š/.-JKLN�/3TÐ+N-‰/άJU030P050˜Íê& Ö©`«�^’ Ô’”šŸ—Z®¡”–Ÿ¯4¹–M}ò{VN°Mk®ÉJœ“£Ø&'q q逌Ñ›ãÎé1Y†UU›]@y�îvE�N}°&¨¨€ì†ø-¾(¤KÂÓQ0¢ÉŒ¬Ò“û9%µy h ‚1ŠU: º† : “ Y &+°åMöc·Ÿ¼—=™I“ ë’Úã›HxœkŸ*ºá ‹”¾¾‚[fQq‰ŽBb^rF~‘BI¾BR~II~î作ú‚©É9¥Å™e©ñUùy© F“?±hO–a5“J,-É�G•ŽOÍKLÊI�lÅ*3ÙŸKer=;Çä¬ü““9t8Œ t€Ú &ïe âZ”ZœY• ²CÝMk®ÉQ¦ìÆF M&“§qxËõ—¥&–$g($&å+䤦•(¤¦¤c5¨dÌFVóɺlþ“p*‰B�@2¡$¿�6²ÚOng·›ÌÏÍ>y&§¼¤‰†&W5—Mr„WQfzF û�²N–ãN%\y´î�LxœU�1NÃ@EdG‘)¢¡$ "Ö‰ˆP .T4Ö:e-Ö;ÑîlLÒPä{8'¢ãl¿}ééÍh¶‡Û½÷¯d_gÚ»f…Õ† BY„ïä,¥—oÒ3Uÿq…FÖO»ÆÌ©ÞdÄ÷Ĭ$[�PiÒ"²­Ôz ãjÏ0“jb…6B3Ú %XIíÑý-{R�ÿèpLÑðAÂÒR<Ýæ ]9ÂÆ€b^º©‹†•¯ÏgÔŠ®í:±à—‘–k´#§Pk±ôq\]‡�ô¶7wéAxȆ½Éo; Ôï'eü'<.eQä0)ŠðÙ/Ãñ.ì ³~buå ƒxœ›*:�ÃA–É�l.“UX¿MþÆ&2Y�ÍÃÛ)¿¤$?W!7±(=3O¡8#¿4'E!#±,U!/_!5--5¹D!?O!µ"9§´8(\•Ÿ—ª�TZ¢P’_€ªmr ›½œ©�¦5—¤—dÇç$V¦Å§–ÄCTN>È©,kR‚[‡£éd].3&M.Û>"ê\ŠBxœ…TÍnÓ@’[¡B+˜*Ùi’:‘Š@¡´5IU“T‰CÕÓjco’Uíµeo’¶å*ùÂ#på%8qàÈ�áÆ®�¦)ô'RäÝÙ™of¾ùùòè×âiãlyþq@{}%­ô6Š˜8¨å1òÍ(ß?·I—2{•ºn š¾S­™P,iétÈ1§Tùá>e¶7ÊÂ(þ–§OC�ÚÐÇÌvê 8÷ò†Jlc÷‡lb“ƒª}¨ ã›±d‰¼¨é…Oi¿©A¢Ÿw^PÌ9¶úˆ{h‹•ª¹‹>èõ¶â6Psp[o†*"’ ]/¥G oA+‹Ï(jâ°¶FUH<ýëíÝÝÕ‰ ”7# ¤Bôûž:žx’ò‰øsâ§Ç'a¡°ï�vœ$¬òôY^‘ïùa~Uò¾¿³]ÕM%IGʶwQµYù¸cæP«Ñ6k³²To4¥H9£²jÌdtk~é–�¦WOFN€|L‚.¶¡Êpé  4µ­Ì?¬€`.k×í’Š¯Kš,½Z†õuˆuÝAÈÁÅÜêöþ¾Ò6L$bÚk›2p³”�å¹®Ç ýÜÅÂoH¾’äDI&îDÇá�ÃÑóäA™ámB+¶m§F8õàIBÁfÛ4u´×Ô[-ô¡ÒÚvQ&¥�'¹ »ø¤L[NðkTô&jÕtØœÍÆÞŒÃs[†]ú‚­©}ÆÇŒ8™Kµ1³ú^âj|™/]”UºÊ�Ùlë7A\‰`èïÍ› ®É8ÆhÊz\BŽ,gÒ!A'b)L±f«)™öï®D�ÔýqòÅÿ^XÑ_=†$– #?'2Yùx9äãÂfD°h«b›•­]å’¢^œíisDû©WÑòüã§Gv�\ÔÁ!�‚l)˜4|ÒßÑ×Ô²øÌL�TŒç¤z|J”íé](¸sæXL‚ÙƒàY‡²Õ}/¤\ììŒðq!avšv$þr¸Ð=GDtioˆ“.HGKñ$&Ô ñ\ƒc !Iþ‘Z›+mDR+Ñ÷´6§¦ÿL Ý–ê….xœ»!±�o‚ÿÆB –ɇY¤d‹KK2“ÜK²Ã3óRò˵ŠK“ŠK‹Ò“S­'_e3P*ω %ççäg–ä)襧–Ä#TiZOþÈ&¢QSÐ+ªkÈÌÏSÐ52P06*œÏn0YŒC¾žK�”Û*¸‡xLJ{ú¹ø‡k¤—dÇ—ƒŸ—Z®��‰ð ÐÔ´k)KÎÏ+IÌÌK-ŠOLI«tö÷ qôôs Ò@r¡XqNbRjØÈz¥`¸¤²yPkAŽÏÍOIÌA2DG!$(Ô»Ú’¢Ä¼âÌÔ¼’ø´ü"=E躤Ê�Æ—¥¢¨†‘0�&;rNvåRæFrþd5&M.fË—|ä �xœÛÆ×ʳa!Ëäý, “ýX='?`Sæ.ÏÌKÉ/×Ô´æª�¼�EœÕPC“«z²«ˆ\UyNQ|NbejQ|qiQZbrj|™¡‚^JjqIQ~åd>Vi°(\h†¤—dÇCL�OÎÉ/NÕ€p&ó±« �ä“sr’“³ã u¸4¹]²0ì¦"xœû¡½�w‚-£ÁF;e–Éÿ¸Ô&ÛrKM6›¼Š‡k²³�óäI<“ƒ%'æQcR0šìÀ§59›Oò ~U°Ô!IÕªòœ¢øœÄÊÔ¢øâÒ¢´ÄäÔø2C½âÔˆ¨‚±¦5×ä‰|æìþa®A>Ž‘“'ˆº1irã×'×ï «HxœkŸÃ·á Ëæß,|Œ’©É9¥Å™e©ñUùy©å™y)ùå: ÆF“çrJOfeWUÏHÍɉ/3TÐKO-‰ÏI¬L-Š/.-JKLNU0TPJËÏOJ,RÒ´ž¼—SßÕ4 )šÖ\ @ààêïëìèîªQž7B/9?77³dò^6ãÉŠÜLš\7Â4ñè�xœ›*úG`ÃA–É*¬J“UØ&e5ŸìÏ*?y>«—…cp°kPˆ†bzIv|NbejQ|biI~|jErNiqfYj|U~^j|fq|j^bRNjŠFyf^J~¹æälVKs¨f’õ*²YBôÆ»"iOO-AÓ Ó¢£`d` £ ¤š¢Ô- ô‚êäol�õ$›b5EÓš‹KÈôP;HwB_1.[AN‡*';°u¹Ü˜4¹¡¶á€9xœû#ð˜oÃk–ÉZ¬ò“½Xý'‡²k±è((©¦(M>ήÃbh¤i=Ù‹Ý”ÉÐhò6ÉÉ~l²“½ÙM8 �Š˜ jP4êB9t ¡¢&jLš\Ö™£ì^xœ{Ìw‘gÃA;׈Wç�x_×à`GwW�ªòœ¢øœÄÊÔ¢øâÒ¢´ÄäÔø2C½âÔ’øÔŠäœÒâÌ²Ôøªü¼T]CMk®É{X¦OþǪ;ù*›£Ñä læ“�Ù 3­!åƒxœ›Ãw›{Ãt&­É3Y4lõõB22‹Š3òKsRÒ3s’R“K‹SJ2€8?;5¯X!±(U!3,R^”Ÿ—®�_”’Z4y"›ˆ¦^zjI|NbejQ|qiQZbrªBUyNL(#5''¾ÌPÓškò–°É‡Ø<˜4¹,.ôç�Èxxœ;Úο!›¥>3¯$5½(±$3?O·$µ¸D79?77?O/C‰‹«¸(ž¬à^’ž™—’_®¥P¦­áReù™) ɉ99I‰ÉÙñš\Õ\ @Q§`«�\”šX’Ÿ’š–XšS×д«J/ÉÖ‹ÏI¬L-ŠÏÌË,‰OË/‚©€PPuŽÁÁ®A!ñ®�“¥•¥9a’:“U”yu”TS” *'¯S‘æ€Êƒ¤å¥üýâ]\}ý\›%ï WŒb(Й@g¥¤§–Ägä—ǽ«áâîéâî3�óŰ›�áèÉuJœ‚D˜R§7ª–‹+Ä58$ÞÙÑÇÇÉÑÙ;Xl"vt¸4¹çò•‡î²xœk?Ç¿á‹LFjNN|™¡‚^zjI|NbejQ|qiQZbrª‚ÑäÏ,íÌ!þ“÷²Õp�åŒ5­¹&odžƪÂîæäã9y.§ôäë&P%%ªœŽÎÞîAþ¡~.@EþP†º“布™4¹”Ø,éá}xœ;Ç?‡ÃA–I¬õ“íÙ¸4€À18Ø5($Þ5P#½$;>'±2µ(>=µÂÒ(ÏÌKÉ/ל¼�M¨ƒ�MGAI5Eir«Áälf“ëY¹T‰6€;Ä?¢_Ózò|6’¬Wâ@Òjaèâï«iÍEŠ’Ü`]P/”±«1irGŠV ç‰@xœÛλŠoÃA–Éj¬fÜå9ñù¥%¥%šÖ“Xƒêƒƒ]ƒBâ]5ÒS²ãS2‹ r+ãÓSKâóâsóó2Kò‹Š1¤RRÓKsJ445u u”TS”4­¹€À=%Û¢KA ª]ÁV]?Th®#ƒu  &B¨áîâïïëïçâ¤5Aè C6aq˜�å™y)ùå: PþäÕ¬“eXXó2s *EÐUú…úøLÎg3cÒä$×cRá€Gxœ[Å÷„oÃA–Íì¬BŒÆŽÁÁ®A!ñ®�é%Ùñ9‰•©Eñé©%ñ¹ùy™%ùEå™y)ùåš: ~¡>>: JªJ“O²©N6gã3#Z/TªÝŽÍÀJÈÔ~’MÇœ$íH.ÿĦƤɤ~Uvçº8xœûÌ{†{ë䛬ºÉùyÅ% ɉE Zy‰¹©ñʼnɩ ¶ é%Ùñ9‰•©Eñé©%ñ )°Ì$VQåà� x×@ „r% r]°rÝâŒÔœœÉ§Ø4¡ó%Nëbxœ;Ãý‰{ƒ«JNbejQ|qjI|^bnjqAbrªFyf^J~¹Ž‚RZ~~Rb‘ÒdvÖ^6{òbVM˜ lï ´Axœû¡ýŠ{‚-£ÁF;e–Éÿ¸Ô&ÛrKM6›¼ŠGÒÞ9È3ÄÓÙÑGÁ½$;<3/%¿\!³X!/¿D!Q!'±2µH¡¸´(-19UOÁ71;ÄKU¨Ì/UHNÌÉIM™œÇ'Ū ¡©i=Ù™O=W’óóŠK’3‹´òsSã‹ €ÚlÒK²ãÁFƧ§–ă¤À2å`k5­¹@ºƒƒ]ƒBâƒC‚â]5úu”€úuÁúu‹3Rsr”&OUcÒäPDItã˜Dxœ»!ñ‡o‚ÿÆB –ɇYÔ'?fÓSÈHÍɉ/3TÐKO-‰ÏI¬L-Š/.-JKLNUÈËÌÑ´žü‘M›#9'¿85ÌI w òóôsWpÎÏ-È/Î,É/R€È+€µ+À´'¥¦å¥*§æ¥dæ¥+dæe–d&æ(è%çç¥e¦—¥jZs)Ayf^J~¹‚­Bz|~RVjrI|QjšÆäuìâL@y9¬Ør‹Ò3ó&ÛpÊqû¸º…è(ÁdENËÉ✑“}¹\ªÁf9»…Ä»j¤—dÇ—g¦€|¹‰©)î!Þñáž.î®!k55uÜ}‚]u”T'ËñX2ä`î½+xœ{ÀsŽwÃ|¹øDk®â’Ä’Ìd÷’ìð̼”ür-…r0Ÿ4y>‹”“89ŠU�.Å*3¹”Uµ>>QÓš‹Kƒƒ]ƒB4ÒK²ãs+S‹â3‹¡ ˆ& ˜Aš@H êHÒ„ÙRZž™’žZ_œvÔ+Vf ‘“/±ê°CÖN^À"žf¨¡ÉUMswM¶`SéINÌÉIJLÎŽ7ÔáÒ伨uåÀpxœûÌ;™kCËä•,®ŽÁÁ®A!é%Ùñ9‰•©Eñ™ÅñÅ¥ùE%©)ššÖ\ @@@ÕäÓlã¨xœ›!¶ChÃt&-…É3Y4&ÏääßlÊ:™‘UA«¸4iò69 �›šWªiÍ¥î%Ùá™)é©% ZÉ9ùÅ©“ 9Ô&§°ë²BxylR“×±+ŠÃ4é(€Åã3KRs�&LVâÌ�,Ã*)W‘’_žŸ”Xœª 4-$P\Z”–˜œªi=Y‘Udò N ­ Œ/3„(,È/(-P)³ Šá| ¢¢Ä$ èN¿ÉǸ ˜4¹ìCJ¡ì3©Oxœ�QAKAEq»—ˆÌ¡Æ=ÄazH3Y,¢%\%;Ä0´ÎÈîlb—~BÁ\ògtïÐê?´ë®¤�( Ìæ½ï½ï}ß{æ;ýVK”?.îSÉc ¦)MLå-q&å…*í\ßÕ[Æ•Ñ 61ÏæÌ–Ã<‚�ƒ&Äžïö‘E@�ô™K€G(¶éØÔæ6r@Áb´o|—ä«"'¥�±ç%L<î²I¾ª€àt ½{«7ÚðF7ÍzSמǎ g.óøTZRTÄgêò%,}¡Å†CD±¦ÆÂ�ù|äs2úGÇr âD=›R!¡¨ç¬¦³nÇ6Ål jÀÚ�˜ô‘ïpšò].$ÖMSoµµÜ€?Ã;D£ÁÉi‘�‚QVVdÜHä<&.Ï!î=ÊJÅbqö„¹£¿(�ØVÄ«¼_^½œ¸�•þ{1o�àÓ°ÌÆcÈŠSEIv q$UÖn”bÄP´ŸÏá·¼nf×µû¥t$aãàÃxœkß,¼aK\qjI|b^rF~Q±†{Ivxf^J~¹–B9˜ÖQHOÊÏÏIMÌS(É/@â%å—”äç" 䤦• q‹2Ó3J4¹ª'od5çñÐéŸìÈ*:ÙŸKer=;ĸP> È ¸?7;+Ø”És9¥'og1aƒ¸ròG–èÉ'ØT�¸€Ùõ0'»9ú»b£&Ïá”™ÌÊnbÑ¡`¬i�Ó�� PW‰nÄuS¨†„Í€êq&Oç 5�é4%Òv¨FÅÉÓ¹LÌ š ðèÅâýÉ"ÜùÚ�Jå®@xœ›!v^|ÃB–Œâ’Ä’Ìd÷’ìðÌ”ôÔ­‚ü‚Ò‚øÜÔ¼Rk..}}… Ôô¢ÔââÌü<…’Ôâ…´ü"…Œ’’‚b+}ýôÌ’ŒÒ$½äü\ýòÜòrýô’lÝœÄÊÔ"ÝâŒÔœýÌââÒÔb}Cs‹É X'ÏääßlÊÚŸùÛQ¦É2¬’“Wp¶Oîç4˜Ï•>y!‹Ød}.Y°ôR€–åWjZ+\š¤ rÒdEV‘Њ”ôxlŠ|� ¸€èŒør°wâKór 4�@¸$¿ 'µ,5GáWÍɸ"n&bˆã°2xœ›!¶XtÃA–É39ù7›²F±L–a•œ¼‚³}r?§Áäx®åLš\Ú¢ 6ì†xœe‘ÁJÃ@†Q´ÑàÁC¥ ¦¡BÕÖ‚§ÒB+Qð ØÖ›°¤Ùi›ì†d·¥xè+‹>„7Á÷ðì;˜¤í¡t�;|ßðÿó—ÿ8|klU¿šß;E`žoÕuHÞÔc”O¡CÂû/è áÀTó\A½çjsß™aDbL¿ã0»>�Ñ\`eèužì¥©ÕíÚ�±Í!êÅa“a‚2pæ Å#ŠGú´¬2\–Á8£ÆR7c2U?¹’*i õ©hébjÕUKË«ç½cµ›;-@ÕH(.E(SY,">Cj¨¢v2¯™–þš¹*h£ëÈaŠ�¤�µ4 xÄW…Ä#.} #g‚ÐGdàHÁGx®ãû3�,pÂ) "dÜE¦¢€d"ݪ~÷¯î׋¹%í;ûº·¬Ï:o&e—K&6â¯Î!Yz� Žô›mKÿ­¤•ªâ�\xœ›)ðƒ«XrQjbIj|JjZbiNI|yf^J~¹Æd=V½¨´ü"�̼…L[k e£àâïãéìáêãïêâîïêïêëäT¦­­©�^’Ÿ“X™Z_œZŸ˜—œ‘_¤1\G!srë-¦P¿Él�ìŽÁÁ®A!“ÃÙ €|þÉXx&;°ñøæç¥e¦—¥*XX(XjZs)Aqj^J|r~nnb^ІÔõù¥%¥%0•J: J‘xˆ|ŠÒä‹ìLBNTí ËMxœkŸÈ·á Ëäß,ž“/²ÛÊjZs)�cp°kPH¼k FzIv|NbejQ|úä‡ìÚ\š: †: Jª)J“YE'ûs©LÎgc“5"¤‘¡¨tò~6½ÉYÍ•Cüttñ[{�U�¨Éfin5&M._^4rášexœkç_É·á K½kD€«sH¼¯kp°£»«FUyNQ|NbejQ|qiQZbrj|™¡‚^qjI|vjeR~bQJ|f^IjQbrIfYfI¥‚¡¦5—�gP’I™„d�РÉ{X‚&od š¬È¦ ¤�Xüüý\'¯b󜼎•gr»*õZ0ç‰QxœÛ,æ� ÐÉRìÊ“9¬- ¢¢`Q/Îp&M.+”Œã&Ñ(xœ]RÁjÜ0¥°ía¡ô†äbÓMÏ¡Ç7�Ù Á¡G¡Hc[YG*’\Ó„°±_‘CK?¥×Þúù�Ž¥ 5²%�fÞ{ó¬ç·Oï~ýœmŒfy¯î1)ýú‹’-z8ÜAûÕ(íÑ“Üótþ0z’oFÉ4„NB ”T©¥ᨕk6ÆõGhý´™ ØôÆx#i(ÝÀbu§4Æ tPÖà å8®¬7؆ œ’Àw)ƒ±S¢‹nè½¥§qƒJ·À½ç¢CI[*B0ƒ18oî‚bb ˜µ¦§ó›[„Á)7’Ë…0ºQí`Q¾0 >8É5 µ¤eÐ=:#ÕôÈ£¶=óÐ4h]è‚íº`d°°“=r½Ù™Â"'sêNõܾ¤B²÷3ƒ"?¿Z±bµ¬Ï–5ÍÕêŠåÕå§<ƒÇÇá]ý�O¢óÖ|O„¥ãÇùÜyî•€é/nÌNΉµÕ¼gÔ¯&’’­N?Ÿu‰Ó vwä ƒ’yU�æÅE²¿;”²¼®ªíßÙûíŸ×é«Çíï7‡ÿt™ÒÇ¿xœ��AnÄ E÷9…¥lÚ‘’bnP©ëŠ€‡¸Œ°3Mn_Géf¶ÝX|›çϧ‡OÁóº øý:$òXÇjMízøÀ䔞ÊP£WhÌ ®—ܲ3-0FRŠ…›Kºð�£¨Û!»Ša³[JP® 3ÅðÞÝNôª×‚ë¼lsw›{“9XÁ@ÊÍsyP4©û í9Ù­—r&èáÎæME oIÉüàonév ¢�J[�Q¸ŒóFétZ�â ?ÕQס6zNâU•ÉsÀr~ê—wöÖ7½Oxœ�SM�›0½ó+|3Q�íÇ ©—j¥Õj÷Ô�Sµ²3 VŒ�œPé÷ñÀ.ÿñ™7» Þ6Í5™wíî§ü—h×êäÀ$Q5(º­À"à?®É�œŸ“ÇÎ�ßFhè˜Ïs®¶$Y'-ƒ�…¶bhþ_Ф1ãäÒ-r>f-ô­óÖÔ®Š"Í̸¬R  xœ340031QÈM-ÎÏÓK*ÍÌIa‹ÙÀ9õˆõ½ÍJÆç Ïðõ7„*ËOÎÖ-N-*K-ÒKf¸]u¸à÷~ËÝOß•U*ÞX†åmçr, 3ÚŒ’šU æ‡ïn;}àcž†.óÿÎÕP…ù@E™)©Å@órv¬`Ê;?ÕZâ¶�àÄÌ£­Êå[bÖ?–¹xœeŽKÂ0 ÷9Ev¥RÃ�8 TEÁ1PÇ•�ò¹=�!�woü461\¼¢\Q¼ ¨ÝÚã�PWÆÖi¨®Ý¼^Ÿ›îÂrM2DÔŠZcèë­J¼#L%þ{ý<ÒÙ%ŒÂ…�ÓLß]ðANº±ûÆí2»)OŠÑ�Aa©Æ~®E1GÌ0à«| �r\Žt¶ LĹoͽ�X3·Èxœ­Yms›Hþî_Ñ«Tl¤`ËñÝæÃ*I¶pLE–\¯7—¤(ƒÄ��}Þ$ÿýº‡^¼—§rI0ÓÓïýLÏx<gåÀ’"Ê„QÌ_ãÈgIÎØ'ËàÚr`„p‘îž²h½)àìôì–én1¸‹’‚e‚ä†eÛ(Ï£4!N–±Õ¬3IÂŒ1HCð7^¶f:)xÉìX–ã‚tUxQ%kðÀGQDY�~y�jè%xyžú‘‡ü Hýý•÷ ’Gêç ƒ¥X1r!óbˆ 99�Q±I÷d,/²È':ùñ> ätm#!�–sûsbºÏÑÒS‡mD!ý2nÖn¿Š£|£CëÕ¾ÀÁœ¹cu²cœf�³8&êÍm­µã4$eG-„‹¸ÜÇMºmZ‚. ÷Y‚"_¤è2.ñOæ4BäaÇé#™æ§I‘Eùo"lÎ{«ô�qƒÊ'i�ú–zPvuhÅT¾ñЀ^CáècO±)#ò£aviÆ…¶m=‘J\™°\\:w†m‚µ„{ñ»55§00–ø>ÐáÎr®· …mÌ�O°¸cþ >Zó©æ7¶¹\ÂÂëúff™8fÍ/f·SkþÎqÝ|áÀÌ„F¦Î‚ ¬,sIÌ®Mûâ _�skf9Ÿt¸´œ9ñ¼D¦Ü¶c]ÜÎ nní›ÅÒDñSd;·æ—6J1¯Í¹s‚Rq Ìßñ–WÆlÆE·¨½Íõ»XÜ|²­W\-fSÏMÔÌ8Ÿ™¥(4êbfX×:L�kãƒÉW-�‹ÍÉ„vwW&Byþ]8ÖbNf\,æŽ�¯:Zi;ÕÒ;kiê`ØÖ’ri/�=¹W,8\77K.äêfD�„Þo—f­ËÔ4fÈkI‹Ub éøàà…H lSÿþ8gÙËN6eæmè'E|²y¯ åOù³¦h�Á*M9é–Õ3û1v±Äv±÷4ñï`~;›Mˆ«Ö‡‡4 ݱÄõÓí³ÙÅÅÌÛjÃIÅ'cÿÞ# ¸XYÌ-àÛà+%/ –¶eyî­ÙÄÄSu–‡û„ã ò‘�%eÍ ‚°%¹Ÿü˜¨ñ‰6ÇI4áÑ{ŠÉaíç³_ýŠ&;E&œ@Faêî¼bÓ%AÀÛ¡]ìÕoǧȵaÃ%ÄhéD.]³‚%Úàƒóq¶tsé¸SË K»£´_$µäDŸL?¸ö-–䵩²øÑeß¿“Gc–h5ïwïàT•pi8ÆÌ½¼vÚº<µdÁv�¶! å¬hÊ5–KÓv´Ž´·¸ž Ê|—áVj­é0x™�×Å}œÓÂc1ŽxVñi2èİÃBP<Ï¢ãIÒdòãà@Dó ŽÝv&j½uÁ[€Ðó±2ªG½“.‰·£?S12Š!JC¶a–œNðçm-åøý–áF`uï“'_½R£Où‚júÛ�Æ¥w柣¯'4×M¥b»1ùŒZùèªÔ×^ã¦ýÅÒP{vÙPJ~ä ©Ã!¹ö*ùÜ:é/\Øù ‚AB.´Ã¸èpX³#Lj阱Û‹zì‡RJyYRkx™c‚ ö °ñ°«ð¤'xH0h„¢Œ �OA�Aô€…Þ>.x5y…�-¥š‚”«ˆý^ᕉU¾cj§ûL¦àÙüã 3+ÝùiÐÈËç‘],Mȱ8�ý*o5G€Oy•šõz)r>î.Ô�VŽÇ`ñ®§l×�Ö‰‡ÉÁ&0ò'àw‹oäSY%GpxÈ}÷¿°àà—žb«]§xÔ-Gµ¥ÈëŠ7×ç†irl¨wVTz“ÛKMºDm¶XÔôþ_Å®Véà!Á•mw±&Ll‘­pô¾]`Рߣ =îB|y‹/  Pø«WrÃRSó¤—ð€‘v™ço49�°ÐZAî±*IÚ1îWåY½*>™ :wíÏ8KX­à}ì\�óÒ§AçÕˆ ª:ÚjP!•†Ü&;ñíC„Š—�©ÊÃNC…¶Ú]ÜÓ9 ¨ß °äúÈUë8]y±»Š’@Q©¬¬aþ««èY£¤Èwe$ ºx×·ãcݯv«ÛuD�K­ê¢Ö%’Óq¸(j¤û¼ ø?hЮ¡áè(TRbÙ”R±ì3Cã*S=+£Ê[uÂ)—6-×£ý;`ÿÑþçÆÅµä´RǺwúAzµêœ(#*µR•fŠZB�)ç¬CIlSë�¾•îã¤Ût$ÕÎ /©Wn!B©X§„Ê4D¯' ó ­y2c Ë`$ŸÊ ‚e}·DšÕ†>îVâ°®—Œ­‰k&·±ò§¡^�*MeëñwB›ºª éTܤÔÅ©³6;gIP‘åÙ@ R«ùõ`¤…´kÓ1»÷è±pïìÅ|öi8¤Ô‘&51RŸÏNù)Tˆû1Q[JÒ×bÇÚÐM%æaS­:?ËqÞx@'1ëi8úrzÔL?®I#E$á+)X¡ò»)YšÄ—Ô{qÛNéd¿A˜IŸk$zØpêc! „¸ ÊUº<ïÊrhåÏβÁ—ÓþµJãÇiN“�>Ñ+h_‘”�Vv‚­—ß·“þBX”08¿½¼4mwiý˄קgÿÕÀp0[íÃ�eŸº¯•ˆ¤¡mëbSb•kÌüF'!LäoUáJ·PÃ[)#Y®¨Ir:°¡e«'<»ä¤¦î@Ÿ¼7id—JøŽ_·Ó«îªìà Ö³¸ÛÿÖÁQ‡Ÿ¹½úi©¢ÿ’ôÔBo¨QPÇû1¢ôí°#ú[Ï> ?xAÿhÕ¶1¹×à¶À‘=ÐÉžC}«Îd2˜êd«#T§ÐÖ-ö­MòƵ”‚ˆÛ{¾¾KœÓ7oÞ4KU�Íú…kO 5á;>ÎóóÙââcB[ÖUzÇiºs½Ùšj•èNøÁ¦¦”M‹r´Á쯞ïf.¿ËvmÓ˜Òu=Õƒõ$ùš¿”`CNÚzQ¢ID×Ud,·¥­wQß�ÅP ƒz?)†ê{`Å\Ñ›)·‘Ê$¹)Oý{VÔÍ[ëoØ9®*�Gy­ ¡‡HÅÿÿ"öXz¼+ùTZñ‹�6óÆ¹í¹«êVIi¹¥—g¥jó¯­8|¦=PÝW1ÅÕa^èeÜ<®Ñ' ŒÃ"ËPî'ÃI[•lŸhMS”Iyžª :'©ÿâaÞÔ³×xœ­Vas›Hýî_ÑeWÝÁrâ«ÝÑeSDF6µ²ðŠ/r‚‘Äb´0Xq¥üßïõ€dÅkg?ܹä’f¦»ßëî7 gE«¢!Q©¢´(JAX–E&ªFäÔV¹¨éÚ‹ŽÈ‡Frs_Ë•¢ó×ç¯)”›U!趨”¨{“Q¯‹¦)dÅ‘V¢ó{ZÖ)Lr›µ$”­Òz)lR’Òêž6¢nà ç*-ª¢ZRJ ØR1¿F.Ô6ôÊ)m™)âQ.³v ò©b<¦ß�¡V‚ŽÃÞãØÔ ¹HK**â³Ým µ’­¢Z4ª.2ŽaÃ(+Ûœ9ìŽËb]ôì®óo8hÛ æiÓZæÅ‚¿…NkÓÎË¢YÙ”zÞ*l6¼© ksg²¦F”%G(À[çúÈNÛ0ʆ ªúiÜíJ®¿Ï%Z´uH¡}r‰’iÄ‹Lñ›/dYÊ-§–É*/8£æmß¶çé\Þ �P×ãJ*ðíxp6�­í�šUŠ梯ÀQãô §š94 Ý/Ѐ�¬5èÓ\;W.…þ8ºu—¼�nÿ£wá^бb}lÓ­]ù³ˆ`8Óèùcr¦Ÿè7oza“ûÏ›À Còò®o&ž‹=o:šÌ.¼é%}€ßÔ�hâAÐù°å¹!»vƒÑ–ÎoâEŸl{Ñ”cŽÔ¡'ˆ¼Ñlât3 nüÐüÂN½é8Š{íN£P±GîG,(¼r& åÌÀ>ÐüFþͧÀ»¼ŠèÊŸ\¸Øüà‚™óaâvPHj4q¼k›.œkçÒÕ^>¢Ú¬gw{åê-à9øŒ"ÏŸr#XÚÈ2ˆö®·^èÚä^È>Âs9ááë ð›º].õ÷� ¯g¡ûÈåÂu&ˆ²ó¡1·ôìèèì¬0ø¤ëM)ï+T�âædÿ¡Ûô¾d™er½‘M¡ ›þ.&äÊQ<QCQT'·<6 Jh¹¿–M†ÑRu·h²*ï1)XéˆÄ˜¼ÂA-þh1ërÂXºŒ~Óg�X´%Ž–â«Q–ªD;n›Z*™Ér@ã¢nTwó1ÐpW"å9©Ç'¸To9踨ò·dàþ·¸‚F¼}e&zP.RÜ#þö>®b5°LËü~‰+#VŸÏèKl ¬¸2_ÅêN9ÅFliïØ4bÃX†�Ó÷¦›æ!††a|þ×»/ìd™qÕpy3μA­:xøëÿw�Aø‹ýÎöUlw¾Có=sĦd–<ßRü8Ã}äŽÖyÿÿWˆÇÑÆNäLܯ„¯;€Û·7¯^ t~P ^@àÒÄ?31†°u˜ó‡‡a'&Q¡OkTºë]´ÍB$¥»_÷äõÌâ ÐÄfƒd‚ŠÇ�utt²©Óå:…L2�U?¾è˜w ®e5X?ücÛ©à´õ�¨«_ΕÏ%„q¸yü5_žb—åÞåøÉiŽ‘(—§wožµ_öõ´=-qG^²Û–õi™Þ‹ºGk¹ÜóR|o„p¢®võÚ– žL(Ó½Eý�!Ê�‹EQ Üßgn%þG70uG u¿ü€ÃC17éP |rr’œœt‡ÅGÜÄG,<`e[g¢.Hwþ�`µF¶éFýûq*±Ý¯vvmÅCfxµÐot±lÌ}^û|úŸ¤TpåJFù£… h¬®‹\ëÜ Ö¦>UûiîæACŒÜÄ .{ *] ~ÁÈÅW“œ0tƒŽB’É\$©Jô‘±O¿·|÷Ž~’?™Ã�K~ú+û6ŸµÕ¶úOø<[<ðÀõ2ž93¹b}È�î³™yÓHgò?äÐr-ÈýýŸ ~87?N‰žÑÃ_wƒãtb� úÊØÑÇ«ÞÜE�de�nï6Ð:ýÍŠ»Â#¢d2ë5~GºŸw0ÿ/´Ã†dµÖ xœÝ=ýsÛ6²¿ç¯@ÔI"»Œ?r÷úæê:7ŠM'šÊ’O’›Ëëe8”I|¡HIÙq¯ùßß.¾� EÙêLçyÒZ"‹ýÂb±XÀLJd¼ RB£,H(™!%ð5 ¦4JéŒl¢MÈuwüŒÂ?r¯’`±ÌÈ›“7'd¯—%ƒ(£‰¹¡É*HÓ ŽÓ’&tò@‰ 3‡ÌJI<'Ó¥Ÿ,¨C²˜øÑYÓ$…ñ$óƒ(ˆÄ'Sè !3¤/�çÙ½úÑŒøiOð‘Y<ݬ€x?Ãþ�ü”´³%%­‘hÑ:`�̨’ "øN¾"÷A¶Œ7Ihš%Áq84 73¤A¾ƒU zÀæŒÿ‘nRàétÈ*žsüM[ëÍ$ Ò¥Cf¢žl2x˜âC&Xù8Ž’Ò0D ÐÍxÍ©c0ØËš ±~ï—ñÊäD4ß$tIY›Y "c=þ/�føÁçqÆ÷ÈÚ4Žfr”þ(Ô6†÷þ$¾£Œ!®ã(΀^Nja�«V¼J—>00¡BjÐ9ÈØ×xJ�†4í €uœ°N‹¼I">¸d4¸ì ]Ò‘›áà—î¥{IZ�|o9äcwüap;&1ìôÇŸÈàŠtúŸÈÏÝþ¥CÜÞ Ýш †¤{}Óëºð¬Û¿èÝ^vûïÉ;h׌I¯  HÇÖ¡@ÕuGˆìÚ^|€¯�wÝ^wüÉ!WÝqq^Ò¹é ÇÝ‹Û^gHnn‡7ƒ‘ Ý_Ú~·5„^Ük·?>‚^áq�/dô¡Óë±®:·@ý�Ñw1¸ù4ì¾ÿ0&½K¾s�²Î»žË»¦.z�îµC.;×�÷.k5,C&¨ûøÁe� ¿ü»w}dãbÐá«\ǪéÇîÈuHgØ¡@®†@�℆Úõ]ŽEmj@ðûíÈÍi¹t;=À5ÂÆ:0ªôøÙ³ï„a�Ö*ž~y�ÒäŽ&GË–ö" ¢Í×ã Zo2|ñì»�EºnnÇÞ¨7�ÈéÉ3H°e‘éÍüÌ÷2òŸg~Ä«ûÐ[„ñÄ ÿ}ÆÞ‚w"igù·û`–Áà\R4ó³gßÎò^/Àjúå^§an¦ªWþöP@ÙzÍaÁ/d4¯ ®†aãS=+‚cŠ7É”‚£ðK˜ò·ë˜yàj.´ôW]¦Ÿ9ã4Ú¬`�&sJ½$©âot;¼ê\¸ÞpÐs=°p—œ“§üꟗï½ñà¦ÖÞ«x}3¸¹½±¼ëu>¹CËóŒ8°A¯7¸øÙ1ô#Á¸‚Þ(Il2”gjÉÆ=þªÑ®Q�púðæ‰¿j6ÙÌç :r|Ì}p–ùSôâ0g —Oº÷Ÿ±'ÉNâ8ßÁø(¸ÐÃÐå~•¿!K?ßL#B¿®aæ ²ð�h’Âø£ 8ôSÙKŽ[R³Å,¾÷4^éðÇ罤4h‹(mü�-¼,^‡ôކÕRB¨u¼Þ¬ëA¶*%ô@TÛÁÀI™PLÀ¢§ô`j ƒÏáƒÎ¢×цM�UŠƒ§C�Ò›Ç 3=†qDÙt} “<ˆÔgƈÓû4¡,â`x*ºä1‚T©?À'ú ±è”Ë VÂ8Õ ¦ÀS&‡aøSˆ‚Â3ÆP‡ñÚ*N0PˆæÁbÒ+ö‘‚õxêµê‚Šñ‡R}*@""¬ˆ~5¨GW*gÞ½+Ì�#Ú7h;Fq,K……Ï�ÑlÏ_Þx—M—q>|�F¯ÀØkÝÞ¸g’ÁpHHç™ò˲xåðø’ß7ÑÉ ‚å ªì„¿6©'‡¦L04Õp§íB #•½N} õMÊCGÖ�k"·‹n)§"þL4U¨FC¬:ÆÂh�XýéÓŸ  x àAY+k¡KÙœçç\ø6Ø2?Éd0ÊC°*‚3r¾uBï‚x“rd^@h-Ò«8Y¡#Ð0a 0­ÅÉP­a,Gæ\o„<‡Ê›{ü¹’>ÿ úý÷´€~ç�1Æç;ƒñþ-D¢Á�­ëü,‡îhE†Ë€�¸n*bÇI-³@ãa<„AÄÌÚ° šZ»·+Ðì?'ˆS´¼‹ƒ™pŠ{[‹ìð#×gF‹.õâ½ uP¤ÈŒ÷ø·ôW=8T™ qˆøšþjÄ-m�©hºIÐ`ˆ±—‡S4CŸ=c'03 .Á£‘? Aíçdî‡)P&Ç©‡¨ôÉÐãBÌ[)¥I]kVÃåÃ^Â+"ë”|sÌB 6þå§á1qâcúª Iù·ŸŒÐ=ñý÷Êã*‚�C}û©d­ùKóÞsÒ–v!ûú|$mW¶MŸç¬ð'¡,ÛÉËR3>þÅk.Îo[ ²¿ %õí’ܳv. ö�5IðÄ×R&ñE›Ql Ä!PŽWžZEµ«–Uu ™ª/1#õÌð�¨�ÕÊVÞ€³uÕwzÞÕõ¸Ý ¢;?DßýX·ÒFßrx 0î̸´�Fã@�h—�ÖböL>V8 Ú˜C4ƲŸÂÔ�yQÌâÑvÅpÇß’šÎhäÇíçøìõ[­œU¼•¿ÀðKv->ç¼ÀÄÔ§tÆòb˜ëü]à„Îc–-|àtÌò`0+ãCð§‹Â…J1`|¸MNåÒQŠ �SÍHx~^^PhÑ gÔsÿ¡5rÿëŦ%ä,Kä íùyõbœüþ;©bKò2í\WÒŸ?ç]@(LMÚ�›íž5YÁÁ�.!âí-ø¹›ÁØRS-W2f:1áœÆdÌ JÉŽ•Z{ç¾ ‚™~5=Ñóµ­# t\[Z‚?®X0$H“Ö�éM´ò×M†r]TªÔRŽ£YÛû%î´ùØ×lTsÃâ^²Ú@ ”‘³Î‡(¨ABxЛªv u€?’>öûõÛŠÀ\˜`­ó›Íز´Çß\°FÒpc€Áh­Ù8£ÒÁÈco ‹ˆÞ«>Κ1À !$Cw4¸‚ê ß#Ï�8Dþ–Rݧ&¢ â\ô§ƒ˜U Df¿rË3;{Õ—- YLêî\Œy3“½â\+Ã"¬w|çel¿Ÿ— ²Í+Aš‰A Í‰cDV“;« '.UÀ¨²¶-)Ü�¼ŠÍê Bløõºž+ïn3œ!ÅÜï6åTˬ<óWر¥ÉÁ¦¥ÆçaS‰•Êy�Z‘æÒ@5B-ÅÊœJEX’îÛøW¾‰»p™n±6+ª äm¸©¥@+ЪLæ×¤�Ë�k FPE‚1ÕÈ©“KaGE°F¹ßÍ”_»ÖÖžª(—“M�ÔªéÊÆ¬fXå%'÷sZ«3ùu’¶ún¶^Ï)·EùUáE1ŒÛ=¾ Ú{„±ÓîK±â Žr�ø:Nƒ,†hAÔ�¨%ÊöjQOpRµskt`KåZŠŸÌ*ÖF¡ËKUIRÂ'ö¨KÏ¿ÿ^lÎhÒO éëa_oâ•{åº ìâ7ã;„(Õ„ðÆ !¶¨@½ƒ¼ x›Ê‡-@ä~«*?»£IÊŽj¨'Áì :{äjó^ÙZC`ïKÏ FÎ ¬-Ùx)ÙeeÚ|).€xŽôâÉ"Ž`µÛU½Kl|°ûk„àVhZná��=ð_�±wѹ§¼›A·?ßñ»íåÏî§wƒÎðò€—‡Km‰j‘?H_…RQf&�æ�V¬Y}‚ÃAeŽJkIÆv¥Ëzž�Ô®ª5,¶f%•ä%FÊ@dušÈ*žÑv>ÎA颴îzòâv8tûc‡˜9 b&-òÃɉZ¼:`ó»F6NõÂ̧qlaOþW V-ËÞ,R¯š/Tq»ä~åM|<Ë…¬»Qö%«š:Õ‡Ó'.ZÍ™óÑnÞ¬aØAût!Úò [ ˆŠõG6þòèPêÞØ/½®Ú•/Ú¶ß·D%† ÙÆI{Ó,Qò<>ÍÌèk1çrK’�Ä~Sx,‡ò_žhx•Šâ)ú·ŠHñX$ëóçuKUÒÒ@h¸Â(E»ûãêV N¦Þ%ýš‹¢ÀeyƒÜÝ*휖ÖÇkÕ¹™ � 5*ö.{aŸá++*öá‘Tá…-ºl²˜©•^^5±Gße÷M%'TÇjîU,E\M3ÌÂ(I2æ#T6=u>z¢õðŠÄG›NÓ©@:ç²Uá¨æ²°lLØ«Á¼­BóD@EÑ™†Ï`ëÛ~ ^T‰,²S×E[ tX€ÇÿRåIO ­aÙ+oPàå�Ê…Mëźeiú„®¹ëòK¶ÀU‡¤ìµ¸’Üm­ 5ºMT±GÇc©¹ÞîuÌ å‘.Ǻ»Íƒ^]"ÙSÀ?}ÇÆÚ³Ádžw>Ãê`¢æ\œØ{ËX_VbXÄb¥‘ ƒ±§zÝ= ‚Å¡Ø!’m�5?êìü#ÄÄë<¤ ¤<ØQH)%ù01e$˳’Å—Ÿ¤|jx»g‰jõ.çxà³"5RZyŽŠóŽ�Z�ŒŽ•ÿœ³Ã¥»«i{ô ¼˜¬>üE^{\rûük9�’7µN34Ÿšó¶Mýâ’†¡j¡|q×TA’gad²&ûI)4ߨ^ÄS£þÝÜTMî9?¿V^yjrpãE;ˆX*AjP©ôM£­X§ ‹Ýþ½âù�¥c|úF~³£œ:Ó¶Ãrú¹Z~X®°{m?ÿg©m+ž¦­,FûVÐÂ¶Šº�­òNvõr{MÔ¹°?k"¡B.{ MÏ£–äÜ#bÑR©àÊ�üàG–ð�Q‘f9^­‹·„LÖ‚»`×lÞ¬ª¦«âwÙeÕv‰_*:ÜAÅ¢ ,pìtìùˆRŠ#��LšQ†Wm"†ÉÇ­Ù£8™ãe è!™F¬˜¬|Ø:¨géys–ÊÈMî,,‘•©Ø’¶rÍ⣶öÿIø`X<5ŠÐdjbJ7æðgïš áI‰ššbçŠÉªrby¬kHøsLYµ2ÚÓĵ]V[+ÞzÐøŽóÐhéÆ3dÈ­�»Ár‡ÞØV×5!b:.þYn_‹:éªýë7N~éC©k§P) [‰[DŠm4yœ¨PS¤Î‰Œ ·.…":¬y)( êÊ—fŠY­Ò¦òJ ½ú¦Y³q#‹x ¬6cJÚ˲+:'å% þl?êRÙi!\/ÃUèß´Ž1Æí”ÍaŸßž›8â>C”¥kE´;6„Êp£U^·!ÄðbÖrä�=bšæ›ÐU|GÛ²jA`çP‡J�ö‘Áîâ‘¥IìD®”²ò=ÂÙŠ¶Ï¶Cê…‹µ€¢ê½¤tuÕ 5o5øKÕ°µ%5mš2°us¼I;sÎlÒÂØM­iPÞ@lŠ]OHY¡õ­ší`[ºo˜ˆjÐÚžÞß½!K’ïÞL$�wk¸UÞ�»µÝ¢�í«Ô¦ ÓYÅâi—æ¥UHÓÆ; ¸Q¨'òcf˜ué^un{cYçö±{9þà�ÂÓ.;Š-ÔBÅêÍí9F!«Œ E¯Ýr¥c;mÞ’ÍX3z€¤åèáYsLùô #øëLl&vMÙѦ�Çð`qm�á¤f@>�©,qâ…Gqy êÕeÐZu­º*úP~â¶…ó�Õ¸Z´“¿;À–Æ ñ©µ‡f‹¶ -°z¾N@s€šÑØi]ˆkÔfÚ}Øtö¯¨åЬó˜�_~ÈohóÒ,ÙZ’743ÂÍ®Š³_wPÓE ³£a€F¾È÷h¹É2 p!¦‰þeÜ �aÍðÆýH30=_ÆL!¡ ´�d¿·ç–-Ï-Ò1W!9@¥ XöÔm|²€¼ !–��å•åõ#ü»óíÈ‚‚1|3—>5fiÚdq‹CÉ™íÊ¥¦$'¯×h!»€”Ŷ/¶D’2ñ#\ð?¥ÁIEA¥-ÃÁÌâÍ$¤X©6Ú¬&HQáÕÐK?9D µÂ/ a+­¢y®N5heH:¶)´˜3ðgdjßUzÞ>œâÒðÕÉ+†sJ~‚/{u ¾=‡oG¯l¦"i³ÜZPÜf*eá-mó•ësö¶”6/{äÂ#¯^¤¯Zì0#Å+ŽÎûYŒ”pè¢YB¬Rvè >éR;$ÖÞ©ÁY4Ã÷âxhõ£tÅŒð¢ÊâÀ-!#/R ^€ ”@s‡$,`ºZ·YË“Ïðšß×2xìTë€Ý¢b\ÖUyE­™NlUÀ·Œí+I w -46ßk´Óܰý¼ðÂ2ó•…ðãcr�°…_^ÍO0Ä‘ø[/2¯Ä/ˆ|z3vm=ü‹(�ñ3ÑY, æ!>Þ�fÁïsæb+m$^Óˆ_~Y6˜_Ú'Ò—&Ýzácõ_®À:£™<|¦ÎfœÕwS‚7çÓyð•μŒ|åOþ•E5ÜG¶ É4qúùÀŽã¡9Ž7E‚8žž£ø±-9ÛîÆsJRY…¯y¨é‡%ŸÚe¹X$¨ê›ÞÕgëo‘MåŒÕõÃ.-{7î³+¶ØÑ$q8Í{w;úÞhÜ»û³>îå#˜ÜFÜV©7¢oèöÜÎã”nÄ”’Ç„ÔÌ[è+_‹›Ðo겚ù™ª²íVsÎaÍwÕ�D’;‘oÅbg^Á–·pUØÐV p‘Fkt°‰¾@ð®&hm~=ù¬êÿîù¥'¶°xœÍZ[sÛHv~ׯèµ+pÌ‹¤©Lf”p+´D[,ӒФÆãÒªX Ñ$1,.’¹O©<å̾å1¯©Êoš_’ïœnÝ$$;•MU\36�>}úÜo�×èYÚ[Q/Ùå›8úþÈqœ£Ù&È„Œò •b„Rà1 –2ʤ/ŠÈ—©ø8šu�ŽÎãd—ëM.N�O�Å4N6�Ÿ‚(—éÑÑ�L·A–qD62•‹�X§Vý¶X¥RŠx%–/]˶ÈcáE;‘È4Æx‘{ADká‰%N!ÈœÈÊâUþä�0/ò…—eñ2ð€Oøñ²Ø‚f/§óˆêL¸ùFŠWS½ãU‹ñ¥Š ´V.‰§Ü¹He–§Á’p´´ Ÿh(—Ã`èh;³žÒ"Dg[lc?XÑ¿’ÙJŠEd›¶ðB½(r¼Ìè%˳M|ôâTd2 Cº™×š:†¡Sh®EÄç>mâ­Í D´*ÒGJÞãÇŸø«\æô†ÀWqÆOÄÚ2Žü€8ÊÎŽ xÈu?JæEi6Šs�ªH $µVõR¶ñ@ûBj�á\ˆ×3ØIéø,‡âÈ>‰S>oŸMØÓìr(¦×ïfŸ“¡MÅÍäúçÑÅðB¼Lñüª->�f—×·3ˆÉàjöY\¿ƒ«ÏâÃèê¢-†¿ÜL†Ó©¸žˆÑÇ›ñhˆw£«óñíÅèê½x‹}W×31Áz�tv-è@�j4œ²�ÃÉù%oGãÑìs[¼Í®ç; ˆ›Ád6:¿&âævrs=âø  ½]½›à”áÇáÕ¬‹SñN ƃ˜^Æc>jp ê'LßùõÍçÉèýåL\^�/†xùvÊoÇCu˜:FÛâbðqð~È»®�eÂ`šºO—C~…óøï|6º¾"6ί¯f<¶ÁådVmý4šÛb0MI ï&×@OâÄŽkF‚}WC……D-,�„žo§Ãš–‹á` \SÚlw9’½L²e$p¯j÷”#ÁåºbŠ`@¿z“áàâã°»õa£)œa ÌŽŠÌ[KÑÎ-ý8:ZÂmÿDwȼt÷GœlÉÌDœ­R¸Hœ ý&ñòM¹ší²êç¦Èƒ°|ʃ­¬VŠE’ÆK™U°ù&•Å…;ß%äHzq 7‡ÊC Ù„8ôBH<Ú=zá: ".Ó4Nç©Ìá¨óeìg'§ß“´ä6ðoÁl-#™âq'Bo»ð½¬%¼$‘‘¯œ›á%G„R.H{±æåìZ,½ <@âÛ£e(½¨Hæ«"Zf8÷îþïHÅ eHüÐ:;ø“âèÈ—+±–ùœ„=÷ƒÔm‰Îâš"�Ó¿ç�@ºƒ¨§;$±™ÖYµØå]£\,¼L*â"„þT ]iŠØ.nF`ré&é-7 Œ€3¢Nâ‘´d(")}" ˆ‚?rÿ¨’Ð|›T¼�uõð¢·ÎÂŒ­ªãˆ7Ä�g]°�¾ÛjñÆ`%\²¤®üég®‰¨¥EF^‹Oã"ô¡¢D27tÓšh äÒd´Î ¾H¬ÖÓÖU"¡�µÈ­£Ômš—,mw•é¸Ê”Î^«v+ µ¤¤lÀÞ™œ‘�4×°InÖE¼OóŒ2§Ëru�~�©ù9_.Þ±¤Nšl„VnÚM·9ª7iir¼ Ÿ“͆sbÑK3›&¨û@2)Y¼•,m®8¼t—uð(#Ž¥�d¡”`Îó8îŸ(³Á3•}ñ÷Ýc~Cˆ"ÕÍZºˆon Ô3�˜¦2-(1–jÙW¯hw—¹D†Ö�Àj¿M`®Ì˧Ì\ò¢J3Ö$µ,“”ú3GËl½|0émÝÈÛJ–`[¨7Jœ�Þ>X¡ÚC®‡ß¡pâŠM¤ø"O …ƒÂ>DNOk«èE–©J6&q:giJ/Û g•ÓN¹ VE8”W#u1<•ˆaœ"î-┪S® 1aû{8O<j;ýQtD(#æ³%z=qªª×®tôîïÄ©è#TךR:xgáßz_\þÝ¥ªŒ•ÔZÙ€GÉÑç÷ßþÅßi$oèù?Xut8«ì÷ßþÝQ ˜Ö€ÿ¦øóƒÇ@#ûSôûoÿªÞ.b‡Wz±ûkD®Ã˜Ã ’,mþKQŠê’†·ÕÍ’0Èi-+ß*Žs‹ÚŽ- tÈp~ÿëÒš¹7eo5o˜À¿þÁ(”–ê„‹I9Õ´F…Ÿræ™™§#«lg¤Í¶ªÌ´Ì•�î›!~™Ê®Š˜Sò¾e–†¥ÓU¿Á�”³1aNIaK àO‘S™f£6,…ðÍK±Á)°‡Q†™lBæ(h!·I'²ˆ§e£\(é–A£–x•úêWäÇ� wZ,I‰­’"lô™‰�vÿôÌî2½2d½Œäää9äzåáñ+ûxfûñ$ï­P‚J_g*؈‰ÌÅóÞ3ü¬7ÞÐúŠò„;4À´«©õ²ˆ»Af¶Ã3õtÙP(n¸j6*ÚîÍèfÈ�ø_tÝU¯ �†Â1T¯Hà4+ôä¹,½aQ¬V(úxŽ¢!‹ÃGFçAÇ£èÐ.GÅfí=‚ëg:�þõ�´68Q•×hmƒpŸ$êŒãÐo´,5QÜ£—}.*ü\ná¿Þ#”HE5úò-êÀ#•R$ }“çIvÖë­Qª‹î2Þöž¶OOTvBo'Ózé0ì¡é-dÖûéä5ÿÍ:ÿpòÓñ�§Ç?œb'Þ”+qºî!¨¤YÏ£áIÖKb¤üÞéññ�½ãï{'ß÷¦��KT�M‡TÓ ²Î..ÒÚ€í®gé•Âå|Nú˜ÏÝL†«¶¨â¤QKœcÄGè#TWµ(Ök#|)sò|PÕg�³¨úTZ’ÒÜVF'uW”ÊJ0{É÷r”–èw±Øå”9le¯gUstzï|G5ª×bB6ÀÕ2"$mÉ^(t•ý ÙŒÙCÓ°|X§qÁ âÂÀÂfò€ªI™ˆ2XendÕ„“ì[a¼|Œª[Kùk®M²_·~Ýÿâxܧ¿�GhŽ•÷«}m�Yß­Û:�„�\Õˆ°^,Ýyÿ2Œ3I‰¹²&IÙBy*§IÛH´™B]õ®h²—k—SŽ˜!c >$3 …UgZH»beE±É0µ=�·,PNªPÙV¬¥µOÒy¤žô÷YÚt°‡*i”§¶@ö jË`)º2ÞJ’ ز;gq¨šÏ†“åqRö¬¤OST”Ø5ds/„Ðݽ’¿bBûXë�XWr:CöOlpÝW‘˜_$EÀ;•ß2 ¡ò,®íÝ1ä¢Ê|*G”t¬2ëPHŒŒÔ�~\ú:ø“"j‘)Ãñ%¥Iʲâvö®óc³9Â¥)XœŠG{D“Üt}V©¼«t�"_á´†=Úž­¸¤s®�³J¼ªœ¬ËͲÀ$¹"ocž‘˾%Œ«rÆ3%“m8ÂãO—îÊæìÞ°"¦‹Ã}Ÿ±Ø ºöës‘pPI¶€Q“4S!»\§è¾U^Pîw‰‰²Ní”ÀÄË‚¸oœÉ¯ÁgÿÿOÂẠ©XÉ6Z¨ÚMÎÄ*Œ=3 æé®Á’ &hºPyYY�V;ä—¥LÌ)aw¦@†_’-inE¨Ýíïuò*¨Ê_Õ5n]áSa^)š”BDùB)±âžOÙ `¥#Ôìó~eÀ7ß(0S{$é8õ2Yň4+ígÂуυÄO;Ù©Ÿ‘¾³ï¿Ímæ!SœØ_䵦³ N™¹3[Í`dö˜!>Íëƒþ—³Ú¾ÔPÔ=8â[åù²AýÁj›ìü KÅð›á+GÐ�š[µ±Uû<•Œ¾ezN©ˆfƒ5S@£‚ŸWªšqÐ<=aDœ™KO:ØçñƒŒÌpÏÉuÇa•U&:w+£tãáM‰I†/¨Zb|å]§‚Uó1׋vjJç c™?I,«-­29­¸ªîs±WÍ9†±*zMc¨Ö˜-K[LeŸÿ¹£¿ºϾËH4eSg÷upµ:a“¢w–Ì6˜”€e¬vs•ŸYÀ–Pka\… uXoÌ¢CÇtã@ ã ÊŽ–.6å—JõÔ5&]��Òe}³Q_bé ¬R Õ ¡¤¨"ë^ÝÅP$×@ý(çß�I\͹ )µ~›Ï *-‰±·K¿q½žf·«¡ ŒŠ­L½\*YÚù„UiÎú‡¿Ü Ïggð)[s53Uöæ­4bt[w'g÷ûYeŸƒÊ •i4~{õÜñ Bý?¤ãÎi±éðÎЯ[úµëü³Sz ]�;¯«Ç=²Ñ y»�öpÓ.ÂxÍ`ûíTÍUu®cÚÈÝñ}ë°ï2PôMàÃ!Kë8”&Ñßd¸ÇM°Ý°·�D¥�¥Öƒ=uRÊG/ÊK§MiÃQ][î�å+gÂÎÉ}ë°U¤?‡™:Vç¢"R6�l³E>¡Ûiвä˜úЇôBŸÜÀ-¿2jèfúî;؉²çÌiª¯Å: ÿ¬â0T7IÑ1äVQ®xV]Iè\lX W�óËáù¡|@Ÿ(LÅùõÇ›ñp6¼pßÀŒ‰ŠS¢c^ÕПæÈgùó�ä™úZ`“õ}ÅòÊë=JŸ{¦fërßÊ Ò«dÁ.ðµ8©¿Ð’Q;&RDÀëñšWᣕy÷ïGNΟT_R™iµ¼|±.lŸ¤ðcÚöDBF_�¡Ü‹òp§>OxÖèÊ‘qJß`¸`·`²é³ãüòZt WQ_9Ô­‘* æ‹€L Û!�¤ëÇ»�¼U÷ÍW»ô9=»õ¥Žúž ¢a•¯Æfèwƒ4ŽèS×y?û0žÎŒßOFW_S8'úB¯B_³Ð¼ïííh|1¿Mœ–©·jc­�Ÿ�4Ó5ú#LÖÕ5ýf¬�Áðùw7 ¢_=§µXž=´ZhfÕÛÐY8ŽšÛZïzp¹Ã¦Â9ç0xä(sú~-’j£ñ� ÏC÷¤Ç¹.RËÂÂÝÒ¥VŠ Q›C³,È)lãåCGÁï?¶ŒúÐ’®¡‡¶É ûY¥æMdjd°�oâ TmzûËüúpæL±D7‹ò‹\<ßwž£¬f¼�2µXRVƒ6SV¯›”’}�2ãûÛ$²žTý„W(ñ¼�¥£¶m}¢!µJà]¶›Ñ§‘nµv§=n6œÎØá¨4¯¾–©€~¹x?ŸÜ^ÍF‡/@}|®Èô¦7ãÁg†Ú£¿xøöö=ƒ–ñv‡Ü:'>ñÀ*4À¦Ç£·“Áäóüf0»t ö—[íGÍF�¥K„¥º%§¯¤2“ôŠI%qã‘J¸¯:^kI›K¿œ£ºNå&wµ¨¨Þb î>ÚœæV�ëªä@cYiPé¨ý!×aFf¨—æsh÷e9ÄÑFcPJ¥¦:�ŽWä±s_FéýÌP“¶‡Å DN¹Í±ÇK@O£�NuVA ÓækæþNf €´Äà4é—\<¥Ý:@ñç"�y=º¯CQý�†0 E«Ë3ÎÔ1jvïußk©_mïê‰o9¬ýḵq8éú³Pd”ý¢¥\‹ŒÒ0l2NZûs*`9ZTÏWAl›Ê�ìž}ªaVÛxT¥äì©X}°µ½ütHeèç©ùÚW-R˵ö`UJ ÏY›®·É\tc¥È2?v²müæé¾AÇötÉ<ì›”øí‚P YD6ǘ€n�hç|ÎUâ|NUë|îX_dR_T«\Mž"üŒ"]‹¨Ø.€iöìrÖæ¯®ÕDÈ Bk†d‰DÉuºHéûGç†ö�o’ŠÆoub �¾É…KÑwÆà » ¥Hãnho^ˆWnUÓ1†?!Ö¹pÎÿ”µÜ xœ340031QÈM-ÎÏÓK*ÍÌIa8P¿àa¸ó¦h×»ë>rŽ–Mr1„(+I-.ÑMÎÏÍÍÏÓË`¨;Âñä�w[Ó¯½Ÿ™wÝv]ÜÐö±ã!cµxœ+I-.‰OÎÏÍÍÏS°UHIMÎI,J�OI-HÍKIÍK®ÔàR‚̼äœÒ xfQjrI~Qfj±6A u=uMM.A�@ã4€êxœ…“ÍjÛ@…7YyhèþFƒa„cwÕÚuA$Rl°ãÔ7Ã0–FÉ€<2Ò˜LжÙòy„,ò,y€>IGVü“ßîçÞ¹çÓÕÌ㧇ÝûdgoÐïNX§‚BI%€ºþíÍÎçü{¦C™Ô.~T�TA<V,'Ͻ¹’Æ~Ù—Ju^xë±G®çŒz” FôtDÙY÷ˆv ñíKý�ŽœuÜîq‡B£þµ¾ã9Ôé1¯Oq”¤S®m¨ÕjÂÑ̄ꑦ6X×<£“¸†jÖ¬† •˜4Á‚rXceÙÀ˜×í¹Œª×=qóRFs !Æ~9ÌûŒ‘ˆK©qƒ´®à÷…Œà:yΈ§"Ëø¹ [ÈV53I«Âºßñ}wH1Ï2‘j™¨åçä Þßr ,¶F�Àº7a–hs¦uõ`ÈÜŸ˜Û0±ŸQn±LÅ<'Ðnž�7rÅåLºÌ+ºªÙ’&Ðs7¶V ûí—‹F&™Ð"ú¾Üút¸‚ÜÀ™»Lg¥ù´¿×^±�ÍO[¦t÷$ß�Bkª *4ÉÅÛ ìpõb*ÿ˜ëü¦æ‡ÃPxœe�1NÃ@EªåÓÙXÂPFé¸E�¢µ=±GÚݱfw!nÈ•8Ç db iºÿþÿúó½ùºýìoN/#H#B˜"XAœƒmö÷*Q„Ø M "Ê*£KŽÂ°Ø²j9NÎRˆKÚ$¬~o ù‰E­s4¦cDzµr Åë±iO»§ÇMã‹U ,Þº‹¸{PA°ãp aß±÷6ô*^çÜAá1r€6“ë¡®/|Ö%õó²j›$c¡èu‰™„Bú(‹™3ø¬+$8Sÿµ(K�‰ðïeåûˆÚõÛOë|›Æ3©Ù²*=inb™«¢2x¤T6•ùQY‰ ¥xœ340031QÈM-ÎÏÓK*ÍÌIaX¬*©Â·þ]päÃVáê•“~ïŽ?fQV’Z\¢›žZ¢[–ZTœ Ô‘Ìk©^°h]]�§Õªä)§ï‰!w¯ ½¼ xœeŽ]! „ß9E߀„õ&žÄ²²#¢ül€5z{A7ÆdûФ3_§­(U[Tý@..E:ž0KÏ‚Q+^34fX®>úÅy±qw†Ë/0aFœ�CÙÓÑÖ»¢Ö´_Ⱥ\á½¢¾®M !Å“d¬�ÛL¾r�*³í±¥�sºÁü\![Ø¢aL©ç�…8xœ›m´„gC6‹vzI¶nNbej‘nqFjNŽ^†—rf^rNiJª‚RIjq‰nr~nn~PbòyQÅz®j. p v ‰w ÔH,JOÖQ0ÒQPRMQÒ´æK'g$)”¥gæçÅå—e¦¤¦Ä'UÆ­‹[¶.ÚÐÀÈ$Ö¬§¸ (3¯$MÌlúëÑ Ð�«Z¯FJ1„âôÔ’øÜĬü¢x¨©š8•eæáV–�¬.¹(¡æuhȇ�B‡ˆ0ÐQaY´a,Ì„¢Ô’Ò¢<k®Z.Ú‡ yc¿¥^b’-Ò‚ŠÑ üÈ â’Îwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a1/0000755000175100017510000000000015222270712023655 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a1/a8ea0e66d56a21e589b9200d370efe125305f3wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/a1/a8ea0e66d56a21e589b9200d370efe125305f0000444000175100017510000000070615222270712030637 0ustar runnerrunnerx+)JMU056b040031QrutñuÕËMaºµ¡çêk]§ŸÕW— ¬²Òþ4Y¨ÈÜÔT!9#59[71'G·$µ¸¤X7±(U73O77µ8?O¯ ’áþijmzuyí[š ü£"}Œm¬GÑš“™œšWœZ R,´s–ߥbÿoþl½¼K®úÈÆ —dL €@!3¯$5½(±$3?l‘nr~nn~CΞȬ—;Vü’oû05]¸qËïôLì:Š*ÿŸšspÞ�ߌÚï ´6ÌÚòâiyÔ«PGèf¦çå¥ê•T”0h?’¯ŠÜñ+òÌ?µœ ߥï?èêÄ@UC<—Tš™“Âà¢ûÎOFÀå¬trËöï§½¸?|~Ùq@n>0`ŠS‹ÊR‹¸ç§ÝóxÑ{û–¢¹FmAÑí(KMh(•æ Õw °Xbõ`»Õm†uº~JgÎp_èd°ðÄ`P@Ãü¿Û+QÜÖ½o ÓOãïSO1~‰¸b^u%$>òòKtSó“rRS@¦.Ⱥñ�[¢Øiý‘ˆ öõö›­>§@L-ÍË,�Ä!ƒÉ­‹¦|ózš†gÁ÷–€‰ÙŸ%qåÜRwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1e/0000755000175100017510000000000015222270712023661 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1e/e09f37c77c4e0cd1f017162df317c58aaab2fewlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1e/e09f37c77c4e0cd1f017162df317c58aaab2f0000444000175100017510000000015015222270712031051 0ustar runnerrunnerx+)JMU°°`040031Q((Íɉ/J-,M-.‰/IÍ-ÈI,IÕËMa°n¸¿éýã¹l×›ï‘ï>/ÎXø¥×ÄÊó‹²ÓròË‹vŠ”7²‹=zòƒM~Ú—`žiÊÓö'#wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1a/0000755000175100017510000000000015222270712023655 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1a/8f96f9eb5a5c124e76cfe19ad326e2fffc978awlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/1a/8f96f9eb5a5c124e76cfe19ad326e2fffc9780000444000175100017510000000654315222270712031213 0ustar runnerrunnerxÕ[[SÛHÞg~E‡­É:”' Ì^ª6•­R°®1¶G–Ãd_T²ÝÆÚØ’K’!d&ÿ}¿Ó}Zjɲ ÌÎP`õés¿õi1Y&qüÃ?NNþôêHø‹(2ΣTŠy´”—ÑTÆ™œ‰M<“©¸èúâßâ4YߦÑÕ"'¯O^‹Q²^DR\Fq.SÊteY”Ä„i!S9¹Wi�Y[ÌS)E2ÓE˜^ɶÈÆ·b-Ó ’IFq_‰PLAŠ sâ/KæùMÃx&Â,K¦Q|b–L7+0æD�ØÏD+_Hq8â‡/‘™ —"Š�M‚kFvå‹d“‹TfyM G@ÓåfF<˜åe´Š˜mWògÄù&ƒÄg[¬’Y4§ßR‰µÞL–Q¶h‹YD¨'›�=TŠm“¯’Tdr¹$ øV²–Ü)¢Ý€>«HѽY$«ª$PÑ|“Æ ¥`Ï,�ÊÅÿÈiNOˆóy²\&7$Ú4‰gÉ›ý“Íæc=œ$×R ¤m'9øU:WVPœhÓòR¶!ÀN£´âÐ1=22AÄÍ$ËaýX'©"Z—õ¥aâÜ£Á™éx®èŽÄÐ|èvÜŽ8tFø|Ø—]ÿ|0ö <§ïƒ3áô?Š»ýN[¸?=w4Ot/†½®‹gÝþioÜéöß‹wØ×ø¢×…C©?>2ª®‹}gâÂõNÏ�Ùy×íuý�mqÖõû„ó H1t<¿{:î9ž޽á`ä‚|hûÝþ™*î…Û÷_‚*ž ÷>ˆÑ¹Óë)RÎÜ{Š¿ÓÁð£×}î‹óA¯ãâá;œ9ïz®&¡N{N÷¢-:Î…óž¸óÄ { Œ¹»Ö¹¤èø+¾ÎMG›J,&Rkì è0Á}l‹ŸaT¦Øo³1-wÕ¹Äß>Åè .\©ó‚¦H”9¹Çñ…Ṗ²Ÿj¤ímêíÜé°Ä~ÈÚ¬¢¯$† Ñ½Ê*ø¼Ý*£ÇZ'Y”cž¢ûzsÒièU©Ö'ƒ9Ý4™Ò¤Ÿê9çaÇÆùÀSH4c%7¹¦ÇƒS„€7>õíyo©ddcæHG-p†©áf™Ñj½lñ*ïEüÿV#‡ò¼Çp¸ÁÀ,A[¼8ÂÒ$fÅ3Ë´ú¬v$ôï¶ X3¯(f'| ñ ƒšAîÊð¢õ,+Ç<Ì£õÒÙ³å M:*àòD( ’…†·5²EÑàQ9n®ÃI´ÄÔPf-‹•¦F®ã§Î�o LÚ¿R«/þè~|7p<Ê__162êä¹ÓS)´2Öb•Vž}[© ~?µVh0](–—Ÿ:©†Ç€FCZëGpm±üÎxïS�Mì”bRdµÂ±hÕ‡Í:-ê¤íÄ4fÑÜ4ž Ÿ6¨KýïDÄîÞ„iwüºb“wV-@ÕäfLBäâ–>szø?Y¢>))’UñÇ1›¥© Xü?ž�,¤�U ¶2•EƒÅÕ§Ã5�ª3<Öïµ��îÊÜoÑ)òÙ¿Z« f}jt¦‰"S`d±m�G Ýû)ÄdÖˆ p]QI2sç¶ÀîÁÊm,4Ô£§- eôB2TžZ9÷h• K5z@¯d«óÉ"İÍö*�-LÓ�&¸ÇËtg÷WÁK”·žë%ÞWáUwæ.®eHÐù…¾«; ¬©Ä1“ ˆ�åkx�/~ó�Î<>ÈÓ 䲪ñ£[ ÕÜ×.Œìk&¼;o”ªá¦±š4Ì2|ÊüÙ˜2Ìâ)glÅ>+«|n¤ºkRP™åw”ôp¥±ßÙŽìék(ÄÊ'Ëvj.©Õ¢N™P>~�xÆ+ ˜@©mûý«}ýº;RYìÆ0UkðRõ»1@ÕJ€wú&OíŸ[�Îjx­Mý4fcR¤®Ìô¥<,ÈöÓåœîÃõ†Êáí𻵙Ûpt­�I�uÉ_\—7q­ˆ?PkS¦#vì­4e¬ßظì<ç�¾Myjw(L­É•ÓžC­”³m\â—ULÃðÍÑ[~‰e+Dvk)‹¾ÈßLG<Êß }ËP†Éoª;õ¢5•ôŠÒ¶jiy�eÍã^Í.ðê,]{P]W>µ‚wtÑ[©JÍè%\õ`©ð²+5eäȼƥAI?Ö`îMw³+>Õ ¡bÎ,÷ó.ÖïåOµ7¢(\k]ªº½åM¶æªÇÚ { [^¯Ü.à €Ãbç^Õ¦%�ý#Ô‹JN,¼²( j2§Nf~;øàzÞÕ<÷§±;òéVÝœÐë˜l ½�öD{õ+9wAÉêÞZÞ]€Uõ®§a}Ï&ÝSPöáùÙ.Xê=y|ÖOªŽ¾%©GÕ£“}ø MÛã„}ÌiäþØUƒ°µ€¤hïö áìÆ_+±x»èØ»‹ø£þÑ4,ÿÃFªá÷ÜVʨz D†µòM™¹~Ç,�‚†®#Ê -þÞVC$5PktQÀ ìÇ´=¶?±pñª…ÍÜícn±²y3�övRBAæ»Æ?”«0ÆkŒ©�é„%º¦2Zm½;üGJ3Ž»ŠcE´ÍÂ]e°½Òt1–ÁI’¯ÿãoÌ$wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/3d/0000755000175100017510000000000015222270712023662 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/3d/7209633c8d324ceb12b01e7dbd375eeb5c7995wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/3d/7209633c8d324ceb12b01e7dbd375eeb5c7990000444000175100017510000002463215222270712030741 0ustar runnerrunnerxí�árÜ6’Çﳞ‚•­º²çÇ’œÝ[»v«t–¬èbÇ.[ÙT>±83œOr–äÈV\y {�{²k€h„zwä-mÝÅ4øã×�œçå<:|vtüç?þÛw³èäçËwçg?�}8¹<;}�¾‹~zw��^\F§Î^]¾ùõ šEÓ4Z7×ßn«ìæ»g'§oÏžn–Ѫ¬¢MY¥QVÀ�›¤ÉÊ’³—WY­²þVGÛ¤j¢rÅMäÉmZ}[_¥y.’¾*··U¶¾j¢G¯G‡þó÷ßÂþ½O›´ŠÞ&M“ÕO¢�Û´XÀï?f›y’çQR,£ÿ.ë+H°8-‹$_²b•­ÿûßèèÙÑsåv½¨²mS·(—éÓí­îç¶*×U²a®®*¨j]®šOI•¾ŒnË]´HЍJ—YÝTÙ|×@�VúweÅJÜ”ËluË®íŠ%ø×\¥ø½©Y…Ù/ç?ý½Iëþvži•äÑûÝ<Ï,÷›l‘u%ÐHì"´Ë2šßòŒ¯™+…+Ñëìó~¥æ¥ß¤U �ËÒ„Á'Hó(iXª¨Ü2eƒ×·Qž°Vùž2Øÿs¹´fPµ]‚¸Ü�«r 5»›P×OH0O£]�®vùfš&úåâò‡w?_F'?ýýròáÃÉO—¿¾„ÄÍU MoÒÖT¶ÙæTÚ¸JŠæœgÞž}xõd9ù¯‹7—¿²*¼¾¸üéìãÇèõ»ÑIôþäÃåÅ«Ÿßœ|ˆÞÿüáý»�gO#Þ7E;3#ΦîÚ¹ë±Ë´I²¼îàWº/óet•ܤ ø"ÍnÀÇ$Z@ÿ”Í‹Š)K`.$yY¬y­¡½X÷o»×Ë([EEÙ<‰>Ut£¦”™7êE±xú$úþ0z u�ƒè`ãu¶j®¢×yYVO¢ÿ*ë¦,žDoO¢èÙÑáá³o�Ÿ>‰~þxÂjøÝÁÁ²tÏUt~úcüËÅO§ï~‰/Þ¾¿zsòñcüþÃÅßâþ)²ºìP"0U,òÝ2�¾Y”›MY<½úæà ¹Ý¦Ì>@²[4Q|¾¼þ%+–å§ ÐúUžÔud_zypðÝwÑßD¾8�žñß“–++¶»&Î+…ç©@›ÍlÌW…`ŒáÁ¢_f›¿Í't+W+†®Nüj²­¹øÏ/üï»t—Bàj²ôó¶t�9rLUšì¶0m†l0ê.a�¨JŒ9ŠBœú`„�F’×`˜†¹€îÛZÐ�×b‘Æ% ÷¢3Áfž¦[4¡|U.v…;èÍÇGQ“mØÀ·Ùõdš²Ù�é®b´xÆÀaã@Äâ?·Tµ¡®û“ȃ&-à.)É<蚃'´²××Ù6n PÏa^Å}sLÌ4…XúZfp[ÛÂäl‚-žÜ²´«ÖpWzëãOÚ›Ä�22xsˆ¾Ýp."}4“Ù4í˜n5Œ•åF i²óð>�5¹3&ɱ^\%U4kx:½O³ÜL*Ý�§õNdæÉúy¡OVÍngØd€G(aAKÜ·ÓÀ=P�±i<Ðåv¦ë®lFÁfü†dlÌYÁ�~§Ÿl>Á›ôƒ9±2¬°æ€É]UÁ,"ï2cs!·;]·¶Ó÷+—,é–Õ Ç\Æ#iß7Ãpóþ§Œ¤};èÂR31 ßfŸç»•9ûfíÃsÐÒnŒ>Óéų̰¦âÊj±L}SÖM¶¸µ·ƒYµOºMònñÜ·†ƒ»Â;)¬¢ä°B•¦ÎQܰª’r™¡­ >»ƒ[tžBob¸'ÌocÍÄ]§VFÉ»BåmMÖ ®ad‹~+5°SXå+ÖFi��y 7�®òôˆ¯Ђ€œ�±{'V‰hJ-ÔF3¸Ã†Å>+ó2]”_¤CB9sííi—*Ò30}º€!FYýïÎì„ûå‚®8kÍÕ®X°•À!ç^‹4‘J¬›içÏímp Ëk·.�gËu¥ðÞ|Ö\žÍ5`á�/^ñÎjÜ;´· òà§u‰ß5:ô)<-¸‰ë[¸o®´ßÒ%LŒ‹U¶ÞUã@u)cXwã‹¿®æ´ºY¹Mp‹ïJ �%î‰d"]=Ö àž&ϰ‰ŒÆ]Ôê6ä V2‡‚4ô€Sè﫲)e.Ad Ű Ê/º+Ðõ�h6ƒ™ ÚF°™} ËñÒ½¸À‹Ò_•EC}»ºÇ‹æ�-·ßEÄ(×z!×C>óûbhhÖyoù/†ƒP»¬÷Çí�GÍÖqàÂÙì»q'§78ë[`„í.°é¼øN7°¹c ý€±^œ—·zlQ€!Aò›$ç¨÷Ò0:ðkhhY¾‚ÂîøŠ~{w�»Ðâír[4¡å… PÔ]½`3>Ðc›V.PÈISnØ"=OÅkȯ°;Ö0|5¿½î~ÍÓbÍ–gyØ.ÓöªTÎY²hvp¿#,°–•—ä/Ï(/vÖví|w¬æÍ*\/`1Y:énÍѪ0G¤�ø=T8e{Xà=#XU\ ˜ ¢~E õÛðù²Þ/ÛZOô©í'Ì1ÂÕ‹â'¬Â#“-&'ÌÒX–%,ýº§`í¢ù¬K§­¤«‹Ž/!hÂ*Ãðz _ºTë .bÁ,°ÀîGÌ wËÅ»@ž®`Ù–ÿÈ÷@Åϰr-~š—0:nl¼`Œ³–¤Ù¹‹‘9¬ÑŒ¯ur+píü�žÅ:g#‹—nc]€�UÛd•¼Ãk¶ Ê£à¬ª`ën6ƒ[$˜˜².ÓeÑa&åâ±âL?…gNëò.7+ xe|µzyÌ–fùÝž»:ý±þ:”4ñÚÕX0˜|n.ðpê—âè-r@7épI*öòàwk7í�í¦�f«ìjóKËå®–›¿/ØŸÿ#2Õáwÿl•e KIȆë~É– áy›Úô/Ý[x°q÷}·‡÷üYôhWTU’¦!czÏããïvôvô`_ðaGïaG��yr3ž‡">ÄñÍB>Ê©åOsØyØÑ{ØÑ{ØÑk⇽‡=CÙ<ßìwbìnÝÉ2nÃØ�Ü3>ìèõ7˜XÃÈ­¹‡½‡½‡½ö¸¢ï>ÝÃŽÛ¸}ØÑcçW!–v[‚°ð!ÜÉuCtŸÎ©Ð”üæAÚ}ØÑc‹rl�Ľ\ÖIð°£×õÅvýÕèp°×îý=ìè÷資=¶=÷°£×žÑ…›ˆ©ûŠ;zúæäÃŽ^·Í÷°£§Ž:<ìè±gJ¾ö=ò�9±Eøvg!pÀ“7ð,*<Á•E~{ÀBÉzy-7•Ù3šíãr°ÃŸÝÀ²F‹§Fá ï£ÇÑØ×eOnd þ¼šú[ô—èÛC¶ë ÿO<>ÒÿÂþÔf…Ê5×­ix$ÎgÄìw™–oä>â3+0ž±'J°Ì1W6Y[¼"9x3ƒ‡šŸ=ƒíL•`øu „?à2<�µ¸ŠqƒÒö< ´ÃÑØxn³ÿuW­+GÖ•cëÊsëÊ÷Ö•?ZWþd]ùOëÊŸûW-Ÿ-Ÿ-Ÿ-Ÿ-Ÿ-Ÿ-Ÿ-Ÿ-Ÿ-Ÿ�,Ÿ�,Ÿ�,Ÿ�,Ÿ�,Ÿ�,Ÿ�,Ÿ�,Ÿ�,Ÿ�,Ÿ�{>[½®ôjWzµ€+½Z<·ìÀ•ž¸Ò³Wl;½Ö€4½Ö€+½Ö€+½Ö€+½Ö€+½Öxþ¬ßëàŠås¿×AËç~¯ƒ4–Ïý^i,Ÿû½îù³~ƒ+–‡ý>i,û} ÒXöû¤±<ì÷1Hcµj¿�=vlù|lõ„cËçc«'[>[=áØòùØê Ç�ÏQ4‡Ã<×]h�à8ó*Ùå� PZŒ‡Gý #Âo’Ûxž²Ó\»í¶¬à8ª ÷¿·CG;x´qÿ¯‰Ž %¾WñZ°¾ø—ˆ�6Qô{”æÅÙÀÄþg$yƼ„$ð_a¿Já|K¡¥‚#4üyôn¥£{týÅ‹v︌'ËáùÔáQSÏo›J=RÞÃW�æ+1¨ŠjĬªÞ£Ç²]ø˜õ윱âu{d:úøß=v~ö˜»òí_u¿ÛÖãæï`ž�ä°/»O´ŽùÙ±Gß\´Gº¤JÑÅé7�_Ÿᙂªi;Ìï²Yô´²zþ�4µef@B×ê¹®Sd´œwk9fÒ�±®?Â7 ÛX³ ÅU�çª{2[œb‘©5‰6Ë7ˆkÖµ—*⊠*Ð8Z̰t£å„« ç ÏåÊ}ë^¼zOF Cñ^aSýQ­V¶¢='=úRŽö6ˆ.b�a/7éèP­ 7Þ3|x*_éÖõ+1†"è#èû‰C•¬‡þLI†ÌO)=¼!ñ…½çà@ž UØBKŒ÷&$Dt {/ÙÙg4`! ^™ ×ŇîávҽᄥsЉ°™ŸÐëyóÝ}”Ƭ…k‚°Â^åÍ K<‘21ä-Í”’ùK€„2Þü>H 7¯B‚¥s!!nå çHàº�RêÓæô|ÞJwïù˜µð¦Gz>{K{䯻÷Ë _Òg LSárø� Ë›ZÑ ÓÞ#"¤K@…ü‘`¾%MaVÃ%Aá/öóÆƒ§ÆsΤö::p‡ ÀÐì„KàCÅH +$xÂ{Ä÷ºmÛRw#p{á ð×Vz3ÀSM p‡ Ðì„KàÃÀH +xÂ{Ä÷h[êî àöÂ@0^ÊêÍ‚‘k"p2×ý–×}Þ`"`Æa/\:vŒqECFK¨Ndû¤;Ã_6Þ°×!Ê· ÌÀ {ƒöÃ…aP>=C™×Ý9C3ÎÈc¯I¾TÊú‚iš —t ›²L\&O™!TîÀ௹Þ/­Ò} (y‰�Yir¬”p™rµ÷†{C«åÁ;Âk·,Êî'¾†|Ÿ ku&`ز®«¾Zq¸`Š\-¹Ú»ˆ¸šµ*bÚo kÖl‡+œáqÌlÓcö)Ž}¢oÖ‰~—Áp}ð7KÄeQÀÌ�„L­ý�hº ,šÆ~Óàp á‚"D²"ü�R5úí•føÔ¯ÊŠKïœ� ²c®¼&`®o,\ÞTixƒóy”z]˜xñM«°‹7è œÿg飼ª@W¿ФŒá–ÃeB(bKЏ’¬Ý‡éQYp1�ôôæÅ¶/„£R2+U¬úÆÂõòÁJ•†+aa8N¸8ûáLÕMŒ+­ëœùX× á¬JÛG6ºåƒqÒd&\]œ3ùR‡�� í“:Y5æLSáÊù'ËÂ�WCËoÁ€À2½kãÄyÈ´þ¤ç@Ÿü‘€=i ³®Âƒ}©ÜoÒ­›‘×§ÐúhÁòº÷ÅÀ}Bh5�ˆÍpa}°´ ŵ2ÇCC`Òq÷èUm1Tµ" vz9ár#³É±|{£7Âz&¼G ô¶_��Àþ8ç>!Ö+IÀ¯m.\Ktõòp�µzz7°S„Û·zYýWZusCÖÃu`”ªý,í$Lµ|x/"U‚Ú}D×øZUûÝ[øÞΚ%ƒ�M¦‹VM"V-‹á²ú⪉ke«e€ÖOC2'�_ê©'ÀÒêED®fq¤Œp¡øSöò xöH¿•Ybí£gÆ{B1¬áöçYòsi~Ÿ“Þ7Ûzí‰à¶M†‹îK·^&.¢y›ÛßÍø�Þùz£,õKDìë&ÇJ ïýÚ××'�¯åû‹“y29CFcÍmX-káòøpª‡·¹‰¨–ÅE'D45š4ªj^A‡×~# J³6`;\(„#ød¼Mà½xÞóß.®åýã§sš€žž­pI|Øé Ã[[Í]»Ä÷ˆ™Î'èÕÝϼt¶P»á ¬°y|™p SÍþÇøø»ä‡7"»Ü¸’÷�›Îinz¶Âåñá¦+ omù~õNùE.ò=b¨« ôõîg†:[¨Ýp‘ÊŠíî®$i6p…ï'OšëDTYÃeóeK+oœ0-û=ãLóLP¡]!bN³8RF¸�<´±/·Ï³B~�Ø{êçÊŒ«ï¤º„ók¶­é6‹ï·ÊŽå2›øu¿«¢® €7Þ|Hv•‹ ª&š®|.Ž ÔÞÏ�ž«‚€ªë2ALp™õ)-¼s ÑA L¡17�w¬ÅH�·A¼3Þ1|¢^:.±Šxî©‚WÈÆÿHMpãþ%‡w $²ü}—îÒ8)š,ý¼-kÿ7Åõ3â] ‹"ü9u“§˃j1dI·_1‚°à6®¥O0è—‰ë£B@?þ€pû™ôýžú—øí›+%\_„Õ%¼ì¢*o½o Dz\y„Ìn¿¶J£RÞ'ª¢‚„–Â…ósTÅ£HêÆp’JûáRø Èö½û³›Ã6Ã¥f.†—…§Ùº˜ÊžÌ7‘A2¹B@ÑÂÒu:ºL‹áRM L‰·¿E›Ìâ¢æš£_“ö7{Š(ž¤¯£Û´2ÂÅC8›§évâ��t‹«êÙÈÄ ¡ŠU�%e&\~X9xóö¤Û{D sá-u÷ñ³Þü«r±ƒ‡�¹8ãuñÔ¸@ÎþªíàDÁñQÔdöâ´ ÈFÏB�àu `B³®Š#�®†žÐ…„K2 &�)ÜEÀ¤m¼»s‚Û ×!…=HÒÜnÓøjÊù#×dràÔaû�Ý%üû^%+œLº|Œ `ä°.�NF�¸" +#ƒ/D'2¡&1fø ¬¿ÌÙ {ƒöÃ…DdG4íf ¸üΡŒLÏð Ç ÀsØ ×Ë<£@¼åµG‹»¨ÇdÕ¥wQ¸§»$Ã/ Áø�€6ÃÞ ýpõÚè›r™äÓ‡<• Ú‰˜6Çç6öŠœÙ̹ †Ëæ�Y".†9Ü)ù\¤Y‘‰4yœSŽŠ�H] `Ïl¼áÂe ¯¾Î¶q“Ô×󤚡•—ŒEfª–ž�É2 2M¬Š¡‰Ø —Ö—P«`\)T+ã¯,½Ò‘LÈÉÜZ~ ¸¬ëD[v½Ê ~Œém²NC‰Vyñ^âÇ3·DÖ îD³ª%Ë}«á‚N"Y‹+äàXeó¢˜'ßÓ¬·‹ÂÊg�(u•’_eÕ£¬p©ØÝUë´XÜNŠõŒxŸ£–[Ùÿò�^"Zm“áúù¢ª—‰kbrªç�”'möȧî«F¿DD¦nr¬”pM˜”oÖáPúoe°ffÅûÂe÷Úìó´Ü¤MuͤEmƒ-ÛÖ°,PnîÇû"¥‹m{ñë2®¶/Áf©¸~&Ãf.7ÅaÒ’M¥&ϧÍ: Í‹D¼›FÇK ïÌ7Y“ûe¨óx$|½¸JªhÆM�I:[æ^Q«Ù —ÉÖLFE[+Á¢Ù§¬X–Ÿä3$†&d¢LæŽû) h[óî{š]wE톋5ÀTUNDŠeP]l¶ù«<©ëh;Nùʇ(f�L»P ˜D<)Sá ùâ4Üú&Mm;+­˜x22=&³ÄŠ]žýH4^ Y Wi€#8áR5»mœ-½�Õð»é.›Ò)„)U<™’¡d)Wˆøê ×Ï—2U"®ŠÉšÊ14³×–˜L­ÉÜ)'}êƒÊàp ázðØTIQgiѰc«“�4râú##]w8!šµ¯O'S8”G£>DH:l†«èK¥Q(.Œ ¦‘Éͦ-™f“©4Ü•3A½+±9­œpmBÙñ†U•lÒ8ýܤþ 8VN¼#à„ö¾‡G&w¢V…El†Ë胨U(®ŒBÔÊ„"Ú׌L´IŒZþ£Ö5F-›£å„‹‹0ʦ§åMZUÙ’½$d™ÁzþoˆsæÆ{Âj÷Ü™å™ü!Ì:+GÀí€Ýpy}ØuŒ«¥øuft3< %™˜“Xvú.Æ\«¿0^^¸øl'‹EºeÓã)�‚°:èñ>2F´n…LÿP˜ugˆ8¶M†«è‹°^&®ŒI¯žg\#)™f“™ÕݸꗈHÕMŽ•®ìŸ̸,`ÿžVä]`üœ;ãSψ÷‚1>u+dZ‡ò©;Cħm2\E_>õ2qeL>õ<#|IÉ4›Ì§î† G¿Dħnr¬”peøÌ`·8γzÚœ¸Ë…ë��yþÊ‚U¥ìó|·ªÉä E²«�={á’ùÂØˆ+a’ØepcØÓ‡L Éüu~ ,ºß‰Èëì ÚpŒ¹Ö�& ˆÜa– Wa®="–í™2Uï„ó„;e/\µIØ ‹áÀ®m{¥²—É“‘iFóA'ƒýNIÞ¨ýp ò?ÙÊÿ]P"½RËkG“L¶´„ËP–Â¥ðÁi´�H"©kä‚Ð÷eï™)²Åî~†fØf¸ËTè=u9¾&6:§ èèÙ —ć�®0¼µ!]â{ÄHçPÒýL0–t¶P»á ¬ÔM¶¸öæ„§ÆUsÎÖÈÂXÈøÁ&àC³.�#-¬¸à ïÜ6¿bŠ€Ü^¸»‚æM�Hÿ5q \& Á°.… £í¬hIïÂ# B¶ØÝçQÃ6ÃÅ@¸Ø$Ÿ³Mö›ÿM½Ìð5‘!}&@Ã4.‡²,¼©2í=¢CºxÈ Æ i ³. BÈ®�ENlB2M&Ϲ›bhâ?Õµj7\«¢–颬’&+ ÿ÷z0Wµ|¸¸t½=í †ã’�dš†ŒV½zѦծµ®£/sZ‘¸8&yZ×ðÕò‡‰F¦Úd5·7Ú"*5‹#e„+‹ʆY­øIBíåÅ;‚'¥3Í2ÁC0íUŒS§Åp1}0퉫£-�ˆ9¦¡ƒÊ¨³b³UC92é&±Ú«.pÔ»BÀjÏâHáò"¬²Ñcµ+ÓÇÒ.—Óë),”_‹Bá<ƒ(�Lã<�v €Óa/\;4�qIÌñSk{•¥‡¤C(2¥&ÑhÔPŒi]Hô·®$Bá<…ó(ð ¬Î(ÇË*Y{/rZ9•Žhä¯À>[®Ó(…ÿpBOÓ›l‘F…Ù¿âÈÅ|×4e!~©Ê²‰?ë¿ÜÂ/÷ê›ÌVë��Ø ï>t[…â2+­LøùÙö-¬ÊCİ®D Ëæh9á=e0zl`w2$vtùð.…ϵÿ…¢D×d1¢g1\wÿщ‹Ù�]4:�‰¼ÏHÐU³ã³»B:‹#e„÷$¤E2ÏÓ¸¾-Wjý-]ÆðJ…U¶ÞUþG­àÊȤ™ÌV„oûáûÀ>è®”BЀ+€È_öp‚oÐO lðï\ÚŸT~x‡@˜ïøŽWY‘ÕW©ÿw!ì¬xŸ¹tÛÞ � —͇c»T\¯�ëk;˜Ø Ø´�Ž—.'B![4(·É"küß^¤åÁåvr÷×ËrC}$Ê$‹Ê!ìV-káZù §‡‹ ˜Ó’»`³…!Sfò™èƒæ4ošµÛ᪠¶(7۲Κ cóVeÃ%Æ8›—𭨤€Ð%‹&S4”5å n}ƒáÚù§JÄ1¡S9îB‘)5™=å­@D] "P.!\K„ÃeZ7Uye3åU}f6\u'‡dB† gzN€œË`¸L>È™%â�¯�3s¸�¡¾ìáNÐt ú¾y�€.Óàp á²!tñm`Øø‰·UÙ”‹2ç'mOáÊ{qÁó´-Û=êŒàŠ;q3ΫÍàÃ�à™Ú!Z­B@!b3\Q­BqeÚÝ·¾ðìH�®¬‹L§|dúM ­úNÖ5f-›£å„ëŒ�[ÁÞmݤU¼,üW`ôLxWpBJ&h�ºß,ÚæÂåñÁP/ov5êé]Äíi,ÔÝ‚¯ÿJÀ”nnÈz¸TI<ÀñýÔnð{U |ÀO<ª°¼ŽÛ¯uÇ|:^°ÏBÃX—ÝÄ]àÙqi�D9Ï;ˆ—í·ƒ`-N9|ŽÙ¡ñË-ÿe¯8ª#€±o,\_Ui¸ZÝPht•Ó…äÝÄ$SsÒh©j¼©_XVÆpËá:#Ãv‘™»í2iÒÎ5-vUí¿‰äÇû Bõ"ɪJ_Ãs1Ä�ö'2‰CÆO¤jôZ—Øe¤h\/5À"YÝ`#z’ :‰YÄsÀ ù ͈eÏ2Ã;Â9;nx𤼡ÀûÈ¢™ ï%NªÉÄ¡×ôœZ—Áp™|X5KÄ_!jæp‘¹§Y°é@`^ àÍ48\B¸l]Ùf—Ãø /�­ÅîÛ¦�jq��tÉi®œá¿ì¼ß!ó’ÑîÅnQdü€Äï!bÁT…ßž@ð�s¢ðYãµ!,‚ˆÐSÄž6p§²ßo÷š‡ ª8-†wŸÀÒ+×X-,ô²†–»k¿ŸÑ«=€Ü»B)zGÊï H¼hGÿéñ¢—ïKžñ‚Là� C¯2t;-†‹çCw¯H\Ew/‹Ý è—½œ-cÓÔ®£'=ï XìY)#\N„E6i¨ <$¸‚98ûÜÿÆøàÝψk螺LÐöú¾Àç6.—}ý2q ÄĆ©ÚÓÛÅßžhë»(ô/ðÖ79VJ¸„ÄÁgs˘½ØÃ{ƒŒ¹näÄw2÷$b“¢YgâS¶d·äæÅ«4[_íÿtgç$k!">6ÃÕõÔ(L �–Ê.Dûª IIj'ßëu ׈x6lŽ–®9B´xêï»T*ñ&Úʉw„häìÕt)dtµ*E€/b3\J|­Bqu¾V&7¾ˆnûaÕò²®°jÙ-'\àVë«dY~‚#£0üMBUψ÷„T>¯ÎÓ•\Ë®Ø8+&fM¹?ÍKx“Ö†¬„ò«×”_Ûd¸¸¾ôêeâ‚™ðêyÜìNR’LÊI£/#M¯ˆM¿Dijnr¬”pÁ1š¯8ÇÃlw“vÁê^f¼ƒ DÃ’2?ÚÍø¹2¥ƒ íU†Z§Ép ½ í•‰kñRÛ‹êké·/™bÓØìÕ‘QÓ»DÁfÏäX)áº"lÂç�Ù)µ5<�Ñ>¬ÁÞ8cœÙ_oT[6ðþ€0Úu’¤i’¼ ‡oWk^@üªØNôYU•l'9eÿ’uŒ”­J°ŒØ Ýf«P\½¾*ì¡V+»ê@…É$žÄ¾U'ÀÒºF@¿es´œð®€ðøçìµµzè´__¶³ã½a½ºˆB÷ u»’¬cFÃö�Ý.×Ë»íœnÐýäÜ×v8û"Ù¶Ññ’•GØÎ 8ö�±§¸b8_é§xU%ÿ¥lÌÞgÆûë(í³e1Œéû=†U�€ïaÓáZûPŽ•�ë¦n®±¼nÚ‡„Ýã˜ÿÀö'Þ1Ó¾¥†÷„}1µ€'H?3ûS¸�{LïuSxGÂ?ÅÕï-÷à‘NG;À�Z ×Ù‡{G±¸R|2ïê+üC¢î‡~‡ï€ ã*ó«e…÷„tùâ#�OïõrWf¼Ë pÔ¯ß�Ä4ž¬„Ü¡»êFÀ6n6\Z¸]åâR©Ý•ÏÍó€ŽdBNºw¹ˆ¹.ðì2ëSZ¸ìÑMÖäé<©à½(uÃ_ßÝ‘¯‡Wâú9ãz·Ý–¼yó™‚Å×'o>ž9qºüð³øCxËt_‚°ê ·:É*�Ã�DZKÑôçmËÃÑ&®Yã­c¾rùè`íþÙ–î;?/^ôkÁË£NÁ¨,¢æ*«£óË¿yü2Z‹Z>vŠ0ÊÙóøøûÙã:ÍW�¿ýk¿äÖd¸|>ñ¬_f<®›¹"Ñ7€µKDÔ¯HÕ»É Q¬ßXF¼ úðþˆl±@vúcüËÅO§ï~‰/Þ¾¿zsòñcüþÃÅßâþ«Owlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/d1/0000755000175100017510000000000015222270712023660 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/d1/669286f955a7a432d5582b768df36daeca4fc2wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/d1/669286f955a7a432d5582b768df36daeca4fc0000444000175100017510000000472015222270712030760 0ustar runnerrunnerxµYkoÚJÞÏüŠ÷d¥ " Ù®ŽV»=‰D I¬C ÒË'ËØxklÖ6¡ì9ýïû¼s±0 U»UÚÆž÷~yæ�ñ4J¦ô�þzù—‹sš,ÂŒDœ‡© Y Âcú"ÎD@ë8)=8“�ㇺÉj›†óENo/ß^Ò8Y-BAÃ8©&yé2̲0‰YÒB¤bº¥yê�$hÒ,‚’ù /�‹&å yñ–V"ÍÀ�Ls/ŒÃxNùPÅ”9Û—%³|ãÁB/È˲Ä=È£ ñ×Kïå¬�ÍϨž/��5ÇYC* „QCš€ÕZØ&ÌÉ:§Tdyú,£ "?ZlƒYŽÂe¨50»ô?cË×<`;›´L‚pÆÿ éÖj=�ÂlѤ dÑÓuÊŒ_ÊÀ6Ù�‹$¥LDKa·ôµ´NÒ°Äúuˆ¤ÞÍ"Yîz‚ÍÖi • x‚!“ÿ-üœß°å³$Š’ »æ'q²¿Ù¿tÚ&X÷¦É³�©ÇI{eÌe¤%*µz)[xp`Š¢‘QƒrĘ_ŸàâzšåÈ~ˆ¬’T*Ý÷µmŒ¸ïÑxx;ùØõÈÓãhøÁ¹éÝÐYgŒç³&}t&÷ç �bÔL>Óð–:ƒÏô»3¸iRïÓã¨7ÓpDÎÃcßéá�3èöŸnœÁ½ß`8¡¾ƒ‚†ÐÉ�&P¨E9=ðÝÒCoÔ½‡äÎ{§ïL>7éÖ™ Xæ-„vè±3š8ݧ~gD�O£Çá¸õ7;p·#hé=ô“6´âõ>à�Æ÷�~_ªê<Áú‘´¯;|ü c/’ƒSÕ¼¥úKŸ5l \›¬™F,UZûÎ*Y­W¸n«Àp©^Ø RE»÷¯¥Êâ\Š´7–WÚÈ’±J2y“H†óûO+?k[7)µŠ`îú)´§¨Î7 ¦ª3¶Dw°ŽÁQ„�b³ê&8kË«ÒgvÜAÊ+³…—É�‹á~*@ï§‚¯ϬJV#Èaíê÷l�–n›qk�y}WeE(í™Ñ¾ 8^!»Øýj©~:~ŠzqãÂÒ¾–Û‰m­ÚxõÓ|�œ±Ç!ŠP=ëÅbÊe Û›YMX±¤*µ­ðð™\Ô„E ¡5ëš¼l¸#ô�ŰÿølºGï$vùäf®€0´È‘€g–ÔУ,ÙkûªG»šè¼¼U:YÀ±Ê1í¯�ÞÊ,=bW";½yC¿à: c½¾ërŸDêoTiÜ|á~¼¸kØ[Òz”�åa¹-`²,QãÐ�{ý—å)ð"÷|_¬qæÇߤEQÖ¦I"�ý¯&EŠÁ35)Ë|šMOÍòéf™®ªIE|ól‘–ªSÝ¥êÙ¥™ñ¬+Hféd¡² ¥ Ø{Ȫ»|S©"-«"›Fƒ¦‹ÍŒ4Ø×‹l™ œ.¬—¤|S‚ÒÙSË|–Íù3¶Ö››EVÞõÔ,#è›M…‘%Š {äãE^¨2],!Ý«§NÆ düFD‚÷á._Æœ@DóM±Jsf9D&ÿšN+>!åó|±ÈÈÚ4_Í2ò[¾lµ®ðjr“ߧ‹Öì*¯@ªˆ[ Dh­šWåݴߤF`À ñò‘eÜmnÊ ŠÏ ûu^¾:›}à;T—ç'Wã¡]ª‹ñùÏ£ãá±z6¸Ä÷g=õatõöüý•ˆñàìê£:?Qƒ³�êÏ£³ãžþåb<¼¼Tçc5zwq:âÙèìèôýñèì�z�ygç0ߌ@¯ÎP£!æ�¨wÃñÑ[@¼�Ž®>öÔÉèêŒ0Ot .ã«ÑÑûÓÁX]¼_œ_�þ`ÏFg'c`¾ž]õ�ÏÔðg|Q—o§§‚jðÔ�…¾£ó‹�ãÑ›·Wêíùéñ_AÙàõéP£SG§ƒÑ»ž:¼¼!ucu‚Ç2ÌP÷áíPßÿ]�ÎÏÈÆÑùÙÕ_{àr|å¦~]{j0]R 'ãs€§81ƒ2;ã¼³¡†BQ ÕN#B�½ÓŽ–ãáà° ž³hp¿ÅhÑÊ–T·Zä··°¶Ö¼€½Îà°U¶„½è—ö»œ—z\^ÚëIugß©ým¹œ¬õÈ)ÌÖM+¶S΋ÎìÎlÇWérÍÀ`¿Ã(×E>MËÒѹžˆkB夬,¬çúÉ=Bƒ‹!ûèüÝëóäB>÷ÔÏúe«EVÓBZžû·iu*Ï:I²š,Ó$é¶Z0WšÆ¡j+ü÷È ^Y¨Ût•bâš³´ø7($9¼rømõËÞbò˜{ððÅ">}sqúcòv8€qÄ~ÅH Þl—Œ½kmä|¥ó�šNVˆv>()ŠZ:‚ñÁf!JÀ@@_ºÀôæì½:…Øðî��»PÑZ&3 0+øÀ¢>cÎ ¾‹¸'9€K°~¥RD_$ +ßmd0Ðz réL*Ò]¨|M=wAì£ZÀ’ eæ1~ÔøöìIL"wù:9€þ2¸ ýùfÑk!:»èb£Ì+—ÒûT§Á"sÈEÌhLP­Zì Ù_Š}¥.!R©¶¥jó­ØÇ2GȎ‹„*K¤®ÅLÝM·‹tšf÷ +L›»Ycvk²ÈM¢1Þz^©l®ç{ê¡È*ªóÇ.]öZ£Õ´ßSÿpuOV¿,� .+(N²yu§Ny^ôÔ뼬˜cß ”Úÿáà`ïàÇýƒbËÀDŽ£óãa2üËÕðì1í–]¦UçºÝ¿k÷þ]¯åçTÿk¿MÛŸá]³ÕÂã?’*O�w7ÓJ|¯ƒ§)w_¶>EZ!WªvÒVßs_éÙpÚ),)arÓ4‘z¢S¦“bz—̲ÂÍ/7‹ ¤]xt`H >‡ ¦ì#ñW¼=�¸×˜Æ¸Öÿkž­‚!¨;¡+ð8ò—aYIPkƒ™oøAÁB ¾?Dàh xíᤠ©„åV鯀]|&­5YkáhE²^§«æ„’ÓèµÈŠt‰Ê!¹K'e JQuôƒ@j:Bö³Õ<ï´�*£�f*7Öb¨‚¨–`úS2†yòcû! 2µý-Âç4_.ó¬LJ}M`‡…÷ %ukPBb€ÆìŠô6ý•6&;�):,ÚúÕ§ò{2ŒÃ·¢ý©|þé7Äz64WL`Ï7ùì±CÃLPÃU(}QåyK7àQšÝ£¢<„¡ô… KÂN²P™0µ¶a9‹tÕÑÀºêOêÏ|1ÉÊT�7(î–é°(rØ»'Ô·?²¦ ”í‡Éá4Ÿ¥ ¥+#:&®æ›Û; ÌH rÈéé¬Ûn$â§o$°„ÃdëbF½ÿéÑÈ(\H ¹”ʽ�ÀÓd"xxàìpÊÙZ*ð$/Èkªh&¿Õ¬$ŸÕ�ˆA¼SÏùý‰9{á` áýéPí{4Ùéä!V¾‘% �Êúȧ6«)J“E2�”)cæC^ÌÊËg§q¨ã/ >YUq†'‚vÓÏÊ BGÑaÊF¾Ä"ð5!!52ñXK#›Þ€ñ\ã‰~ýÒ/6+”FÄ ÚA_›®“ß?£A1YCXi‚ìºÞTÂZ"OÀ'ãrͯ�µô¡ÅæïP"Ã1hRT%Û&�öŸÚ�–©iA?i¿x¡öhSœr}ðòs¨k” ¸ï§§àIöl‚gôCÜ­RQYªqZæÈŽGÌž¿V:(1Á& :OU’ ([Ì{\G±ub³kÏ®7®8°oÆAxæ7Ñù�×:)â­þ%~i—>‡¸¥ñRR©Y\î$Ñ%Ó¯¢–jÁ2 –Ìkmh,ãàlê_QÙ¡š£Æ™ÌŒÅ;Î0ç~�ù›gEY%Û\?"!(å‹c´ð¤–w¢d`±~Œ–9 Pß7œ�5J¡y†ÅçbÑ)Ú×GÓÏv”ê?ïüëá§UN0ÞÕ~çè4µAT‡Ã1—æU›ÏÎk´úòo‚'bl}É/1x©‹¥vb‹íì´LT ¤–ÆÌe�SŠ ìÖ8…ÐëÅs�É»z”øÔ|ÒIdñ„6Š7G‹Ñ‚•ámÊW‡‡ð!´ÔÌ…jƒDCûˆ­Ôòa B½�¹yé§HÁß2‘ñHíI޶ÄFD¬¥hÌ&�7#ÚÂXÏ6sPÃz´:ŒÂLÝ@E™g&&þç+o1X﬒ÍÊYÐ×ËWƒT&³Pžä.Àl•)é³Óíú.iEÉçÜ ^œ®°�MmG.qÇ�‰“½Ôm2Eÿ—¶3AúRyýqnK`Ó7ôe‚oiy�}¶¿×²�WaŠòLȈõÇ·žÐPôÒMBbXÛuÖŒ@a» xœ®ƒñ2ƒÁLÐÕ{©n“T:�ÏF+Éé6ô æ=ë¾ÂëÉ ‚b§[« ¬q·÷‚ÞQ-ˆ]®‘Ta%Ø|jˆ’”õ.ª‚yCF 1Si^LÚQ0ª Xm‘¾¤Äæ ØÅR˜VA¼y‘¬6Üø£0ø1ö XbÄaüiã ×2:𸶄)ךUë¨�×?a—ÕLÌ }Ët�NÂÂö˪k;ñœ®Æï‡_/Ä“Áé%†{+þ*)^£ó¤×µ¢+Ü‘°½�ßRÑhIÙøÄòeyKoyælÀ˜,Ç$¬áåK÷ÒˆTN„ÈzsëªgÞ� m<…èh�aÌÀþ mÚ1ðÅ nR¯›ÄÉÊW{=<^–§øËvóÿÒåÿ'®íCNƒÝ|£EÚø×l—h=Ø�GßçÙ,ðÓÿ—T Êþ€hÞSaJ2Ý*F~[üvÿØ€ÿM�ŽÇmG4Ñ_a¿ ;Zõ€õgv“¨ÜŽ÷¦'¿³©ã¸úª¸õi6.ÌêFøÜ±l "%c”¯�¼p¢|å=&•ZÀ,ì’Žf=u´Áù�%[—Ç\v àpÌ£ŒŠkÊ&úXx˜�ê©Û.%›¦nM{‚©ø•�q{�Ú¤�Ïö&.¥­¡™a"¨í˜lhCØ$…Ý'�®\Õ"¯µIØŠCy×Á•x¦]@³’óUšjW$v-#еAÍ r/ I{‘@„ö9ÎH¬¹_vóXaÏ�Ÿ(`5ðÅbrSÍ÷þ¹�nºK£8 ôËfm §jÚ^ilà¼*Ui?´Ul5­<#›@v¬Ñlæ a¨q ðºssiDv¬°cÄX4âzïà³4Ã=L/Ó&“–%ðY¾ªµ#\-Nè#ç…YaxgÔ‘ÝZa i2ذ¦m†‹uºnÚîì<8 ˺Åcj%sìæ‰¾Êàxõ-'Ó"÷-—mI‰vBBºÛ-œ¾9¦àÁNf3+4c›~—¶éÜ`pû´n„«?-°2»k¯B`7¹' ±ö¸¿YqˈëoŒ|‡­NZ»ÌåÜ$ÎÜc¦T8<‘ª%Îk9�,å\ãü±îÜG¶ö›ºµ(Á÷ûÎ3é¹÷llF¢Š4EÐyJ$|@gbŸø>žFñnáÆ~…?SÃüÔ�E{8`æ>zàhçÔm_­´ª¼÷†¶û©~6':'‡a]Ó§>Yz@8‹»ÂÙβÄYÛÕ�¹®°°>ùÊq Œ†‚pƒL,©#U Ø{’#â´é/ãŠBÝ<ø'üpÁàP�û“×ÁN±é“‰14÷Ç®=¥Ì¶Ü�õûã [Œ�·s?4)ªÁ O™ØšÄÜ6ÕÄ^sÐçë}1/Ó¡Ù624ÚÂa_ÞIÙÞã~ÚfïB”)4loné¡<÷mý�ªÃ!mœ�m¢+²McÓ6™‡„s¹vªa¤aWÍ ÇùÇÛU.Ç.qXDŠ’`c‡ñ'ÎÁxjy9˱㚤Ý,F³‹±Íý[Ó½�Xê�]›;™÷9KK³Ã6À€¡v¬uÁº;è“Ö÷ù‰›[¼‘•W4Lª )Á[lòFv×çÉÒ褩±±”»U„ÎÞP'“Hƒ¿ásÙ4ïðfäÖáb�€å¥§Ü†§ c—&�Yº5!1ay&m®8¯ck£îû:€Hï œ¸Å1.�6Õбh(G¼>M‹+XNÉî�cŸ7’ ’ÊJB潃¨«ëƇ+)÷6ÛñÐüöj�};Ö·™q÷B×:ÚŽ~·@¤ÝŒÆ²oÒØùÑ’Ê>ô@Éêé›, å½Ìà�4N>Wûûû¨µü<w­�mQ|¯ÝýÜ5Öit>mo×/±wC–÷,½ œ™Ãæ6 ã÷4KK”áû¹ú�•c´×¡c»�Ã]¼~Ù¤:áGÑ �¦qO±oÐc™%ÛMÓc4Nó̑ޗ“Gô 7 v¨ÃkÜ�] ó¹.$~1à° îPìÉÿ5%@IsúnÕ´éÚq¼Áf“(Øp3)>p@k UÖ­mUY½·Ý±P2Í�}ã-aÝI<ÚÒ wÁ"ïn€k¶o8à+x6+NÄ–ªFuøS§!ú桦¬òüDä›·ñö*ôúĮ޹^ʘ`îŽð*7]¶™o¾sÒ~ñ\á.âù›áÙp<À]H\2<—»‘ÃcÜï=�qåìôcèÁd„#ÞY‚OìqsùÅWÏÞ ûK}Ô@.(ñnG±”ë];�ì|¡äo!ð Nȹ» Ñe8è{'ˆðê@sÓŠ7pO®8$8-É 8¸¤Â<ØÙýÅNèâãĹ|„å‡ç �¦}dŸ¨ÿüçCö‚c•?àüó#ú)º¦§�/õâ‹EÿæAE¦{ýn­é0çz wpù€�bm�À}Jd�vòie›f@xÏ0:þëE åÌ¡¢w�È¾Ëæ¸o¨oýHØ –õ"¯à¾Ã$JMÂ5_9o8}ï7ÇØä*г#w<6 1sÐד´Ã˜ºü ;kQÂmL‡¨ÖEzœ5„ÙIçˆzçG²ÚúÒ©¤)}Ýr+ÐÎeÿlŽ5™X’è(Bl"{9ó:4ξÔ�þEÇ(ç<œŠð\ce;9ÅT〻^'qb–oJ[F¥�óLÙº¥�̯¡ç±{‚3>£xý÷/?o™ QÔ @DĬ§å‹v…5¨åBþºÂ éqk;š‹ÇP%�xÐŽ ~‡Çz¯¤ ­i;µåD¬ �l­uî÷G<†mE8¹øùíïp·ö¿Ò‹£CC©¿µ†¿Q&ìL£ ÅÕ»Np…³“À#“'«âÑóãÅã›NÄË@^=ïóŸÎe9�Kìó¬{4‡ò~pt„?�0=  ZÇZ Fø�ÕŸ�Ö×wL;¥I4é¯Ót�žÎoôí:^¤]¤gy%M#¹på}¨�»™{Xûñƒæ•¢ªmZà0[Îë™z㤓¶cÄ ì0ظýÕiR;¾¬d/%¶Žl€ƒA™_5ŠÚn„yG0¿™Pb½´áŒ†©_‰u ®òwOGmÛ!¥§ â/�x””Ù ¦šš+x²-}W0¢d×Ên“°ÔóƒáÓ@7ßv@¶b¨v¨ïÕmÖ¬7 $S�ó¦t¶R‡&áÇamìg¸�€ÔœK§Î=:�´{s“ZLÞÏŸ:•r8"�w:hÜrä)ãxûµîón$··åÒvèøi?I'EâƒÈŸÑÈW®#_‰Ã A�dÊ�=§†·4oå\lÜm  v "öEQ?B¢4Ø‚ÙÛ´¥Èèfq5\ F�Ì¿U •‡F?£½±,©‰!$ Þ°¦Ûˆ™}0ÁÜÅ¢öà«ÑG¨ðRþÉ œ‘vw‚åXo‰cj vlpl“à ª”R]ƒ^rd¸&V0öR"–©®ÂûW4ës~´ã:?1ü ¬ìÚÇÉ@Ã�ß!x¸ã*ë ç cF»Çr›wô³Ä•MQ{ŽJdTþ!'¯,X Ñ–2^!‰·d¸ß”èÎ7ˆ‰f‹ùÁ°4Ó¬¹äéð#¨àÉ"ݾc>{a?1ä>’³1ì`T,‚‘¾“›Å¬ô­$[Zë;+�õ�SmúÎfðWHÅ®?¹ Žh4BüFáhrY?ü›‹Ø�¶Xc¯‰Lêþ˜ ʺ™´µµò¬eé 34ûç B6›}1J_ÓCzêÙó¦Â޵·|�r¤|6½š Œû¿§À5Êwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/85/0000755000175100017510000000000015222270712023610 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/85/d9f3adde30307ce131c46a63cc90739da26c74wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/85/d9f3adde30307ce131c46a63cc90739da26c70000444000175100017510000000061315222270712030726 0ustar runnerrunnerx+)JMU017g040031QHÌKÎÈ/ÒMÎÏ+)ÊÏÑKfÈãŠdxâþî_£Ö�$ãƒÆÒ¶ñ^Pµé%Ùº9‰•©Eº)©¹ù@µ–}GîKK:´9Í>�æÊ­“,‹]mÃͳY¬=Þýe<þ}u\&—X¤AÕf§V&å'¥è§æ¤&—dæçÍ>¸õÏ6Þ{«š§ìøÜ:#8ïßÎMÍ&Põ7 +®*YXÞp“[øU]ð·ù©_ÅNé…*ÎM,JÏÌCò «ì‚I}ý3¹8äÝRŸ'ZåžéS›šWª›”X´þ\T £ùæ å3{¸Æ-vø°/t�:\U1Ð…I¥™9) &¬róê ¥¸ôÃÝÞ‹î|Ê嶦,?/³´Èîäß­÷C®tÆëG þÜÖxÚÖhµ^`LyqrŽnI~zzNj1Ðþž¢Ä½·±ìÝêyÞÆF-°ÆxÅZG¨Ê²Ô¢b`éfæ¥�"áðÑà(·ÀË¢ÿ¶—óúEUíáŸ}Þ§·úwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/fb/0000755000175100017510000000000015222270712023743 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/fb/5f1753b10a5f2c9339e22567ea5c084d12e211wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/fb/5f1753b10a5f2c9339e22567ea5c084d12e210000444000175100017510000000130615222270712030556 0ustar runnerrunnerx�TËnÛ0ìÙ_±@rKµã´ ¸@O½äÐC�Š€ É•̆&U>ìèﻤ$Gu >Äñp9œ}Ìî´ÝÁêî~ý¡uöŠ0/šð\jÞ¡+ýµ.3 ÏÏBOý¿Gt^Y³�bYÝW·C€V�GB¾}8®ø€ÞözåËvYÝ}ªVñĚG˜mQúMz‡ù ·�‰+ ‚âÄ�Q¦a�¨·ëâéz6ÓjǼYaKJŠÙŒKɆ,wM<  ~>¨/�-[”Ü%oùˆÎY·�&z”e�HÆ$57MäMÊGô$U…Þ‘Ø¢‘hD—ëtS®«%‘MÓ[W··^ÏN¼#É„V$äâòpXö‡«jµÌ³+°FwàðwT%ÔÖAØ#ôÁŸ_ðèHÁ;/ô‡ÿ~aqfÞ@͵GJô Fá^pcˆWùsØv1À �Ž VF‚¢Ÿ*ì-ÁísS kjÕ¼*(þ®Ü˜üðÀj[€áA©#ÁÅ^)u|çàE6ý¼�@°Âj8íÑ?r¥ùNãYÓxîß)Øùü­®Ï�½x­åÍú¼‰QZëÈEöMBcì'gùw*wR¹y¡ŒÐQ"A#2Š˜@É—­SÇ äÝ8›,;–eÇæ¼„æÙd^³´2Ï,õkD7½ÄÈS½½1Œ”Ñ>°N!Ùò2š‚&úñ…Z�>å¤jhp4ô¼�V$8­�17‚!©úMxì‹ø„¥ 4Ÿ™ç ¾J <­�À5Í“/kê3Ù,}k�½BCB¦±Ô¸¾!ÑaÙÞD€&M‡ÌÌmGµKc�öHãø�úØÑp›õ )ËcÆ6\c^8Õ&w׊ 0Ïë®Ñ9ò<í¨èu�z| 7P|L÷ó_I9�Tm7áŸ?’»�Fq4vm·ZjiC¾'‚6c_Ô?á;ñ¿wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/dc/0000755000175100017510000000000015222270712023742 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/dc/4fb5067db92645168b28fa84f29db75385bd3ewlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/dc/4fb5067db92645168b28fa84f29db75385bd30000444000175100017510000000315315222270712030674 0ustar runnerrunnerx¥WkoâFígÿŠÛm% ËÒ<ú¡[5U0«`�q6]u[ËØÌ®ñP��tUõ¿÷ܱ��d£JE‰Œgîãœûša™ª%]~þý�NÇšºMd,2-,k¨vŸr¹ÞtqvqF µÛHAw2+DnYs‘o¥ÖRe$5mD.–Ÿh�GØMú´Ê… µ¢xåkѧBQ”}¢�È5Ô²ˆd&³5Eà K˜ÑjUmU"Wü†Ö®\¦Roú”H6½, Hj^41ì3�oUNZ¤)[�Àm¸¶èŒ {Alà¿‘ñû°QÛc&ѪÌ3¸DP “(„Ìxü â‚WùJ¥©z`j±ÊÉ|õ–`+Zª{a¸T™ÍT¨&Ü&D•ÕzKo"`_Š:`ð‹ðòRCìÊ¥.�x‰ØïTnü�ÒÀÿ�C‹Ù8¸³}‡ÜÍýÙ[wäŒè…½Àû‹>ݹÁÍì6 Hø¶¼£Ù˜lïýâz£>9¿Î}g± ™Oît>q¬¹Þpr;r½7t =o†òuQÄ0̈Ö¦\zcš:þð–íkwâïú4v�mŽaÔ¦¹íîðvbû4¿õç³…÷#˜õ\oìË3u¼`¯X#ç-^hqcO&Æ•} ô¾Á7œÍßùî››€nf“‘ƒÅkÈìë‰S¹©áÄv§}ÙSû £óiÀ¾«ÑÝÝ8f þlü wæ1�áÌ |¼öÁÒöªwîÂé“í» ÈØŸÁ<‡3�õ<§²Â¡6¨÷�ì¤÷XFŽ=�-¤Ç;X<-,¹åtSªÖkT[óº*³¸P*ÕÍBŽa²ÊQË(Ç•\S­6u½pq;Ÿƒ‚3 ß¿ôijÿz¼dYl\ätÕx¬E11kÝ0Ì¢­ÃfÕlz= Lj °_ÑùÙÙ™eý¼G2(T¥¡Ê‘s_|ÅRÁ#¥®åí.Ê%f“6ã€>”º ñ§iŒ´°âÈÞbz¡—~°ŸD¬( 1ÅŠ0좿W}Zã;#Âx�™ÊùçhÏ\¤"Ò"éUš¬Í ƒF€›¯Æô^À˜Á®yžnÁ´ÙÂóx«q‡Ýæ«uYü¹¬0-òT¹(0]*pµï+2Bƒê•Dz�n˜ÑU»Ë(œ¤mTN�ÈUc1:4�×6@ƒc8Ì÷Ç=šCÖ"Õâ9MöÓjâ­EŠb ã�ˆ?â|0¹3©üLHöIÚ•:Ô厛@$§šèßÄS(ó\¾¯ê‡ÏK• z˜œF™›‰ ó§&ÝÝ/ð"Çœ~2…Ð: {ÈÊcé!ýõ_¥¿¼¢e”„u½„—áÅwáùk4{çRÓ¦˜N‰¦©‘iM„Õv©žS«ªëÓ·ô²©�“šÂ [WîQ~t™(õÎýå ÃºEnPtÍÖ`�«r×=ïµÑ­çÄ#™‹™º~êéÊOh�—¢²yÜ굦‡n²˜.q ¡êñßuññý«Ë÷¯ if[«üÂ5$cæÄz¢?¯òõèˆìYO7õ‘Òé!Ú³žèmä¾­‹Îåàâ»ÁùëNG£¹bFq[D´V*iΑà #M:Ää «˜ ͉ÓM…®ã^÷Íýö» W9ùÉòm55Ãì$ÚRB62| ÕßG#øÉ¦D»�È’n­QYÜà‡ ~„M�þýÏÞ=ƒ«p·ðŽä»¯¦Ëï"òÑåW´[ŸŽ¤ø¦b¶pâôéUÓ�*f짉^ë©i«*;Õ~SË#�#îF¾cðYÂyâhM°DCì1$£ha8žïM²Rà«æ®S;>mÈq„#·M «¶éy6%,ZÅöÉ´Ô['j·²_Ýÿ2[©ng¬J¤¾€©Èº•BÏÌùæ8Åo”šˆîTFꮬ„­­�Èwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/dd/0000755000175100017510000000000015222270712023743 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/dd/47d527a757da3dab5dcc7ea9b3b0fd07efbb5ewlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/dd/47d527a757da3dab5dcc7ea9b3b0fd07efbb50000444000175100017510000000014115222270712031440 0ustar runnerrunnerx+)JMU°0a040031QH/ÉÖÍI¬L-Ò-ÎHÍÉÑË`˜Z¿5/ò¼C¯Åô9?„Ÿ'È[Þš Uœ›ZœŸ§—T𙓰Kx÷~» ‹¶0©MÿwÃU¸Až¹ç5"dwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/44/0000755000175100017510000000000015222270712023603 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/44/2dee4e1c1044cd1b6384b7f7cb4a0bf0f3e982wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/44/2dee4e1c1044cd1b6384b7f7cb4a0bf0f3e980000444000175100017510000000065115222270712031061 0ustar runnerrunnerx�SÁnƒ0 Ý™¯È-LƒNÕ¤I«´/™¦(·D ŠÃ:þ~Nh eLÚÆ�ƒýì÷üìTÆUl»}~¹Ã¾ªµÏy ¥rmë,¿Ï.ÑÖ©c‰à?ÀÏ¢Ú8x´³åzÝ�³êÞê�Êb0ëöÊöÚÖ¢óŽš¶9ï†Ð8ûDYß[ *¯»��0o�ݨÞ{°”t½W â÷ì�ñG*+— 6Ýéö΃T ›¥Û} aÆè[z…W!|–gzä$a»“ª�"þØ>�êé¯ú +yŠ®q?f’Š)]C¶«4àŽ½±`'9I¶*£É¤‚QP9€Ø€1Å7‘bÜþûEe*ˆ½Ô†Ô.ÚžthrŸXFàyÀ¸±ß Õ ÓظÞÔ‰nGÞ\¹'ÄÉù#­xÇ~^þ–þ�¸úÝ]ÓŽÉÛ¦U¯IÌtPk;�êÉ1²ã|YY¦PdµV`ð�§›ŠËKñx´ÉO~›á#ûØyÎUJ’uÖ ÐV¤)ÿ¥H3Þw)=Ðã*GÃÒ£šë[Ç­©]Eš¿?‹�˜wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/57/0000755000175100017510000000000015222270712023607 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/57/c56fcb159c22e045d8bb2a4018738ec865b5a2wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/57/c56fcb159c22e045d8bb2a4018738ec865b5a0000444000175100017510000000120015222270712030642 0ustar runnerrunnerx+)JMU072a040031QÐKMÉ,É/JÎÏKËLg˜èh4õEÂ���]�—ÓåóDõœ‰(è¥g–d”&1È=˜o~¼Æ�çâq1ÝÏâG»Vmú3 ¨$3=/¿(•AiŸLT÷tÿu‡Ù~ô5}žV­ Uåìáèçîêãï®—›Â0ƒ½ÎceEnëza鲤°9uTBÕùx:»ú»Æ»øè•T”0|ê(ÐÕ_˜ksŸÁtcÔüÓGƒÎv¹î´úâêºÕx  ´Ýv÷¬*ޥׂ*ž ï'üƒ‹V§Ã¤Z×à¾Ïü�ô®>öÛçCqÑëœzxxì�³ÖqÇ“¤°¨“N«}Ù§­ËÖ9q×=0Üg0ÅÝÍ…Ç�@¯…'Ãv¯KË8éu‡}ülb•ýa9õ¦=ðš¢ÕoH gýГ81ƒdÖ¥y]Ob!Q3×¥FB»Æ¢K^N½V¸ ž®L*}½³óGeb7 ¢ì _fã`Nwí±Iqw Æ§ð5óx¶H¢˜ì=¢Ù¬;>ZæE:;€é'‰�ß` ô«à!�ÓU¯’Ì&|°œÃVoAíþíÁ(‰Y\ZŸÂ‰"dF «ï@è5þ;œ¾·†xÞ• ¼ßÙyÍÁtÍCø3ï0� ?�?#¸Dÿ^"‰˜|q™e`‚\*É ‡83†IUHÀóä »èF!NÉ1G˜ ·\M£yùiŒ¾…q(˜�¦)�RˆEhžO¢�µÇk†Aä7Dĉ �2«él‘æql  Hjq­�ãÉhA�¹{½#®ˆû4wXë¾² ŸfáJ�™Ì@ZŒØG¬ïí|Ùøœw}H*= üJjR„GUÇï•쬕þ¸Ã“!ÛFC¨uÿz>üàwZ½¾?¸ð:ß;=÷üŽw6üçžxõŠgÙ§Ìb§þçÞžø"©Ñ<›¿ÃUSq$ÞüÈx¿>�©aïêù<÷†ÃÞå6¦¦ïêu\Õ0ÿ])ã`„ÇWZ„«Õýö[)3gº"³e¾„p¤§ÔY%Ú¸¥4IÚ¤_9DZ.ÂIÃúóί9—6æß¿µ¬¯EÓA mú»�o)¢—A¥¤eÖþUÙ¬J™BÆìûaºús.&Q:‹ŠìAL‘¶!]BÃ9Ã8Ͱٓ+KP@‘/¢Q<†ÇR�Ù{,ÀP8òK·—.¹9Ât"° éå"��Yˆ3‘®æ2ªIJ !²Äàb1¨â%˜HDR¬¨ CDœ,qxF‹Ñ˜E)(ÂJA©ÍŸŸ8«™![a)ì©ÅRfª#Vè�ÿl˜ &ËÖ n‹jËEˆdUJb{L»a‰}ì!¾RÂ"6ío>ïoe¤œ HZP�Æ C h,#æYàžŠzh’–±çà­4~Xú>=eûùßGH͘vº5’^#¹–FþT&_.b–‚ÚÌ¥Ñl²0ÏûsÇåä(ÛÝ!,š–Já¿Î`ðiË f– ÙdbÌ]f~ŒÙp°ÓáÀWáÀ ?)Du¯;�'¾büO=?ýà_´»Cÿ²Ýõí_<ñ›0ÏZ?ó3mׯ_Ëv£ìXæK$6¨7£Bg>T� <£*Q �û;Û‚ŸÎÙ5už‰¢�LlQÔ.�X| gDT+Á½Cq‘®8Ûñ¤ÁKN•i¾MQ¾‚!Š b–¬u•¯c”Ñ4hg‚Ó ×to£ø¢%mÀeå„®±rŠƒH÷,ÆQØr¬„(a˜‚…9RCŠu2É;¤8 (ÄQ°CøŠ‚ãÇ`M•2H®ã‰ó¬T„'@+eI<3Ì(‰ÀÖrÁ±=€ˆ(h{:—xóC©,¢!Cù%ñ0,²y÷L]!EÉŒÂY´ Åüi'6¯YŠð‹‚…$jJŒ’ J7QÆ/¤Î9b8ÒæQ�WÛ•SEHl—›³RäÃa™=M4(uûˆêÁãö M/Q‚×fHjeæñ8š%Â÷ßûÐe”¡”~Æ„Õ3`§:ÁÞH·hÁj‡¨]ŽŒîl1éÅ)žµÇnŒž Ñ8w2{;g+çíZ{Ö²Ë@'‡¦rÅ_¯®8»ñsÔŸ¤”(=]÷Û¯wò'œŒ˜ÉîéIja�Õï”1I+Ê�hí¢až@•Æe�1QºÅÎ}4à  YÅËÇG*7·êÅ£ lÕ ¥Ž™µ–ãjYC³Òš;d ›¼Œ ²[’´'Ô¥§R}¡0±4‹”£ùQ8‰ü Ë‚Ÿr9ÛŸx¥ˆæFú‹*U!yR}ì9nãÎt$Ó†-…ë«¥ËmEù -[lhÿ�t63­Ñëð€Ú_ÛûãLJ„þ/§Kdy}aOá £”Uÿ #Ueû c•m �¢7®Qý,XˆÚÊ"_¢Œþ²ôùU¢%öÍ÷­‘­bD{ŒP…µ‰�rÝl?ûã NDã»›(w“õ¸£HìÛ7Á©ZÐÒU3Ž]¯¨¡ûêï‘ $[*ÌÄŸY:§f›½C–ðh7úz‰ Ž— å(Õ ÑH´5pl‰æË™\t@âÅË@«äh‹C~ßÌxÔnË(×h¸í(¡©fEOµ6y©ßçøŠ‰Î7cÁ/€LŠûñR_�1Í�6^£m£¶^Ü-ç.z¸Mƒ,ô‘›EY€Ã´û¸x¨mÄ!npƒ¶œ‚óHq³™@ô çry|ùŸ©š°MIG�ØU˜ÓAݦ:ÍöV¹ÒÞŸ¤ñÆQµ;©²Õ÷訢ƒ¬V¼¥2W³=y³¸”«]|™8Ö\óCJ�î?<ÝÄ6¡‘KR’­šúå!:Ç:1¬]’xåjMCKEƒÄÖÝk9ß¾}óeÔî°÷¸C:hNë½B ŒôU〈‰fx$aÇó ¡Âæ­û[–Nœ9ëõ•€yïVìO|>áo4&‹”#÷ Þ;aÇhMíy8Vóé­ƒýʉ}9²y1[LÖ¯{ÆûŒÎ'’º>Ýì9Ö|>ÜÂaŠ �OQö-‚drdCµi�uJ1ñG¸Ð\ y~ªYæÝ\ÉeOwºèŒOõd�;—Mr•àrß@?‰›EhàpCiD�¨(ܵ Pæ_ŽÅ1ÓŽ8X]FsG©Íߺ¼q·J)Êü£>LX&PãM&Ë4Ôó%gv”@/¶�Æ8“¨ÆÒ’tBÒ÷8[ÝlpZé²(W©+¹‡"ݰÉì}yÓêÊPcû2a\ïÛÿgº˜_»WOËJ™b[µêÅ3é ‡Ò5«ãüTA”Q¢ ¤œ°±  ÖM°«ËU«&X©ïîé�¡Ê³jfâüJ}¤²tÍq›¦Ü¾Ä1xü‚Uû‘ªºëëH®¤ÄÑQ‰R£–¥µnÅ ‹¥ùO48§/žÈ•²9xúdXUh©Î£UUØGÂ9’"BRó1jïzµs%ë§ñªk')“Ĭ�¸Uh| ãȼ†î_ôõ£�€ç3 …ö2<(l’�¯"JÐj#Q°�”YÞß.fëXé9R–m‚—Y"ãz¾œ•w¼ ™’²9æ¬17g¯\]XŸb+Gš{eVÝ$Wrš“_ÙXIsõÛ#]—©P«Ï¥ë³ö|ëæÀæNéúY÷ ÑËD»Â:�–;1½éco‘bßÜFz|fgíhÂÝ�-6Tp7ùzM’§¯P«ááà½|hÝby$ˆ‰Ê|UÙw€6a @!öÍu¦ªÞ˃�5]a™cP²b õ©)Æ17S›Ó²vÛÖL+U¹RqVI»àL¼þõƒ ­Õ² w�̬l òY„¼ÓÇɦb�›EªÕ×À9)Mà…CˆËq ÙO|pðÆÍ'šâ­â_*�3¼C“�UªR5 ý¹ö¶î4¸s¶žë‰}®�šÖa‹æöÛ쥴•7FãÆ.ÔC˜i_Q¨v. ”M´íyu÷|ž6S5AÇ”´’"+Ï«×ñäÕ<ìû6¦¤¬ÁT‰ˆå½6I§&/޳VgàÙœU [¼�ǽVÿÔ¿ì�z>î;³•Eª^¥^,KÞ¤ñÜ{J3¾Ïn±áæ1ñ£ ÅjMá:y_XÛvàE6š-Þ¨œÇ@5í$0§Áú¤>•\†”5„ÊtC4¬æ±ÒÍúÿ1«¸êÊb‹;éÞäüM&¶|¥1I$�!± ÅWÃÏ6´òÀ¿7äžÿ˜s 4¦.ǵ • Á‘ÓÿrÓéëKÞ×ý³Ö‰çÿôÖxCýoŠà^ÅÀ;öµ|Ž…á7*éâ ß2ý},‹SCÄJøáF3ѽ1¶"˜‰Å¨±W9åÁ©œ¥ú‰e"ÙÀK¨q[x¨Tµù)Ë…ÓúRnUÓ‰vêR„‹@¼CóÔ$¨•AÙY¥å›Œ¡‚ à>Y÷vwztU"ŽšlŒÄËæÇE¼a둲rA- ! /¡l³Ýgk"ˆÍéý¾º„H«š„´åü`=ÖÖŰ­ªAâÓ·ÿD¯ØfÍniZkÌΞ÷A]ϸÄU zY¹Üâhµô‡k™[ßöºþ©w‰ Å6—šÜÍä>”ïà^w‘ŽÒD'nújI…hëíÝ‘øÁFLã\bÂKÍxi§QV7ôœ>»'æ}C¼Á„wÖiÛ—¥£ÐHÿ„7ÎWÓ—îååwzWh¹ Ë㸑~¢–Áµq-J¼ºlÝ\Ù5ýIPh¬S–Â{Dv¦7@È´ÕÒwã’š”™PÜžÕ´fn.˜„zA/Ñ;ÒØêéåfØðBËûÝ\´«»,@嘼yWã¶‹S”27s¬*®45«b_`� ¬ëìÂôí׉w©…òFãÈžwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/5b/0000755000175100017510000000000015222270712023662 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/5b/dfc65eb4f809c98a6a0629c1bc1ab85ba32658wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/5b/dfc65eb4f809c98a6a0629c1bc1ab85ba32650000444000175100017510000000434515222270712031074 0ustar runnerrunnerxÕ[moâ¸ÞÏùG3ÒíRJûÖÞ]-[hm§­(ÝQ?E!15ÄÈ ô¢Õü û7î/»ÇÎ Æ.¡Ð@g¥-9ñ9~üøñ9Nâ:³iþüÓw§ÇÐzèß^un:½V¿Ó®BûnnûÐiwûÐîö:ýëGŽážÅO'SæÏO{�Vûs§6ñ`HL(#à‡øsâÄ> ±9÷è�ý†~€÷"˜:,:!gAØI4&A�6½ ÓóGã*G`Õë¿À…ÃÁ•ÃBÇSø·+ £ßG!��e£ßx'KÏÿý­ædä2G§.õHmº�QM1gÂ� ,¢ÃøÙaät®#žÅÌÌbăz§”ñ'Ôó‡ n›…a� Ä„M"><~quó×$ŠðÞ s¸› ßåÞ×¾Kˆ€ƒ”p#²àÁ`!/9”û \RŒ/ø<âc`Ñûœ°)†FÖ[° 8'æ`@§|ŽõÑAêWãø?19 ËÑz8•ΘNqdcŒ‰c}öƒf΂*�ÔÀ—nÿÓíCZ7�ð¥Õëµnú�çØ8S¼Kæ$ åO¦��EŽ™Æ Ï#|îô.>¡Kë�îu·ÿȇpÙíßtîïáò¶-¸kõúÝ‹‡ëVîzw·÷�%¦<ó /R�óœëÓ#±ãQNÀ#Nt„(ÆÎœà„»ÄŸ#F\TcFïÚÉÌzàœ€†#1jä‹‹=‘×9øCi\…g棌bZ8Í‚ÔnèÖªðƒ —HÔS€Sq£,0Æ¥?ŒÇpPʪð�bVás  n™fýÄlÔÍ*<Ü·øO ã£?Dyáªý§ý¥õxݺiÛw·Ý›~§g·[ý–}×ëþe2>b#?DÕnh‡C7˜y>¸t2¡amüÁ0âÅ”ð^p©ÌÜì+ï鋳pµÜQ?Dáµ�Ø�­ç†qz ¥zî¶¡.®�ÀO²ÊUÿO˜7j–U«Ã‰øÕ¬5ëP™…Œĉˆwdvk϶eÙuão?ôè3©;‹ÎÑæQ\�¸øglè¸ÄþÏ ¶·!üÏ|Ñû„õq¼\øH¾-ÚD¬޶aÙ1Äþ„p§Ü@8 6fß VnL& ég)…ˆÉi`?‹‹aÏzHÿØéàxš�žRð3áÒ:vÅÿ¹qÄÑArmóx¸ZmßÓnùgDlÄA9+nÉ=�`6_ñc8`ãM1dä:�`ãêþÓ©0m'\;—¸&„ë(›PÌÙh97¾&ºÄ± ­…˜i1? ƒ…Á8òžìçDŽvF ¦TÇæEÌ‘ØNÓ"޽r#3QŒ)×åñ²”‰÷àW81ù ñ?\Ò•¥üÊo%®Hkü”„vxö�m~�µ�saWŽx ÂÙ§“Åȼ‡2ñCÊ²æˆæÌz½ßK \F— R<Ó­;†Š˜A¬bH……µ´~&ú”,¦f±4KC³45ËšåGÍò“fùY³ü¢ZL ³©a65̦†ÙÔ0›fSÃlj˜M ³©a¶4Ì–†ÙÒ0[fKÃli˜- ³¥a¶4Ì–†¹¡`nhjA‹2 ´(£@‹2Ц-J´(qТÇQØÀ6 hQØ@‹ÂZ6Т°Ñ¬«ªC‹†YU¶Ñ0«ªÃ6fUuØF쪮YW5† ¡ª1l£!T5†m4„ªÆ°�†PÕ¶ÑXU5Ö¬74Ì M sCSBCÃÜДÐÐ074%4rÌFœ§<µâ� �Yg JÊñ¸— W2üÄYØbÏÂh6�R“<ÝMJˆ ÃóVDi©'5�7J2ÏX(µÂZ'vK/n¤ÎÎÄŽÆ0¤ Ä+JŸpª¼kOD‚aZÓò²]1Íê�¨Bõ3Ü^‹!U*k˼\á��Dÿ'¿å{µåLÀÈ&ŒQVùÐ EQÍx‚nûÃÑ9ÞvÈ~Rj¿râæy.†aÞLHUÞFâ£/î#Ëäh'rp‘�´”î[¸Â-ù‹’8;˷Ɇ‘nœ‹)å{šÜç[×Y4Y‹Ùªß›Öòþ ‰©j�(ï)·3J.ÿ}~(Ù-v�Ý¢�Ý2¹Ü1½e4Š'¨·,ÛW¦¸¼¿Bb4Ù-ʤjÛ,—c–d·Ø»ìÔg|Q]WÞo()<ÿ©QìiÌ ©/“èL“˜ÏW�ýkÛ9PÇñÖ,¹¾Ê𗆑¿<Ù\b¸ÃÁ)Ýr“½±9À ßHGuùfŠ·-SpÛªŠ÷�‹Z°³ï2"¿…ÛJ_²ã·®3ë[—è++ŠÜe!=’îdŸ÷ÔŸŒu(_î½¾È/·Ò£ìXHx™Ìf¥d˼'c=�å. é‘ô(û”ÉÚ¶ùPÆ�z”/÷®Gé‹Ã¶/$×BÆË¤vGAJP¤G©ÇBrV_-H^e²¶­ %¨Géê0r䃭r£@Ƚ ©.“Ñ·èPì{ħ™½¿nx1RJÌÊ$k'ùq–2ñ Æö½SÌ>¦Ï�Fö‘YþÎúŠG?%Ì·®Hî�²£Òk!IÕì�ÁÊD(ÞS® ^Á•Ïñû–nòA_Ôïì›ÿ+”šxr_&­;¦Ìå�tù JDÁÎYNʤiÛd™ @Ñ%?ö^¡“n¤Ã#†‘ÙüÊFsýghOëAe(õ[HTuݱž÷—¥4„\¡’íPb•Ž3m­VÉ·pÊ$ûM©Ò–V¯RÇ…T)‚•ÜÊ$q·D*³·”¬ñ°šÇìvÔ¬ð-œˆ2é.C³ù¡Â¼0O‹Q2³›©zQ³Â­LߤY�FÕlBé6 ÚñÏ­T«yÿt«a>\¶Õº.¤KÒ®æøÎêÕð$úÕÌ{ϺÊ#[v@Ÿ©Ò̯x Z£pnÊäǼö�Ô¼¦÷BÒð,›•5îeÒºmR^ •½æÎÞÅ-ÎÍ'¯÷WþH#?a�ÿ6?¾‰(ïu’aõ¯K$àÛjãx«º?’ÿ2ø�† @óš-™6wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/fa/0000755000175100017510000000000015222270712023742 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/fa/62199af92422c14e02c9621a2b72883bcc6a1cwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/fa/62199af92422c14e02c9621a2b72883bcc6a10000444000175100017510000000164215222270712030557 0ustar runnerrunnerx�U]oÛ6ݳÅö°¤°å¯¬í ¤@†m±,-’ÃP2%][„%R)+òãw.;Njïa�hòžûqÎ!“6¡Éh:úégúp÷¥¶¬”׉.´ßôâøúóÝìùœ©UµÑf‰µòTh³âŒ6¶!o±¥UjÉ$ßÚ&*)6¤¨FHÎu X²_© ×t›sQP®BrUgäuÉôŠï«B§Ú›W䚪²µ—z¬ÒœÖ\;m Ù…äéSÒx2ÜRc#Qé1Y‹Ò:�O«6ŽZ[¯Hô¥R¯SŽâ¸· û;X—›ö{|N£èm4ê&Ü2 �'̆|S¶‹%Êx¡šÂGôÉSª B@À€ È@û0Ñ#ÄWÏnâÛ�³««øëõí×/_>ßÜÍ.c9ûëâæúÓõ‡óñ¼ëõw^ØšÉV@ní\ƒ ´ Ô×\YR‰)8SUEi­\ö€�B0å €í1!/9‹C‹Ó‰”³¦ºÖì¼^*ÏÀë…ý :€ò­öŽØd}J kø¥¬OsNW$ÍHE¯–¡Š¬·*ŠmZF¾Æ‰Â'|Oó¥k‚àz½ŽFóSdK]døÖ�¡y4 ?‡|¯Êª`7\úÕ S 2.í¼k68TÎvs²ëê„›À[¯w òàÛB—ÚÃíÖ¸�³äì[ c°žá܈ټˆfÖÆsmP%¢™8´æäášÀn0�ö9 XlGÞæ§÷çÓh2�£@e G¬ç=Â;g/8«+ÓTDpTª Ù4Ur �wC ´ZIŠ2¢[fú&TTµ^of—΢2û~òãÞi£;eì•Fÿ½Þ]ÐI&K¸°-¹Ü¶˜!טë%»ëîU˜ó)p{ˆ;°�ÞPnSTj0·â¾,‚äÚ,,Á{â„\­á,ƒùAÿ4b=HÒ'H,”~ãïÔû~’{_¹wÃ!,“7I„'kØ–m»g'/Ì0ˆíN1àÃc<Ðl÷Ül_ÜR™ö�>êeŽ&¯•±-®™íÞ‘.ÑvLÚ† E½‡Á`ðì¥ãvG£8Fê5äAþAX�EãñãæY49Ãr I:\r29™ƒLŽV9;™…üú ãóüW•ÉaˆÐ$4‚±×ÏÛq"qd*äí‘üæ$Ä=B^è‚ÿ«LŸ ˽*o£ÃãOß<5&Ëg�ÃãO{‚Èr )•Ãt¸±�}zåwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/7b/0000755000175100017510000000000015222270712023664 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/7b/51dce9806adc4bf8fcfa193f426f332426bd34wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/7b/51dce9806adc4bf8fcfa193f426f332426bd30000444000175100017510000000465515222270712031105 0ustar runnerrunnerxÝZmoÛF¾ÏþSZH†»ýr¸¦iA[´Í‹^ ŠŽc%®$Ö)�”U§È¿gv—W¢§¸èN‘»³3Ï<ó²»Å鈾?;ûç?þvzLÞ,ÊI$E” šD± |Œ£±HrÒ2 EF=Ç;¢cüÐEºxʢ鬠Î~8£aº˜E‚™r#²y”çQ𰤙ÈÄ艦Y€!a›&™”Nh< ²©hS‘R�<ÑBd9&¤£"ˆ’(™R@c,Å# Ö/O'Å*€†AR�çé8 �Ât¼œCù àõXýœšÅLPc¨g4Zr‘P1E ¤ h­…­¢b–. ÊD^dјe´1h/CÖ¡|GóH¯ÀÓ¥ý9k¾ÌaëÙ¦yFþ-¤Y‹å(ŽòY›ÂˆE�–FæüPÛf;NÓŒrÇ,!‚ÞÒÖ�vr ¯l°¾†H®»š¥sÓ@4Yf –(˜¦€L®ø«ü„5Ÿ¤qœ®Ø´qš„Û›ÿ¨Ýæá}0J…4Hù8I è+1—^�š(×êWù,€#�F¢†Å�1?*m‚‰ËQ^Àû°H3¹è¶­oJ%®m.½;˵ÉÒ�;øàtì5¬!>7Útçx׃[�0µúÞ= .ÉêßÓ{§ßi“ýñƵ‡C¸äônºŽ�gNÿ¢{ÛqúWtŽyý�G]„†Po@Ô¢ó.©g»×�l�;]Ç»oÓ¥ãõYæ%„Ztc¹žsqÛµ\º¹uoCËw ¶ïô/]¬b÷ì¾÷«âÙð�†×V·+—²n¡½+õ»ÜÜ»ÎÕµG׃nÇÆÃsšYç][-£.º–ÓkSÇêYW¬�K(ìÊaZ»»k[>Âz~.:š.‘îŽ`Ž/EûSQøóà×4óµÔlý~Dø“‰QIWÞ{¿kÝÛ®?¼¶»]¿gýkà¾=ú¼GV”üYNÿYYã,}¹^Î…;Pz�Ò4AR13Êý|¹àF”¯-Œ&ÔüæªóÞw†þ�ußE0øgxs© à|$Àp’(…b,ã³[-‰O£K«;´ßʧ ­ÜG5(ühâ'B„r]5D[ÉnP‘üò§¨rH9Í}óŽú·Ýî^ÀYZ¤ã4ÞÅé`Ö�½Ø2”‹%2ö§Uœæ<~OÇ5æ½£a –—æì Ùñ…ÖVƒ\£Šôæší5%ÖHû¨Ôå ªø;–„B Ó•¢çª·Ô¼*ÐLà%«Ae$M}¥�ÿÄ̇IÁ·jL[º·¥l¼� cÈ)F¯IÇŠ:{�†l£ƒÊ1¾9JËÖR%rÆ€ý¾¦ðÔg¨¦c¦[ccŠ2Ê$Zi$ ¡Ê^Ô ô”. ³b0›Ý¨À�”Ÿ (ëÀà†Ö¢.ŠÐüà³T£ òš(ÕwTgaü¦…»¤Q÷š®°'»$¥•fs3XƒF›1ÈèNå~´ óEšGZ£yð$'褣hDCvì ðÐÜ5è%Iù˜Fa5«q~©‚½qß6¿œ•ªN8V^GΔ^ë ]�ÐV%«3™@£ÿ•­q³q Êp7 ÆÜÛ}ì\i°£$/Dr%•QxQ³Ô"G¿£ƒ^,D�ÚRÍ͉P©Ð„®"4oì§Uš=ÄûN=]+‡Ê¦c¬­ŠgÅJÔTûq�±Ì\TÛc7Éh_ËÊ\[•{ÊÀÜÏ•g³•,u¯�®ÖÉJGF¥ÞJ¦hŸ6wÔE¡QŽkª�â–Ïõ®§\Ý«…h3jÍxÞ.N¸Ê‘u™P½y&2µi2Óé)ÙYÆIKäy0Å–)ΆOˆÊt…}p¢ªô¦ä(騔®©ƒêägc\]þÊ�K`Ùãšú‰ý)vÚ¼ßË‹c⑾ZÖÕWÀèí!à1€ðK«•-Ô4Þ¶«fJ̤õ ˆmN=‡ÝÁ1Mœ‰Y¸,2‡Ú$¾�$³+&8µ¤cxñ“<˜Nz[Å‘a–Sj‰…ìו­—dù?eÚÁa«¶­*î[zgõ•¨¥m–¿ÊÒÖÖÆIBíš¾•ª´„½Bµ7å&5mï:ÕÔܵgòÕ¾<5OÑl!Iîfì6]…=ýúX�ûëóhmð“J—*–o±h=ÿOåÑW.qÚÈ}¤ ’ñlgª ÈQvþiÓT·n8¸ä¹hG}~ñ×çS‰Å�”Õ[ÆJ•ÞÐZâÆ;MãVyî­ÍÍÂÎ Õ÷ë3ÊõqŸ]6ʆ”¿Q@¸ÁX ú6Äå?&Žo§ë£ÿçŒÁ÷¦T\ÇÏ‚E¾o>¾’€ já8>—ºã«á/�¶!ðI·7êt�RJèZ†:&ÛÈ4äÑ/;�Õ{ûþ|`¹¿7è Åúˆ«ù¡óÁ¦¿0÷ëÜ}ñ�éî¹,Ÿ ¾˜¥«�F÷½&P�!“&ªK"¦†|ÀÎñ | ²loºMƒÙ ¦ÒËF¯áÙs°ö‚䨯a†¾¦G@|i • )}@>fã·a{‹g||ñ÷€Pÿqë°g!LëÍÃø±A,í/ƒ€ûbÔ«•áÐ;ãêpk3³ãã’ ëfK_+6¥¼Ý]�éÊêö@Û×­\t3lŠ6ÿÃyóW(SFÎQPÕI~·‰]ˆàÆÊH>ò«ºµÁ,‰åïqM ý+ ö‚ˆ<¨õEÌ‚!ü/»X†Pwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/b9/0000755000175100017510000000000015222270712023666 5ustar runnerrunner././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/b9/37b82078f26af83829601a49a3fb03d65a3535wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/b9/37b82078f26af83829601a49a3fb03d65a3530000444000175100017510000000021715222270712030435 0ustar runnerrunnerx+)JMU04¶d040031QÈM-ÎÏÓK*ÍÌIaÐyðàè}­•Ñk¾N‹}rw¾Qã'¨²òœ"ÝœÄÊÔ"ÝâŒÔœÝÒ¼â’ĤœTÝ2C½ŠÜ†kúWå&\lr_ºƒ³xêaMé‚™‹ Z+RÒ!šÀ E.ü½ìáwÛ—ïåýš(þ5ãš?õí>L././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/b9/147783f23e5dc5c9f03c6f66f4530c96239610wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/objects/b9/147783f23e5dc5c9f03c6f66f4530c96239610000444000175100017510000000020515222270712030453 0ustar runnerrunnerx+)JMU04²d040031QH*ÍÌI‰OÌK‰/I-.Ñ«ÌÍa¸ùsÓ¥ÃqJ³U2ßÔþÿxMóä̇[¡ª“3R“³ã ŠÀêþ%©_·Üúêc�­ÝDϦ*;îçPu)©9ù•ñ)ùÉÅ`¥‡¼2æ[.c±0ìD~çêc â/YùÑ8wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/FETCH_HEAD0000644000175100017510000000020015222270712023250 0ustar runnerrunner5f71546112fd284aced13e7b2391a601204bcacd '5f71546112fd284aced13e7b2391a601204bcacd' of https://github.com/wmww/gtk-layer-shell wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/refs/0000755000175100017510000000000015222270711022661 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/refs/tags/0000755000175100017510000000000015222270710023616 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/refs/remotes/0000755000175100017510000000000015222270711024337 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/refs/remotes/origin/0000755000175100017510000000000015222270712025627 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/refs/remotes/origin/HEAD0000644000175100017510000000004015222270711026244 0ustar runnerrunnerref: refs/remotes/origin/master wlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/refs/heads/0000755000175100017510000000000015222270711023745 5ustar runnerrunnerwlmaker-0.8.1/.git/modules/examples/gtk-layer-shell/refs/heads/master0000644000175100017510000000005115222270711025157 0ustar runnerrunner5952eac2b084551e4fa340c98c8c3aab0215b9c2 wlmaker-0.8.1/.git/config0000644000175100017510000000227215222270710014622 0ustar runnerrunner[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = https://github.com/phkaeser/wlmaker fetch = +refs/heads/*:refs/remotes/origin/* [gc] auto = 0 [http "https://github.com/"] extraheader = AUTHORIZATION: basic eC1hY2Nlc3MtdG9rZW46Z2hzXzE1MzY4X2V5SmhiR2NpT2lKRlV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS5leUpoYVdRaU9qRTFNelk0TENKaGRXUWlPaUpoZFhSb2JtUWlMQ0poZW1NaU9sc2ljMmwwWlM4eU1EUTVNemcwTURrNU9TSmRMQ0psZUhBaU9qRTNPRE15TURFM05UQXNJbWxoZENJNk1UYzRNekU1T0RFMU1Dd2lhWE56SWpvaVoybDBhSFZpSWl3aWFuUnBJam9pT0RKbFpXTmxZMlV0T1RkbE5TMDBOakkwTFRreVlqWXROVFJoWVdZNE16aGhaVGhsSWl3aWMzVmlJam9pYVc1MFpXZHlZWFJwYjI0dk1UVXpOamdpTENKMlpYSWlPako5LmYtN0ljd1lxcDhaUUMzYlV4bWlFa3NzN0pqdzVFZ19tdVduU3JXWVMtMDNzM0ROS2Q0Wi1Kd3dzaHRvUnB0X0FnVm96amdaRGhmZDlOMElOU1ExVndn [submodule "dependencies"] active = true url = https://github.com/phkaeser/wlmaker-dependencies.git [submodule "examples/gtk-layer-shell"] active = true url = https://github.com/wmww/gtk-layer-shell.git [submodule "submodules/inih"] active = true url = https://github.com/benhoyt/inih.git [submodule "submodules/libbase"] active = true url = https://github.com/phkaeser/libbase.git update = rebase wlmaker-0.8.1/.git/description0000755000175100017510000000011115222270707015677 0ustar runnerrunnerUnnamed repository; edit this file 'description' to name the repository. wlmaker-0.8.1/.git/hooks/0000755000175100017510000000000015222270707014560 5ustar runnerrunnerwlmaker-0.8.1/.git/hooks/pre-commit.sample0000755000175100017510000000316115222270707020043 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git commit" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message if # it wants to stop the commit. # # To enable this hook, rename this file to "pre-commit". if git rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else # Initial commit: diff against an empty tree object against=$(git hash-object -t tree /dev/null) fi # If you want to allow non-ASCII filenames set this variable to true. allownonascii=$(git config --type=bool hooks.allownonascii) # Redirect output to stderr. exec 1>&2 # Cross platform projects tend to avoid non-ASCII filenames; prevent # them from being added to the repository. We exploit the fact that the # printable range starts at the space character and ends with tilde. if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. test $(git diff-index --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF Error: Attempt to add a non-ASCII file name. This can cause problems if you want to work with people on other platforms. To be portable it is advisable to rename the file. If you know what you are doing you can disable this check using: git config hooks.allownonascii true EOF exit 1 fi # If there are whitespace errors, print the offending file names and fail. exec git diff-index --check --cached $against -- wlmaker-0.8.1/.git/hooks/push-to-checkout.sample0000755000175100017510000000533715222270707021200 0ustar runnerrunner#!/bin/sh # An example hook script to update a checked-out tree on a git push. # # This hook is invoked by git-receive-pack(1) when it reacts to git # push and updates reference(s) in its repository, and when the push # tries to update the branch that is currently checked out and the # receive.denyCurrentBranch configuration variable is set to # updateInstead. # # By default, such a push is refused if the working tree and the index # of the remote repository has any difference from the currently # checked out commit; when both the working tree and the index match # the current commit, they are updated to match the newly pushed tip # of the branch. This hook is to be used to override the default # behaviour; however the code below reimplements the default behaviour # as a starting point for convenient modification. # # The hook receives the commit with which the tip of the current # branch is going to be updated: commit=$1 # It can exit with a non-zero status to refuse the push (when it does # so, it must not modify the index or the working tree). die () { echo >&2 "$*" exit 1 } # Or it can make any necessary changes to the working tree and to the # index to bring them to the desired state when the tip of the current # branch is updated to the new commit, and exit with a zero status. # # For example, the hook can simply run git read-tree -u -m HEAD "$1" # in order to emulate git fetch that is run in the reverse direction # with git push, as the two-tree form of git read-tree -u -m is # essentially the same as git switch or git checkout that switches # branches while keeping the local changes in the working tree that do # not interfere with the difference between the branches. # The below is a more-or-less exact translation to shell of the C code # for the default behaviour for git's push-to-checkout hook defined in # the push_to_deploy() function in builtin/receive-pack.c. # # Note that the hook will be executed from the repository directory, # not from the working tree, so if you want to perform operations on # the working tree, you will have to adapt your code accordingly, e.g. # by adding "cd .." or using relative paths. if ! git update-index -q --ignore-submodules --refresh then die "Up-to-date check failed" fi if ! git diff-files --quiet --ignore-submodules -- then die "Working directory has unstaged changes" fi # This is a rough translation of: # # head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX if git cat-file -e HEAD 2>/dev/null then head=HEAD else head=$(git hash-object -t tree --stdin &2 exit 1 } unset GIT_DIR GIT_WORK_TREE cd "$worktree" && if grep -q "^diff --git " "$1" then validate_patch "$1" else validate_cover_letter "$1" fi && if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" then git config --unset-all sendemail.validateWorktree && trap 'git worktree remove -ff "$worktree"' EXIT && validate_series fi wlmaker-0.8.1/.git/hooks/prepare-commit-msg.sample0000755000175100017510000000272415222270707021503 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare the commit log message. # Called by "git commit" with the name of the file that has the # commit message, followed by the description of the commit # message's source. The hook's purpose is to edit the commit # message file. If the hook fails with a non-zero status, # the commit is aborted. # # To enable this hook, rename this file to "prepare-commit-msg". # This hook includes three examples. The first one removes the # "# Please enter the commit message..." help message. # # The second includes the output of "git diff --name-status -r" # into the message, just before the "git status" output. It is # commented because it doesn't cope with --amend or with squashed # commits. # # The third example adds a Signed-off-by line to the message, that can # still be edited. This is rarely a good idea. COMMIT_MSG_FILE=$1 COMMIT_SOURCE=$2 SHA1=$3 /usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" # case "$COMMIT_SOURCE,$SHA1" in # ,|template,) # /usr/bin/perl -i.bak -pe ' # print "\n" . `git diff --cached --name-status -r` # if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; # *) ;; # esac # SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" # if test -z "$COMMIT_SOURCE" # then # /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" # fi wlmaker-0.8.1/.git/hooks/update.sample0000755000175100017510000000710215222270707017250 0ustar runnerrunner#!/bin/sh # # An example hook script to block unannotated tags from entering. # Called by "git receive-pack" with arguments: refname sha1-old sha1-new # # To enable this hook, rename this file to "update". # # Config # ------ # hooks.allowunannotated # This boolean sets whether unannotated tags will be allowed into the # repository. By default they won't be. # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. # hooks.allowmodifytag # This boolean sets whether a tag may be modified after creation. By default # it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. # hooks.denycreatebranch # This boolean sets whether remotely creating branches will be denied # in the repository. By default this is allowed. # # --- Command line refname="$1" oldrev="$2" newrev="$3" # --- Safety check if [ -z "$GIT_DIR" ]; then echo "Don't run this script from the command line." >&2 echo " (if you want, you could supply GIT_DIR then run" >&2 echo " $0 )" >&2 exit 1 fi if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then echo "usage: $0 " >&2 exit 1 fi # --- Config allowunannotated=$(git config --type=bool hooks.allowunannotated) allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) denycreatebranch=$(git config --type=bool hooks.denycreatebranch) allowdeletetag=$(git config --type=bool hooks.allowdeletetag) allowmodifytag=$(git config --type=bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") case "$projectdesc" in "Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 ;; esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. zero=$(git hash-object --stdin &2 echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1 fi ;; refs/tags/*,delete) # delete tag if [ "$allowdeletetag" != "true" ]; then echo "*** Deleting a tag is not allowed in this repository" >&2 exit 1 fi ;; refs/tags/*,tag) # annotated tag if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 then echo "*** Tag '$refname' already exists." >&2 echo "*** Modifying a tag is not allowed in this repository." >&2 exit 1 fi ;; refs/heads/*,commit) # branch if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then echo "*** Creating a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/heads/*,delete) # delete branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a branch is not allowed in this repository" >&2 exit 1 fi ;; refs/remotes/*,commit) # tracking branch ;; refs/remotes/*,delete) # delete tracking branch if [ "$allowdeletebranch" != "true" ]; then echo "*** Deleting a tracking branch is not allowed in this repository" >&2 exit 1 fi ;; *) # Anything else (is there anything else?) echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 exit 1 ;; esac # --- Finished exit 0 wlmaker-0.8.1/.git/hooks/pre-rebase.sample0000755000175100017510000001144215222270707020015 0ustar runnerrunner#!/bin/sh # # Copyright (c) 2006, 2008 Junio C Hamano # # The "pre-rebase" hook is run just before "git rebase" starts doing # its job, and can prevent the command from running by exiting with # non-zero status. # # The hook is called with the following parameters: # # $1 -- the upstream the series was forked from. # $2 -- the branch being rebased (or empty when rebasing the current branch). # # This sample shows how to prevent topic branches that are already # merged to 'next' branch from getting rebased, because allowing it # would result in rebasing already published history. publish=next basebranch="$1" if test "$#" = 2 then topic="refs/heads/$2" else topic=`git symbolic-ref HEAD` || exit 0 ;# we do not interrupt rebasing detached HEAD fi case "$topic" in refs/heads/??/*) ;; *) exit 0 ;# we do not interrupt others. ;; esac # Now we are dealing with a topic branch being rebased # on top of master. Is it OK to rebase it? # Does the topic really exist? git show-ref -q "$topic" || { echo >&2 "No such branch $topic" exit 1 } # Is topic fully merged to master? not_in_master=`git rev-list --pretty=oneline ^master "$topic"` if test -z "$not_in_master" then echo >&2 "$topic is fully merged to master; better remove it." exit 1 ;# we could allow it, but there is no point. fi # Is topic ever merged to next? If so you should not be rebasing it. only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` only_next_2=`git rev-list ^master ${publish} | sort` if test "$only_next_1" = "$only_next_2" then not_in_topic=`git rev-list "^$topic" master` if test -z "$not_in_topic" then echo >&2 "$topic is already up to date with master" exit 1 ;# we could allow it, but there is no point. else exit 0 fi else not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` /usr/bin/perl -e ' my $topic = $ARGV[0]; my $msg = "* $topic has commits already merged to public branch:\n"; my (%not_in_next) = map { /^([0-9a-f]+) /; ($1 => 1); } split(/\n/, $ARGV[1]); for my $elem (map { /^([0-9a-f]+) (.*)$/; [$1 => $2]; } split(/\n/, $ARGV[2])) { if (!exists $not_in_next{$elem->[0]}) { if ($msg) { print STDERR $msg; undef $msg; } print STDERR " $elem->[1]\n"; } } ' "$topic" "$not_in_next" "$not_in_master" exit 1 fi <<\DOC_END This sample hook safeguards topic branches that have been published from being rewound. The workflow assumed here is: * Once a topic branch forks from "master", "master" is never merged into it again (either directly or indirectly). * Once a topic branch is fully cooked and merged into "master", it is deleted. If you need to build on top of it to correct earlier mistakes, a new topic branch is created by forking at the tip of the "master". This is not strictly necessary, but it makes it easier to keep your history simple. * Whenever you need to test or publish your changes to topic branches, merge them into "next" branch. The script, being an example, hardcodes the publish branch name to be "next", but it is trivial to make it configurable via $GIT_DIR/config mechanism. With this workflow, you would want to know: (1) ... if a topic branch has ever been merged to "next". Young topic branches can have stupid mistakes you would rather clean up before publishing, and things that have not been merged into other branches can be easily rebased without affecting other people. But once it is published, you would not want to rewind it. (2) ... if a topic branch has been fully merged to "master". Then you can delete it. More importantly, you should not build on top of it -- other people may already want to change things related to the topic as patches against your "master", so if you need further changes, it is better to fork the topic (perhaps with the same name) afresh from the tip of "master". Let's look at this example: o---o---o---o---o---o---o---o---o---o "next" / / / / / a---a---b A / / / / / / / / c---c---c---c B / / / / \ / / / / b---b C \ / / / / / \ / ---o---o---o---o---o---o---o---o---o---o---o "master" A, B and C are topic branches. * A has one fix since it was merged up to "next". * B has finished. It has been fully merged up to "master" and "next", and is ready to be deleted. * C has not merged to "next" at all. We would want to allow C to be rebased, refuse A, and encourage B to be deleted. To compute (1): git rev-list ^master ^topic next git rev-list ^master next if these match, topic has not merged in next at all. To compute (2): git rev-list master..topic if this is empty, it is fully merged to "master". DOC_END wlmaker-0.8.1/.git/hooks/applypatch-msg.sample0000755000175100017510000000073615222270707020725 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message taken by # applypatch from an e-mail message. # # The hook should exit with non-zero status after issuing an # appropriate message if it wants to stop the commit. The hook is # allowed to edit the commit message file. # # To enable this hook, rename this file to "applypatch-msg". . git-sh-setup commitmsg="$(git rev-parse --git-path hooks/commit-msg)" test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} : wlmaker-0.8.1/.git/hooks/commit-msg.sample0000755000175100017510000000366415222270707020053 0ustar runnerrunner#!/bin/sh # # An example hook script to check the commit log message. # Called by "git commit" with one argument, the name of the file # that has the commit message. The hook should exit with non-zero # status after issuing an appropriate message if it wants to stop the # commit. The hook is allowed to edit the commit message file. # # To enable this hook, rename this file to "commit-msg". # Uncomment the below to add a Signed-off-by line to the message. # Doing this in a hook is a bad idea in general, but the prepare-commit-msg # hook is more suited to it. # # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" # This example catches duplicate Signed-off-by lines and messages that # would confuse 'git am'. ret=0 test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. ret=1 } comment_re="$( { git config --get-regexp "^core\.comment(char|string)\$" || echo '#' } | sed -n -e ' ${ s/^[^ ]* // s|[][*./\]|\\&|g s/^auto$/[#;@!$%^&|:]/ p }' )" scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$" comment_line="^${comment_re}.*" blank_line='^[ ]*$' # Disallow lines starting with "diff -" or "Index: " in the body of the # message. Stop looking if we see a scissors line. line="$(sed -n -e " # Skip comments and blank lines at the start of the file. /${scissors_line}/q /${comment_line}/d /${blank_line}/d # The first paragraph will become the subject header so # does not need to be checked. : subject n /${scissors_line}/q /${blank_line}/!b subject # Check the body of the message for problematic # prefixes. : body n /${scissors_line}/q /${comment_line}/b body /^diff -/{p;q;} /^Index: /{p;q;} b body " "$1")" if test -n "$line" then echo >&2 "Message contains a diff that will confuse 'git am'." echo >&2 "To fix this indent the diff." ret=1 fi exit $ret wlmaker-0.8.1/.git/hooks/pre-receive.sample0000755000175100017510000000104015222270707020167 0ustar runnerrunner#!/bin/sh # # An example hook script to make use of push options. # The example simply echoes all push options that start with 'echoback=' # and rejects all pushes when the "reject" push option is used. # # To enable this hook, rename this file to "pre-receive". if test -n "$GIT_PUSH_OPTION_COUNT" then i=0 while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" do eval "value=\$GIT_PUSH_OPTION_$i" case "$value" in echoback=*) echo "echo from the pre-receive-hook: ${value#*=}" >&2 ;; reject) exit 1 esac i=$((i + 1)) done fi wlmaker-0.8.1/.git/hooks/fsmonitor-watchman.sample0000755000175100017510000001100315222270707021601 0ustar runnerrunner#!/usr/bin/perl use strict; use warnings; use IPC::Open2; # An example hook script to integrate Watchman # (https://facebook.github.io/watchman/) with git to speed up detecting # new and modified files. # # The hook is passed a version (currently 2) and last update token # formatted as a string and outputs to stdout a new update token and # all files that have been modified since the update token. Paths must # be relative to the root of the working tree and separated by a single NUL. # # To enable this hook, rename this file to "query-watchman" and set # 'git config core.fsmonitor .git/hooks/query-watchman' # my ($version, $last_update_token) = @ARGV; # Uncomment for debugging # print STDERR "$0 $version $last_update_token\n"; # Check the hook interface version if ($version ne 2) { die "Unsupported query-fsmonitor hook version '$version'.\n" . "Falling back to scanning...\n"; } my $git_work_tree = get_working_dir(); my $json_pkg; eval { require JSON::XS; $json_pkg = "JSON::XS"; 1; } or do { require JSON::PP; $json_pkg = "JSON::PP"; }; launch_watchman(); sub launch_watchman { my $o = watchman_query(); if (is_work_tree_watched($o)) { output_result($o->{clock}, @{$o->{files}}); } } sub output_result { my ($clockid, @files) = @_; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # binmode $fh, ":utf8"; # print $fh "$clockid\n@files\n"; # close $fh; binmode STDOUT, ":utf8"; print $clockid; print "\0"; local $, = "\0"; print @files; } sub watchman_clock { my $response = qx/watchman clock "$git_work_tree"/; die "Failed to get clock id on '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; return $json_pkg->new->utf8->decode($response); } sub watchman_query { my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') or die "open2() failed: $!\n" . "Falling back to scanning...\n"; # In the query expression below we're asking for names of files that # changed since $last_update_token but not from the .git folder. # # To accomplish this, we're using the "since" generator to use the # recency index to select candidate nodes and "fields" to limit the # output to file names only. Then we're using the "expression" term to # further constrain the results. my $last_update_line = ""; if (substr($last_update_token, 0, 1) eq "c") { $last_update_token = "\"$last_update_token\""; $last_update_line = qq[\n"since": $last_update_token,]; } my $query = <<" END"; ["query", "$git_work_tree", {$last_update_line "fields": ["name"], "expression": ["not", ["dirname", ".git"]] }] END # Uncomment for debugging the watchman query # open (my $fh, ">", ".git/watchman-query.json"); # print $fh $query; # close $fh; print CHLD_IN $query; close CHLD_IN; my $response = do {local $/; }; # Uncomment for debugging the watch response # open ($fh, ">", ".git/watchman-response.json"); # print $fh $response; # close $fh; die "Watchman: command returned no output.\n" . "Falling back to scanning...\n" if $response eq ""; die "Watchman: command returned invalid output: $response\n" . "Falling back to scanning...\n" unless $response =~ /^\{/; return $json_pkg->new->utf8->decode($response); } sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; $output = $json_pkg->new->utf8->decode($response); $error = $output->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; # Uncomment for debugging watchman output # open (my $fh, ">", ".git/watchman-output.out"); # close $fh; # Watchman will always return all files on the first query so # return the fast "everything is dirty" flag to git and do the # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); $error = $o->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); return 0; } die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; return 1; } sub get_working_dir { my $working_dir; if ($^O =~ 'msys' || $^O =~ 'cygwin') { $working_dir = Win32::GetCwd(); $working_dir =~ tr/\\/\//; } else { require Cwd; $working_dir = Cwd::cwd(); } return $working_dir; } wlmaker-0.8.1/.git/hooks/post-update.sample0000755000175100017510000000027515222270707020237 0ustar runnerrunner#!/bin/sh # # An example hook script to prepare a packed repository for use over # dumb transports. # # To enable this hook, rename this file to "post-update". exec git update-server-info wlmaker-0.8.1/.git/hooks/pre-merge-commit.sample0000755000175100017510000000064015222270707021137 0ustar runnerrunner#!/bin/sh # # An example hook script to verify what is about to be committed. # Called by "git merge" with no arguments. The hook should # exit with non-zero status after issuing an appropriate message to # stderr if it wants to stop the merge commit. # # To enable this hook, rename this file to "pre-merge-commit". . git-sh-setup test -x "$GIT_DIR/hooks/pre-commit" && exec "$GIT_DIR/hooks/pre-commit" : wlmaker-0.8.1/.git/hooks/pre-push.sample0000755000175100017510000000253615222270707017537 0ustar runnerrunner#!/bin/sh # An example hook script to verify what is about to be pushed. Called by "git # push" after it has checked the remote status, but before anything has been # pushed. If this script exits with a non-zero status nothing will be pushed. # # This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal. # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # # # This sample shows how to prevent push of commits where the log message starts # with "WIP" (work in progress). remote="$1" url="$2" zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" exit 1 fi fi done exit 0 wlmaker-0.8.1/.git/objects/0000755000175100017510000000000015222270710015060 5ustar runnerrunnerwlmaker-0.8.1/.git/objects/info/0000755000175100017510000000000015222270707016021 5ustar runnerrunnerwlmaker-0.8.1/.git/objects/pack/0000755000175100017510000000000015222270710015776 5ustar runnerrunnerwlmaker-0.8.1/.git/objects/pack/pack-1ea372b300770a0afce4787817459a62b797bb52.rev0000444000175100017510000000266015222270710024650 0ustar runnerrunnerRIDX¢;4¼aP b£WÞÛ­ (*@–$MèÇ ²�.�% Ýupã# ,UÈ~О¤7îÄŒ ™½BFH«ÊI÷LÜ,A±ÀÓì¿óŽRíz<VˆQùh<#:E/Öð`1ׂo»Eä >'·üDZæ0"•Ï6Ú˜dëආ*Jý?l¯|õ�FÁá ©ÿ—YÂ4�+v&^òÒ1HG‹cJN°95!úI.‘�â[¹]KVrÑC?T )/ŸNA^[9\Õ_Æû”ç)ßy¬ƒ=ØS›Ô(ÍX§ ¨OZþ%éxjPCų¦…ª6+0;{U¾ö2" 3$k¸!ÉŠ8„t®} G‡êg=’8sOmñK“'¥Ë‰Xï2qåÎY5M>´¡e-w7ø@\Q]L€3 TÃR:BiW̺n-SDœf&ôšµÙ£r³w üäxxEšb·—»Rˆ pڬ紼Kt+XñRèÌ‘½wlmaker-0.8.1/.git/objects/pack/pack-1ea372b300770a0afce4787817459a62b797bb52.pack0000444000175100017510000261262215222270710025001 0ustar runnerrunnerPACK_�xœ�ŽIÂ0 ïy…?@e;! Bˆ;¾·­Øª�ò~ñNs™‘¦UU™}Ñ!âÂ)ÄâÐ3‹0)JvëÔ“W2s®zk0X+ou�±Øèä4 )¹Þ¶9Ç|°dòÒÆ{…ã8]¦y†CÖ‡VØž¿ÜŸíúq¢¥DäVèM¿^§Öôÿ–]øµF–é2làXõóû€ò^xb;êÌ ‚MGT©Jxœ31½ôÌ’ŒÒ$†Ç=D7×þ`K½âùHŸ�ÑÍùK ¡��™‰ XIfz^~Q*Ä›VÜ÷Ó_ž{ñ~Ö>��.0/Ÿ�¤*7?¥4'µ˜!æßZµpïl³=[ÍS6e«}å a‚*söMÌNõÉ,.)Ö+©(a˜ñ"1¤�õ“n \Ò�¬§‰É×`*ý]\ãýÝâ�ýý\B�CôrSÊLûßH\7HÑ|§ÆÒô—ááÚÎipÕ~!AžN¡!ž~î ¥[fKï�ß9%øÄÑKÚOþ.0ò8ªÔÇÓÙÕ/Ø•¡ÊËn‘“çï'½®QoåVM¹)äa UâçTÄ Å»`�mR©ç†[¹-Ì!�Ïf^€ªrutñuÙ´Žù ÓU«÷ŒN_͉5›£-wïöw¸"WÇ`°*–ÕöõB!×§fœø·HR{÷oëÐMPUÁ!‘>`5Ýü.-“0Z•¶Ï˜òi›C*ßkp,…¤æä$–ƒ¶mZ”Ò›KVöîþ§°êü4×â¯Ë!j м{üÜ~þÑ·nÉê‰w¤'Nðšð h�¹©©BRifNŠnrqr~Aªnf^Jj…^qüí³ôμ›ü*]mnnÙùïy…õ^ó’s�1Åà½Ãw‚å×®ô?z©Ïn5ÔõÏ6 44+HI-H“—œ t×ÑÉ…«O]¸ú2dgR-ïÃ{ΗcÏ¥AÌIÉOf¨r¼ÙõvñD®X�4É}çN\Z|×}D6µ$™áGÅÝÞ…‡ Îht·/þ¯X°›;*[‘ô8ÐøÅW´÷>>01ˆ+–c…Í,…,…;o!J2ó’sJSR»L¶ª‹¹ýÆ9ÿ‚`H«ŽÎ�ÏÐÎ,¯,Õ͆Of^z±^fnÃÎðÐlyNÃ�gÄÔVª�ß¿Ãa×f¨âœÌ¤äÄÌ¢|Ý´ü¼’äü¼´Ìt½âÒ‚†÷ú¿®Þðé:ô¢Ãð“K£èÅCú–Q=)E¹e*A6S¿9>³õ ±�€÷Ñi{¾ýD(KOÌÉÉ,…*ÍØ´MKd«ÃúäíŽþ¼:ûsEÚjˆ§ ŠòKò“ósŠÎ÷ýzÒ­# %b¥|å£(Ç­>CÔg$säÞe‰¾¯ŸGOï™ï¬ì)w[ÍfÒ£%Pù¢d†k‡ïª<8örÑ¢§æGž?™`Õ•-M‚åÖ+ÚZ!ÇØ¦²jTÄ2Ý;0ãå’•ºEÀWRÌ`Í{ÿ]eSñ»¾ ?Q#ëåC�Y’‘Y”_�XTRÉ�ž:_�é)ã!—öMç[¿/OU‡ªÊÏÏa¨˜9igõ‚gÏ¥ªMæôæ¾Uî¶L„KYbNz0A‚²@¬ù'K¯£ÿÅýÒNF¬ÉÞ7çÊ~Æ·Õã¤xœ31…òü¢ì´œüòb†û3ÿHTø0&©z_©Ø·2MÌ^óÞQî~`£xœ340031QH*ÍÌIÑMË/ÒMIMÊLÌ1³+õ*ss„O¸0ó9ÊÿøP-™Àú4m«°!]%E™™©ºå9E†–`ÝË+¶­ú£ÐW×½kò‡5æœÕ›ö†¡ÛȬ[æöéíÓ¿Ï´>p8dR%›ØË¶˜füºÁÚ¤¿$ÝšÞö(!'ÓY"tbúÚÃW1´¥¥¦ä%š‚u„Ìß9Ë|I«ŸÐ®¥ãºÄŒÌ“X�©£(55©8¬á†¥ë«Û® Û­/IÕ~?:åic¯)††ü‚Ô¼âÒâTÝ’ÒܤœÔòÔTˆæ`®�¶7sY;VFþLÜÜvbYÞ ¨æ‚ÄäìÄôTÝ¢ÔœÔÄbˆwŽÏ›{{ÙËI×ûðÖ}1yήU÷`ÊK“r2‹3tSò“ÁJ¿/»ó¯ËhcgÙrmßÚ³Jœµåp�¿ ¬xœ340031Qqõ ðq qÕKf8údOè£ú t?3-ÿÔò6bíéS†hª2Ú�…o{ÿÏŸp}_ï¡G‹Bí?®”fh?‡­xœ340031QpöMÌNõÉ,.)Ö+©(a0^øOïÉŸ7¾'7” û_;~,J£DÊ¢2µ"1· '5¾$H–¥æè%3˜ï_�»‡—[îËtöõ†ý÷ãçL21…œÌ¤òœäœÌÔ¼›‰<Ý+_í¼·¸ˆ]$[m ï»»ŠÒUE™¹™%™e©Å <ÓWE/xÈ#:]ÿC¨»ó¢ýq —ç䯧%dÄg$¥¦-öݶe¹�ÕÐ]9ý¿ÕóxfŸLÄ¥:ƒáñC[…ÅÌušíÞ3ÓLTò¾r¥\à@¨NJ,)I-ªšºwçõE�‹ïÊ>ª”Z¹¤2[dê÷—ê’sò“³�ª„ò®S‰=íþrþ�ûFWÕ.¦þFRUP ¶¨ðÒ�óFñÿg^?þ#ÉBë»Ê‚��» S+S‹�Š«XÈ7d¿N:”×´ÿ€ÃS¿üZ„¢ÜÔ\˜i¦É}é5í”<¤f/Y•´Ò·4ØñBa^j LáÝM‹N-WÈoo¿Ë÷aiðBOÅ*½XÑÅê§xœ340031QpöMÌNõÉ,.)Ö+©(a¨ ºöòò©� ™˜Ý�N}›xºñ�!DerNfj^‰^2Ã4>ã¨ì‹ªþž�»Âr›+çKùo¡jR’r’JÓ€j̯ñ�~ÖžsªæE–t¥Z¶jýT5 ›? <¸‰áëoÏ'â®>Rš×m j2“óó€¦|\0ç¹xžóÍ òåÒö§8ÎiÈ «È`X$¾ü`Õc¶«™›ä5¿Y?ÙßQU‘“™TžutC“y'ûìN6w«s©××Zq¦|ü UX‘’_’_�“Z–š´´caÌY£Ùé‡VÆ¿ îX$±>ú—U 6• ÊKº>ï¨+¾õâ¾uàTµÉžÏ2hv‰K¢xœ340031QpöMÌNõÉ,.)Ö+©(ax;›óq‡üw�ë�}¾ó(\Ó‰3„¨,(ÊÌÍ,É,K-ÖKf�xÕÍ|üz‚ã³õº³�5SÍT–Ù®ÂT—ÁÐ^üüFé¼ej Ú+ÖìŸgÖÛ¹Nª®85=75¯$>%³¸ '±h(¿iÞÑ«»$ÿï1I:wc¶WÈOWŠ36]Èv<°øó?ÉöUѾžá1¹q·y±+Ž74«02Ñ+ÈKgHëÝ ]QÑk}nÖ2Ó£T£¾Ü�ÙˆC“Y…X‹]ɹÍ÷™;X¹ß¼¸«­Ö¹qÏ´ß8´˜W€õ¬¹Õ¬�ãKÁÆÕóÒò”}¥fž�„COIjq Ðëö?k>öZþ«™P¹íÔTûÿ“�^Š.¨V©i¢xœ340031QpÍMJMqÊÌK,ªÔKÎMÌNeXßeœŸá»ùƵ»3²=ݪ57|•!DqxbeNb^J@Q~I~r~Tƒ7SÌÙ˜¬äù¶³?û ¼¾ªÝ¬«[Õ� 2¢L/3�AmÛ•€¼Sf|Ø]{¯ÂþÙ}ÿ“ûg!«ŒO‚:¤8~ºšòÇÀ¿Q §.®LÝo,h»âB:6Å Åó‹ f½°uùû3t·�fŒÃÝÙ¢kZ…¦#xœ340031Qp õôqÑËMaºÊñù~ëJ®Ž“¾gCóÕgéÜ?nQã웘�ê“Y\R¬WRQÂÐfϽ>#úI„�wÀgû§òó¤îó}€ªtɯ¨LËÌIÕËÌcx³øKLŸ²ú¬¥¢~Ÿ{ô}¾ÇJõ¿†*ssu r Ù»|O§+ßÝ%mbgؚ˛ä³å3  Ê‚ü]|@ª8.·ŠÄÍ]ûç—Âé’�0ãÜÉË—>€© õ©xjíüPõÉ|ÖÎä #v³æï·m¸U‘œŸ››˜—’“™— R)Ïã¦Ù³,Ѷûý\ÖÈcg®ükq¼ W™—–™RÔüòÏ¿ºùy{õ?EYîõ=Ж¿B‡ªhVibŽ^rq1Ãî­{3Ý8úÚÂm’¥‹ÿqæ>>TU@£Þ‰ñݘ/µ¤xœ31…¤ÄäìÔ¼† «|³ŸÛÿØÅLǹ—)ÌSŸÉØ×¬¢$??';³„áÜ>o‹�Vß>W®[çÆÜ¨¥ @í  xœ340031QHJLÎNÍKÑË`XÒ½g±Ôö/%ï·dˆrX®zD§Ò¢(¿´¤ ´¨†w¡W¶EH?w[áÝ¢rÒ£Ž«ÀEM|r~^Zf:Piâ3EŸ…)ÿŸØsø×Ï[Ie Öé¨JsóÓS‹€jßG.ªˆ3{^®òf£cQ¿[VyÍÕo*€Bä©Nxœ340031QHJ,NÕË`0ø—~ñŠ¥¯¼X…êãU~]ʾ!bU‘_”’Z”šT%ÕtûóçØýÏÿô¬Þ¾Fpÿ~»«¹áª*€ e…‹Ï¼tœ`½ñSue2ŸÛV˜‚Ò´´Ô" šÍs»Lµ¿y»b“êÿ‹M[xznÃÕ””äçÕ¤/žµ'–û¿gÏ�â$í{?ê%¡j’óóJ3óÀFEÇ=øåTþr¥¹Êeÿ6j–ýrjvPe)ùÉÙ@f¶³Å6kô_œ<Á0÷ÿ©ù›—¸AU¤æ¤æ¦æ•±÷ç]þŸød®äãc,Gû*mnó0�*J+Î*Xr\ŸA#ã{ð†�oNÄoЭ۵à‘TAzZÐg@5œ“#÷]�[±Tá¨Fzñœ%'÷9êTBÕdæ&¦ƒyOÏußïÍÚO©<\|ñ×ßÿçÀ”䔂œ’Ý1I;d¹^âQÓî™ý�kw*¼„*ÉI¬{Úµ_&eYÖå ^oO”;(%Ó7ý,T Ð?¥@�+¸¶]_»âtêcg‰ÿÒ6«GBRŸY’ òUÑ»Ë;ì^²÷pžSV�@iKÐô»Peù¥%@÷Ä—%&gƒ-½¾Q|¿Dæú3L�Æÿ#£“üN?ƒù® 1/5¨„ßfÖ¡“úû˜ªÔTu |ù÷1-„)É/(-9li“æ; NæöµŽí?Ó„•/sÍ€ÙXP”™›Y’Y–Z T·&ùuô3–oµ7Ô²EB.¹§éÆ}†ª+JM.IÌKÏ…g�µxkÉ—ïÂͪ·¯´ýVï½µ|7\YqfUjR"Èíaº‡%ÃoxO¼õ¥ëÑÏÿßox¶,]Y|bQj"(üßìÓÍ›©6ûõn�ó/ î¶-凩ÍÏÅ�ÜíÒºïi{Ý»ïKøÜèuígn<UQ\R v×ÿ-¥vfAåo÷™ªì»qxoݾ÷Ò`JJ‹Ò“AŠ6I%ßÿðŒ}9ï¢È¥—$˜o¸¯? ª¨$µdÓ=Ñ—Æ;orÖ8gš¢¼ë¼ïb˜ŠL°E>��¶™Ö6¿Hþ³IˆI‘‘¼öª ®¢$êók Oýšò{²ç§ŒiªêüÿmZ¼ÑTÅÃsžµE͜ҟ¢�ó>�<¼¥_äãç‹IèŠÁ Pnße×{Oq¦ìªÌ >ß�º;µï‡©ÍÏÏÉÎùâÝw§ìŠíê}é¼9íQ2/…„¦1tA•½Tñ(?~7[ßãí¾Îÿ|·'N‚ª(ÏÌKÉ/ª™�š¤Û·™sÅ:+ß{f§cž¯ajò‹²‹ �zÿë–-Iqm_¢|úêýƒ2ý·–¿’ûƪ xœ340031QpöMÌNõÉ,.)Ö+©(a`žnr¸_AúéÎÛ¬ —¯»šZ´ýî0„¨L­(ÑÍÌ+(-ÑÍO*N-*K,ÉÌÏÓ-3Ô«ÈÍaˆ×[çân9Iª÷Xòù—þ³ :š�„ê+ÏÉZQ¤›™ T^šW\’˜”“ Ó·\´+CjíY‰è—Í\s&øošyN9›:_ª—­¢�Œ¦ H'%–”¤U"›o÷Ãñâ§Yq¶ug‹Ÿ}-ì3>óLê&¦Æäœüäldm‡çI­ØÚâï¡{|iµÙ»Ø$¹”9[±h+(M/J,È@ÖyíQIýæ;»§¶…ÉËwUjLð¸$Ït`óÎ_n˜:óRK0tž3jÜöúý¢ÿ¹WÕ;r¦vþžI_P� uFÆ]GÞò¯´ä¶¾mØxK|Ý\„ÙÐÐFŠz­GB¿£MƒŠ­�?Àwî B=8�‘T»_Ö?·¤z§Ù M�u\»›ÎX\Ÿæ�¤¶Hæ%4ªÎ|*â’[`þòÏËwu-‚   ERìñ(ÕîܯE· çV‹qÎ[ùgší„bXH"iè=PÊ\·Êøk”N«Ý_}†¯¢.ù 4ÀIîù2=R;­›¤R®Hñ•¹h¦Ÿ°` ­ xœ340031QpIMÊLÌÓ+ÈÉ,.aøÏv泈…iO¦Ì ¥Êõâ»Ïì0DVç’_𔓠Um±3xoOš³ÀóÙ{Ž…&ߺ¹o ŠjŸÄ¢ôÔ"¨êÖX�pÛI–7ú-?z­ó§4}\uZbiN TáŸI “ó•ü¢îü7kȼÄÄ]}ļBÑ xœ31…Ìäü¼b†c®·3ßmWß߯.5»ðÖ¾¸` Ö¤xœ340031QrutñuÕËMaøxl¢æÆO~•“þ¿–Kæ­/Y_®cQ”œQ”Ÿ›ªkjVaj¦W�—Îp¸ª’¹-wUœŠÂ§¬ˆæR‰y ’Èj3Ks‘Tïß2?ïò£ý/4›w|˜pÊp¹ÌßÙaªs2 �T¾ˆÌ¾g¾ãA°Á¿M›{ç\å®�úU™–Y”š–_�¤Øã”rà”x�$gqÇ›.ÜF‹¯C—¤åfæ%æ ©v[üéÏË%Û\üq`˽3 î=‰aƒfo¬“xœuÕ{<ÔéðÇêLS(–‰¨ìJaóZ˃\V‡F±T;æf~c®Í…éH®£)V«”Žš’l/Ç­�\ÚrKÊ‹STÌPJ*÷l^œ—˜bÏojfwÄþþ~¿>¿çù>Ï÷û`��œ¾òÞEdPƒè|ß^ W'¢5ÿZxÐð~¯¼ '¸Lüáõ˜O’HÐ9l{2¸7yêrE9~ ¾È3ù¿–8”Þbó™°†ìì;ÑB›¢3j:`0¹Èè* k(%DZ�Ã�51CÆ_’ÞË–�HxŽ‹¶ZÛ‘x -¶òˆšþP±Íõª“#ü}E"’T6XPö£cfcXÉ«ËìFÙÙ�ÉͯTQJBãq„l ü˃½½åÉîó—º+³Ï¼äµÑT’—:È­ÇÅbãµ=ü²U¿¶�¶ïoˆ.Ck8�H¦Âqæ÷0Cw­><"w{JŠžnGxoQ12“Î…ÅÙÌí£mFÕÅqfßøo*[©3_¼US@`J'KRz�/ôäm® 5{¡U4¥v4�§Ü ÙÑ6l²5×8·ÕL½ ‡Ì€SÂ'_¡‚ÒØbOïïŠûŽ4Þóíz­) ðÞ¤?~‰½>¼Ï µñiNoJÜ•ˆ¦3©|8$8Û€³EP£ËŸ !vnÐòƘÉ"W†3ò,)`ìU¤[¸´6­ËoJá©Þ6�Edi÷åTx¥:Wû4é¤aÖ僽çýbÑþ.ËI˜.4öǦ¶Ï UÙÉjÌ•jIa*ÏÔ±"³ŠöRèÙ,bäHfn]»n3ª) �’åZйÕÄá(ZRW䙘“:[üé:ÒÙ\¡`L}×ýžB ÛÖÐlõ�¡èÊ‚ã4u‚8$>•KTõTÆtÈÎ`Òϳޙ»÷Ì|ÙoÁÓÆý-‡ÀÍúòù® í~S|Ëï»È×6;ç«×Ç$ ÙdècE*{Št‡¤‡bi†û?ÜÐõÀãÓ­r?W@Ôά±¾õÇí5ŠlªÃ÷RLߟê•GàÙT&÷\V¶Ztªº1cò-yo��h㙺?–�(´]ØñŸô›ÄÏI¥¥ï°:[PëúA>De*›w+ê²*V¹Å›­?µê^·ïíü2áÝ�C ™ÿ›ë �¿ïrB/] áÛF`Ñ”ûíwDbÖxUûî¿Óf[.l²Ž¢K?W8¬‹Ýñcr•\ÖL4_Œ&Þ7?¨R<G@`QÙB8l#OÖásóÜÞ\×$TFi¨BœÚIZ àLù—Iø†.aßvyÂ÷Híž�˜6¡Š)ìã9„là6Aúïm*?ùoq£†fÔ-6˜;ò2£ö­×Ô›#ýÃrKBáߪ��ÄåqÈT>ŸÀâ°éŽ2ó‹QÛxdsä×5ï0˜Mø=ïpNÝï!�fy À8äË埧•é¢ÖѰ@•ù ~àØG ¿�¡–ä¹·›Ý¾›5g0aÔ3žº„A ÇÔ|2â¼Ûˆ7á˜Ü–4Å>‡R3¦º‚¬ô!Úo{ñTÏ‹q™‡ "¨ œû A€rA2¼O‘Ù=6èý´ DÍçÏ´£?õ‘€Ãa2èÀî8ñFm/ƒsµâ@·õ)+c!MÇdÁ¯—²� 97o'±‘MA5ZQÏê!‚ÿBšãZªhI™ØGþ÷ÑÖ‰é4|hË×}— –“ñýíg_·ÖѦ¹<' UU~¤\%E �,äñáÒŠ¸,Päf\î·ÕÔD¬y@™Ÿz§†" �@¡ÂƒUÝégoÊ¢g©ÖÞñÛô [½{g[–·h»˜Y¿†ä‡ÍúâD«Éª�D Ëåp…Ê÷eíÛ9ÔèwãX© Î2YK\¿½„A Ugn—~b¢ÅL[0Hx=`¡w\ƒ©›²Å�Qù|ìCQÙq’Y±GÀÐz"v ƒ@;Wº*ÿ�ö—2¯q/×;§™�º¢Á.“ûqn óÌ7œ,4t�×ṙ�¿vV;/'!€©-ÜðÀ{áþÉ^c«k-µïôa�ZÆ)£U}‘Ÿ=홨7i,±!¦;Ù•<ÑPlc‰†1‰›N¼ÈK’ƒ§¯ñî_È‹þt (Caö¦#{¦-uWnº“x×=¿ãñ2xß4}ûØÖûG„oŒ«ïi}¶OöÿÅN¶sª xœ340031QpöMÌNõÉ,.)Ö+©(aXñDN¦Sç•óúÊú,�ÿßîÿJtºoQ™”˜œ�š—¢—Ì�°ê„¡fÝê3Å2Ÿý3_úå­‡ TQ~iIAi P��Ž™Ô/ûŽ�g.¸ÄöCÒÏ…™½EM|r~^Zf:PéüºôÅê¯w¯3�ʽçóÈÎ7²«Uinb^bzjPí÷9¯¸¦ZåhLñ¨ÍJ½{£suÿÇŒRK¢xœ340031QpöMÌNõÉ,.)Ö+©(a`´)[» >Åð˜MÅÛûzSæ…õ{\1„¨L.-*Î/ÒKf¨•ôæ½,HtéÛ>sé¯ÊýÖ�¨j2Ê…Í(÷ÞX»$Ư.Rhºó íÖP5Ù©•Iù‰E)@“”—T=¬œ<ó‡å©çt\vûK¦Yz£«Ê`P3Tœqi¹U¸÷9-F“éÿ‚¡ªróÓSAŽÒýù§¹éMt´{^ÏÂ{Ê"Y³«JÐe0DFöÜo>ýîìõ á!+-ì}nEìÞUT�Ÿ™W6ÉþÖ–¦5Óï×¥y‰žü÷òËN�NñhŠ2Úc&©¸'^Ö–Ÿý8rá×5FÑ6eŸÕ³ƒæ¤Qxœ340031QpöMÌNõÉ,.)Ö+©(a¨~®[Ó^ ·.&ÿ´ÜÆË×m6ÚfQ™”Xœª—ÌÀè¹DjîáêâyÜ3wÍ‘=)0ßàLE~QJjQj PÕ#©Z«O�%?¾en½«» %~ËÝh¸ª  ‚Dõ¶9ë_rœ\�2cgE§j—À· 0¥ii©E@5�L ‚²„ß·ÛsÞ¤þŠuCÃ¥"p5%%ùy@5Ñ;/^ ¼²2êÞTåÝóU›ß>U“œŸW’˜™6*¤}oØëê6K=UOs©Ç¿f²XB•¥ä'gUðÍ>2éEþ»×.¿œ[*›;n=€ù+5'575¯¨(IxÃõ<ž0ÎmŸL×õ©„Ô�ͺ ž´â\ _3ÃÈîÃÛ¶]±wû#&û™Û¡ª =­è3 šÇßÎe”Äï¶ä}Б¢;7áýü P5™¹‰é @ä_}srU]VÁ”eæf–d–¥ÕÍuýý¶þQÑâ�ÒŸ|»=±kÊ´ûPuE©É%‰yé9 ð üñÑÙ;czã½ÿS£¾<’»iÊÃy®¬8³*5)äöõ³Oø¿{V·p¢|_È&ÏïËuw +‹O,JMªýÀêšßmž|ÎægÐÆàWd[å«ÂÔæçƒbèí»ÕËj8ºîÝÎüQÙMËìåÊn!¨Šâ’J°»²,÷<~SöJyi‘Œç*‰»|¾ÇyÀ””¥%&ƒ][a¾)윰 ÿ=Y›=moÛ(½ª¨$µdSUìÌ•~‚¶m2ïkÎ+¶�œß¬­S‘ ¶H,éŒé{ço�n´¸»&U³])ÿ´®¢$êÿi>ßäò>üz.|³iÁ�θ=ÿ³®£©Š‡ç÷œÔåæâ7«GÜ-Œªxœ340031QpöMÌNõÉ,.)Ö+©(aø¸äº¶�M�G‚ÌÎ;=3ÎÌÝeªcQ™”˜œ�š—_’Z\¢—Ì�¼nÑK¹f¾°orþ†WVe8¿ñßÄRKVuå?¹›¹ þ~eè©Ç6âœ_l¢ ¥¤g—äÄ$§ÁÌ;XÐh¿kíŒ>%'¾ÏÓ—ò™ˆ§þƒjÈÌ+(-�©ÓgæŠ~ùþ}ð´»eõ7ßj¿ö=ÿ T]I~~Nv&\¥­ì£®¼¢OY»N-fŽOW¶üpª²<'èm¸Ý¸S®¬™°âløc™ˆ×^*J醷µ*AÆÂTîØ}¶|ÿö2¹fÝA·Ÿ�¸3;ú›ª‡­xœ31…�ŒÔÜÔb†>»Ïój?;�<Ï+ +UYøJ›m«¡��™‰‰BrNfA|AQjqqjŠ^A^:CÓÞˆî颯,;Ú—Ÿk¹úøù dµE‰™0¥•ýRºªÏdŒs$2˜Ö{tОÙÜn¶73¯ ´„áRÈÔÝ�‘qÒ rÓ—^�É]z8å³Ô¨ìÔʤüÄ¢†›‡ô¼\ï¹½òŠ˺æäÞMÜÎáP5¹©y¥ºé©y©E‰%©z9™Å% asÕo[úÚsGe£u–SÝ»%å™yÉ9¥)0Õ—¯q=cª8�gù»£×ÛµRãöÜ CR Uµ¥)ÿ‡Í´¯GþM ?»Î[RdÛ¾!o—&¥d14e[ý[ýÝ‘aëlÿºsm£ ¹Â Jòós²3KnûFGV9Ì9óÞÇÄ’=qbŒEjP‹Êsr³S‹ôRR‹³Kò ξc¶“Ü6×zz“ÇÓ`Ë–JÛ3>Õ3«µ xœ340031Q(ÉHÍM5Ô+ÈÉ,.a8S•§^±þ Ï‘I ËŸpú×"©3‚ª3(2íÒ _qç“û'ŽuR»÷Üvó%®xœ340031QH.-*Î/ÒÍÌKI­Ð+ÉHÍMeðù%y„+ÃÛ{í{qqÿ?פ©6Óo¦Ÿë›ûÁ;>ÚÙ¤âÔˆî^‹ƒ_ØÜ_³üûëòB³P6qÖ™¦Ò¨æ‚¢ÌÜÌ’Ì²ÔøäœüâÔøÌäü<°ñ·/\–W;vfÇ–$á:á;©ÏÊ:ñè‰ÏI,JO댽®µ§@ùZ÷ñ�v[§1=ãÈ_Š¡3-3''>1%½(1%35¯¬QíFû5ýŒ{oÏn®JoýÿNÏJΦ»FT}Gö¬N^>eÍo£­.5·§w²�±*À®/Eß÷Åi[æöØèXòŸÝxÚVI2p]Uv}Åù9™� |ù»\ùÇvìzÊPìêúp(c§jcú»GöÝLæ‰ÅЗ›™dV!ÅÂ÷îê�oo,ûûø…X×ú‚„—ÜÏñkCŠÝm“ËÂÒoŸËºaZëì3¥¶¶÷šKR+ .|QØûüêu­;^<þÿžU‡G†êò̼”üòø’Ì’ˆþ©U§S'×_>!Ì+õ?ÊO Êþ�:TWQj1ÐQI‰Eñ‰E©‰ñ@~14A:…¿+a,z·½2†Ó¬mYc÷ý5Øu¥æ¤&´MWys*W´ÍW?nne˜è¹£þÀB£$µ¸l­÷ªþÉ„ò.±²VZÆøÐ£Ï½p3L!ÈéñI9¥EEPSƒ3ÌZQÓ2[ô­üÃÖ '~ê´µibS_œ‘_ +ƥЉû®k12Z°\œ•ìR߀ª¥´¤ÈÖØ�ß[PWÀü¬=1#EáÆÕM×þ?½ƒMOZ~r)( PìÔ2KάÁ¯6Þîímüé©Ì$.ˆW3J¸™†œyÿ@r×Ë]Nѯ¢:SäWr/Eµ¦ ¬º¨¼­ÒðîFKãöÅ{§û›Ò¾“¸'<ç¤xœ31…‚¢ü’üäüœbÙ¿{t~*³Œû:uCþŽ®O”¥xœ340031QpöMÌNõÉ,.)Ö+©(að•ÓÎpøgsuÓÏÖüI— ް.*[lQYžS¤›“X™Z¤[œ‘š“£[šW\’˜”“ª[f¨W‘›ÃpMÿªÜ„‹MîKwpO=¬)]0sq€8'Ç xœ340031QpöMÌNõÉ,.)Ö+©(a8~³Nè›Ñ%¶ E£ƒ;-oñš~}ib )©ÅÙ%ùº‰EÅ©E ‹\æž²ŸÇn\Ëôã>ßfIŸ"Cˆ‘™%©¹zÉ Í\koX—eì^sÌðËÏ1ÃÕQÈ*2<š±yµêŸW.(äŸëÔ|çüœÛª"75¯hƱÀ潟§zù3ªt]•‘ÙñYEƒ:ËìG[嬉<Ð|jYßd|¸?ûTEyNnI~~ÐÉàg_K³Äy–³gT^âó¾ÿŬ¶@0ct¢xœ340031QpöMÌNõÉ,.)Ö+©(aHLVÐ~·µcÍNí�lŠËÕV2NÛkQ™’Zœ]’_ [�XTœZ¤—̰9;¡�•uû�4á„”›G¼J¨Ã®6ƒ¡PHD彿[}þ‡žÿ¥’~sqLtþÁ0¾Txœ­TM�Ó0½çWŒ Ò^ê–�‘VBí‚Ä�]©BpT9ö41uìà�~hÙÿÎ8ɶM»Ë‰^"ϳ߼y3SÃkÌa•–°²n°PÜÀGëÖû1lU¨@«b«�µÁ³W“×ï˜Ä 4\¬y‰YfMž4ÑWé P8nD…>‡ï0ª¹2#øÙ^ÐzéðwDþq1ûe Ÿð" ZÖ<8µëîûàxÀrß�N±ô¶n”F×ò•BŒÆ0š›²à¢ñŒäB,¢ ‘i¢ó!ëÞš@ èqO§j*.Ùš‘¯’Y¯ßÝb`Zï>¸Ö�–У�D#úIÏG¹søsÐÊ›ÀJ I".ªgd{øqÉå­DÚú†‘š¯qYiÜ äÍð¬ÂàLí.¨²[`Ûë3Ppåì›§#¢ó蟂.è ßÉ’Ü£TînÖ%£­Ô°Ô†Š±ÖƒànË÷—Ù Eó ÅÚÆ@�–8&…ŽR‘u©v‹Úʨ�ÝŠ>�%AYã§¢ü~ó¶ÇÓVïÜZj¥O|W“jƒWÐ8Ü  VD•PÜ‘©'ù @m·'D§£3�ÀZî¬Ñ{0ˆ%ÛmôžÂÊÙ¼�Nàä@vÌ’CpÏ iíŒÄ¼Õib„ÊÙXV0ÿLÇgÆw�uæóëÑËûû~'�‹£ã6¶sÈf�Ô)°›®ƒËo‹»ÅõÝíPЬ¯¨óßҳΨNÃ0á"H;–þž[S‘@bIF“y(¦ãet}þšýÚ•‰ùâ ƒQxœ;ǵ†K>/17ÕJÁ©43'E!-¿HÁ-5%¿(QÁ'3¯´BÁÄp¢Ý}Î4°�•‰ádMFÕ”¼4ÝJ…Ò‚ô¢Ä”T.(€ gæ—$æäLžÇ¨À•˜Y”¯›’Z–š3ycêäv&~™Ô…¸…¼äÒ¢âÔb¨¢iL–剕9‰y)ºEù%ùÉù9P9]å9Eùù%™É™ÄXt‹Jó6_g*gó³CÆê…xœ;ǵ†S9/17ÕJÁ©43'E!-¿H!¿ 5/84ØU!¤47)'µ<55e¢]$kIjq‰Õħ ùâÒâTý¸üdMF…ªÊ‚‚Ô"]ݼü<Ý̼’Ô¢Ää’̲T…̼â’ÄœœÉó¸3‹òuSRËRs&b æ¬HN‚òÊ™äëSsb¸à '3©";)9?7h XŠt^riQqj1™òÄʜļ<2Eù%ùÉù9(º…aŠrŠòóK R“2‰±Am¾ÎÊ89œ%dò5– Ô´fá¿ÃxœíVßoÛ6~×_A}Ø€Òr“´N˜c»�Q'.\¯Ý° EÑg‰Tù#vÐæßÑRÓ‘Ö+°—å!†î»ûtüîtGA ¡KËó­¤B#s"ÐBñ-gÏц› mr%¥Ñ¨×}q�´� ™Øœ�Q€Piuæ~Š4c:B¿¡NA¸è ?vy¾Tì³eÚü�cÀÄ­ƒ'7ƒétù~°¸ŠPçÙ—«Ùõø>ÜäY3Õ9ð˜N.çƒù¯-žaÎãðÙI¹N1Q4ã†QcCøóh|¹¼š}X,¯ž.&ƒùðêÇCÞ÷ïÞg7o&o¿ snTŠOÃè˜HgD±‡Nü)cídˆ]I–1P‰J5m1,½«ž:ÄÜ•EÉs¦vª¦”vž£͉H«2 ¤¬ÐІll…±8:m‚*V¨×t¼ )4F²k‡ÈìÚ!¨Ó`¥®Ü0»ö™mHž£’Ð5„ATÉDÂåLwkBxy„¾î“%¥Á)3È– dñÄÌkF|‡~߃ ×Rx–Ý}‹“Ö³¬r¶õ ŽÿÌ�÷œm )â™ ä1œJ@NØí1H Wò¤ I¸.sr‡¹XÉF\Mf.Jkš€’o "ð‹&L3ÒcÁÖdß�;Ð/i‚¶4Æ®£¤†öîµy$ŠŸ¶alSdm§”gmh©˜f¢ñ$V®¹¶†ç­‰C+¦³¢Û¶êh£æÛ$Å1Ñ,áª^Ç c!Ű`ÇÍ\ß2¶È=£«sâY*Y*i$•¹öÐm Þg;Ì]KkþÉ÷ê>šKÁPf |ÿa–ÌÆ]8\XfkSs {„àø8£Ôäã½Æß Œa6&è´ß�Ï_ž“¤—ôúç¯.X�¬âz²êÇäåIÿ”0vqöª·òö;ª/@ÆwœŠQ«4¿u¦‚©”)²Ãn”ƒXÕöÄ [Ôùֱض”Ê áð5ì’/õäî> ltß9v…´}Œ��Q¶µUêÊ#“)iÓ ¯á±¥Qþo„o6U—¡å§ñ|>›¿žÝt†W‚êÖZËÿÝÿÕ:lô£³Î­@îBänlm3ú_€:vHÃý`Øgu&‰axŸgþüüF†�ï†CxœÛ/ñKl‚“‘ÁFƒ¿,,ºÙI“°ˆOÖeÝ%™›S’¯Ã¥Å9%EùÉ“o²êL>ΑÀQ”š\ZTœY6ù?›ûä\F“g°”KI-HÍKIÍKÎL-ÖWÐuR@˜ÜÇ!+„"’Tš™“2‰]b³3ç&Ÿ3ê‡CxœMŽKJA„‰&¨I$ ˆYø£�¬&ñ±QÁ€#àq!23ýϤ™N÷Ð�ŒY¹—í9ÜxÝyoà94šEjUTõ¾˜žkKŒz “Bhå¬5Ö·‚ˆ]?rÃtûü-Í\dÊU¤E씡c4eÊ~Mµ²4[�>šá¾‘ •ýÈ.'Y¿AÅäªù8 _ð€†ö%·b¿sËös¶õ°™‡?%T÷vÆ`N…£(¡.qĦ¬C,q€\+Pû½Q‹÷Zº Œ×Ä0Tà!Ɉ`Œœ ÷)ªf.{¨\‰ 8B�#$ x†2ãïHÑ%Œô±aËsO%¿ïFÎþÿ®iÓ|¥Ôî^ß\Ý�œµw;{Ýcûš¿µ�…Ž�/nüÓs2¿Gxœ}SÁnÛ0 ½û+ˆ^|™â¤(†!À€"ívÛ dÀvØŠ@’[³,º¢”4À>~’�Iêö$Hï‘||¤,oÕVA›¶èà«S V?îaq3»Î2´Ë   T§@8ne­h ¿áªåÚ^ÁcO0fãÔSPäß!fQPÂýÈsÁ‹U ˆ`}`†'¬‡È«ŽÛ뼫•l0x�Xª ­4¡Ô¶‚J{  Z,ƒQ4ëƒ%\z�– y ¾ÝÝñ½öǾúzcü¼ *;/�v««àbYÑ»ÅmÙ÷{ÓòF9ðµÃPÕp÷-^Ï%H‡D¬‹ÍE�[ö¢h8owóÙõÇÙ|RvÊq[ÜÐ!Ò.a'$¨;åH'ó4²||�Î.áßxMý•¤J`òr*žw^9s(Û‹ åeÑ5U‘!îÁLƾ/“DØÍé‹D¹Nä”Z¡^%I,#î™R¦w°3�i0Yß�½±›�C„R;XYÓFg‡g�J޶÷\ÖÒuœE‘eGð)ËmK)Eo³Š—¯nî¯?ž/ïï~xˇ'�;evœÛ®¤¿%ü,Öè²çRíÉ�2±(jN–½ è­ôHŸ²Î½Ù Þ[²!C*å`42B¬åXOa�Ámd×qÁI¤]Àš$ö=¢ŽÌÉN`Rç�5k‹ÎÅ6SÔoÎþ ¸6=nÑS©àöîžÊª+T·"¾ …‘àz‘RN2PÇmiê5t±ILÛ)ô¸È“¨v¬ÊÚšÐ0‹bÌX¦7Å-`EíH‚ümJ—fàq^úê˜Ø‚ˆBS£ å½ˆKòL¥ãÄÍ^†˜EN4§yƒPíCž¤ŠG�à ŒÊÖó‚ò-¥Ð…·òAbÏœÇÎíÝò!î�vž$£âWºÈ:¡®I ‰n1øFüûø :ŸÓXCèj»ez Ü|ŽŽi€¤£rÚªVlpb«ÍÃŽÄšXW &¦uUM-ÒO,J–•�Ö|�׸}~Æàн|Ø+kŒwù—‹¯¾Ùã¡^ç¥pXKû‚C·YçT¥•\OµlhÕÄܳ6›Ôé¢Ájc‚§‚×È�U©Pó�q².”-uº—,8f‘6ÌI5\·ýæÑƒwc1 ý„R€·ŸQˆIZ�½ŠiÊhZÖ \üDŸÇiÙu{£wRµóË$Êòòî·ß¯Þß./~¹¹¿¹8ûñôîòs(ƒTõÉ”Ã9[Gdi@Ïãý…>Ó3Ä7_Ok¼-_rï�Ç ñnûÝï#-QÇE·öWb‡x3çÂz¹¢“·*v´FÈ’°Ø'½±»‘½$Mó¤ñ8i|«NæéͺŒkÑE¶)ü”ìôaòi™¬¯ôVÖi•¡ÞÒj¶î3IÔÇM(SòÃA°ª€/>}J›kñ´ÂÔÉ]ðnÁÞKrƒÏŸÿoQjÄš´ïß,iø�Þ«ñ»´â×EúyÒeÆ?QfüË#=ø©|r•¸%Þ z«´_r*pz s¾2ß³zqñî{®nú“u1"õJÇ$‡}+~›ý"T¼xœÓÒÒתãÒÒÒwöMÌNuËÌI-Öׂó�“3RõJ*J@"I¥™9)I0S79'1/(âèÌ•\œœ_�ª—2ÆÉÌÓË/-�ñ�˜ù  û'λ xœ•‘Í Ã „ÏÍSHîIî…>Ièa�K”¨°yûiS©„ROâÌì7¸£ T­,H$íquƒ”‚ÃöÞ�×1à9¹‘Ús8‚•ÉÀ½7î: ³ð<Ð~ZÕ`øèо}ÒŽµ §}²¡?³ �éÕbfŒEM|€2{�Ù/�„ mç8JY—=sþ®UŒß©\»,ÂРf¨'�®†—ꥂ%!ËxE.¶ ´à ~©÷³+Íå”HQóuóÙ–OüG­|°ðxœ­Xks«Èý®_1ÅMm¤ŠA¶o²µqÖ·‚–Ù«WZÙ•›°#ɬ(<,+‰ÿ{N !;ù�O²gº{zúqú Ÿ˜ovI°zÌXÛë°ËóËïÙä1ƒÍ†}å" k?Éß?¯òÅBhÞc§õ©¦�ϬÇ«P°ÁÀh}Âî ðD” Ÿå‘Ù£`ú†{ø)wÎØÏ"Iƒ8b—Ú9k“€Rn)�?ÁÂ.ÎÙšïXg,OL)[8C¼xb“± b^¼Þ„�<Á¶Aö(�)�h0ñPšˆ‡4‡üÿ-ërŒgÒáÇ,Û¤WÝîv»Õ¸ôT‹“U7,¤ÒîÀ2ÌÑÔTá­”ŸE¡HS–ˆäA‚{.vŒoàŒÇp1ä['Œ¯�½,&g·I�Ñꌥñ2ÛòDÀФY,òì(R•k¸p]±âSô)³¦ »Ñ§Öô 6æ–s7ž9l®Û¶>r,sÊÆ63Æ£žåXãþ»eúè�}µF½3&'#^6 ù'Š¡ð)`S!ŽXÆ…CéFxÁ2ðp¯h•ó•`«øY$®Ã6"Y)e2…{>¬„Á:Èx&WÞ\J“Ñë‰%ÏÃŒñd•¯E”¥WXôÖüI0µg õ¯¦k�¦Ž>¸Û¼µî¯&ºswý›Ý�‡ækW c�‡õâh¬ÜÞøþ¡oŽ\ÃÆ• }p=6ç¦m�íbÉš?ÌÜá¸g^ßêÖ€©7l‘!9m×ÝîðB9vCùÿù¡JŠúJ‚¯tEæ)ZK:它(Xçk·*ºöϦ=EŠÙgíâ÷�Ö&‰^Ön1¶ I!Á_•ˆr®ý ](Xé™Sø7qäêœïp¤Ï†$ÎT¦³j…z+NƒŒ*$J7U‘σÈ�·…nà!\;ÀhâP{pvì“„°Žö-R £“Pptš,:^ÚMóÅÞØ™lBñÂÑ¥âj¯VViY7ûå¶ç—6¾ûNÚìà^"òqñN+Y{> ÚnYîPÿ `Q‹æÑE¥Q˽£EïiÝ΃%ÿÄÊEö\N°÷tÅæ�"’ÀÇPR@Û²$–­ Ù`¹ºðLBLiÖ*+@P”Õ8QC€�Ä"€(I“ÁRN;®Œ¢Î¦ã™m˜E]i–"^„0vÑkŠ¥¨c<ê£ÞAµ\x4˜¸ ’Ùh¶Hùá‹ÔÌ#£*·|—â�Œ¯èÊÙN ä'3„M·-ýf`’¢[^ ¾?uÐÝ®cX¬Qß�ßYŽ9�è†)…ls:œAçŒézÃa�™™évÞ‘Š4‡¢UÛ6ûæ=êŽ�&üûsû¯çêÿö»Î·oZõ§Âü|½Þ¹Ï\ÞºæeÑdò”ÑØaE<¤)Dl6rÊ/ ©C;Ö:‘)#´gÏ?Êý¢ý(KîË>ãßNŽü¦”‡V]P:ð~ɳ�9­•AáßÖK��öné7j_V7çN}”ÀéyÚ© öåš)z ý×>èÊÓ8|ˆ'×ÿ@£BƒÆ+„oj£r®ú=P…”XT7O“®º f ‰Œ˜MC‰ähãÈ s_´û£™Ulô‚$•Àß4Ïz¥¨9…LK>±[b=8”–%£ÛY1Èó¤ä6þï{ýrM\�š'ZëøæÕ¹ e·VŸº¿˜íݵŸŒ¸w˜ ™›ÿ¦[»Ú›>Li[‚Õ‰É2”Uh G¤èõ%æ8&Ÿ÷DÅ8yZÅ(–möNÁ"ˆ69ô6O+Wb³»Žý¸mè–=Þ+2k8ÛŽÙsÝî›&f�Ä_®/´‹ïµó&ýû¯7„—`#ïÚxyZ€J¬ãˆìü¡ÙÌÀºA.ÀŠM л¦P7ˆ�ºÀв’½KYf Ì‹�ÞQœPÅ-Ž,áž|]8˜!ûcP.4lø3Bùˆ£pÇ–I¼. ågíò­* %<Ù‘Ç7ºñHm˜loZk}«NL�lgx¹�A¬YÝ£+H]ëµ^M§êuEùÀZ’= ä. %ñ£g½¨ÀÇ4å¨�zh`nk¼ ŽcÈWqôE"’<Ò4­oH²]1dófÖgŠU#Zf‘¯VÄ“‚M¶–V�´£Î‰Þ|uÈ”1×Á?‹„Üíí©à)Å}òɧ–ȱ ?~Ù­@,ð #’Öli^‚óPR³C`’½Z�"Iâ$ÕJÔ¸{vOKÌÐ�;“�ÓøÈˆýlwù/ žÑ nD/2zh‰"¢�V¶kf¦òÓvJËÈ*l(8FðC6™1's|‡Ä×L fÙ7 &é>½<´PDb&Ö€ð§WÌ{ÐuÅ ±^%VrßwK«niµ­Î)Vê\¼ ¼JNqÑb,4«ÊXÖA]]ÖÎÚ«•¿ÀÛj/ÖdCÖÑ6Æ’|ìç=©óº}94d0dë9™c|‘Æ!ˆ¢rv!Ðg lÖ+žØ®{‹àº.Xº—Ð÷Ù)íI{ËQ”“@d)ÎF[¥4–Ùc\ `�ƺKå°|“hïäHQ—òXu“ˆeð¢®ùæú@j õšÊPúc£X©VÙ–ùç´"ºµ’?ŒTY‰úѸ]ThåbØ$ñ*áë6ãD[BéH¢ôÊ©[°}uç*}’fVÉíÞ?­¤OÔ¤Å)ToL¹¨¸Ñ!ïo…Λ‰ä‰·o´)E.:¬ ~�Á�è¾Öè�z/寧òôëJ–þyeûM´X„’ïVäÊɳ—@j7À*—ž�Í9î’d%—jÁzÓpÑÓ R�á9h¿½L§Â¸ bÒBRó}j*bN!'e„gÅéxÒW¦Œ¾‚=ó0òùçÇÑo3 4k ´vÜÈ Ä»Œ6¦Œ¥i‰Š{w®Þx­±ažf oµ7˜ÝHa©*HEôF=Ý‹N+ Ò¬­O&&^Ž%s÷fƒ·8ê¹’dçPW×?´‘¾´a³a�–Å^6id�ABÄ3§û‘\úÈÑ´ž4�˜�t6žÇaò{rv龟Òç�’ä±�§Ù™Ä9YÅW¢ô‰ L÷wªxA`i­äðZƒ�{‹�ÖlcN ³™xœ}WÁ’ã¶½ã+Pµ'[M¹Ê'ç’Ézâl*ëÝÚ—) ²)!C J+Ÿòù½|‰ßk€”äõæ05 4º_¿~Ýxeß„Vlèðlç&óê•}?Gû¡—v/Ƽm>ˆõc–()si~øqoÝhÃ$#þ·ö$}¾•ñècó¯­K¦ cŽ~7ç“®,âOð{Ò£lxûB“‹Ù7~rÙ‡GÛ‡¦þ%MænÃß8l˜GŸÏÖÙƒ‹.%x×E+Ÿ&8(c#p6Z9J<‡Q66ÊÞŶ—”ˆÛãÕ.´g“ü/xl}r;ßÃæÆJ>Œð��{[‰Ö·°¯çÁœ8üÛØö{Ø3—c±hªÿ›BãÃ�àÓØ”]žÓÆŽúÙ•ã°1ñ‡uÓ$ˆ†6Lt�úÜû½B(I>ÍXuãKà™Y­m×ü=e|B¬É˜ÇOnÆòNîèa(\¶kZðð7Q`…yµSH>û£\'Óø±éçV¾5æµµöÇÄ¥—´Ó]‘¸¯wã~Àºô/ÂlB»™`ì®+,:z9M|H ´Ť›¿' ØÔ#Ú¦‘I=„ç)G–g5Ñ“/iÐõ ͬ®�;'FéÂu; [.ÌÑåO‡pRÖàÛá N„Í,½fÙ ÃIº…s‹Kn×ËÛÝùBaFu‹Û3\˜“]É&¸×®x1É~ÀC,É�Ç‚°,©w93÷.DsÖµG·‚õCß#œ MsO¬îAÞ°w(½³†ƒÝ`m¯¤Ã™Sè b9À5/Å܇y׃²üýÑe¹*µË‚tP¸ Xúj]éG>,507™u8'=‡…Ùƒ`—oj$-‹ ¢áó!Ì™TèY„ôtðZnzêûš•,$ÙÃxæ¾ÇCÚ�,;Q– T¢�™¨¬à\ñÍ:=¯ºRÇð(I&`k }aiA5Áaó¡ŠÐµ|¹(Jm]ÙÅiz}w&,d\ZŠQUgeŒYÃlйßd¸‹jÌîEìµÓ\Ô9ÏÀc”B}×T�4ÕÕQ7ž™ÿ¬¼ø"O·¿>Šzýþ z{ OÅ‘'DÂQeÎg¦(4d.ã“/­0¥/^Wã 29KåàRfUˆL*Ú8Ìðê7VD(åß4,=ßvèE5"�GöIY�Æ�ð†Ê1ž¯›�&©½ Q�Å»³mE†[Ælð�ê(#‹Ëh×H�ÓãQªˆïSƒ–hÌóïøÊlöàzN>(ÉÁÃ¥¯¥É1YEEíT ¯¼4§ƒ6Y|hýÑ·Ú g¢Tš-¦(!��U»¶öJ³ÌÍ6w½é"vU±lR¶ù®C!âÜe¹Ü�3½YK #1w7ëÙüðo@ê©æ±äŒÌ¥m†L`ؤ½xç ¿³Z¡i*�°«�h¹cÞ¢B/«©ˆÝU@ƒ£˜nŽšçV:OàRž¿(ÕŸ×ÀöKùëSX“�¢¸Ü _“¨ùâûZ_æ) » ˜B \Eе[]eüu‚¿J—~ÂH´*�1À(0íŠYÿæ•’p¨á3-ôZdÆü$¶ –¦>hÍÓ‰¾g5–õ윮ý“=ˆëÙ[Ù-BÄYi�YK;AèPöì ¼NH PÚÚ¿…'0Ô½ZùƧB•†©ëÑ ¼–qÏfˆJ¸�XðÔ;W.Qÿï?ÿMª2LUm ümgÏa†¨CÑ?1ÿÕÝK¹üfËF—« � iÄùª:•â†[VgZO†°°Ša-$:{ ýQÚ­}§¬*gLj†~´ÿž}óÒŸ�Ž:.A«Šª¸íb (V;„(E`!ht´Óq �5â£|&¸­­3‚¹°K…ZÏÕ­g‡¢}t°ÐP‹Ñ«±K?òéZïªÆ¡ïëd� ©Dý�D<®EucÚ-x!+ 0û("¿YX{3u‚ÉypˆBr� wõˆdÓA»º’$®�ñõ??||ÿ÷Ç7Ïöéùñ§‡�ßÙÞ=þ!ýÑ>üð�}|÷ðöøñóë�µô¸Zƒ\F.F¸ùç×À/—¸×²]¶(ÁÚY;ªU&¶ìB�Šœ«c+ùùŒ—ÿÈ”>�æâK!‘«-3K\Š bï­&µ5þž—¯'pµÑ0÷Ñ É¾§ôJ唨ïCØ÷7KVD²öGÜ ¼¹¥id �ྕ¹›2éa4ÓvüR^æjA´;ðr˜ôÌ?ïÕ�-ìmU{T s¿§¨è%�L=…3œB>Sý(S+ð• ËT¾,öt¬žY� w™åöØJæø¨L®�JNŒ¡Ã¸Œ§y¢’.iäEGoXEu¸¤¸”A‘{º“Ë]µ`äý2npŠ›{ΔºÚ\ ´ŽÝåùN½NKÛÐrÒ8`Ó£Œ–‰¨ÚJ'Sk¯š9Öþ3”›8?•þ wˆÁ nz |G)a‚—y1éx‰û"o#ëm²r¶â‹3(¾ ´l/ĤÈoºÝ`Ò¬­±]ÈQ}Ã0н¶8^ãê¼dÑ(æR-— B÷�Úq¹g耧b”êl )G{¤Tðê|MO—KxËý�86RšàC^'Ì/tw¼r­›n}s53¾��ŽÔU5½_o¿ÙwZ .›CÎSúöþþt:m¯æÍ»¦î݆¸¿¯Û￾ÿæžúpº»ª÷æWŠ¡\³µˆR›?³\Íì`FðW²«XvĶ÷Òµò—á �£�\ÂkDž–ì÷í 7WtI¡ßo%çwQÏQükž(U×u£yP×* Åwï×Fëôw~9éx²8CÆT‚IÞÆ3<%ÛbŽåŒoV:rŽf†�'¢Þá7†ã„^„nð¯ׄ$Ï °ìö^v¥M&.‰•êfurG#f/&+jñË©T¾ËšqjFËœ—l'Ëd\ZÎÿ¦œ3]m�86¹nXX´JxC•eŸSSéŒAŽ7Î[2\J°CjJ r¯å„�CÞë†R,2«XY[%÷é랥0c©~ /T=½ÅxœÝZ[sÛÆ~÷¯Ør¦Si¦�4i牱䆭CiD¹n&“‡%° ·°È. Šýõ=—½�¤dwúVM¦5IìÙ³çò�ïœÅ ñ¹¿E/Ë�ïu©:§^<óä?”uÚtâëùëBüMv£´ñõë×ß<¹h7 ý›W¯öûý\Ò6sc·¯Þʽz� ï¯ï~Z‹ÅêJ¼½Y]-ï—7«µxws'>¬¯ qw}{wsõá-~]ÐSWËõýÝò‡ø øj.®T­;=€rnþÂk3ó'š ·“M#Z%;1ÀIe['dW‰Òt¯µ±btªVõÖTc‰_^>[i7X½ñ{!�¨pKU‰ÍA¬UÉB¾ùÖŒÛ�øN˜>hxΔc«ºáX/cO+M°z»„ÙwÊ P êá ä8ìŒÕÿ¦ý¼œs+†�lºµv[zÈÛ!S@me#®Iô‰c‡$í•�%I Z€àY/ÆÀ^A­o ¬i !­ RºÀÓà·cWÁ²Ò´­é¼$ÿ ØëaÇrxùxg,éÑ�¶71ɪÑáÁG3/eFGqâB_òR³W¶÷Yð*¡;þw!#J NÇç¼þ‰,`E+;¹Uè<Ü×�åÎ+VˆýNÑñÁû´¯$Ù¹eö£ ¤\hЄÜãvºGIµ®Áš½²%оøöõï/i;æaÃAãà°:úÜd• AäFu`„Rƒ+'Ò3=“Ë6ãL\ÀZü—�]æ^‡ÿÐ&ºQ–y|xê´Õ½[í<Å'¹å$ÔÖ°[ )éÕGZoU­¬…åôkMÿ„[´¦Òp4IY¬»²É�„¢3ƒht«qwð£3õ°Çðr´!8¥ë‡Ü#A^ ?P„ü¯õv´ô;¸¥Q|Ülþ¡pªºìü¸cl(?jkZø±ÜÉ´ QÑ9|R†€¢oÿ±R°yH\1= —qtLH›^cBRÎs ‘g€¯'ÎÑ NúÀèíPçn«*-Åpèóc4öÓ (ìáKÒ˜p#-¥€îÂ1b°éü±ZY�”dÄ…€n`x8Â[ ÖdV9 X[ÈBA[/â eÛÃΰ œâ“‹¾W°ó#$Scö—É WÊê°âƒh7;ŽÜã¼ üé½$¶AP|#:¯£T¬pŒ~ˆÆ*ÜŠÜ…¹°ßér—�8k€™iÕƒ&Wbƒi|ž66|ÞÍy6yaX唃H!ëKØÌ4”°Lou»œúü�NÕ“ô/ıù¼õ0š½ïH¼¯VµRÇüT½´)h:F«¬j�Ý'2Ü¢㤓­º N×D¶–%‰"«‘Ѩ'J¡u”©“×ß"”ûÖãÇ9S6Û/Ð'\¨¥Q6ñ Åpå™H�dØ6´ ~Jù"KŠQßÀÖM€m7n;h#ÂñZ¼LåîÙj‘DeÚã}£À˜5˜âiòòeÕ^Ìâ™f^×û˰H5�€Öè…�l(Žö×uD>ÆÎ[_`äFWÉPh§Á¥d!û»âÙR±+ßþK:"ê7@)AZV²"r7¨Öå5wTXBJª‘þ v?V>f+‘kåF/2™DAfm´pÜrtTåiÇ–ðÒÓÈ�„x©4©Ç`„éYC<ÂQ\¯ËÑŒ’·•öBŸMì(P.åô¶#ì‡PD‘aÏF"‚Õlö–"ÏÕùì4…�øu »Ê¾o°Ý48�¬ŒØåU+©ÁÞülv8°" É­q³ƒìuNZMÙY[@ŸÐÑ(j_žøîÚ`Ó)_þ€‘DVOËŽ„q‡ë«-¨Ï$oªœßb�®µn.–5ú?öB� c::eÐ[VAn%þL ç÷‹T°"·¶Æ¹—d0”õ˜)èÔY³"(Iœófb<¨2ðAèÅ—Ø5sÞÂgíJÁ2%¶Sù”Þ/IÙ Ä6}Ô”AÇdëd¡IT¹guø>óI�åy=@ ]'œÁ’¹MðT¾±Å©•eàzÙ”Ë÷g¬Te èÙY ÐV/ñ�‡è›çsÐ0#±PšÐûwaˆ_§fÎüMä�[é8äƒ"5¯ÈP¦êøÜ"Ä:Lfó±lȪÂ[ìwòˆÌ¤Õ½…¾$ ¶¾Gäg¢~ ÇU¥ºjlm�DLîÿ‚;�1� †`†³ÉDÓ*虘Øñ8þØ0OÝ[œ5Qê*ˆ¶Ò°ž ÀÑà+s ñçÈUÆ‘œFÖ:a¹g|í�¹2b1Ù]‘©ÏhS¤´©©Y<<ÑŠäÓ¹˜J$·Î¦yI�“ÛªIެgÉD¥1Ž&c™Ø©u‡|KÍŽ¿ à^5±@7:¨¢Žœ¦a£RcûK³ ’8ß8³Èl˜•�±ž]%¦�;r˜êmòéóÓšyšEjfÃ"˜ºVáö‘ׯ̀‹âí Õ—�ᦠÓvKí–RÍ�Pœª_ad.ñ1»à)X1¶D[èé(ð>C¨#S�ªÌ ž€7Ī­´|¯tÜ{ø»€?â3]BÎ�)wv#„†÷jL_Â5†lqn N½”}À™¾ÿ:ùæ‡CÐ�C¤¤6ÕªßFío�° ;ð –tr)~Óâõ4jVÞQ½+bÓ�“Ú“ùlȦà7_ Δ¶ÔŸçâJ;j�ðÒ¶�‚]1 ¢ª›7°Ôyc‹•`€¼HÍKš‚Éa>÷]RõuÅ¡Áq‹š?�ãˉs/q®�?[¬År=?,ÖËu0îÇåý�7îÅÇÅÝÝbu¿¼^‹›»üZþæ�X¬~_®®€îh¾~Äé¨K'Ñ„+U6&MDsRpêM.™Š"{ ±`Ìûåýû묾z¹\½»[®þzýÓõê¾?]ß½ý´\ü°|¿¼ÿ™BèÝò~u½æ×^ÆíâöáýâNÜ~¸»½Y_sµåÛÂo@ÿ6Õtë@73ÜNÃî�ºOÓõø²ðÙë¶ÀBË�1<¥Içä²�f®ÀÛjExPGÊ®T|ˆžÇ ýwªíðÕ’4c³6Awa6�ŸBoy…°ƒÌ—¯Zà<˜/¾¿ÒAcƒñ£Ùc'Ä­d4Ù3œÎGo´tMv9·¿¡!®ÿ�4Á(éKL'Ý¢$DO“¢, üL{&]3>cÂs¾“mêh›JÕЮð `ÆÕ™Ñ¹´-!Q ×ÑŠ)�GkÓm™Ÿ&CWŽÍ*Q‹Ó¹ñæàÉF:Ð-�lÉü>‹ÆŒ6F]8€¯WWXWϽG¿/noá‘å?ß  iZˆzð¯/ä¯îáo¤Ê>Þ%Áßý.(ükÓiB Õ²ÆB>„©F‘:ùZ«¦r $;ƒþo)Dæì—_gøh2á«Ý!¡ªïú²Nz..®L÷‡ø¾@–£Aøï.uëÔ¦:  @ñ£¾;ÈÊvv7‹¹â€ç�ñ"”šzVp6/¨øi?' (NÏrÜ@”!cå¶‹hfŠq¸ZݨôÊ Ý�M.œ�r4¸F ža­˜Þ|ú—_PM<ïã½å½kϤ!‡´åo¬9Òeâ/øûUüBzƒžG·¬¿Òã>Hª¬gš†O‘¿*.ð�øÎåå÷("ô#\¾üø<ÐxÝù6” 1FT¤8"uýfCÓ29Ù…@–C÷Ͻrú¸ûj}ýT¦%_ÂПâþ�3“�ÔNßpÂKƒü�§øÿH¿ñ&³­•š¨‚œh Ä ­ÛŽp@  ,tÇoöùiIâëîô\óÿM¨ìf¹!xœm�ÑJ1EßóƒO-´»µRŸÄµŠXŠP`²;mB³É’ÌvÝ¿wºÅZŠ!„̽÷Üé\�{ŠªMíÎ0ŒÊ1Ìgó{ø4ÖÙ¦�¤DFûá~ÚµZSVš±R_† < ¹ to!ìÁjU€¦mˆè+h=[7Og‹éít˜äØUÇÅ}ÚRŒòæp.¶gé";&ÛM U[2X_º¶¢)l¹ÃxEWDjd˜›ô˜ç;˦ÕYê¼1§j¹³Zc¢ñu€n}åÄŸ¥¯õÖ€ FŒý:cK2Š´µ¤È¬#ܬ©ƒçÍË�ÝM ‡­‹°$Ÿ(x *bQ¥ $¢Á<´õè~ç†ÍýmXY/û¬‘mð©Õµ Z~ÄÊWïÅr½YfüÍñdõO]MÞ„žOí‚ÎkL,õ/ä™úœl¸7¹ìxœ•WïoÛ8ýî¿‚I€�Xr²H±Möz‡4IwƒkÓ"?n AMKc‰gJH*®ÿû{CJ¶ÓÛ=\>$±%ÎpæÍ›™—ñ7UÂÙìݾ+¥¥ÉJWrI6uÏžp~­éÝ~Iª(ý¹8IO¨úU¬TîËþÛ¾�Zõ»}ošý¿‹?äZË:ÿÄ>D":oƒÁ…˜v¯ž†¥÷�;ŸLVñAº°D9¹%<¤Æ“‘ÈLÕ§¼±BÕ®Q–r1_¦¨:7+¼ïøY­ÒUxC>ÒÁàŸ´ ’¾µä΃#q¹u»ÀO´q¸‰Êl©êBT&§GïÛ¦1Ö;QµÚ«F“X»t�ÌÈñû‹¦!ie�Ñn„b7À1.Ñڬح/Ihc–ù"&ÒÂ,Ä-}}¸¸þÂþ®¥[ oDëh,4ý  ã üªªT¶tÑ\:Ï&W&‹þ2­š1ÏIÐwOuŽ`8Á'ä±Ë¦AÔƒßáJùˆ ÛÖ5džôe‡®ï£Íi!‘:®èíMï3Ô©v¥ñ|WÙ?óò4ÄÅ=‘’ÎQâ6æiS£Áààà@\¶ÖRíß:ÔèèZZ½2Îîèè¼'T„Äx&ëiU{i×»TY´uæ•©AH¿UE`š’ÊÔá½i}ÓúT|Ñ$ K\a±*¥?D�• ¹ãÜÜš%Õ{ƒÁ=‘˜–d)fôáúâáñîú>­òQÀW%/•ÜZ¹‹ªšžáDƒØkÊ= Çü�á�Z#óJ6ÑóÝç‹«O_6Ž»˜:ëðˆ�ÚÙ"ȃ€DCMÁ’ÀŽe Ý 1œ§oÓ“m³Ê—í<…£IS.%9²}©&–2nâe1y†£Q蜣šVGâ}[,Ôwrðzðæøìÿs \[¸ÄùÑX°ÝÛWÚ½íìN_iw:m™Û#+e]�x`FbÔ­gïX€—«šIÌ8[Ž£ˆ-·Y×Ò¨E Û·&€Îpë\zOv=ñìGhÙ`–‰îEèÑÆ¬@ßHsöpS5\ÎŃi³²‘¹p1ÐsL©z¡ŠÖJæ²0 ÿqØ‹åB'èwt>a2…#ìî_Ò*Ó:1o ±)ÑééëJ„óêÓ_Þ¼Îî—7lwzöJ³³7£ÿ¾U:w¾Ó•fô�8NOÞŽù÷YÈü8ýùø…w-çÿµ8:ÛÍß„‰ìF;ó<ôÈf¤Ç�l³y˜•¾´¦-J�ÒÚ$žH*YË"T?ikç’ç“™@ ½ÉŒC*Òh���ÛÙ_rå@šµ›…¦w=©¶iôȤZÊl[§¿ –¾Ò؈²æñ^PM Ïk*ñõê·0ÆÂü[sÍÏŽ_Uƒ³cT®‹½òÆè¨Ùªâ«qÙl‰×]_ËgU„ë±ë–´žióoîÂÉgfyÄz¼…[)Á\ቔhôN�ýÎT(U]ª¹ú lC»üì2ckþ�'Yß$a½e]�l––‡|Ï!gsع?üÝž)°cßÇÙ„)î-šHC4ˆéœ7E¶Û‘OCòÙ$Ú§Áb41 ZéiUT˜1nrãé�ñÔß�€½0'>q‘ㆄ]ñÞvâ§É�ßc|Pw–“ß0kÖ yIZ÷À‡±d“ð& o^ ‹8­Æ’ÝÝU?!m‡?h›Jú¬Ä4ÙÕ5çâŠP ÙcŽ`ÇAƒüØgýêf–Ƈ•q^Ç¥­ ¹ðJé1 K/(¤vƒ•­ä6%ö÷õä„• ¦_¼[ óñ‘7ßäÊÔBeAÆA-Yê÷:œ ñˆZÎ’PXÿí{'?AD[´ Ó†êÀˆ¯]ìcè%,b³X0ã;zílÄ0cqÅ}•1½¸óûýbØy”5maeSâYš¦,RY]š¨¹XM¸DåÁmü?‡ƒ¶3ë:ÞoM©g¡ œlÛô{d§F¨z_¡�^ …‰ N@�€=¾ãÏgZ›Ñy¯ ¢¾�zf£„æÁ¢ØÛ6öeÔ8ïo>^±ÂA!¢xßšd¾Nø¯(ZÅÊ7}fâ1CØ63Q¼¿ý‰ã»ÇÛ œ°,˶:.y©wU® ›¸Î¥65Å÷>ù¶F¶¢ÆŠq!® �jÖ Þ3éõ8`#�%œ±èÓ%– ‚Þ›~ _™˜qéoÇ2Ó]›bVÖ\æM05=X­©u˜ç»ÿ†�þÂEû7e~3Ò{Q¤58oÕ¼õ¬Ûo¡gÑŒMH{¾Ó~Ü-%«Éu߉{<Lg—ŸoînÞ?>ÜÜþ¨gOÞDÕÚÑ7:˜fhAVÄ€5G½ØæêúÛçß`{õxù°1«Œ¥À;ñQeø�ðß"ÀDÉN� õŸÎ>Þ\^ßÞ_ãêîÓ‹+ÓÁ å'‚½%xœeRÝKÃ0ï_qàƒPfû>†0æ�‰³£S_Dhl¯m\“”Ë­ÛŸoÒ�úîr¿¯ä 6Ú2s–F[( aƒÂJ]Á©Qâ€!¼´…`„·ÕÖU¥eñ™ßç@{d ñÙë:Ýo’§ ¤®²]š<¬WÏ’бt]â&�Ódy·]î"UŒxCÓödD¡D{mÁb/XrƒÀÆQ6RüÉ_ôôƒt×Õ/–tíÖ ¾O’k¨eU7nñ�‚‡Ì�D¨:$ëîEýlŠÊt8˜ i˜AKXÊ3”dT_GA�Dšæœöð^W¢¡fní<Ž+Ç}üˆr£â¶>´Hñ˜{<Ú±3ïH”SÈc9ì嬽•¡3zÿ3ÿΈ Y·PâÓÐm´PR»=»L¡Oy nÊößÜÀ¢@›“l}f"Ø¡.Ü7™Ã=j$5Áhãž/ ¾øKÌ´¿³xœ}VÛrÛ6}÷WìÄ�©­‘䯽<ôM–äTSÇNu©›'"Wj`P¶ÒÉ¿w)%iú‡Âe÷ìÁÙœÃø­xBXøƒBxÓÈ•ÔèÎΖ;é 4ES¡ö`ÆÁï\X\½§)i´ƒÆa R‡Ù�f7rÛXÁ `#íÝK+(lmÍßXøáÙÙù9¼ÂØT•Ð%ð¿ÛFaÏX8©·g=èõîÌ3ÚB8Vº_{=)ëF*? ÌmÖ8"mR$úež­ô5£T]¸ n‡}È‹Švg•Ô²jªÌâ‡FZ,sšqèù?Q–¾`Ñx±VÈ#^Ø-úŒXy¢?k+¬DÇR;/”Ê/cy×C˜é’ˆ Lô©Ví­QpK(úç �VÔu[ïÉ.ôzàjQ Aï†A&Ò™Ó®jJMÈ܇keŠ'J5¹Üäp9ê’¾':ÙÆÊ0éLj-Aa×Åe?))Šb×ý®DaÍq6ñN—P5΃6,Ö(|Pá+e¨‚À�AbÌ&Ìšõ †U{„Á•z%ïIƒQpÙdz³zsÙÖ7JÁ;z©È3€,vÆú>8YÕêT6$”'¬Iï04ËÂïˆ =\ŒßŽ~Ÿfãl±ÝOFó ¼~2rà;sJ_¼!äÏÒïˆ å%çëj¾zÂó±e+Q‰e?®f†»•P+R@qi|Ž ˜ž( šZà‚P‘­H&{oãŸqùm9K]¨¦Ä¬$ýÞM§Ê–'é£&¸š;’aÎÇá 3ÚœÁ)D•øÑ:” 9tE=,e>ûg�Ö)¹Õ Pp ]s¨‹Æ5Ô[îâ îAëE‰ð$õÔ‚œ€O,KÕ‘ïÔh}*Š»óÇ!Ü‹ŠKí,yN!T`¸¶r/<žZœé æóx´“†-Ú { nEqhŽ€•Gí¼[ÝÜÍÆÙoÓÑd:ÏngwÓS¿xXÍÇñsòð×û7ÓûlvŸ_öÉ܈¢ÍF’�lÉ›…& ƒ¡j‘«mÂaôz�¡0*¹×sšý,�´éëf­d‘í¨oÐfÁ’ƒÃ™ÆÌ‹¥”ÎSÌFºÀ~kªh®ß»£ÛvTÄ6{¨oWc’ì<Ý[ ÏúŒ`“LÄÑ„¯*ù‚å X[ÜÈ—£-Ÿôt~üý8�Ïæ˜jÝ4t O Ý9{|¿ÊÞ>L¦íAÿ4„?ã[‡ý3a|©¹&÷µð»{bžý@[06gç¶L\\؇-j¤+ŽF)ŽEÇ�\tuç-%sÁŒ… ޶ÿ:i‹÷Õwÿ$WYÍçÓûev3»Íßg“ÙüÓ•ÐB>¢¯ò´v5_L$“ñÝj2åU‹O¯ZÓ›±!È�DïÁÖc¸®•%ÅJbW„Î"ÐÝ%Öïz®Ý+¢J¨­¨m•ôTµ:Þ´uMì1/GËÙ8ïÔÎ_·£åè.K‡�ãç!Ü2�¿1†ðã j»/ŽÈÓ=ÚCºß÷kÜRƒG‡ “&H¥°¥üHU�ê Àëá²@í0!¯Vnw¢øÛË/&ä$‹òóŠ4Î=šX¶Ó÷ŸÂ=Ó ˆ5ÈÆÅ·|0°Ï6G}°aurl¤uÁú QK²)ÞgÃØË’Uâøj‚]aeì«HÙ/CX†ÜÖâøô… zG݃í±ÖaÕgï° lê°³f2MLÑïJž—i0]prD-ð³ç¨vqWcA¢+Rú֜ڋ¥—ý¨øŸ;ç8›pe èÌœ þöÃìˆv¿*CO†ãÛ*³êªÍ}<ó Šî º÷ôÂü Iù_{Äèr¼¬xœí=ýsÚÈ’¿ç¯˜õ«$†‡qœ«Úºs6©em|¡bCð:¹W¯!  !é$a›Ýõÿ~Ýó!�¤‘×ÕQÙ5H3ý5=====3ÇMò~WÒ<~qÜl¾ MòëÔq)¹¿s�hNM‹¶-xÌÞX~° �Ù<Æ_gò9´äí›·?“ÏsÇu‚€|2iDCrø�ýýu¶œLʼ‘­Uþ�ü§ïÏÝåå™@réXÔ‹¨M–ž 0â9%�À´à�xÓ"¿Ó0r|�¼m¿!‡Xà@¼:h¼C+IæŠx~L–NDWôÞ¢AL�Xþ"pÓ³(¹sâ9Ã# ´ÆWßÄ&7 òNü©Z�˜± zÇAtz||ww×6µm?œ»¼\t|Ù;ëöGÝ# XÔ¸ö\E$¤ÿ³tBàv²"fYæètÍ;â‡Äœ…ÞÅ>R|:±ãÍZ$ò§ñ�Rc;Q:“eœ˜¤ØV €ÈL�tF¤7: ¿uF½Q �ÜôÆ×crÓ;ýq¯;"ƒ!9ôÏ{ãÞ ¿.H§ÿ•|êõÏ[„‚¸½BäÈtP”ÔfrQš!aês’¢€ZÎÔ±€5o¶4g”Ìü[zÀ h¸p"lÒ´Œë,œØŒÙ£_ˆèøÅ‹¿9SbÓ©ãQûðæòªó©;4>v~ï_n:_/;ýó–ñ,wiƒŠ¨ =?PÞüâxq¼ hÔžPžºÎdbFôXü;Œb{âûná!Î>»3WÀ°}ݸ=²ü�Cât“›Ë¡q=ê×ýѸóÛeW­é†ÇŒ¬cøf ºú‘ûa>”ºåÝ¡øâ^`O¾°"(Îi³"*Ç –ñ±µ #DwPx³0=hÀÜ+ÎaöY 2úîÄÇâ/“û1Z-rN-× E?Êj¡Ù"#Ð*{ç—Î-tlÏöïˆlu, �aiÅĸsæwŠV1ùó�üè»vD~ ARÚ¢§§òk‡¨´ÄÛgK\C†À vüÝ@ ø™>{÷"Áxæ‡ÐŸß³±K@óù~¥¼¨L`Áê†l]Iiêž"¡ ºßLëû‘ëxßѺˆ¶ShfóÇ*ÝMñˆÁÒ‚Ê4@ \”dVM|”%ð õ ãK òͦÀ´¿úF"gæ™.6÷7§ßŠB2\ M|¬äù;=ÞohŸ¡(ô?oêÌ–!Ý öT…Ž ˜Qä[húNˆH ­¾³Sä)¸z¬G46b'vwƒ=�V‹u,ÀxëÅ;CÎÁÕÆnS8¸±Ü ÌÚt̨¿ q¸›.¨¬EÁ vƒXD€W ïÒÛ5f±n÷Á’È7@Á+®í\r„ƒ1Š�–½ÍŒp„K¬”ëGš�§€ZÈT[Çd~TÓŠ�[°v}ü™j�Á/‰œ?¶`kíût麑óosÒº» daÞƒWþÇ&mQ¨ªSÈ1(ºðS7gà¹0#º5—Mˆ|œ¨½N†,ûuÎÁJ}¸ôÓÔ{>*:Nf‹M͈Ï\¤eû4bSȹy S=Âmv¯à3ƒV<ËøHƒ°Å€s )›=›1ÃÞ"iMœºõ¯//µ¾S–Ù¦Lʹýrs)1¾ŽøÚä &·ŠVþ€ Pé.fP™LŸ³æfH´Ÿ&FÎ@á¤Í"·¾c“ ­s0•PPø]‡eêÓ”ßd‹•b°š¶›ø føëp¼«GaË¢Ó5sî©KØ ¹ŽÈW1iʯ:ÐN}ß«¼R_l‡‚ÒàI¼®½r“ºW{G£¸P{Ç%=¥=7Qêí—#ÕzLÜÚ³%8uŽÐî˜\�8ã=%bÕ÷ùxSçG`Oœœ=õQŒ¯ŠFGï4ÕŸkºŠiÿk ô›“Èw—0#üjÐ X΄ã!ÂÜï•dHDü€ÅǯW$¿ß�0^Ât ŒâÜ·I̽DÄùu¤ú´É,‚€LàDq®ÀíÇéõÃ<”#ïE¼°-Æ|xPÛ?`ÎL÷>ðC ÏsV¶ ‡ Á�£]}`Iûës—1§ù›Ä&[š¡ fß%aI%Ùxñç�ZÊõg3˜ŽX¦ëú'?'-‚ÆËŸj!5Ü¥w¦äÝsl¿|G#!�—¡ÇÜw^:WâèC™ €®òÐ-gOÿöèƒÍºWU ~!VQ¾¼\*_(ª†~!ü¤Ä·ØW 6µAi4¢Ç”µ”tž‹¶KÖ*ÈöAãhÓ{0²ö¡ŠJÒ(Š”�Ùh%u^Õ² ! Ža;.Î:™oUñPŒ›WÓž”ß‹øÓÉ[¥ºÃík4ÆQ¨ÞÙÉ\°Šêb˜¾šè¤ü^$�N,×Ñœ‹î¯'šWØՊ˸ŽtݪÀzú•Z{cBN€×qPXRXO¾¬"U}¼û³ÃßFF¯1h‘ƒ3SiäeÀ§Z'çep�b%…�µU:ŽK Ä0WÍvïã±é@Õ`\s~!°‚ìÎ…sœ•ÝA«à7 Ö×D¨êÙ6òFc¶ `^5à´:TÔêúÀt]ê1ðrÖå1 ²Æu[HšØTqlÝR¹§±5›9—«¡¸Ál.ðSÁý>ú�„ÇU×wRš÷qÕ¢©»[^rÕ VXb>êœñ2'·ÌÙÏÌCRà:jÒÁüçÒ‡I9ä“êÆ¶³Ø½Ì_EFãGfEt™$l|aKºsÖom™ÿ]Ï\5ˆ£<–1œ‰UÝшa\÷¯GÝsÃÈG‹6�ô‚U>ôÇ�^¿;4éœAª‡×*ä̤iƒ‰ÜF°²VÖäî<}{ïr1êi›{­­¡R‰‹¼iMgü« M2Uær 9v’bíú28%ªguðòàó°gŸü|ÐJ¾±a?—øó^ù¾0£ïäÍ={pòs¥—¦¾Ë�ôᾕ²ª*~çØñ¼PeN1 ºªR+‡™ãcrÑûrÕ=•¿Ž>àÐsGùú´ ü”Xsj}'w¸¨ÌV–Ô·Ÿ'9c‚!Œr$ö%ø(kÞ§§|™„på Û ê^ŒéÉÔ #žMíQ 6>öq ¹sö‰Ú�I Ð.\åú„vTªnÚ4´Ë^mÕyìgó.t¦,íX˜¢1 }Á—ì1f‰ŸSOÍ)H—ï…õ¥v‹L–1+òS¡ËÁgƒ© +·±mywÄ5†5°€fº0õ±WPÃÔ€0¤ ÿ°OC��\6©áuÄðm2öIòîoûKŒÏ[.(ã2h¡ÖZ¦‡ÉÌ V¡˜Ü€ZÇR#²VZYZW,ò×ëwf} ©fê�pòšI4ZSÁ=Ü’½“ÿà¤�(ô �U÷îÙßJ¶ëfÃöX[t:�ÙŒ†l'ÌBA¿œœHMŸÀ4m] Í1BXÉÞ�¤M®q78»Ì£[PPC¨{ÒÆkŒÅEêSGÅ6>òï¹)Y!¹C5ã°Eë£,¨c©nU¡ªH õ ÛÄ£Éì8ÙëÀÆõ³ä@å=œÚøÜ´¥K¼ºˆå~9=kqMKW©ÃúŒlaÖÚ|ËäN@íyŸ%9õJL1ŽÁ9šù,ÓÎúŽ»"Ái¿3C;½¯â2s¿ÆÞø|.JºnqWТۺËX®Ýúû·2j™ J”óâÏ}–Z¾Q‹n}rê3kÑÒ…ôò•ŵ;c œH‘e•‘Óî…Ñ4…ÞÚ){ÒÒL¬îø¹wf$�V@-½Ø­)zäÉ»?Ôüèò1ø¦E%†2ñïIþ'È#±ÇcÞ´ðŸÀœ�üÞ÷`6ž«ô' „z3ó‰zI„Ô(¼€Iç°±Üé�ÊûQ*µ¯W휯��ƒº‘¦ò¾¾†¡­Ê‹jK5ËS�� f<¿z5þ_ÅšU•ž'Ĉ§û6e�§�&G‚D!›ã2‹?�&+"˜ŠöÌÍ;|—bU®Ic÷ú Ð|IÅ—ñÏö�éHÊ'»ìG»8üYu#m®YÚ‹Ò×…N„·6â僿w‡£Þ oô¯¯È‡÷äðä?È/¿�o$êÃõàèMÞàÎnÇPç~øG+m,ã–‚ò˜ál‰Ñ¹Šg°£æÚbË~še±ì9åoÔ�èSP¤CíÙδÔ~ÔB«dpàïoueDcåd7�ªçш�¥ìâšdîH,ÙȱS²VQ\HÚìÈËÌqòX†§ty1ÛØU'~hÓ÷Õ&ë¾xµç?þ™ÙöÕïŽ�›+¹�lüõs×—ãÞçRãªóÅèŒWFïüáQq„rKß±Í N$ß¿ †çÝ!Þò ?ƽñe—y°ÝnáZ‘2�œpŽAÞÚjŒ®‡�³®qÞ= ;ìZ£,¾z•”ÿ Q:aîõèŠlµ’ÖÌožÍŒq^ÔW#Ñ×-w3Âð!3¼Ú—§B<5Ýd“Ú>·:l%‡Ç–årNºÐ5Y% SèW(kSÙ5)ÕdÍ@b r¿WUfb�·dK¢öfu0Wö¯k^ëÓ„4�CýÁVÉôì Wqu‹7{SN½Ä—#õ<«dE�ó…¥VºRuWï¶×°Ûâ-Ù"òÒ‘q8éµnÛ˜¢.mu­†r8©z}JÌÂel¿U’Æ>‰ ö¤Ëþ…KWØc�¹›%¥3ŧ UìRCg–Ö^µÑÔ;�¼”¥É…³S¡}£âÝ(’H‹__¬Utù^ÌþdªE²":héGZÙJ\B²0ÿ% �S;îŽÆÆYgÔ5FÝþ¼ÛËûN%K3ËF-'ï½{Ÿ€uÇ\ž‚¥øAIø"á[\q³k3(´è\lÃT Œ²—N@êjanæ�ܟ¿`«=¬=ÑSûP\­§ÚãÝ×dŽg3º‚ù£2´˜xž‹¼âÜJIJåaÒ»øjô»ÿu(å%kÓ‰Ê(~êóò„n`×à“îÂ=áQ‹8´-FE(“Sš #TKY¤ÉO$%WP”»éÌš$á€öZ­ßÔÓ$YºúÀ8YjÍ‘uŠÚ¢Wc04†Ýñõ°_P½Ô¶"8ÙH'µDp²…Nv'‚“ Eðv#¼­%‚·[ˆàíîDð6#ñ¬-©Þ§B—¬¨{ôŒÅ’]k–rÜ.^E’9X*)‡iX'oE¯Jž®ØÓÍQ%WoäO±*5„9îk?Ù¬ø›ýÚØ À„j蘮óå“‘oe>Õ·ÔÂîÏo¶¹ô5hßÃ È 9Χ�Ïïìª{#UcËêºë›¶T¼KiSFœÇB(^2´€Ü…?[@Ð]¹³åêèF<`ÈFv›N\„›Ë«Î§îÐøØù½›„hòêB�‹¥ôѶµù< ;Îÿä6õüä «sxœ;3•éç„XÝ�´ÌœTݼÄ\ ‘Ÿ—’Y”š\’_T© ‘Tš––Z¤ —ÖÔL˜˜¥¬Ô�_”›X¢[’ ÔR\R”™—® ¤©¤™ �PZ Ô‘›˜™£›˜’R”Z\œ°ñ¼!“ŠÁä-,¡“WIN>,4¹E#p²b­_€¾–‚­­‚k^ŠB~šIêC Zú\¼ºHj³˜xœ­VQoÛ6~ׯ88K O*R`Í6ÔuÜՈ綻 {(ê$•E�¤ªxÃþûŽ"•ÈN¬ÃïŽÇï»;~V4†Ÿþ¯ÆQ�ÇŒám&J…Zü‰ S1SÈ‚<�“Ëz¯D^»šõ 8çpùêòøXˆRÔ5Ü0Ô¨àüK÷~›7I‚!/.·Ñ–×ð‹”9�¸\Îü!KÁ±Ò˜BS¥”ÃÓšqzyÏ~C¥…¬à2|ç6`ä]£‹+›b/ر=TÒ@£‘r 1¼çXp¹«KÁ*ŽÐ Stçø,¡ÍñÙç�‰aÎÀr™ �º0¦Öo¢¨mÛ�uhC©ò¨tq:Z.fóÕfþ=!ö;>U%jM•þ£ŠØ&{`5!â,!œ%kA*`¹Bòi·JQåÐ23-5ƦI…6J$�9(X��h¨d¬‚Ñt‹ÍÞM7‹ÍÄ&¹[l?Ü~ÚÂÝt½ž®¶‹ùn×0»]]/¶‹Û­ÞÃtõn«ë •‹ÎÁûZYSØRbÚÕmƒx!“’®‘‹Lp¢Vå ËrùUEŒ FµÚ¶TÀÔ¦)ÅNf:Ó^ö (892ˆã»å¯Û›x=ß,~Ÿ¿›®ãéz>�?ÄqpF~QáK!”¦âe“"üXŠ$a#ÿ‹ŸNmÒDÊ’ŒððDQGÏÚ#EeûȆ¯/cØë異6¦�"/™êȾ�y‰;¬L?i71´”;FK™ZÚpq[îÌ—øð¶ÆN˜¯‚Ào<Ðf_âÕ #t0Âb40¶¢Jekm¶ø®ö¼.mÞê'Œf#ø+8Ã*YG|ÓËÌŒ¤n?šQ#«°×ššJ²wd\50bš£î㚆Žý(©¶vLäaÝi£àDaÆÏÚy‡ñ<°ýsŒÝðkÒùûÆ:h¤B=Ñk¤’˽>Õ—÷�ìG: ¾J‘>ßÓÔeb|JîiÖ¾5¦Šµž5q‚ØÔ)³¢QK-ìhÚ›IžÔÿuï̳û¤ÉŽŒ}ʃ>SâÁº›H¿mØfš_´:¦ÎI}\[í|ž¾êè}k}\O{ôôÈÃ7¼Ò"¯å92w|œ�“†xéæÁø�®o õÅÒu}qü�&Ò¤”1áÈP…ná{,M­‡�Sî='ÇÛûÿãìÐÿ—0@WØ'­t{p„©|Ök4'%Éû¯žÕ“¿;õxT•“šâíÑø�wŸ8ôÉsc×ãOœoüš²ùþ� "uà†IxœkžÀ5!V7A#-3'U7/1Häç¥d¥&—äU*h$•¦¥¥éÂ¥55&æ*ë5äå&–è–dµ—eæ¥+(©F*i&($”uä&fæè&¦¤¥'l¼dËTïèퟠQZ�œXœª€ÓF„xqb^±njEIj^qf~6·]ïÏ¥œ’š–™—ªîã‹Ãg*X4¹–ÅŠIÅ`òY×z}}¼öQÃ_úZ ¶¶ ®y) ùi ¨‘dJ –€Ñ€‚–>Šd©ó»Èx½UMoã6½ûW œ=Ø@êì&@í¥ZY»+Ä•IN�AIc™ˆ¾JRQŒ¢ÿ½CY²'© ÄÙƒ-9|óÞ É9»¬ÖR¤+ £x —Ÿ/…›•ÈDUÁ5G…Fí÷�´Ž"œÄ«ñàloí¸‚ïe™f³™=8£Õ™ˆ±P˜@]$ WVÅcút+çp‹R‰²€ËÉg‡a·4ÿN벆œ¯¡(5Ô B(X Š�O1VDq™W™àEŒÐ½jÃt ‚¸ï ÊHsòæä_­¡\îû×-á•Ö•úíâ¢iš o™NJ™^dêbæÚŽ8¿ÛÖQd¨Hü«’tFkà‘‰yD3Þ@)�§iM—†l#…Ezª\ê†K$†‰PZЍÖÏ2ÕI¼ï@¹â ­Ü`_­À Î ãÎ Ì!ÜY¾oy¡ë0÷Áž{S7tçYßÀòîáÚõ¦ç€”'*>UÒð'’Âä“°Mž·5‚%-‹¥ˆIW‘Ö´@õæÆwo­Ð�á§¿í?­k‡Ù ßw¼�ó…o;lêúÿ ·�™(؆’AÀ‹ø4µ'�¬CAø„q­ÍÙ2Ûñ‰SÑ�é’þ1{eꈢCx¯¢C@¢”ÓoOÕ¦2Ï…P⬌ȳ!Þ»½›pôœ$Y»Ô“‘¿Áר6¤Íè8gãu Ê]Üÿ™Úªnÿ†hÜYÇÉöž§ Ücm8<»Œ4µw<ÈÚeþ�¬ç˜ï ê­ã‚zÏSê±N"¨@½'¨·Ž ê=O!¨Ç:‰ ˆk�r½ÁêŒãr:ÇS¨Ù ³q…DåíS& ¥y–™75´üïNlß!3ho­y(º«cæúú›qŸ:3Þž:AèzÔg¨›m„ëQë™ÍØWêÂ›æ –#ѬkVq½bÔ9YYµ=sLd5¢®U²¤«»Æñ²uPÒæ7Žoú>m°™ëÙ³ÅÔaw?¬�ÝÏl´�êÕh¦Mýg¼Ã‡ý£ã™t›–ðât�ò‡¦ÏÈéOÏWŠBmçLJڞý÷‡Â"¡Ë0ü ¹Nî&†xœë–<ijaS}NfRQbQ¥FAQfnfIfYj±Bpˆcˆ§³&WIbQzjI|q~iQrj±—‚’¢€ Ï0ÇWA½ dN2P®85=75¯$>%³¸ '±¨Mdr‡¼(V“';²I×#„�j€NMJ,NÕäJLI‰O­HM.-ILÊIÕ@32¾$µ¸Ón°0VGau(Øû^ ÿ'ççdæ¤Æ§¤¦e擟 lÆ#…ŠRˆkpH¼‹cˆc¼‹g�mÌä,2Ì1JJ“o2Ëš¡]¥ÚÙÑ3È?ÞÓÏÙ'ÔÅdHðä‡ì²õ4B È ÈNwÎÏKËL·² 7�r�óý}]±:(çìïëëèç‚UšU“+3McònÑÉ9å¹4ùg‚6“x¼/=Ú€²ìxœµXmsÛ6þî_±ÕM2¤"K²›¸¹øe"Ër¢±*]%ù’L§ÃIˆÂ˜"X´¤¶ùï·ßeÙq{-gl‘àîâÙÅîƒ;M8ÿ».hv:Íæ4áý‚…ÖáÊ ¹w×öpL6dã ǰàBæ®C/d4RíLä½Çã­`ÁRé§~þ–gÃq÷øþ³d!CýB%`Ý™ß÷A⺴í-íºª¼†œ8ëhÔÏ&1�F’ú�D>ÚPK ½˜xø“½iÁ©�ŒGpÜî‚¥Ù«†}ªMly+²…ˆ+H$EL‚ñšn<+`x½e$ò0L-Í<™í.|ÉlpW' }¾¨ Qè¥R±|×é¬×ë61hÛ\�0•“�Ѱ?χˆ8Ó¸�B*%úkÂzënuäQ�¸ˆ3$kÀ5 � øNq�x-˜bQÐÉjMÕf|&•`n¢jËñ¡ÛU ‰ Ñ›ÁpÖ€ËÞl8ki#Ÿ†ó�“Û9|êM§½ñ|8˜Ád ýÉøj8NÆøt ½ñ¸ޝZ@1\8ÝÄB{€0™%õMÜf”Ö èTÒÏ2¦[0]‹‚„~OE„ALÅŠI½¤úÚLÈVLe†ø¥'êü‹E^˜øÎ<Âo//*C˜¾.‘´“ýÖ_®ˆZÖG¤ò]Îà ³w MÖÇÖd‹^ù‡˜ëèÒ¡ÇÝ/�(¶3ÅæÎÅL\ñ¨SÜÞÑ­Ü®ä7µ@)ѨlgãŽâXÎ÷'l<"U-qÒ3Œ:Íó<{§£� : ö\Íâu¬Ä©14ÂÄ£.œÎt K¤¤1&õÒz€™™x ©È sa}9(]Œ¤ÆzX~!‹$—xw�à˜US®t‚ÅÆMu|ÍRÚ1ä©vÿ®+ãUøˆË‹ýˆ£÷œùà,�ŒöÎrœÛñílpå8û‚ÐÌït4±B¹ŒÕ¦OÑröÁïF²X ECÒLoP Î!×ÀPh¶ë»âýá…)kêÛÈM*Ñ©ò–D@DVôç£ã·¿œŠ]8;ÌH'MZ' ÊÑbVÅ`úªeÔ‘½Øo”/,ý`Û6ü^¸‡KòÀºœ9Ãñõ¤�ŒeÊ1‚z”ÝSÿ¼��Ô��bøZúðùæÒ¹|qÒ#1Å­`€?þ(¦ËÅ}¶äO{%«.±×´N%F^3[„eU ¤ÀþõŸIÆ+AÖ·‰Å�ùIŒ›zÆúXPQ«$=p<†ºR¬j5³;�ƒY®%~5Ó2 š?y� ‘t4Õb†uOë©X¥#m¹¸O25›ê”Yp5¸¼ý€iÐÏÀAŠ^Ę%6;KãºÞ©­*ø$�ë˜}CJoR4é½'(.›³|妭ڴyâ�oG#�…‘¼t`ABIksIRO9 ŽQY�ZEîÚèòGÞSŒ-AOû½átâ\OÆsg6ÂÝÙO¦?öF%-T> †>Î�ËÉè*×ϚM©‹ÏªÌôýñ9ÉáQ‡ˆÀ}‹WUº»Yd—]Ꭾö=[{»’5ܤ¸ƒ¶gÕ¦YáÎ�ù°Ï÷bè¨Û-Ž»]x¥‡°3�,²ŽÚoèá¿ñ¡>8ên-ùKq£ªÎ4°&wȶó,ó¸QÏëXðm©š¿ÞŸÝ‚š¼Þ[VVáK5÷[eéµj/2øYá�Yô‘Eß$º4îþ-¶F ya”2VIDØ_®[PÜ/s–¨i»i^ä…jLY¨¶ÜSNnQFz$ º&›ï�Ñ”Ò4³ñºéu ÐéÀ\�HÆØGÞ¶mÄuïQâÛjŸ3XBŽOáÕ«m•¿ëâ›T|ƒâë\|S7€ri�+§'ØbZë¿7'¨´<}\ZÛßÔ¤×uité6ÆæBºPï@¬%ì7,eÚÈ‚x<ˆÀ¦S“Eh·kêîáE¾Õÿ¬QásÌ64”ÚÄn#¢XT›_àüA‡a¥ðÎÎàèÄF!+uŸßêGõLs¬{—£ª!uÄþŸ@ñÏ,ÃÓ€ Àî> —\)¾Êš‚ªá41~LK›7IdÿéÀnËÀ¦°¿ ¹,—ã1µ‹ZÎçÀükqµ¬¢Anèolxi6]zèÆóÔ~Hµº]­÷<­‹ 8ʵ´^=ù¾”ÿ3¦psšDþøQŸÛcÁAVýCJN“Uk†‘ˆ`…ƒÕ^^�áÆé!¯—ƒ¦un6qü¾Â‰Øäàv‹+È”f�¬ëÍ{™Ý†%öïÉën ^c0÷�烖'Ñàópî\÷†£Ûé`w'K â1ãçM7Æb“kì™°Öß>1Wíp׎¸b íåp”÷ŽŽ¦J:Ä÷­—…ùôpUo§M+Þ‚—5ÛÕ¥¶UË$޹PÔ¯ÙÛÇ£qÝ/—…­–ßÕ©žê=Z°³¨àøÔãÂ|IÛ5óÍcåS«ÞZè®!ÂãN}³_Ñ×s{–B©ÈŒïÎë2;»gy`J¢}G¤§±ç­ÙžÎÉ0 ÕÑ�ËóÅ`:�Lq-® uóòÔZ¾ˆíÆÞƒø#˜3jÚðpòÏW@.iêouú{e‘�íFýˆ›} ÀuÒí³UOö6’ù�½sêÊócµ>g·ýþ`6ÓÌ™ç òõ Ýªo{ŠE5Gþ ÛIµŸxœ­•Ánã6†ï~Š�Óƒ Äö&zHÑ¢Š¬$jÉ•ä¤9”DËDdQ%©ØF‘wïP–³vâl‹Åžlr†?¿™áŒNÀõFòb¡ÁÊpþåüg˜.xÉëî(SL‚õÜþþ^4iÊFÙbÐ;Ù;…'.àFˆ¢dàûNï­>ÏX¥XM•£€^0°kšáOg9…&œ�¾€eú�©?ø6¢�%Ý@%44Š¡W0çx[g¬ÖÀ+ÈIJ.9­2+®í5�È%ž: ‘jŠÞýk\Í÷ý€êx¡u­.ÇãÕj5¢-éHÈb\n½ÔØ÷7ˆÝ!Ò¶þ³ªdJ�d7\bœéh�0M±¤+h!Ú´0°+É5¯ŠSPb®WT2TɹҒ§�>ÈÔ ÞwÀ\Ñ úv ^܇+;öâSÔxô’Ûp–À£Ev�xn aNL¼Ä \]ƒ<Á�LN�ažð¶®¥áGHnrÈr“°˜±€¹Ø©še|Î3Œ«*Z0(Ä “†5“K®L%âå¨Rò%×T·;‚õzÙ’>3²ä_6K²Ë õàF1òÂÅèìbÐëñ*+›œY�tƒ·æS)´ÈD‰–ú¹ XŸì™,EÞ`¬GûÉ·ƒ q|Ï ˆÜ?g^äNÀ»Ÿ†QâNHbG7n«­Ö0À+ýÛ¯g£s¬çàŠÓ(LB'ôãÿ­;Bet/Î;ЂiòB%7ÏÂê\ÉΕä\~<x.§š¢Ehž“’§’Ê�…¿«r qb'žƒŠi«nR|zdÁ(¦›˜.Q=€<-Óf>Zà_ž‰ªý³'Ñ®×yA´¨KöÂJÜØêM¥G$,¯æ˜�ã€y™º‘ylhØUî {`:»Âf!·®=q#èÿôϸ×~®D#³¶cʾrg;îì=gf8»¼½åõŽWØ%. ì{× s– Ù¾LÃÙ•–\{¾k8�UçuÜTJ›â�Þ-‡;·áËÙh½,ûxAìMÜ.¬ïÃU V–ÿŸt�³=ùõß!Z•¦oåДd?Üc€¸ñ‡ë$$g‘ã’‰½ŽßÞ÷ø3¥‚ÉÖzÈ«ºÑC‘â7ëe[žïÁüLé8f×2݃¶›Å{°“öÆÎܾÿîD7èL Y¦…<Úm­î¢Äá°#^àø³I ¿ö·.Ý]9áý}µ:÷ö�KœY™óW^`GOmì{P%¯ž»±ó‘g»J©b&©Ï…#ª9/./ÉmoH{£Ý1#7Þ,SQºküЩAo;…U»GX»i)^T´œçÐW5Þ­F‹>ÜÚ.‰½›Àö¯'ƒŸ[A˜¼ßø8ÞÝièûŸ�tV‹²ÄÖáKsüÓ”¼OÊAÀ탫rÄ2AÏ-¾Ú4¤¦zALóŠºýDš>¹ßºÎ[uoí„<…32‹ÛWvô"SÞÍ¿a¾P(æ‡Bxœû*ü—gà ¦É›˜¤&Ë1Û€h¶`× 0× É¿8¤&¿aâà+N-*K-²³5Ô32Ò3˜Áìᘙ—œSš’ªžX™“˜—P”_’ŸœŸ£ÉÅ•˜’Ÿ“™T”XT©Qž“›˜�Z_•.Vðô q rstv�<�MJC~² ›ùäb6U6ˆ•8*ºÀ&©W‘›£Ä¥ ìéâªÑt€²‚_jjJf^ºB„‹»BpFjNŽŽB1ÐÁ© %@œ_�“Z–š£�™œŸ§3T¡(5-µ¨(«�Y¢7y›{»XÓëáÖ”$¥§–Ä'ççdæ¤Æç”dæçk�ƒÇ×@Y%•êpÇHG?—xH0Ç;»ù8º×*ᑌ÷ñp *qGQäâïìïŒÃ ty˜1š\·Œ§ß¹ÚxœÝ=ýsÛ6²¿û¯@ý&®äÒrœ»ws×}§ØNë‰ÏÎØNs�^‡¡IHâ3Eòøá�ÞËÿþvñE%ʶÒÌiÒZ�Åbw±»XËÝmrð\²½»±»½½A¶Éß&qBI˜Ä4­F!”°Â0ËŠx:«ðסüAá�¼zùê/äý,Nâ<'ïZÒ‚ nØß¿Mëëk: gC³4ùù1˦ÐÓéé¡èä4iZÒˆÔi0ª%ã<á�x⑟iQÆYJ^�^’VØ�6‡ûâ!«ÉŽ/.ÆgW'Ç—äü‚žŸ�\�œŸÁ¯·d|ö ywrvä ä‚~è}^àÍII#F·KJ &G©ÌiO↖Në`JÉ4»¥E #"9-æq‰,-ÁÁ$ñ<®‚ŠµÆ…ínlüWœ†Iû“øú.Â:ÛÔž|O‹"ÍF³´¢8­ª‡œ–f)€¸Jº+þšó,IÌ’2ž¦�]VEA1m•]gY»bD'­2èÚ.+€ðw”ºŠ­¦÷7×0-çYº«¾a�ÑwÉ<¸¨q˜¥;uZV8[vn÷Ú(mj­è}µ§y]íd׈“«¥­î£éN9£I²¼ZDa˜ª£îð±+r—ø7ôá: Šh_+Ë3�HZèE�â´}ÐËJTÆïº˜!Ýß• &€Ñ0 1eždÐ�K˜}Tê;Á2i°’@Ä—óίȿ7|Xã¨'¦È‚JhŸ’µÂ ªEó ã§)€QIXÇ·P×Ñš²rÑ?¼@kù�wo@kmï+Õ\  òå‹G²ù¶VæçðC‡Œ›¹ B 0¼¬ ¥D¢º€°ÄÂÊ8 äB)A1$´�.lp úǛ҅ÀîF+Õ�Vf"ûžË$GØ#ñ„l‘»M¹*I$³ d?B0w×`õ«G‡B´‰Þ¡(3û{'æÅS;”óKïP–Y¤<…a£|sP´ÁU<ÏbD®Î�Î[¾Ëk ~žÒ_øéxü^!p]úQ’Mù‡ƒÔº}‹nHD˰ˆó Y—Ps1~½<ùñäìJ2÷‡kzip?Î(3Ëï†Òœ“ŠÍRÔ)šYø6K@Mh™ ÀØÒÕ÷7>ï[ª h‹ÁDóöL²¡QgYÄ©s>&‚äœ(àÈ€¯ð©Q #^þÉAÖ é´�,iäç /­`}%ðúBœ#À-šgáL�¬�ªùs‹MÈ)ÂÇ– Üà I®ƒð\§�û5 Gt¬@UM§H‰¶j E+Õ�,h�‹i=G濸Tó“â¨�$“i¾2ý¹éš¡çW‚sG�œ¡š”"Ï®ÿ´v~§è`.fä`Èf-Z�ÞI «ëP;ÀÚf6•·õ_椗n<Ìæ[ØHvýé•‹]¢‘Ë�4PÏ'“’VÒr]gà=6¸{ Á(ü )¡7Mö¡+ãß©«CÂaáüŒõ¡q3Š=‚dºªs¡”AJL‚›Ül³ÊÃ�fC·Å7èeˆŒåíCQ5‚ÿÌšQ«Ì‡ù‘dÓ�Æ� /ÙžÌ+�wølÎ�h—C Ä ÐJ¨?M²kÐ%AšÂèB:hÐÛŽ‚*@D¼.3bÛÏäYÀll!gƒÕB0s¸¿Ó‚ÎÁЭ†§üæ@P#‹=‰p|¡ø‚?S‰^ÝìòLý¢©¯S;ȯž6kv[´h0‚ PÙƒ�’D�áZРúLøºeÏÙF°BÙà˜–}8Á ø¶øâà»oI×ȳ't©K"‚ê¢�ôRD¯ØÝÚ]JoGór¬q‚CI ÕZ¨JñEÊ‹?‰ïAÃA3^îßw>ypPÍBBƒö´Y×–£3ÏPa>fò×B0XˆTÏ�¡N1Úªˆ÷g7Fßk9®Á}ÜšˆO¡¥*BÀ-"ÞIÝ©Iᘫ=°ê7\¿Ìê¢m°Vµ¬oÏU–¯�Ú=qˆbpíhõLã÷䊃•HضbÄÅ~³œØðµn•¬göªÌBœ§yP™(M"{b€›f‘ʉžSs?»% °ùîê$(ŠüoóÙ‰¼Sg1äû`¿×…ߢ‰ z(J'Æó,Š'18škÇz‚yJY˜œÚòËÂR$œu<Ê›֓i‘Áz¡Ç š£Û§“ì†)»/06cj XM¡)•1H€ÿôí(ƒÄ Mi¡b\Ê�÷úwÒŒG&”«_ç0FlT²×¦jÅ’Hðí’±øñEüv/—<½–Xš´*óbOÅSä°Êfs˜µeæÃ*ä”KÜFWw¤¡t‘ß.PG'ØQ“¯Ýº1¦dÒÒjx· êLQ L\Þ·À—©Dw5öHTã&*r¯Ë]Q÷ÈFh&ÝÕ:b.�»{¤Á.G7HQÁhŽÇ¢ðX¯.íø‚&²Ê"^K…°ˆÙJiÜn—*v‹°ùÁ"§Â3%£Û¾[²ÑmK=Õù‚ž• CÑUUU 4¥ÛÕD«bu˜**É­ �Á’�~ýMQøßd ø‚ÛÜY‰áå& ç‘?sŒ á�¬lÒ„ôM�˜Ð'ðÈÙ‡ÓSòÙÓ:)gsúÞ Ð±©ì}4õïæ>îÅ> ¶Þ¾oÎ ü¿WA\„†žQѺø]lWú¸]éσ4˜Â�Û½G È€áÑï8æf+ò©v@sv�{fl‡Õ×vXŸ<âN€.ð—G^²ü !»»`ZÓ*Ni2Úý”ãû<+ðDÄœV3ð¶ç³?…ì<×êñ¸&dÈ£�zð*Ès?æÂ�·Â’ê;<��dÓ)¬Ü1´˜…ƒ=�­žtÂWÃ!�fÇ2`Ô;8 :�!8IU]¤Œ´û2 pQè…v+|^ G§‡b� § ߶½ƒÁ–|ç¾w‹zYÖWˆ|s@´± åfŒ—AÐ6š›êÍÐÁREu>Ð í+@]cîªã`à!#²Ýö�º-"âçóÿ?wÊã)R´’›|jŸ±„2:Ï«(l5†:JbSàÍ¥|qq~Aþ�ð¯gçÙ|Ä ˆ½çE>Üôˆm¿Mâ%Ck 볎rEžØë|"âyò…áãêè¹Ç”Y8Ê›À÷ßœž¾óÌ~±Ý%jgïƒt�Çñ2¨ Gü|#5§‚Ús†9%O vö¬Q_¾=òÏÎÏÆšFyI~°§–‚÷ê4�#MŒ>¿mÄ±ŽŽ‰ô’”Þ þñî�x~vuü�+@Ö{:þñr‰Êm�]@)Ižû~o®�šÆ�«‹< M´qL…�{):aö HÔÙëh}D°¶E͡㮠|:Xn̬/£•Õõª2�Ë0jϧÅý=I¹ÞøæÀÙ€)QkÉÁFo´ÌAlµâ=Öó.f,2W9Ýø2ƒÅþ[™®m­¶d ‡ñŒ&BIN�‡# ‡|Ð_”Wš÷Ö:t!'¾5ê«�]Á#áv&$KÉG¾â~.M°Êhäºwé0 âW‡¿½¶^<­ö— û‹¿Ÿ‚ðæ5¹Ìæt–Ýñ9àYåÁcÄ<މçt³„L`MEÓ¬žÎXØ ô žµ—p´ˆ§vnôsà뱡Roµ¿ñùù—¸r_³ƒ»ÖòU.o[‡·ù!ÀæLÙÝ 5ªUã y¸ižg¹?)ÀE²¡üøáph-2­ó6¢ü¬Íö°éhØ2a›ÞfZä’ü^6·Ã qY7†½œ.í ’…{µšä¶ñ^äC®0ã µŽuó N :,jÖ…£QËÂk9ZÍùp/ñ¤ 1­á"Ìšj+£Ö±ÞÐu!§7]„�¹Rêpü¾ï³^ “¬¤ƒ®Š]8èKÙ—«Ow$¬Äá £t?·^¸AWß–²LpQ§K‡a¢¯_y«ŠZÆ�2˜�´r‹,—qJ•4 Ü ¢ÞÖÂ»Š¸’eƒrp²š\vóB²â›å|éxÏÌÿSþúê·¸,{ùÛˆ.+h3y ÂL®�îûóÓÓ“3g�BUz¹¿Ñª°§sÚÚ6Ľ½îuõŠ—£Š[(Äšƒ­K�¼òÈÞË—Öþ 9ÔßÚ"Ç'gW�ÂÙ'ͺÄ@Îï ·O–4<«hu‹•5¡,Ûî¡DÖm{^ýéšËz·kÖ‘Kç%¡II[»|ÏÄÊfí-g–Pr÷SÙYñ×p)î18'¨ª‰Éwp€Ú¿‡ Õ<Šzð»a2ëüE$bŸ¿×½yû6›¼åÅ 8]†@—oZÐz�ëÍF�pXàWgóæ:&Ž¡¾ø=ê£Õ¥‡äì7ˆEŠ Ðºdh�0¨§³ê5y8HÑ�4°í÷¬¥ xu“ˆÖ)¸ëò½Vt ù\ÿªiM—{^+†ÉÀ� iÛbŠÊå‘J^íz�~ÒxÄaÖ+ç:¶]$;ê)ž^{R„AØ¢5 Í’ hhóÝNïö}_ƒƒƤz¶¿­ó|.IldB¿šlÓÑ3°jz'n�±ªF¼pV‹3ŸåáÉÓ “?‡<|ìʵžÉË.hì u“ñ>4jnp™ÑŒdZ³<(‚9™ÌÉ´¼6¿¡‰ö’;÷�L÷î›ý+<.y‹i¼¨fÀ}ÿíÉé±ïã7púÙ7 L^Ü_G”�“ê'vr“ï…ÊK-,/K÷!n¿ˆä‚\˜ƒ',û´àRÍ'ò‰·þ¤I]œÞf7<ÓCšUñäáqi-±G£¿Òâ�¼¼¤kF–§¦Dxù[A'Ú¤iMí�0ü`N Ç>ñmbü'��d…‹]˜±>g5Ì—8äÏDæ‰í$€Æ�Ѐְq-0¯ã'7>lƒ9 |NñtÔˆñŸ÷A»#ùƒ˜/¸ ?°n9l‹o\nÉK/µ8‘ÎHS•í„v9¯ß}×T´ösÀû )�X6"“1ŒÏQFËôÛŠÌq­Ñ8YÊûê„ó|Ð5n×gÎÜUl-—p3-Nk÷:�óSå-!æa*¼Ub¢2ùª±²ŠÝ5­l-‡¯¢KÖ„Ë‚æYØ¡ñÜ�ŸC²é„…§»øžšfª©ãWO®LÉåuÖwHi3? …h0@1ÚÞ8WÿÊœ^©‹¿3°²3ä ‡07É�ÉKYÇXlöžœ½Åðà3k®±@Å¿ÈÛ$w�±£ý�¶œYª�§ài äÕ‘Ž/c„ÙeQ´Íyy;,®¡~Zn…yÅè?Û;­ácr -·…Ò¢uoÌŽ£¨1öìªÓHóµ‰áDW�+UZÍÒMjní´Vó�­NÐç! g¹È®VMö].*–˜5K­P[ÓèU…›é(« Z,sÆêLŸLÂVð S3Û2�ZŸÅFLJe›y]²4ÈŒO,¥®Ìù„”Âo®Ú¹Øµ—u¾êüJ֜˯‘ñV®ËdMÄÂ<°É*j‘=ÆsÐNÊ爙@53¿^w\E«ª’ç-™šW¨ÊYV'²9à‰åì쌑aOÝѯýÆÃÝ'Z#±Ú6Ê´à:Îfµ™âç/ [wÜ ÖÁ‹ˆÔx8t8 qâH*htMaÐÔt:·Ú„ò´1{dKë•—+›lK™.4%Ë``]Î|Šsöº '=>¢j­Sqq¥Q6áÜ9Ë*úši!®Lïâ$!gçW(™x*O%çœÄEi«R…µÒ°‹ €Z˜áY2=¹�KZ‘:Wù1^‹ä·h6sñmɳÃJw‡gþX!Ñ�R|¼®û‚É‚süFv9&ÎwvzÏç‹u<‚¨²æª)*ýÎnCYEG;Ó‡Zk�@3·°êÂLÏî¡éöIj&ºII²ÑæÝ—H8è¸?Ý0 õ¯èDØ 0.ë¯�—*¿Ö�“l‘�§þåñøÊ?¿¿99=¹úÅ~rvu|ÑÇ”­-"UY·Ìi”Íð=X‹1I!¶IÇk ôäúÁS¦ûV/Ú—­ZYa¼¨R7Ûùß8Hh¯å²oeŠjO šÐ ¤ƒ~0öWeJûÄ®)e*AS1{wüË›óñÅ‘CΜA³Ò?u‰—U�Ç•” ÉGÝ’¤÷%gR*C€Z‰f#A-â|Ó"N· ( \’b“Õ-mªêR±&+«ÛŠ:�ÿUU"@‰e¶1ÓR±|¬ÆŽ ®~�Žß|ø–¿‡ŒZ°Øy�q'á¾È_“å‚E¯ê”hI_×DQ—QÙë9‹Å‹dJµ~œdamy+ÝIg}ÿÃÙ‡Ëã#ßo¥zÔõÈú¨Õv&¯sBs'¨ÕªÞu¦^]\éA…F€l|7?a¯í™á®o”¥üe _–QI–•t>iY¿.>} Ô­ó,mb&™Êß'/ðZ©Ùd•üOdƒÔ"¯mÞžÒ'Ïñ!¿‹9âL�ì„΀~-l`Ù{¿IÜüå…Ý‘Ø-å<ßó×BZžc±mµüÓÏ@ܯfüL´DòÈžfäÊ^« éÉ´ÿH‚�‘0]fo©¤ÞðLüº œ¢ð¦7ͬäk—´ÖS•—Xf0ÿ hÉüË& ¦œ¸…%n‰‰$¨ø(:ÏŠ‡ùޱ¯3Ýz³�ùc�€Ã$¨K áe†ù3<ÌXš¥@`öÂF°Ç°B»¿¹&€o9R±ˆ�§*@�_þ>~ï¿=¿øûøÊÇTU?ï᪛£äÎ+òv|5>…µà‡T,Ü�Äœ(¯›SØB³Ðæ¡{#ÄÒðƒ^Æ——ǘ1ë ×àúa�ò´ E“92…§ DRyäýÅù•q<>òðúy|uì!eÉKmŒ�|rz|Äö/ÄžIÑìH—<£a£�|éBéC©}¶‚ĸHÏ/ÜEÂêg-™ú`iâ¾÷³0ñ�V�Ò;<>Ç<�xır4ô�\iJPTMáe Ö~ÞsV9<ÿû{<ãi%~›×)òIA—ï4pZßãêGÑö„²±€*F^¸ùÚ+ÓÆ²<n¦êy6šß˜Ün©”,¢›ã…dk:^F©vèJ½áðEÎ�<ÝÿÏÍå?7ÍÉ·(Þ© 3 ²š‡\Y®›8¿¿\Àkßb Œ˜qÊU4¿OYöÒ§ÑPÅéG,±|O‹­4ß—`¡¿ë£±œ�R§`<£ÑÃP/Õj|-÷¥Jd Xã~êZ¡ÂÃÇÝV\©v�±â;òW�lI¨Cíô ö³Ôðç{«ã}òÝw±ó  ÖcK"ÀŸýUY÷ågÄÇ忝ño¦‡5’ÒqÀi‰6ÊrŽýË+ðÐÀS;wôHµþll¦‰ëÇtŽiµÛ9f=ûažf1Þ$@Óz.ãóë3xܹ*‚´L�š Ç__ýf õW7ÂǧÇcÀ+ ?ÈÿðÞ[ÖLŒSoutþñŒ·ƒV¾ù€í{yÍÂt¸+ÊÝ“GÔ4…‹`°�8É÷ }!í-ß5�ª„½HÈ ¾9Ý1]ªYz¼÷húeíoCê¯�œüfY¶{(UÅ5Un ¡)Õ°ÇÏK�' ¤Œ„+7²kp­Çò=Pë¾*CÝ0‰§u�çÒñÍ%ìø.{é¾èSd.sZo¢Z¯ó e£yU•+:ÄÞZµÜKÀ7E`~Ò‰<›>òåVH¢ÿã¦5ϲ¢xœÝksGò»~Å„”�(ÊÅueY¹ ÇT0¨�œJ\«ew€9/»›}‘Øÿýºç±3ûBXvRW7•íPDB­YŒG‚.‚ñØš%v»JtáF݃ƒ¯™ïx© Ç/åtÕ0:_Ð(òƒÎê£káø‰—ïb~’lCç{=6ŸÛ1íÊßâ àVX'. J]°¼Ð·�»ëµíç{SÉÍ÷mì-pÊ=v€³~rFA8GþN4u²ñ, zA£3£Ç±=on;ï̾xµ¶Â ðÎ@½Á\�+êxv$ÙûYæíyl@Wü-èÄ(°«Às³Ãwƒ¥Z ?'³MÐÁÅ_»tÁ|J¬Ûáåкº^ܼ´F7¯É©€Û#1ˆG¶°ƒ ¥ ¢òE0”¢éMŽÕ\€� [ɉ?4<ƒé’Á¶œ“F”¯Ä‰E.“¬e}V˜÷ØkvO=pC¿S¤€óAdœ�ŸÇÖrq“­„äÛ‘ìÏÁ¾J�=æ¿CæV“‹S7žãYB p�d/‡úQL”D$g‚ßSÐ8Š ÎÁïlüYÓumù^’9V~?Íá[´©ÒZ 0m´a!rHó õc¶ôr©m˜›¬4õ¯(÷ŸpÅ!¡HИƒK@ª~Œ@Nr<1d ͨ)Áyng Ì&5á×¢h¿-a wVl|þ\éKèq´.íõ(X#w�ý�Ô\CP+±QÈŒÒõ¤8,6ÞVaV6Cli™ñ¼©ez¿�K¨"�,"{-iËÎÐ#G ˜³IfL2¥™·X†Ul�ÕƒY�§ð¨%Í |ÚÔ,ÉD¤]À\í­ù#fXÖÍèfÚ¿²,’ ~w x$lM[š.ëyBû„JµkVhÿ‘ÖÈv…+0-â©ÂÁZù¡'f65„¨­ÐÅÍt¡OXÚÖ®S‘ìÔnTÚ‡}¸*'ïƒõÙ¡ Êí… æ5w@À™‘#0ãÒÚVJŠ?-#ÁþŸŸZ•c$;û&2ôt¯¨âõú%O^ñhîŠeÜ;8­`�•Í|ô-Ÿ©/Þ•¬©Ÿ½Èà¥âó+BáÄb�Ü\4^B‘;-ÐwÏ«Gêܱê,`dŒàþæDL›�o‘?«me·ǃO^âÔqh‹G#ÿ+RÌMƒ’—_#_ÞiyH¾²SSó¨D—‰=j”z…A6±¢0¼ÀíÐÂ+�—ãÉëÞÌêM~ºø'´ŠŠÕ^éíCµ« ®ðV å¿åê!®A´¾êUâ\Íë–?ÀùV1Æ0ã6iÔfÒ:3—<Ä”ØzÜfA"ZE¤3DŽZ•)ñÓjö< f-!O•rrvuèQïÈk‘òYUT�¯W ×òº î�Åó—;‰Þá{I|¢'nÛä[}ã gAyìQ̬äqä yÎêwÿΫ`ó:©¦T–wñÙMM}iKVÁKǵ+ލ6Ðå¨â32…ÿ¡àC×ãéàMþÉ æÿ¡pf9ð+<>õÍÍ™où+ €°¤ YúÚÊèÄEO�ÏùÄ{e; 1BmÚ Îþ±O— x�m£ô.)¨¼ŽÚøvõø[èA`Ø`ÿùÀ*ˆë4Nøã¼(qÅ«aqirÚÁÅç¾=�’Ë'áRÂÍ_G½×}̼ÞîúŽ¿U©˜šy‚âñÍo'ßœés!Çô÷”âkg^lξ^”Þ`ÍY ›Þ2öÀ-šj¿6Q¨�ìÆÖaãz2pá»Á çŸ÷ÏþÑ¥ß�–o Œ#&ÿ2?ž“2k'e§Ixá|m߃‹ZÒ$dn³ÕF“ÆÔÁ?3üµÿâ)EIDiQ#©(ưÿ}w¤K¶ó0`GüñÝÝwßÝi>�ßþ¯&óÁ|2ÀÞ§R HbWé,ÇZ¼© î¤.,¤Ú@­ÍwYdPK—ƒDW1^Áó©0  \ªÙ^±"[™”q1k`Þs]î�ÌrGoËöF| —o.�/¹T²,á3±Fßýïû¬Šc1ãù¸ ¯ü µÎÐúz½lŒ¬%… TE‚.°(ÇŸfg c1¸œ½�6[Ãñ;‚Øë vl…vPY�#'^Ä3¥Y×»RIVpxp.|k0tì˜$N(vÐi÷ 0×8�;WÚ·óy]×3æ½�i“ÍU8gçëÕòz³½þ=nn<JX FüUIƒÑÆ{`%zÄ%C±0S,3÷œ&�k#¦m V§®fFL"­32®\�°Ö? »{€r[Àp±…ÕvÛÕvJ _W÷Ÿnîáëâîn±¹_]oáö–·›«Õýêvƒo7°Ø|ƒÏ«ÍÕÒ…vÄsi(tS•"ñ¼m…è¹@‚£w[ .SÉ1´"«X& ÓOÂ$ÄR˜�´Ö eG0Jî¤cA³'q‘¡ùàB¦¸‘B}]/×ÑÕ‡õ‡‡›èS .pYâÌ^*¸ª¿*ÇÌŠyó;ËïlZ—ÄZ+Z$;Á /Ueéo žºÃåþ\ˆ"‘)À|Þ;3@Þ+î V‘Íwﺯ¡ªÞ ¨fáFLf‰àŠò[¸êÕ¤E&ÄŒbvûR�3 XT+®¢PìawÞø%S*fü;òÇHø *Ì ëélà%É�%{ÒYbPy²pºgޏ€Ñ¤‹ïoD¼A�Üx4|beéspgÒüW:3õ›OZ&0©Zÿ|G@,r鸽H'ðê´4úI&(æDîP $�—öT";*£Hz�Åíg6òÝñ¢L\ÞyÏEÓôüš®ÂìÑÈ S‡µÍ¡Ä”êcó°^Sñ c´™ù”=–öò–Ì­ÞûàS¬~ª�ªôÀÌZÍ%£JÆÊÓ•á†RèË —'îÉ ÉáH–ž3“K!7'ò$˜.g'Çò]8Ø Ûf$+ÐAÏàÑZ`±I<\…@í)g^z^+gˆÅÚü×WÔ�Ȱíá” ¾Ý€}¹ ^žbRî /ùÂk¾S­¦îqåÜéF?�A:ßb+œI4.G—c‚0¾ñ2ðVA±'&•ïJ`ÓI†cï�R¢Cá­¼Ž°wb¾z`®Îƒ–ê± ê‰VŠü¢†Ÿ¬ ø nÉ—ZÒÄDG�jœ|ub  ¤•Âq©BKoix!òIí=\ë9pœY´Ž`†!È4B_ŸÒÙ=ì*Ò$B�Ö‚iÒxÔl<™Nå³Þy<ô®½ÜzÈR'úý¤í{3¸Îf”\ÀŸW±¨K%ž„š6÷�ÀœÑy.äBþh«Ð5�áë#˜Ê¬2âñ¨ôÙY|q¶óô‚H›¶Cû@urÔŒ� m¯<-ÊìŒÅ(t_na²jG‰BìªÕe…ÎrR†¯ä$4˜Wksz²}Ÿœ~„/hü"‡k,dìä�›M7Èÿë·:áý TRâ¾¾ÀxœÕÛnÛ8öÝ_qâE§¶áK›bç!Ù š&éÔ˜ ÄÉvû¤‘%Z&*KZŠŠíÙÉ¿ï!EQÔÕv’.° [¼œû�‡ž àìµL:“Á ø¸ >ê„ÁØÁw9ä„Ñ–QoÉÅÛEö=§ÇߗԧQ¿Ù$& zßå÷G/™ÏÉØYö‹ÛpËø5 =Äs}}¡�\S‡1q! \„Á—Î#ÛÁ/53„Ó0€ãñ;è‰]5ÕíŸ Û0�•½… ä�ÄaÐ$;dã�ˆ À W‘OíÀ!°¦|)ñ((c㛂ι�Ëm¼C¸0‚ÍÑKΣød2Y¯×c[R;™7ñÓuñäzzqu3»!ÅjÇCà“8Fþ�P†Üη`GH‘cÏ‘Nß^CÈÀöÁ9 Š×ŒrxCˆÃ_ÛŒ0.�9£ó„–чl› PdvÝóLg]øt>›Î†È×éý—Û‡{øz~ww~s?½šÁí\ÜÞ\Nï§·7øöÎo¾ÁoÓ›Ë!â!›ˆ �L*DI\)·!aJR‡.¨ƒ¬^b{¼ð‘°9‚ˆ°�…Jc$Ð`|º¢Üær¨Â—@4étþFÇO\T¿´Òe×ú 8ßF$/1F}:ŸÛ1™¨ïâdÌ]/Ž­í-ìŽd0ࣈ…^~žÐ ÿËO�´F$"šÈÀ•–…J›y! t�¯è%»ìÂò3ØÅ™lø,-ÆÚál‡�;O§E²c]¿ª„¥D�¿¢P¦û<0Ý3ŒaÊçêi7Mv§\þ¤`snÏT=4Ö˜Ï5ÜŒQ¡ð«� C+ ¶¡ûÌ*O)F¯õ€å:pPp0ÞsÒ3j¾B‘×ï¤r¢ èwÆI”2Ý+lè+ÉÊì[~èõ>ͬ«»»Û»!t§¢Ä[Ùž]dÅ"N—и«©xá àæáú:|ê«ÓŒŸ¬æC "2�¸2†Nï=žòèŸ$\ôJ{ú �`JÀÌ ô«ˆ³©Ñ/&£ˆ®¦6ך!F®¶ÄyYXOȔܳ%=Ͷ†isu䌋ò•� @yùQÇN™Žví|¶ñ|ï¶Sú&ê�»C äådW•êbÕÌÂmOëd·UäüVÎ(zþä˜2?/<‹qì E é<"A_þÖ¨Œ6½UøØKu°Ú-ë°‹º¦¤Ã³µÜ,“ý„ñ*ÖМîl×ÕI W£ ŠÔsúªÏ�U5+ öÐîW”÷ZX/ÂÑÙ^¢– Ê8ɘr7­%ÇÛR+ÁYASy¢­C€’TaAep ªWÖ÷‹9©XОÁ&£¬ÄVXï–-4E.ž²“VˆÀ'óÛzq‹â<ã"+—…�¢wV�óÇÈz?YpïòZñT5gàÝx™ vÉ£],âi®ñ¯ÃáŒ4ðÆåžw㎑šêƒ’VŽò”Pß&ª”"Åsúë”Hµ ±)«ÖÖ;ýXçÔÿë†?²<ûGš‚Ò¿]¤UÅXGþW»µ¬‡›‡ÙÕ¥e½Úuhä®›QcÇË/IJ0Ún�ZŠ9øë¯.—mhô9uûlÙ¶l…§Ôì©Rs(URW´—I2)Ú½~k®ßžÄÔžnªc)µ'[–V›¸–Ãí|ì±¥©š5"éSÁùÅ%làŠ¿É‹pçÙ×ï‡ÿ µÒÚÕ¸˜xœ­VmoâFþί‘ˆÚ§TºwmuázÖ!RÒ(Ÿ¬õzlV1^ww@Uÿ{gÖv4¤×S‘�Ùyyæ™·Åá~þ¿>0 {áhÔƒ|ÌT� ¤.ƒ5�½HêjoT¾v|švÈ!\¾½|¿­U¡ª ¾ ´h`ðàŸó:I0�ëá±¹ü¿j�Sœù|Ú™+‰¥Åê2% ·F˜TBÒ£ÕŒáw4Vé.ƒ·0`ƒ~«ê?0Ä^×°{(µƒÚ"a( >ÜI¬¨¤ÞT…¥DØ*·öqZ”€1î[ �8Aæ8wÐÙ¡!×’^;WÙ÷a¸ÝnáÙÚäaÑØÙpMg‹åìbÜzÜ–Z ÿ¨•¡l“=ˆŠI‘ÏBlA¹AÒ9ÍŒ·F9Uæc°:s[a�aRe�QIíŽ Öñ£´ ¨d¢„þd ѲŸ&Ëh9f�»hõåúvw“››ÉbÍ–p}ÓëÅU´Š®tú “Å=|�Wc@*ÅÁ]e8¢©¸”˜úº-�(dº¡d+”*S’R+óZä¹~DSRFP¡Ù(Ë-µD0e˜Bm”΋þ‘ {*#Eq<�>Ýͧóh¶XÅÑŽ¿Äqï‚tªÄsjr/eQ§?*I„Ű}ë_”Ö¥‰Ö Ÿ¥}2Ý’’.]°î„!Dw÷·P‘oÄ{x@¬Ø>kʪ¨-{¸s”4ô§}ø³w�eª2ï}hßµ¡§�¢,„ñ•àé£þñJ¾±`©<p!ܾBŽC�®¥ƒ¸c³eìàäü¡×­øÔ aØ4èÖ¼¢€›g·Ê™NcÐÕDÒ84x·ó9�£M+n¸×mjë ¡1 A~ã§\Š¢ ¦32;a˜"e¢÷M—O“ ¤OaÐc¨'Í¡‘>g|Õ@[?M/äì1›|ÃÞ£VéËÔNâuÔ:7¾`¶kô[ÓÅ¥IÑNK]ðŽÚºª´i7”-ÚÉzµa�çò„ÄÒàбDgùb£†$B>4×Ì£~@fÛayn4m $u–QŠï,‘îýb§Fl©—OD--?‹Ó¤ ûØ`NךػÄ]¤óEïD�å�¨N_kÌ™HÿÒ¨±Wû:F�Ïp�Ø8ÏvœÙ¶¿Ø|àQÃdØ8‰ûÞði&í°Äô§¤²½WZ•dCO«ü‚,hÓ‘íÈ€�ºm¯M&$~Wå:Qü�%ìèü·*zÄÃ*¦ºæ¿²ÝÚ_ûW x‘+ùÂíù—¿+Ÿïг7h+Gg®ÿæ=‡Þ›`F£M÷jóžóÝ/RŒ÷7‘�àJ†cxœ�TOkA§&1éZ)’¤<¤u‰©ZQPbk`­Q«D†ÍÎÛìÂtg™�4ö ¹Š‡bÙ"/=û… :“Í6›ØUç¶oÞüþÍ›ý°ø³úéíÂ;Ú#’‡ ÷‘Ùoæn}ùþìÌÆëæÙyÚ±Z¯ZyLˆQ£èú!Vû᮵iµ[Û;$Ûß(|«Þ…Ò–Üé!¤ÀñqaùQ–‰H0‡ùH2]¿gë¦i€ ›T6(š T»¸<„¡-ì=�¾dHB)tÿ¸6ð©ô2ßú=OªÂ¨&PöEpºJà¶_ZðP.êȺñ§ÐK‰óT ñqñJ³n€Z" Žg 0O¤&[ý ò{ÒDîL-‘_.-Õ²Ö©‰±ù”°SªÞž ‘b$?XˉX‰I�–jóš"ò�±økéyAµÆçÎn u•¢Ã…-}•ǧ:(X‰P죀ȧ¸’#hæB0°»L¹Ê®]UòB#&Û�à^Ó¸šw8&n@ÛÕXY®Í"¬« S(*ï‘ç¤hf¤è#øÉÌx —Cîp~Q? ¹�H“aèrUžÊur�(v’ø š}mt«ÿ¼„ÉPŒÀ“<æhü±\¿?ð0Iìö]W奩WAtpTØðÉóæâW¹9õ¼ãbyu)wÄ?*‹ña¹s5-®jÿµüoŽ­Š™Ïñ¾òyaêŸq}þâùlwã 7‹:éß$ �Dá0ƒ7xœ�’½JAÇÑD‹ ¤Ó€… Z“€b…@rh0…ŠÝzsä¼ »“˜.¯ Ü‹è;hísXé#¸w—ˉˆ ÛÌæ?¿æ£ð™~[;y}7×wîüúªÕ4oÍ&¿äœí»ØöžeZu²T—ûž¢rg�1E²ï<ù’ÝN¢çãýêTx´º¸ †/™ó<ê-BVŠìîf’++”]Ö#©ó¢ÔšDêË 53œŒÏ.¦³‹c’¸<ñ9MÐÐøO.5inAd$Q$B¶¾Ø€Ò –iÍ*–x£¥•é²F-ìFhd˜X«e˜Û†Á*ùHm™L¤ÐÍ`¦@'}Ž#¥ïm¸» )ØÖ*î~ñN7x«4y2†£Dh§ï \ˆPd¬èw‚V™f•É¥yd!¨24°PÿŠ”J¥ÀµL¦Ÿ'‡[›É³Li*ì÷9Û$kqC<8³ƒu�AY½á§ÔÅÖ•OcÍžñJ�ÏüÀÒ1Æi–[¸ ¹®Š}éA(�¼)Põ¦¦¶WÏÓQÑx�ºãsW™s¡Þ4€Kí4ïïTÇ:¬ÈšÆB´µ‹+OS3d\0úQ—šºJp¡vEåâ–L5Z”#×£%d7¸… ‰éZðC/pLËe*’Z :Ñ×�БöpþÞðíØQC èÝl×�mê_Pk¿^¯�AZ_lÞtŽ]!4&(|M¸ ß7jñ”‡Ú»B£J!7ijJTd_nš•gÃC ÅZ¾X`Y)…«Âæ^éY‹Ð –¿‘!†F›S—œï<[•”ê ! ÖŠò‹÷¸EHiè3Üc(á4¹‘ˆŸ[)î5ËÉIj[ŒD{;ZäÌÐñbÕ Înm®ó©(kM‹¹”(pܤÚ¡Xx¼O[®ŸÞ»s¨{r½Ck¼Ôðí£,}û� *ÅdUÆj‚ […uö¦©'XMîxùÿ:ì¢iB{rR$ä áÆ*I=Ü‚ô»]å©o7š½Öt“I”Ê~ÎU:OŸÄŽn2äTbÉÓ|Z$G£¨{P´ÓåÄR—2€”‚T´TšÐ‹.dŒÆ§ªÍtó ‹ß^$'ªk?§D!]P ‡OS©D¹„¸Y´¾*–eº”á’¯‘n4ØÒþ5­ªè†j_ t(×y\È×­.|µB/Ñ4ýF_ûðµL¿|’0^ˆnN¦dÛ1Ì´º•1Qèl¾.BÅÙP²t*Åc–ŠÙ¹´_/NcV¤.ƒš~)¼qu†cQãq5s?mÚöð¨•{HÁn>÷³’«?@“»¹ºÈDWøÜž2·ç”b/§5OÈ~ó\}¨¯#ÞÌOèîéÞûÏwÛ©oAï@%}Ø{x-þu�W¯à‚j-%Kã_—Ÿÿw‡ñþîYâ{±² xœÍksÛ6ò»ª›Ô’F–œô®âs¯®­¦žúìŽe§íu:,EB'ÉòaY}ü÷[€€/P²Òrò2,ö½‹å"“!:© 'G“áð Ñ— ÏÇèÙ]Ziùø ûcÆé+'Œ¶±·\¥ä§Kþê;ôæôÍç軕ç{Q„¾µq‚cÔÿ@ÿþr™Íçxì¬ê2XòOô. —°ßÍÍ%ÛäÆsp�`e� 0ÒF‘íÀ_ìͽÇqâ…z3>E}2¡Ç^õgÄ6ÌÐÚÞ¢ LQ–`€á%ˆ’…Ÿ¥È �®#ß³£�—®è> ʘÀø‘Áç© ÓmDhGáBžˆì”!½JÓ(y;™l6›±M±‡ñrâçó’ÉÍõåôv6=ŒÙŠÇÀÇI‚bükæÅ@í|‹ì0rì9àéÛÆÈ^ÆÞ¥!Áx{©,G( éÆŽ1ãzI{ó,UÆñ²å À2;@½‹ºžõÐW³ëÙˆùþúá›»ÇôýÅýýÅíÃõt†îîÑåÝíÕõÃõÝ-üô5º¸ý}{}{5BØûàç(&ša%v)ßf+(,Â¥$ÂŽ·ð -Xfö£eø„ã(BŽ×^BDš‚.ã{k/µS:T¢‹l49:ú‡8~æ‚øm]õ¤Wÿö‚4ÝF8¯¾�F}o>·mã[vÛ[y Éâ…í`1D¸]5Æ% “G­‚º!øð_è ;¾3Iï忈C3PÌÍô‡«wH¨™Âð²6~Î)KA0E¿!xœ¯lçÉïˆñXù …ÌR¤=Cñ*Jc “CüÞ ÜpƒR/õ‚ rd¢³²c Kj0g¹Äò’Ìd¬øXC ƒÛß`'”]I5DIä¢4¦‚|¨b½Œ¿“� A¨DêàŽÁ麄V`–~ áÕÓ6SÞi¼Yaêê@ˆŸÆI*o ¿¨c£6„Ï-�˜‡!§L{Øzl1Ó#Ë4ZÝ0ƒpŽdp7T>qáG5m³r¿£éâ��jjÈi*` !þ`L‚b°ð–Ä£1º´ ‰þ–FP{‘Ò5vj@c¥‚•„@Yx Ä�4Õö6e#ZÕn—¶ïÏÁ‚)×6+œKŠqÔ#Q×v·D¬n �BDXÉO:Ír0`/ÿ§´“ð¸O I0£x™­a„ÿ2Æ TãjÞ¾å°ÄÎO¡çVS;(d®&N…Rêß÷ïHŠ7ê&ý¡ˆò Ÿ«z1DÛQ>{˜o:x ¢KûšP_Z$Ø k—l ò³È%±@(wŒì=t}*X5;óµÅ–76xƒA¸!€£0&ùUÎÿÄ2±hØgoÊ¡ÀùÙþ�»ÃßJšsM�ñ@÷ŸÍéÖ½£bù󌤠xN.V5Þ ƒ•\o_ ªZXÊ‘apÖ›Ç#±G¿P®!¨ƒ=ª‹jr8Ë'eœ«@šgûûZ �¤BÃÈ8T5¼, Lס‹:óy´ì€�¬Kaz¤¨ßÆsÓ•:´Â䨧@ä)&4q¢3¶_?LöÅÕh¡›óŽÉÇa� f›5¸¿ÈžÃ;õðË Vˆ¼CI�„¡ÕÚ>iÅmç¢#.ÌWÍo5»ð $à†ÍGcìyOØz®ßŠc б¹ESnàTŽ'"¯«8«PÞƒFB ¨ªå3®H¼:gGš±PljÖü[@,®v2y9Ò 3“iÝ€ÍcË5½®[­M£!¦ž‘r’\ÇG®- Ë‘¤…•ÍÑgÔ‚§” ¯íÀ^²8\…rS à8µ¥vbŒBZYß…`F°ƒHÃbÈØkv*mìÈ�C¬ FlzÞÁ]qjˆ™OŸYªµ†„2tw¬H03G'/õ€µÉQõ rÜkyUªV'rÌ¥A+E‘�9Ð ñ–°C ]bŒÅ®£ßÍs7�‹.—Ø¥©|èäè‘çõNпápѯ4`ço�ú·�77DZJt·�fq€ÈË|ªüúä ™pÀC­Ò”§ N8ƒÂºYÔoZ0’` ˜‚Ö3ÊÅ=Üöµ„…/*ÑöçQ%‰r ¤DZ¤ÎM=Œ™Jð)ý2FvÊJÅI_fÏ€B–™Ð®â"3`žXÀÐþW3kzw?B½¯mÏ'JÖ„í«h0î��ýQ?°,Šâ S6^T� Ó�;Š,Ï-̲Mò£Ž¢MVkmYaîäYj³È ’ Å]bÊ«h„èïŒüôþäXM´"*è �l‡|–°–XI,:)� ÇXsZe¬ák$è&Šr‰v²ìZ Í }Ié’?eöØ®+R”~ ÷5v$|Úœ�VKspV§bZ–A‰ð׆hi‰œ Õ¤©Jý0AðÅmWÞ&7ô›`B¾oM¥ŒNé¯ÜíîF0õ}r®žõªu©]Ö*Û>­?)Žj�–¼ýùäÜÔÜ¥ ;0éêÕÀ�Rõ �+TÿV³L–õ±T¶”Ÿ½è()y›Y<6´Å dÖ‘§Ö:{%ÜàéíÀ²AOB{g�ªÔ+I·8O¨)5��ë-««6•¥WaoÕçñfÁŒ…dJè¡E B�H|&=õ»¸­F™ÔžÆÌœJ‘ZËL.!�OÛÝóvÃd/™W;¤Of޲¡äªyÉ¢^Sµ )å·(/ P;ùÊFjÊZb–ÿsÌ >­?�èK¶ÕKªµ§R[JÌnQ›ºÞÞzýŸyÂ;ÔÏ»I»öj¤Kƒ4Å<ÒÆãDºpB}¯Üí²Û¦ý±�Î2ZÔK^&ì�5G650‹èª¦“‰û„M_OLª®&÷?…^ÿwÖÃÝw7Ó÷ÓëjzywAn»Yï_[ÿ½»šZ³éýûé½5»¾š¢·Ö]Þ\Ooè:Þ´bÉOÙÍÎoh+]q•ä±û/° RùÞo¡ãW?#;¶×ˆ·sÊÞá1 ì¤ÊEó%€±Nû0&½É{®�jL¤µÍ»ÐŒ–Vïb¢Þîݱ¯‰ïz&ªÞâ;0¸°ÑÒ·@Ñ]¾cBV‹ËZ¥8€ÒÉID¶Ãç'Õ›Ì)ç.µ‰Šrf;ŒA1kJôMÄ“‘fº`‰¯ªž~Ï[–õxû8›^YÖ¡.ì¬ùÌà¤OÒU8”ok‘ ’Ÿ>ûYôl’ç§Sòs¯Ÿô‹\ºþ©‹»%PT�ìuYÞtÙW »ì+-ï rU•:¨}qÎ{"u Oc¯(‡ƒ\ïÎÑi®ö¥ò²Ü^NjÊ’‹Ð•÷U¢Ö—;|ç* šüùsµ“28Ïvw†¶uõjÁÄ<|JfnpçF²c337¹Ñ!­ª¸uRñV¾ŸSÞ²tU§H0/³8šÉýÐrLwü1ü¬íäó4å˜^C8Fó,MÙe¸�oue÷~é�XÅ®[)ùÒ©—C¥"ÝÁÕûX¿£q\yQ½ëõ©¿“¦ÿÕ|¬]d9æ7Ë[8[yýëÐŒm¾öp²|¯ÛF¿ð?ÄÐûÝy¥êˆ½ûíµ2gö¾ÈVÉìÆ;mÒŠý¯·í—é‘Då“–úã�št]/VÁ¡¤ÀØtÙVY¶%ç—šueÌä£Q;z2vfk¶ò~óšó­úV¾–¯uŒ´Lok`3*¶Óe¸¬®¶'�àx«˜(íM—ÄYÇη°ˆáÿíèr±ˆxœí[ÿsÚ:ÿ=…Ž›ë#yÄÚ¦™öÒ{$!-S }@Ò×¹¹ñ[€&ÆòÉ2„¾Éÿ~+É_ÁÈ#$휧 FZ­V»­v%Q>@§ÛzÐAy¯|p°‡Ðoêäq:¦‚NˆoXPª*,æÍ8Ž„üv}AEkU+ÕcôeDêyè&>á¨x£>ý>1¬Ñ~¶4y‰>06„ÞšÍó°“&µˆë® <Ĉ š‡-økJèšpŸ2U� *J‚BXUØ'YÌX€Æx†\&PààA}¤En-â D]d±±çPìZM©©~B.†äñ-äÁú9Frìˆ Ò„‹P葞ÿ¶\žN§VҌˎ¦óËÍÆy½Õ­‚Äa‹+×!¾�8ùo@9Œ¶?C؉,Ü95Z%D@]йõ¸ˆI¥*‰­ôÖ%$#€i‘|�Xt@-š; ð� !›îˆ�Gø˜úÒ¤>hK6Žªha\²£òÞÞß©k9� æOauTHUüÓ¡ý>öI9ü4FïS•¾°m2�e€|˜I¨~ë1.5&bÄlÿa³KÉ ·õH†Fm4uÆø†p3®iaÊ™ ØvŠ{ý] ýâ ^Òå H!ˆÓ3‡¨6’P}ÝþÞŸ)~ÀØ"’ ztV‰0æðQŒ;’sž€ºâeN© ó*jAÇ`ó˜­l¯ê‹)îó FD¹‰å4A‹•Š2qJ誨J˜RØe ÅJy·w÷Æ”2®´'èÝ&¯3ý¸>º`=œlY8¶ÁÁÃBÀdM ¥VùÀaX Ð'8¨~ aÇa]ヵF¨q>|/fÙGp_›Ÿ{ŸÌnï[³nž·›íŽÙm7o…|ú¾9Üöƒ�  ëŸÀc ДìÓ/ÆTJޏs<6|æPÛ°˜Ã@¶ò?ø$|¡D ³8Ò4Ú£2‹,ˆ àbv¤)6} ºy··jp?tj�z«÷vóÎêÌ‹ÔVÒÝ?XS£!Ç6%®0œ�7R¶mS©ÀÄ<­ L_ ÃSâ/×á!Ø£áhÕî‡Áõ¶`P‰×VT8ùp0ù�pp±ew°E,Ø?ì µ á)ŸruÙBf‡ÌÒ�È<‚{Ûć AEÍÈc2.�Ñ·ò?Í ª÷˜£H Ô2S€F’‰D¼ÀbnqæCò¢JûL6> “¿„“Ÿ Á9DíÐh¥Æ‡8R4Ùô¦¡Ã÷‡ ä%x²:ïÒÜ$™¤ÈŒÀ#®M­ÀÁ\‹ëK L1f­XiŒ˜‘Í™CÝœ©ÊÜL‡‘©™–3¦<뚟k�ŒhÅt¿ÎsHY4ìj6ßU–Ã6zz˜G¸-¡ÙÿÁ³rw¹îQ8"ñ:lX°\Özµf ®\?ðÂUGëF£Ø…š ­“a@óÚY»Ó+¦Ê8w#aÒ9ž�…t.¬‚lËg·H:uZu¦MT.½g³âjjÓ»C'Ó�„oþ²–Ê{ó%æÐ+ãÙñì0•“ ë“ïÄѸY�Ìõs %KïMì(÷¢TL§V„y³hL¦T§ÜKò§bGøCÄ�¥gVÍ ùó*†ò5js<Õ*]­ÌÐ%kå§ò`¥dŽ©ŸÒ¯»î.ÅŠ]�P’׺÷s[9þxs 5×–îd¤õ §3!GîGÚÔØÀòk9<¹’f쯵¼ºlˆWZà�GSð4àÑ¢P"©pÎJG=7ìÛXfÙy_‘ê,6X"Ù˜M,xón2C£$Ÿ£�è"DÁ ^+kÓ£Ã,†dëTÁ:|¶Þ>œh‡›2ª¤›oР²zÕ‰àÐe¦x pŒ?poˆû<ümM@d�½ñx×o°5Ó,ÅÚÖ>„Ϧ“f©ó±ó,B娧Pð�˜ÔbîýÞÚÒRv×^a6ò³¡Kö- O9*£jÕ¨¼Û†Î _¢¬ ©*¡2Ú–-TÜ̃lÐy^ktÚæe£Ù4;W�˜×¯ë-³}qq¸z ‰›jæõ¨’÷~/·L~=Åã_ÑÑ~¯x 0{™*8ÉamÊ8ýf™%ž:-‡ùO ͵°Y5^ç*?naaorÍF«nž×¾˜�öUëâ/Â;gUx“`ãM®x9­øv’F\¶à¾•h¡ýÚ=¿Y¯ÏÌþHˆžR×fSSP“%¦sŽì˜+Ô‘�üÔEsÔÖst ˜'UùÓa™€‚ÜæfqÑ1ÀÌ“âð}2ýä“'¢SžËÅ_cIÑ¿RïoåÞ‡‹ÇÄ.<�…l–¶lf¯¬QrlRäœÒ¯Z8XTtŸ2ÿ\iÃyãÉIm¸ ¶[=³Û¬ÁßV»ó¹ÖL9U�OtžÍÙØT$Åù~4Ýþ‚«Ót€ ´CÈCØö�äB.å�X‚UlŽ5¸ÓSÔdà»PÑ—·Z¬ý‡^1Q—K4¤`+ O½¨÷}S}-~$Þ­ZC7"6¹K±n“(‚Ü”~³^”AÖ¤ÍøØü6òêºr©P-|#¼ ‡zDÎ@,OA $ä¬çeÄHÖ73ÇÄõþ¿ÿ!†ž¹Ê�á‚´X¡”XÝ•RµJíQµú’S¯–¡ÒEYÚH¿aô=Ÿ*Ë4[µ€L·×¾h/Üî{‹:ä�¸ò[ 1y³n@‡—7¼ 1†Ôw&Ïmä�0L ˆ™qzKI¸U¸êJöFC*FAauðc„ÒBŠZ�Ö�d”ïÉXdmÚÞUº,¢>ëš½z·!W·nvë­Ä_ÍâþÞà!kcéYDÞ Õ‚u#nÝzOûZÁÐCênȲ%¯ §ž¼)t(·çÇkÕêI.É9 T†oúÜ(©ÑgFÅ#XÄOB¯�ãºÞi\~3[õßÍvÇìÔ{W�V1꣄ZWÍf %l3>3½FÆÛ¡áù”Zb ŠÉFŠCõ!G¼I2QI}¨Ë=ñÌ’�¡EN—Ý(JÖ,C ɶȈ٠}Q^+·ƒÁ«ÊIŪ܅'6wk\-K// ó5ûáò�³«K³þûU­Ù5¿´>$Kt¢æD=öŒ97T”ã¾Í¤Ãs‡…” ?2N¿ÃŠ kNt굆Bãë3k*5¾É”¯Ø ;¤ŽCÝUª•W0Y ÁRÚ ÐÝ_ÑvÜß#k<¹e4§u˜¹°Bl¦óɆ:¿^­óÉŽu>Ù‘Î'Ët~Añ�¹ sIJj‡¹Zïö†z¿X­w{Çz·w¤w{M½U+,ú,Ç ÕOïÓ?ÞÔµgf¼+à|h�æŽ×ã•8Yä#ŠÌR{—j8ŒÝøÑo ÉÝ6#y,O!6=ªÕªV·=ä˜`uô²°i´Æ¾cz—D—XÒÏãa éý‰Ì_Ò?‘q™{ÞSQÛ¥y¨X‘ÅmŽ�×0e_W~HlHÑw‹ ­ù]"$Î,ó|ÄÊm€ŸÔMdÏžÀSdx:(¨K(#qŸÃXcçç'õK�²+·‘à±=‡²ÚÓQÛ¦s Xº‘·¹éO@yÕÊC ·åmŒ¿œÌNcÎB²ž��åÆ=”>gBd&ZH…ÊÓèzš­Õ†þ×zãÃÇžyÖn¦÷FÔaè):zUº'<^yD„NJè赌–ci5º­Š]!r§Nm¯öäfÞãA]anw®Po4êÓ«9¨ß»ý@È?—eñ™€öu Ú•¨]Jþ06q¸ Lvð�ü@0c zÇAôª^ŸÏçŠÉÑ*~8ª»b\Tïv.Û½~»†ˆåŒ[Ï%Q!ù(íp f€ˆ,sˆ8]s~æ($Øû ñ<¤1õFUˆ|'ž›!alÐDqH‡Ó¸ °Š�€*3=8hõ¡Ó?€·­~§_eL~ê þy};€ŸZ77­Þ ÓîÃõ \^÷Þu�ë¾]A«÷~èôÞU� ºp²B&¤L•ÄæzëR€àøR‹:ÔBѼÑÔù3z($œÐˆ™4B€6cãÒ �͘“Öäb ÕK¥¿QÏr§6šÕaǹÞs—‡fDêòW_ä{-“†>£e¤(¶‡¾ï®‘Ÿ\gÁïˆåš¡Äø¬à+ELT f>µÁ͹!*—GhÄp(‚8¬ :.Ã02fÄŠýÐÐäclQoÇ×÷F4žÚÔ3Ý]Ã̘„Åswb§‰ÂTÀ„A+ÐlÊ�™x£xŒ¡™d™¶gù63ü�Æ‘ðn?&8ÏE°M®q"Sèˈ\‘4`N…ÑCX;¾ëúó(�Câ)sú‰Ħ&�DöVï“ñj+ÎÂ#ÀŒ¯Jwww%8¯� µ‹Ò¯ Ú¯¥ù¤•fòiƇ5‹ÃòI_v̆qÖhpio¡¥)õâ3ÔaÚ"3qƒHeý¬©¿Àø�ë·^‡æIãX×Tä]KT¥ð>u¨ªoU>PM©*ëPUK¨£%cõŒªfŽSª–ãÐÈQÙŸ ždT-£6‹$õ´06¡žøjš÷ï¥{t qkWsq–jOñ'"1¾«JS8,º‹�»ÀÐURÆÂo‘ÖHi³”vŒ™üyˆNh½Ï…t²RãÙ�Κ¾ÒÙ# 5×�<ŽÖ\è� x4E_Á£oÀ£ét¤©RI"‡·�²­qBâ±ofç9ÖöÕC¾lP�L[O†�Ù v¦ít,ËBÇ:Ò+ïË•wž¢ ßÛLvœŒÎ’GY) ¾í­~¿}3(s2œÃm§783.ËšZa¥[nbšýßlˇœÇ/hJ™?ðÝ+ÂJÃôxY1 °l+¤Æ[!s3PÒ·AYr¿WáþõÆ™bwÌ&©é$MLÚ4KìÍ8ñ¡}9¸¾1ô+aBÖpÌ µ áÖuîÖ°„è¸G‰®Ä•këC+¡~$Ñ'ˆÌ)§ŽRÉôךBQò£IlDþ4´ˆa†£á¶ pæn™¹¾ƒ²ºP58?µÿHÝ^e."�"2Ô…PŠ jŸwY-.i}ÂÕP•Êg¥ÆŠPÚ¾„Ò%”°�aÚ6¡,k²¢Ù ˆ«�Àn$R•‘×Ál«ô}é@!xq}ïKÇûÒFûZ¤?Nω�ƾôÐØ£¾°/œìK'{NzϪ¹/¡š_×°OvnÁ!$î‚ÅÍî>©üº¾…ûwYœÄ*Ÿ[ÿ½Hå'OÉïP'QVÁTÁ ý Ü ½Þ�ÉÔwyåÜÕï¤vî°ð ± ¾—œ—[ #^ E‚»T |,¹Vù²Ž"oE…8.{6»ÿÁŸˆ]\•yRÇm!µŽ:X‰RוrxÔŸ†!¢aì¸�@8§ð-v)à°Ñüê(-áÅ šµ£�ê;!›ÛJ8뫬Míx'áùÐ-,$ëßAмŒœ3+•ÎÙûÈL¡ºJœp‘p_xÁ~’…Ïð¤ yï"/øý-¯û³`ÏÑ„õs„¼íódaÙ%½ùÉ�+§Ç˜ÿÛ;-y°YY'$®LDø”%ÆÉÔe‰1e[»�þPƒMÚ·˜ödšH'þŒ±ŸÏ“BC ;Aà’Ê"¥,%e)9ÉL�îà\�T}x^z8IÓv¥ ‰K½/!Ivdú†Dz;;-®»M*ÁjÛêNߊj>Çú¦DÙWúy 1‡íì…ª,ëŠâÐÿ´¥d»õh 1uÏøÜ²áƒ ãÈ®‡Ë¨'þŒ©ŸÿŠå׆²{ÛÇŽå7˜[³:øŽ¢G$Zû.�Ìb½8ÕȶªÕ«ÀtXôsö‰à7Ъp€rTSᾚëc‚$�ü2ºÐË¡'Ýâ&VöcE?h÷Æe«ß6úíÞ ÓkwË•Üeo��9ò{ìFàâN7aÚoD´Åá” Œ�Ö£Ô!´¼÷ê\X-ù²‰z­EôÄ—�‘’–=;=++FÎb8euá|Ân¶òÝ f†rOOg2Z¨åÞm·Ëâ"›U‘¶Ï[éªÕé–“UQ¡W&uÙ÷æÍÜ$‘=âi(©÷[ª6Ä)žƒ�*Œ”w9mwä�‡½c1%�=Ï&,§,סl‘YµÌD”Ýê¾ÆŸsÐØïÑQæã.æ“–K•ßíúBUÅ塉†(îãêÂq,ÕR§ÊŸuUWh/ZY·N"ïÚo¢Ùí›ÎÕGãýÕ¿ßÞ^íݶº}ãÇÞû r¦öLiÕµ¯ì °x£Dí¨üþ „"Ëb»bqkê~z0ž¢ú5õÅ¢Q²ÿ+¿Z82_ñxÊLñ-$ƒü'‹H^8ì ÉíÒÓcRc{˜Þx± Løÿ•_-*¹»ˆ°ÔšÌßB\rÐOLöï ž�ûO¨ìÿ1Ÿvþa÷ý««L¸»xœ½VmO9þž_1 _ YHO€zj )�"¡Ÿ¶Î®wc�cïÙÞ¼pê¿@=�°ÑzÆÏ<žW5øø-¨• V«@ >%Br°<såÂXØL²yc„"/�t67"9ú:_|Àn´̓æÜŒ„Yß·ÜÀî_þ÷Sš‡¼�öÖ�á‘p©uŠ&»ÝóÒHWD\YC®bÄp#­ŒEøSJêð�+´‚fãvI¡ZŠª{§1×9ŒÙ”v�[Ž‚¿ŸE2.OÜ)É­ÃÿÎ…ÁÛçÀ2d±!ò”l ÚK G™ÓÄxj„*­ƒÕ‰›2à CäŒænÍa ~xíUtSPmõ¡Ó¯ÂçV¿Ó¯È}gðõún÷­ÛÛVoÐi÷áúί{�A纇__ Õ{€o�ÞE8º íðYfèHS�+yìýÖç|�B¢ J6ã‘HD„WSiÎR©žp£ðF�q3–Bj‘`L0RŒ…cÎo=» *;"AAaxß½ ûíË«vo^tú7ÝÖCø5 +;(Š¿¢� *’yÌáLŠá�Y”¿�ѫ҈ £io¹%”sóŒÛR3)¸D™Ì-ýUøÌáý z^…*;\Å"‚5ª;¸a†�9*[ŠtÌm„{ªBÈŒF9�–èÚd“LaXóÈ!4à"˜ë$±Üù€SZ×!1zŒá)&ã¶q”Á%°G£Ã±Î)ç´?ú„÷%—k#Æâ(Ӿ߾�$Ëý“°,©½²KÞ)rGbNWû¼Â†6$qˆ3h­¨6°P:Ý:tú»½/Þr?¨½ü:(ÞÅøÎ†¶ŠWçò]ükopÂû4ìÍZ¼'xœ|ƒý‰PNG  IHDR }Vö5bKGDÿÿÿ ½§“1IDATh�í™±jÜ@†ÿ²òÒ+˜4ר¬\%1I¡ÆW˜ãŒý�§sŸ¸Ö66‡‹k’@Š7w¾9?†¥Ø“FŸ|p+í¢å¸ùÊa?Íìh¤bAXW²ŒfŒl,YÏŒý8¦ÑÓ`Æ kùög³tÎ7Í¿îõ¯²ï•òKKdx Âf–qòÃ8ôý~ÿÃ3ÿôô«Q~?Ë89<üظþºý»¼ü¶YöÌŠocÿ&ïßÔ÷ËÉ…Rh<iÊ93 €òÏ�Ù,Õò•š<ó§Ó?¸º:nœßÄgÆh{û5NNÞ4ª¿nÿ¢HåDö|û¯“ß¶ïcùo³Ju½øâ7ö½š² XEPpŠ  à@Á)2€‚S|EM§º^|ñû>ºß;Q¤rhœdŸŸ}š>˜© ÀÈ?8xßQj¢åÁšO„.3:iÊ­ø77gg_¬ù¦ýo{ÿU߀  hždûþÆ¢‰oì‡c(õNÛŸL~½ÞÎ\ðî�ÊØëíàúúgkþÖÖ«b8Wã­ù¦ï–û‡4弜^-ªë£Håå××ȯ›¿šÏ4¿kÕúo;¿Ü„ˆ/7!Âú"(8EPpŠ  à@Á)r"¾û›““ì¶OâÝÄñÛN¿ßü&Ť~Óü«ÖÿEù�öã1ïz~x±ä€=¥è×Ó`Æ»D¬å3óÞím6ç›æ—úW·þÿô_æ~Ó°#ëIEND®B`‚2Û;r²xœb�þ‰PNG  IHDR<¥œÉbKGDÿÿÿ ½§“IDAT8�å“1k1†Ÿ„þýâÔÝäJ&á P\J‹¹Õ_ÐMp?ZÜ RÁI·pí¨óý“ç•ã4mÅÛ.[ž¼y¿�/o fKã)Y°Ö¢º]±n4®!ä„gûýG5™<¬‡Ã× ŸŽô>>½‹Åôȧ\w>Sqüìå>_ÿ>N’¸}qù¾Ù¼­„ûüË<¿ïã—úç{IÍÖ•1+�M/‹œ1+�¹“<Š^t«u³ç•ÝîKt:wz0H~¸sh€4ÝêÃtåiºÕQÔÆ9Nr_?eý_uÏŽô¹‘«ê T¥¯_¤­EŸÞZ€sV#4›Å  ¬ßl>ÔxüI¯w¯Â0ù·�Oi?¹Þçÿ §s.¼qýZIEND®B`‚@M¢S¼xœœcþ‰PNG  IHDRF  ´(“bKGDÿÿÿ ½§“QIDATH‰í”1k1†ßÿ‡þ éÔÝäJ&á P\J‹¹Õ_ÐMp?Zº¤‚“"náÚQçû9&.‡VãÇõL¶f:ÞçÞ¼_B¾ø_ÎÅ”2çXàVÓZCt»lÕl^GŒñÀ,3ºß“ÉÃj8|�âøÉ飨hôÍçïÎ=]µÌfo"MŸIFåQ磲ÌìŽo«ÔZ­[ïŒÊs±r/ŠùÌ+5Žým.«±ŒÊs1V�ùÌ+4@; ÕLF幘©À|æZC©¥,žâ¾ç•ZJû}–%É‹l·ov8èÏíö[@§s'ƒì3ò|#ís­Äò|#“ä Æà,£êtùªÔR{ÆÔíù³)„ï‚S·çCÌ&ÿ¾ fLÝž1›üûZC÷™Ö`Œǽ;�¦¢øéÔ·^Šñø ½Þ½ˆãìÏ{R>Ÿu–>*ï3¨2+¼æIEND®B`‚†iÃ϶êxœ½WßsÚ8~ç¯Øá^€!8—™»‡¦É”÷މ lŽö:WØkj,W’ \{ÿû­lŒ!”K3õ‹±´ûíî·?$¬\=×-«fµZ5hÁ«‹)°$ÍT'À…|-àéZ°y¤ôWoó�  ç¿Ã]Äb–¦pK¨¤Ÿò÷«y6�ÒN5wÕPå7øƒó9rœž1â°€&’†�%!b¨ˆB7%¾ÌNþ¢B2žÀEçZ n¶êÍK ±æ,È® “1˜„<º hª0,ø"�I K¦¢ÜŽAéhŒ÷ƒOAq:và³² eœŽ”Jå ËZ.—’{ÛábnÅ…œ´œ~ϸözl4ÆIL¥A?gL`´Ó5�= ÈýŒÉ¸2÷×/S,™·Aò™ZA5LȤlš©Â6þaØe¤Œ$PïºÐwëðºëöݶ™ô½?‡c&ÝѨ;ðú¶ Ãô†ƒ›¾×ðë tïá¶?¸iEºÐ]¥BG€n2M% sÞ\Jw\˜ñÂ%™Ò€ÍX€¡%óŒÌ)Ìù*ŒR*Lê”Jt0Ô01[0ET¾´—6dÕj¿°$ˆ³Ó_”iT/­½ŒÙtJ$µÌ»]—6¥ q=_ éŒ%&ÎÈ»¶?¸^÷µc—„—±°Ô:Å4â/?È„äbnWbUˆø ’`œ…¨Ža¶o» »nhaÂý¡îÕí wœ% )‹�̘v`"H*u•ü×ÂXY À_Æ õÉO -_Á×à󀔫ø¸Õþ˜«k!±Ý‚ÍÓÚ®ù©—�cVø:^‘)Ð+‹»f\ÅEQŠ(%h¢ÀØÖ¦±t)"Gû %�)¬H©Öš.c}—cJå±—µ/ ÂÎ?e)¨¼wuÅ¿ëd$dA¥áÓàI¤ÒöD«Ê»±�«|¸‡+“†ç­wëß ûÏù½ñÈŽüûMwìxZ¬Ž•E²XÕÛGäG¶ÛÿÛöÝ\Ažaøìz’†]¨Ðÿ£3)t–§è¼í¾«DQ°š÷QäÇO7ÓÐ^¥\èQ¸ *âá›mxö\�¬6a«R‚E�4]¥ÙÚOmŸfíë�ÚGg6?Q“óùœ†~@â˜�_ñHÂ,óY£¢ÖlíÈfÐŒGg ÔÄ.T™H@ݳkÛ묕=Å]2àÐ(ÚÓ8@‡Q}tÎÿKP8ž¿4¾pVRRÌ._WßIÔ&™3¼D4Êë?ËI±†öÆw«ª}læ?6ñXxÛáÞíòxx1Á1ñ,Æ^]×yæÂò3g0ôôŽ.¹Î±â„oßj›£ìÀö^‘�"~¨áL mÅTýK8GL9\?Ò8<·†Á·4ý«2ÿßi”öãB<ßJ;ÇŠ�ûæ±ö~8MZQVÂ9µ:�ÔÕ¢Üs§N—BëòùRgOËÎ�|Ü?L}\&¡þËcþ>õ$Ö‰ø¥¡‘Œî,‡uxœuQÑjÔ@”A|ðÅ'ḅ%B"}胥‹[D\¥ØU‘él2› MfÂ�I¥”’_P÷úÞÏè_9ÉnK»ÕyÈLÎ9œ{î½/ÿ<ý5Ä�´¿Å·a�ްýz{ïŒÉK‰éô-„ÎpP¨RÕ5>i%]^½y°|ñ0hÙ–ÒiÙdƒ´!k(.·°Jh‘Ë °6J»MðXžÎ� ¬C=¼À«L.”–Y0Û?œñÉx6æ“÷ŸG, CLÉÔ:mAÆ8, ¡4æXéM ߊC&œˆq(%Žæ¶ëÎq’Ö”'’×ÂG1„m­ŠàN âÄG“:ó1�$wYÖ'ø(”FM­ÉIT¨Ñ®�h´r}}ÛûûFQyiÐ=åi„ÔhŸ.-! =t2bg þ¬ˆë¬µðÆÜ¡¿-öp†¸'º¾x¦ˆ×ŽÐîmD?ßý‡—•Ž»þ²ßtV½¦;ße¥*¾ZÝ�8Ú䯗sOÑÞHÖKý¿Ç}âÓ—é´ÿY‡&éÒ·SÛ ûDëÉuÊ0\Md´ËÎ[~}öüI/�S,Ç�wþ¤åºå‚'xœ›Ä³ˆ{B¬hrbfQ~|qjznj^I|JfqANbåĤX~##c÷üüôœTç�O£™êSRÓô2츸”3ó’sJSR”Ð4êe(qqéki)„æe–(”¤—ë)hés%çç—($ǃ„€¶•Ä—(hÁØÅѱ ¶ Õ\ @ Vž“�ÕM@­"õ:`•~¡>>:\µÖ›g3‹2r€…€`ò3 q!®Á!ñ.Ž!Žñ.žA`ÙZk0U”ZRZ”‡ì–b 0kòbVqLK‹Kô’& j°€üÀAdzã „ xœ›Ä3ƒ{B,_JjqvI~A|AbQqjÑĤFÓ�Ç#™ âºq}T®^†ÝdQfm¶”Ô4 sólfF). HÎÏ+.QH*Ž/I-.‰/N-‰/QКü�E„ÙžÉ�¬¢“ëYy&w±š+ëk)ØÚ*¸æ¥(ä§) ºlŠ^²ÂDAM-}.B¡<¼í7…xœ�‘ÍJÃ@ÇA-bü¸­¤±¥õ´~`µ)KL&1t³ Û¤Uú^¬9ú¾‹¯à#x7›4i’z0·ùÍÌÃüÞç¾ßZÓ=bÊm`ƒ‡V¡V­í¢cËÒ I:øü6'ú²âÝy,æ7 ªWT 60Ó ?ëAVÚ“HwbØbQÍгŒQ`IÖGEÕRÚé1Í ÐI#"»TyLïrú Û2’iø™ØÔ3ã̲luG¸Ïù“ªcüŸ“ÇoÁ{ŽeèFohHÍ  –Ò¹xV¿Â'âmß5Ħ$^–“Ë=‚;.Ó¢sUýuT©ÄËgx“?ûþhi}¨ÇwÇp\»$3½{_m•ˈù5£¨~wz��ÄSéæª¾—÷^§^„`1!Ór+³ Èë™ æúFÝP]¢ëkó.skaÅ}ñ^>¨W£ÕІulˆÍ…0´ãr«óˆÅ𙥥�pÞ�yä;Ðqø=¶ÿ£ ¿Íè¤ ;ɤqIa 0&©›k¢0°£ ÁBu mUò¿¼Œ*ïå…xœ[Ä=�kB,{Rbrvj^ÊÄ¢FÓ�—+˜\ úPZ/C‰K93/9§4%UA &™_ZRPZŸœŸ—–™RÂ¥¯¥¥à‘U(�œÂ¬œU_’Z\_œZR«`«PÍ¥jå9¹I©ñèJt�%Qì@Sâêã£ÃUk=9“¥B¹Ö,V”ZRZ”§�TŒ°QfÁd=VC˜W'ç±LTà`QÐÒçTW‚æ?†Nxœ}ÓÝJÃ0`tŽiÁ� âÅ›^‹>ÁðJ¯dެ9ÝBÓ¤$'Î)ÒWPúÞx->‰àÙ±îÇ5YoÚð圜z>ößz TJÄ¿ôçöžAÆ j�®óƒÚQ+8á2–9.7uÊw{t�f“ÜÎÖÄæ¬všÍ}ƒ}hî{䊼¤xÎÆ"Á¸? Þú'J3ÐÀ|úäE g–UQIw`¨$R.ç±ëÌT»$ Ñ�Y¡‘IÜa<¡CÏÕ�øÊ(N²„#¬—­º²˜Ú¢JMÃØ—=¥„ƒè4"Õ<áÈÁ¸ƒ5„HåP,/•­¹áÏ0 ž³—L5PÏ¥\}.ÄàDTº™•fuDCO¯‘Wãæ‚Â_îB7üSÙJŽé‡;“-Jp˘K¦ÆS:6iõZ7wÝnðz³}ÿÔ¶åæíúÅ|¶sY?'»;¤Ù þB_úë‡Jxœ›Á½’{B,{yNnI~~ÎÄìF³�"™&‡0k)æ%甦¤*(e–¤æêe(q)gÂDrSóJ�"›«˜³u zãAÊâKR‹Kâ‹SKt¸ @ &Ò4ùK2?L¤V/Ya¢ ‹‚–>?V/C±îxœí}kwÛÆ®è÷þŠ©ÏJB9’b»MN—§ÛqœÖk;N®·íÍÎÒ¦$Úæ Eª$e[ÝÍ?æ…!‡”üÊé¾ë¨]±DÎ`0 ƒ0ÏVÅö}}Äê³ož­®~#VÅßÎâ$WÉdpž‡Ó‹AqæÑ¸?‚wøú˜~ z'ò(Gyœž‹Y'qG…8Ër¬~Žr1ÎFŸ{átZôUí¿�²é<�Ï/Jüµ«ˆ`Ôk/ć €3�Š¿‡Q‚Ïô÷oç³á0ê�.:n5ªòS–�Æ»ª‘ƒx¥ 9CìDy‰�i8‚?êMWüß(/â,ý5`�õj¥³… æÙLL¹H³RÌŠ`ÄÐ3$Lt=Ц¥ˆS1Ê&Ó$ÓP+./¨»+~S0²aBñP`ßEvÆ Š°TH_”å´Ø|öìêꪶý,?–ÈrųƒýݽÃã½`¬jœ¦IT0¿Ïb’á\©¡B8<“ðJÀH„çyïÊ 1¾ÊaˆÒó®(²³ò ÆÁŒã¢Ìãá¬t¦ñƒnó@²0+;ÇbÿxE¼Þ9Þ?î"�_öO~~z"~Ù9:Ú9<Ùß;ï�ÄîûÃ7û'ûïá×[±sø›øûþá›®ˆ€\ÐNt=ͱ€fŒ¤.#‹"d(ü]L£Q|� kéù,<�Äyvå)²ß4Ê'q�CZ‚c“Ä“¸ KzTë6ôì›oþ#NGÉl Ã_ã÷‹öúå(Œó¬ñŠ=Šò<­Dyo–Ñy–ÏAX£`¸ “,EÁZoãùs�4µp-@Y Ê/bø™CÚkbÖŽ`BÆå¼+î6ˆ÷kóÌ40‹Óò u0X[ß|ñôcKŽän6™Êµ‰¤˜„)4>ÂÊù—ÌQº[ çù,íøFX ç3Ñë‰cIZ nš^î$I†të�\#…&L˜�kŸÔÊa]÷ÐŽˆüV B„i ¹;Ësl•q!O—U—�>Ih@7ÛÃ7Àw©”û¬kº]+k>^Åãò¢+."œ½ŸD€rb•ÑWDYft ï)Žäw Gdã“íÝOrÈ|à¡‡Óø:J€ã4¢Âšz3RsÈÇ a\&a~�ÕïÑØtºQÖL¯2E�Qé Ë>Ϧ¢D}aSâ«d¶ˆ³Fò°šGô©Î ™•7ž¿`ø¨ÀBá” „ ÊlŠ*M®f€PÔ£¿¨Œò°”—"XXõ{yv ÍÒ•õÐ(¥}UUCØ3=zR¨é *ŒQ(@º• 8% ÏtžMÄ0+KøŠ ÜÐÌ@6óqþI G£ÙW-�5€̳¹–= 1ZjN !f ÑQq‚ú$Á Kƒ*¥c:• ›0Ö _T± ÅV-fŒþyÔ“d0ªºr¯r\ÿ¤˜Ö30«²œ|ozZÁd6s\çãäF�×Òä)§ % ä°…@l ue’¤ÞÖ|0‰S§3rU¢çš%g¨àK ¡Àx�’çÜj ÜuGÉERÛ9Þ"�+±jk)©äð(Ä�|×$Ò–%E…—ÐÎ’xLbAÊ"»ö“Þ0x÷þÍÞ`çàÃÏ;MZÆñûƒý7ƒ×Gû?ý|r¸w|Ndï¥zZ Üq{"r4®�F1iƒLÆ0²Ik%M#©‹ yˆv«¹ ­…?>d4ठâ.»¶aó© RÐzý´Ô<­qo<™ÌHg3©:¥h}Æt°ý‘!CDt´s�:ôwZæ¶ÚŽ´’”ƒ—gñù,wù¢Šî:eI„(¿¹ƒ)±P£‰¤·Y~æcØi�`³ÞÅ€„$ŒGâ2fPÓÇÔ^þ ƒ eƒÚU�2s�4çÎV;ôb€Æ¯û‡;@ò¬”:d°@ÑìzÇDÍ{»¡'¶aƒkœ¶>ƒ2Ì‹5Z,èE­+rO»,È.÷ƒ?"hôùÞšÑ6›¤9‹kÄòL7?ÁÚÈÊFۺūC�c‹Ó&�F%Èæq^-{+/>²€´1øÞÛm³÷õµ÷é|�£½ããÆ§!?Få¨ß·ì„²uܰ†uÈÔü·y-#ê­Íßcýfj µ@ña1âV爠06o´ç$ÑYùÌqþä-Ò+�†³8PåÒ"òö3ò¦jFêš”3²ñåÛö[“¯w±¤2QÁ ™Zaˤ`]ƒ´ HSb`hÕ–o µ•`X ÎÏ®‡³3ê¡ü+{Wju6Æ_ —…W}í«ò[3Òž,>F×@¥@~ó¡£4‚S:(ž‹³Y:º¥:`�”Dï¾>ìüC˜äi™ñûw½a,Çít7d>:ÓU5è˜$LÍ9÷´�‰È¦å·ƒv'ÿ~J =$+kž£¢(¾HÕu¡uá~Ž%>hDyad%4ŽÙ�6;¡Eþ ГŒj•ðšWQ{éÆ*yTÌ]Ó�Y ̦DˆÕ JyTÎòT”¸GÀmìlª®R¶vD‚5ˆ¿ÛØt¤}ôª¦g·ö¨ÅjQ¦èÒø*TK£µ¦š.v¾ù—ÝÁ¯Â4Ú3�üŠm±¶ÅÀ&ŽÁnoG®Ì’Q�eÅ®X_ëÈâñ™žücí‰øv[ƒþ)Öð·„ ¿ÐK3ªêÁ+�sGmNðÒ6�ÁëãÁÞÑÑû£®XÙC–Ú�”ÑJéXÿ’½ë2GòÄ@ŽL]Fùu­sXÉ`œÐÓ"/äùDJ'B rbx,iÞé×§¢©ºäáéÁ®¶T½#~�,Ô $v=„Ó›øª ’lí¦‰hÀÁÒl¾Ãœm8–[¸`ÛΖY&µìÕdZÎÙš½Äâ<‰&£é<@x½WxR¨Öy�¤aßÔo>iP/±#A³v†Åós4ß–ŠÅ4ò‹ð¬¤¥Xa.‰~u�nÍUÛÞ;²1ƒíº,"žŠõ-VÈa(§™D—P£ºô€6 øKuS�j] xLÍ>†ò0D$]VÑJˆb?¨­áììãºvZâ€c(5@—­Ajä :Á•d׬áçˆfÎП/Î/Å)£® þZqH�Ý«óˆégí ™SàÛÆ9ÓE ßwÅnÝé˜ÚG¥eû'YœÈ·Í|\¿ˆé –:,Q7ÍòI˜¬tÌ:ŽTötM¢¬ŽÝ·=gëò0¼Ò륻0Ì’ñ= �ü^–ähZoLžHê]ÿÄ—(ŸÖŒžîUXKα1éÏŽö/õ²J)¥o·ùœì½r"ðº¾Jë­•Ö?¹”Ä�¢Póš@;èÍW·H5ó5‡8í~ÕBNÄo·¥‡l¹X…•l.„FãqkQ)lÊrõuÇ¥” ŒT’ÐDE'áq2×Q¥£p Œ1U¸Ç} N0GËu/D¾¨â‘}|{»¥�f£ÅÛ�“�X¥ýâð}W¬¼ ãDž1…ÞÞ¯8[N-»±·!Ûä61ïÁ®ñ3’).Ê~3)ßù6ÞqˆÔÎld7ƒ0Õ©Ífˆ»°¶ÌvÏdAùâ$%(lFeŒXªå‰ämÅÌFí�í ¾ÕŠ^‚ÅrÝ‘Ãk½kærCM^8ñæ ”q(½]–£ã†;R�õ¨• Í\TÖØVöÔëWev¦£SͦV –œ6 ®›‚뾂\5ªfCðÛWmv„[šV|Sð6NÇ&-K-l½‹áv:>]hXh:ªàŒm7,ù<ïöae™âò’–›<»®ÌAK��)0É’ ¶Õø\ú+M•'7.C:Àºß§4÷M4ǰ^d¹àC†V¢û£ /‰$á¤Üj$9^`ÖIñcMÊû'à&c… �­¡ÕËúÿª.ºÕؘ JP–Å;T«Ç4+èN»KQîeuÉý—+n–™š 2Ü\-àF\©qz¨4(ïPë�‹¥ºL¡ºhnŠùöYÚ³i·ª+©Ÿè0;B¿úþcÆí2|*ƒ¢H N¾‰!Ð;\"³ê¼Ðá*ÿƒ„ë‹#B¸ÐGõ8 ©Ð3H3¼„¡³<Áî’¿´B@®9ÿ¨±ÛlWf͹úÙq6:w^㼜£Ê8ï¹ðš•–BŸܦC„úÅõkÕ¤_ªLôݱrš»SüQ|Zì:Ñ{nxMž*K~~åºj �ëç°äµU’TVû7ïô‘Â!ºuêÊí¡~ÿ‰`{ôIç´2ÐUع'!…ò÷ºE¥Ôµä‚¨{VCÓpUl{ÖjY õÿ‘y£§L8þ¯p„‡cæDÃ6pàñ=íÅp_óˆ‡mHKÿrÑ·ŸKìR}Ní-YÎ=Eõ³› ·©®9m:Xà-ï: í·Zƒµ÷Áo«ÜdÆþÿŸý˜å¶â'ÎÛ¬�z<…£R-ð ãŽ5I¤Ž·—[šù�®ã�V«Îôn–ˆiÁºã}]óùÓ"tËG½— ÄC;‚âc©ã€þ'íb�Rùæ�®¸k­Î- å˜"‡F2E;xºNkSHœ�'u¨ˆaåšÉ DiC‰ŠVd»Ÿk™êG3Ž¿Lø«½ïÝ�šÜOÅ:h;svõ¯LN¼sÙ·•3Sv êÔ¥ÇW#�¿‡Uò`W%yˆ'4yü´Uäa¾ˆÕ³oŸ:ÑtÖO§sL¾€" [zPUs7›Æ&”Q­rê ô5F ,ÖWiÄ£‚yúñÅΤ¸ÖDzÓ^·yq=ÎfùH�¾>A¿ŸE5;;+"–,Nˆ÷ò ë¾jeÿˆ#ð©uõ&q‰JÖ� NSîo¯êÖ$Ù+¦Vã Ë<GƒqAòÝà£vÛ0iÎt·Je¹ðZœLpxoò`8/剨:�å�CƒS]CœÄí1GG¿} TÿÖQ�µ}A-V½YûÔbã+ÈHïžV[3ùtnÌ‹Ò:éð£gðÃèºò¼¶ŒI¥Sýî …e—Ñ�§¤SäyºÍ:ï¾Æ.Â믪bSÀÝ¥t–c‚=í4hvˆê¸¦]Hüä †F]úb——ÌšL}”‚»1§ÿ²±Å®›G½˜vQ [·ì‰‡ü-S¿È Œ¤O•Á*°(±¼ŠÚeØiOš¨‚“Úšy4í¬tE­1bÆ–¸]º.U±L©ãr¼ò\SWÐÍs@úQ’éãH7ù¸0Y¡$>$¥? ¿U’ž&y4–bm᥸Q.~ûÔ1‘|¡P!<ñÝÊJ{Û oÙ¥ýÑBø5¥È11ÞÍ»bÝ81 /Û{Êõ a‡¥uî4üa]2y»î/S¦QÎ uºÕPq÷m¥ÛQ•>À�(ƒólX>Þâv¼‡NXo…^bïWqSe6%ÏlÀ�0¹¡ì¾%àÔZʬÔ’OehÎMÄFšÔÓÔþa¨3+Ï]8å?°kјϢÌ#•¨ž­z“¼†Îš×n¢3RÔóçX¿Ïž{3ßASÚx<ç¤Ú‹öÍ_M8/Þ´ßAjÿayûçYH–$.–Ý%Ô/Œ©n%ÔWf@À¶[ÄŸ¯ªL|¬�Ò õ^á³>=[• QªÚ6,eɬo 8©r[ÅI�ð̦ᘮŠV¹­¼ˆ0f)¢¶2‘3ænº¼£”hØû†ò1ú_]96…€j�Ò©ØF�XzbµnVåZWg<‘ó�³„º£Á æõ\Ñi«^rî/ ß J[îÉ$É&ë{�ÞAÆ…&Ü8Ò•E}ÖíIv‰Æ#ÞaÜÐ9¤A°ƒiX^ðRRXŽ�U@3ÑM„A§_¡pAûVÌÅ;ü> ÷d·6Dväuƒ�þšŸÉ\„#J¹"Ê/#‡±u?(¢�\]Äet».켆.PJAt‘«7Û<Ýn±>¦›×*& âäᜬíúFf~uú½™#ÔnÂ~ŽuÆRÇo>(Ð;.G龄>óí)+¿Îås~{ò&šŸ‹L�„fã|œÝ}íÐK“TÙZN¹d6¶å¶ëˆ)î×i¯ŽFÁ.CÞÝ«Å�ø[òI­Ç%±“Ù±7Nàûcn{E¢®}8› ¥z¬qV�âr�J«©Ñ[oçî¯}Çã�ù¼Ê¤õ`‰[ÚŽÜjײڵ ?CláwÝi Ðøx}³ðÕ¯ËËy¤²ëO9¦¡Õ—�‘�΄I:·ñ¨YûÙÁšÈeúr€×�Q1wu–:6aƨ&Q—ÜéRír÷>n:f{½CÐÑuF9õ0ó/'85×rBGAúê³€~ „·@-âº^LѬ=<³¨ÍKa£Éìùt¨㌯>g(/óð¡?fÙ[L²1Ô/çæ™ñý'9x蛦ýòë6±OK=Ýw “LQSˆb†8äb+…Ù“Òâ±ñü¹'†©ÌJº7î—$¨§Þ0Œj¢îï 7"P&Òˆ, EÑD˜¨{B¸ÿ�L$׃Ëû깦ì�êA×’¢ƒÝH U÷á…Ïv¯sÚÐÊ@wM¶çÉ»ù›Öxi¹õ5÷JÜpɨ&ÕNALN¼Æù‘ªß\/ZÖ}Øz¢ÚxlQ«Ÿ¼eXVŸøËÞa9óÑ,ý5V<¹%×<¼Ü¸d25î`Dy]t…c~‹5õ㔊™È�“ö‹ð>“á�Ó²ÓX¥`SãÏîïÒm×YwXÐÓ�~�riL�Ô*ØÉ&)!fZ19Fäv[_™ëTu7Šý)bi¨«Ø{Ï5*ƒYÝUÚ—š¯Äýè&‘›Üý*ÌM(ñÜÁ:±j„ôÓ>E%fïfJ¥*¨Ëñ˜ë‘s$H'Ƙf ÒnlS25=l ©˜(Ù31©)í$EQdµ|Ü´Ó‘ï&qŠÙMŽxõÀ¿–4;l<+É–zh'iȺ]VbˆÐÙ¤kFÑe'1l†DØ-©$‘ÿ›}60… BC–E¹Hlg\ÅŒ‡½ø²ŽTt×S¨òÏ—Û”^¯!#˯x6Ã<[ª“L™«7cc•ü…Ü Y¶3ü,£�’©èÁrSñ\U8]tç"�÷披<+Õ°øÐ³é�9†ŽµßMĤèdžäýŒN©Èe»6=–Je´xÞÜ"…‘í�ë}�$÷—ªÏ¡ÆWjEW·Ä¦t‹ZOf ÊÏgx!C!o’™ÚcàÊRGÂù쨪Rïë»e/Ê^Ò :…§Óʵï;Ói¢o‡]âîwp%S?z£.rF8½=Éè6*æ",€‘‹$‰º ë—‹ˆÒj@°W�N3¼sZòÂ~1V–{&ï…%‹ŒãbšÈ­Bóù|åîxß8)�JY^e°|W¬£¨S�¾Â8ø‹ì*íŠÞº¹×Q€ *ßò"J~é-B~¨ׯâÐ×ë1ö¿AܺÕõ×ïkæ°È+ä¨ãè,œ%¥’1ê�ÕÕ§·Åú}¶ä½FBêÆN�ºgÐ¶ØØâ%ؽeÜBË®íúyoçDV)Rº�ø,¨�óKÑù‹Ýá·R?Ã3—évlì!û¶¿ žÙÓ~½Ú’·lÖβł+UdG5U¶í rk(2îj×å®ù®¶©5xîd Â®Ž®¤ÖÃ+o]¤ÍªS5‹¨£lðÂ�¼m¾�õìÅ÷¶1ù€ký5P–¾{±_ðšVÚvMkoÝmÒ½£µ�S½Éê„€âÝþZ%GpÌC˜™ ÔD4òàêI>_L8f\–px­máº�n%ö ´„fîLJß¶Éãëi ß·•UãñãVði¢2I‰J‰%hì-E³ºý°~;+ëðb‰D VHׯ)ºm¼…ñŽM[aXi¹Ó{›©<-xò¨xb¯4%’ÈÛrÃ'+õÔ”êããÃækNÛÙ«‰Q¬Fp3A�ºs4(¼¯Þ?Ñ3™}ûù´L‡{—f=*SgßòP½Âu±Cͨ§3(×K\øZ¦ÅØêé?RXdµŠR½R•;•y–€ ?¾ÿp²ÿþðø“©æ¹0WWÂ.Mâxo÷XÔíx¸†ôp Ú@( fS¬ƒŒ‡vZ s.…xM¾Ñ“F7{ú¹°GØùg¼mßJ7�“œÝܺ)pFvå�­¬a|êiûK Ã\‹pÐ ¦ß½‰ ºÄÖѼë-VÁ�ìñ‡½]õî×·'½¢œÓm¸˜àÄôà]–fÅT´MT·¶ðôÆ…]ÿRãÚòNæ?ñªï®¼¯Ù<“—4ÿ©®g^H2ÞR¶EOgÇx="¦ìÄ7uHJ¬×æ�KøæÛ›õ“ÄóŠ–AU[¯’ϱŠBù‰M0Ñož\‰å»èy�ÝB²& fï.þ²·�h aÎ^fx¦Eáíh¥!o^´‘2öAÙ©å�öM�ÔmÞ&ÇM}6æõðsZàzx䢓}±Ã«‹0ÓÕZw Ñ¡Kg$>ƒ‘ê}s°Ù·W%žz¿WGÈøw˰œÈ[M¶oUü¼ � Ö1Ö#,âµ´ú´J]·Iñô{YßÁü–×é¡Ì/Ï+*·ýY¿'´òá·èîÕAÌJ|a€�®õTn™ÖTʶ{é ©ô­²u–‰ºÀtNh2ì2XŒM>'ü,ºm)£ÙÐ?‹Ï{¯êf}&7Mæ=4ça K|ƒB‡\)µõµ6Ò#y%Â9ŪÃ;�G+àsx­S±1'П [Vð‡Ùù�X oʥǺjÔIda×>k.â(ªYœë7]Rø’¾€#Ï`Ü�et‘CY<[áÐé#ᱦfÆ"ÜùÜÉa"ˆI‚ìz®®p~jÁÑÍig{nŒë…kSW±`•Ñ”ê�VKsèp†÷™³Y^‘“Vó¯{/°Ð &`5'ËH¤³4¨±8( $»}wÁ�Ðòj”þ§*Qy¥UÒûõ /F{ â( Ç”8º”üÌŸ€W4µh²�<èVÏÙÙµ<úÆV$Ù—q“S޾E[~«/ë·Y¦Ùþ8A¢Ç¿èA�¡,+Fx3&�¡ø†qz>£ƒ‰»6KE�-›«~ ß ?uióRl,£“Qª;ä´Ÿ$ Ã¼„ÿÔ^{^jhõ^¡W‡sÝíX1�ÉÒOŒ?¦¾ÒÕÌqIÇ£y佈°H9í|™ é 6Q€û%¶ùܧiL(È0Ék“»«]‡´+# ϳƧ£½�7r­Ç1a .¦ùÏ’çóèØ9|ßÚ;Ü;Ú9ÙkÁ™kA¥ûlPˆ€LÆP8 zrÉKÁh—¯Õ† º%³±v3Ñ,ˆA4ú´�ñ òø±j©vk•2«®ˆ^¶Ú­7\Šˆû£˜Ð\é¢xYŸµÚPY­ÊmÉ1QùnÝQ ¾=¤Hù(QF“i–‡°) µ· ´:r•�óDß�cI£|3­{Ÿ0÷sd<×ëš�mF³†q+¯ÛEn1:¢Å½�#ëõ2uGB]¶UèYYo©2 õlèØð™Iàcw¥%Ó´ìת{'’O¤OLÞëjÖs…å ±ÇF>¢L”ÞdÊ/œ;<Ô¨ðУ¢´UOD¯? Û¥ƒjË.ÂÛ¿Œ2sh4t¹w ««Ý[IËá#£`zwNÞžšÈu׳Ê×/Ç\fŸ¶¸)ÿ*µËXLGSPbü ÷z²;÷X7(޺ʎÔ%N¥“Q­%� âyÁ/xkjìátdtŠÍíþ„2#Fyœ�Am­jt\3%1ÞLÊld¿„ó„ô_zƒ1Ö¥"½Õ•itÄeö²«D¡†wî$ïn.kà¥Ã+}Ýd*Q-+‹Þ9hÀ‘Âo,¦†“µâM¥ ÙFA½Íêl¹Í™–ˆ&,<ŸÑ¡Í…¹²u]˜»¾ùëŸáe Íû$Å¥‰vH“0 Ï#2ŒÿU Í2d/¤Kòdà8ü*Šl“°$éyný›oê �R¼‹ç±¨«´o K™oÃ�µó8%ûëøk7¸®=ªÏãØX H/k¾bkMõ@h,Åî·j*âÄOû q6û0&@Ò�ûw9Ðx6COwžÏ” ç <Êšvñò›æiÐ^ÖVÎe¨ÓþáÛ÷,/�koHQñ†ÄG6¨âo'|gô—›í{q¹^Ýb þ¸Á�›¸¤Ñ¬b8ܳ‡lãÔ›qé2ʹSiÄlÛ·û•쌫º°Ö€¨Ýغðßå—´o¾�xœµX]sÛ¶}÷¯Øq®¤‘dÇm2�ø¶ÅVcMÙ#ÉMó¤�HHÂ5I° hY½·ÿ½g‚ú°œd¦7z°,Xìž=û…“ýüÿúPëäè¤Õ:¢½�«DÒ*I§ #òå´X #ãîïøõØý$÷ŽŒÌbiT¶ ÒªDY% škûŽ4ëè¾#ò¼èV»ßF:_µXZþu~P#jÒÙéÙkº]BNžÓ! hܻ﷋r6“ÝhÙÜÝæ¶¼×z�¯¯/ªC®U$³J–¬Ù¥¤^."|UoÚô›4…Ò�uO©Á Ž«WÇÍs±Ö%¥bM™¶T2,c`äc$sK*£H§y¢D-e—îœJ ›KŸ+zf– bÛIÏ·’°•ÒKkóâÍÉÉjµê §mW›ÅIâ×'׃‹þpÜï@ãjÇ]–È¢€þ(»d¶&@� b=±"xB,ŒÄ;«Yã•�‹²E› =·+ø‘ÅΰFÍJ»XÐfo/d"£ãÞ˜ãczׯmòi0¹º¹›Ð§ÞhÔNý1ÝŒèâfx9˜ n†øõ+õ†ŸéÃ`xÙ& ¸pŽ|Ì [5C –9†I¹£Š¹ŒÔ\E0-[”b!i¡¤É˜~¹4©*Ø¥ŒYL¢Re…u�žØÅ�½Ps¼˜Ó§ë�Ó÷£ÞíÕt|Õõ/§WG/ð\eòÐ+lË¢¤Œ%ý»°±ÊlwùË¢ÑH—¼KùCÿY4†pIð,å8‹ $´Ú¬©PzX8´v+ص`ÃBfÒ+§óÌÁÙã5¼Ï½+DRVqZ¯æXˆàÙšë¢`¸”¡Y9Ÿ;>Ò‰{õòŒŠ2ϵ±À5gfýøHWêòvàà Ó4“Ê„¯ÄÊ9Õ\N6Šîìÿá Zç;ˆ{=¶1I[š,„�ß…5À󥘥¾1.�‰ØE< 8¾[!zÙÖâ jËSªŒú½Ëi4B©ú™:/ëX q!imDq"€ë3mRÆë9Q7 çô€‘z+5W‰•:©³T£À/�b‰â&ãæóGL{ÃË­|‰_îrۈŠD³ ÷…ÎæjQVÉÞU�ŠëU×ÀÏÓo’ س‘ŠESSf�¦Kt•,OV/e&—âAiójö6‡Q&RI Ù]tÛtŒ£¢¼t²Ža¾‹5ÖR:§ÀUÔ¸@…Â2\†Z¬Ë:?xÊR&9ë†tѾêUÆäítÜs]Ú¼´ÏJå5€°vè35¨Þ¿Ÿa6K¥{²‘²ÛEÈV;{û¢ãb]X™†d·½ ø ã(3��Ÿ!€yêvíD}w7Ý€ðf“ª«ìƒÞpÖñû;¿p´í$'½n9#]f¶ýDЬ÷sᆠ�B¼ýœåÎ䜳&ë̮ٳðm.ŒH™›SFDºÇlÇæI^e¹ +UT¼ìîɨÀ¨>Uªh5ö²/k|õš)Æ'ª§Áï>ÄÛ‡³ —dŸ¡ð¡¿×ö+c·þ=þÔhídÕfãA«ØsÛ�Ø>�¢©åÿk>eëMÎɆM_S“Äœ}´�Ýž!3ú£l¯x䋯‚Õ¥ k‡éè*Ê$ÞDE­’1pºë\6ºÇ�'ºÅAb&W^û*ŒJƒÆÆ†Ê€áë9 dÁ2¶;ê&OOÛ UóB|^ U–VÜ%lé:Ô—êäúÄ'½F·Ê@l‚‹\µ¥(t`‡è$æÿÜ­>Ò)«±1ŸþSâ]|vË"�É éÃ9 r$‰¡7X†!$e1Hù²\¨G‰t·ß/˜ƒý§ç<‰¾¤²Í§µb�• íì€"ØÚ<,uêòmµ;á´ªÛqÄÙ <O�ÖÎÜõ�QV ooº¦m5¶¢�ËY§wðjSŽRí»âÀûM‘ÜSãËÕgn¤sÈÞIÏDk\º�’žù1ÐíGâÑ¥‰Þ«S |mu[ÎÐ.SïvðO®”w®­J¾‰{~À¸BSèëØ¢ä�iÌînr•D‰r åàzÄwS|³‚ííšQG®á\ðÝdõ&å[V„0öpÇYe¦®¿sE/«¯BÝàËSµ«],Ë»Ž7æF? |Ý…íÖÈ4ïïÜr¸6æ’ØÅ¬É\#Žªíï‹Ë,ÚÞ¨cQ¨^•ñ.áww×<ì­y�‘õÓUXäÕs‹¶Æ�ƒJÆõL¦ã»‹‹þx̬r¿í ®ïF}?¸ÁŽSŸ#¿båÛOè×b}·æµüœ�_ ¢1h€O¯XY�¿�±è!±ð xœÍÔ‹ìv»»;d—ü4÷J®ƒÕÌÉ2šÜö]hÅÇ~ÎmJÄâ:±ãúÙ-™'ÑŠ ÒÛtàNšâèš&všÇqpKü�8ä8r?�â¸/@]žŸ[ŸšÒä§E>›Ñ¾»ì¾À'{ä<„Ayì9`±¤!É–”„ùjF“”,�5%Ž›åN½Ü¥.¨×ç§0_”dd%9!‰Bª�µè¢Of0&ðÝÏ~¸€I”/–¯»|ôUJÉ"qâ¥ï¦d%dí§0‹ÿ‡“ùQ؃î~ ´ø™ïd ¸@+�¦5ãD’{T ñž&+?#Þmè¬|—¤þ”DsF…Ç=âæIBà 9“=ƒ©üê‘,"ßß|/ô“Å·‰¿XføíH~!–Û%χÏÈû¥øqL~fL$fšÃØ�·Q´Éž�‰IÎ|—†)Ìž‡À@ G QxOzäWAèóþ�XØ¡#uº‡â6ÊÉʹ%a”‘<¥œKL�è�Kã ¥ïF«8ð�ÐyúÙ’Í# 0–ý&`D³ÌaÊ‚´K–‰ŽÄÉÒË,‹ÓƒÁõõußaØö£d1x¿tp6>:™LOöc1â* hš’„þwî'@-(83À3p® HÛY$”ËP¸N@Ìá¢GÒhž]; E0žŸf‰?Ë3ƒa? [ï,ý댦d<í�7£éxÚC Æ—ïί.ɇÑÅÅhr9>™’ó rt>9_ŽÏ'ð플&¿‘ŸÇ“ã¡À.˜‡ÞÄ RhúÈJ¡ñSJ Piñ{Sןƒê°:rgz�–†¨ô1*gŠ"MAÁ>¨+Sò´JN4ØÙù«º¨8ùÑóQyûËWZ[šyN²¨´ùQ¥)ðgå¶Ð2Ûâ1ò× Ìâ£Ùà\°’•�ö;™½fNJâ�= Ñ$Ç U"ˆþ*£‰V,Ïø�(!d·1(X@b'[¦=âÙ­ >K˜¶„Aâ]°WÎ�¿ÊW}÷W�Î}°ZÓߦ§SûÍèòòäâ7ûýèò�ýËèöÙÉ„<ÿá€OºHf05ˆ §M¨ã¡ŒSøнv‚œ¦Ä üÏT™ê®1˯£3ûÍÕ){ð=§e‚¹ si8SPŽ<%4(âùÐÿÜ!ð’èN/G—WSûjòóäüļ$Ã^Ýó£w£‹·ãÉÛÚ‡ÇãiëóÉùe;€Ó«³³�/‡œ¨£( ©‹â–ä€ya»q<'R ÒDƒIÚèxô 7’Vz~>9ONê�žâ3…Ú{†…Ø+qNV¢%H�–ä %°!ÛÆ¾ÊCg`x� . 9 è Ô9hÛO �³ñBj/^xAÁ†…àÈ,µ½ (vV9¬ÀœiÏѽZ'‘�KlíÛñAv.1'í‘E‡±.ãÇÙ÷âEAMAXr >ŒÄ„°±Sð'úàPtFGÃNW�‡$!»ø\5K£6þÑÎÓ%Ìê~¶žÆéÞ+åÕôÈSø s± ¦ùKÁBƒ”6“Xoö±Í›é×�,·–»�-£< Z|ÜLùNñ× ¢”Jä„JÇ\=š!=æÎAZ±kEèÆÌ£ofk@¼×Jc ›�³|Ëæ”{A3:·‘÷÷Ï…�,™9aá Í£Ä¦Ž»¬hf�¯Øã‹þ°D!åF?Ë€SªÇá¯TfûEÜ-‚MC –¢_ßðÒnE~-²ÙøÁ´¹™‚3°mZE#¬gCDý%úDª7{±]Œs{ JÓûÌ[A¤×NâióƵٰÂVut>¹�''öù©ib4ºD�TÌ@%¦àñ·-ÜQ+îÿ™9e“|O`ØT8ŠU�¹¦Ðžp«˜%`äÁ9qVQ–òŸ÷4}ºo¹ÅàM~uA,L1i¡¹dÕZ�Ü ±Eêj ¶(Àc7yœløÄŸ&�DcY›9á9º�Û |%ý¨ gÀɘýE( q¬�Ú‡’À3WØèØqÄh9 ÊR›â9 §¨XÑU”Ó�»’®� (›‘"¦1xÒü<­6ú ™œF#ÌHl·´ÌòHhxîpo? ¦=}ʹ$<äCòÝw¾n#üMF[ç_Š}œµVyÑP600…… ññ+2D?±Â¯{dÿ#ž9<û¯ðY·Ô �Ãg‡&ƒÄÁÆËú£�bÊJZÑ]_BTfô™æ7†5g„ô&Ž6;å¦{Xòå‘ßÿ÷FË�ìHâ�ÅmÑüaùQÂSá¸Ke‰sÐí(4½ëa1 _’(°^à‚/9 ‰Ï ¶ðš\û°"ÁKÁÅÎ?@�<JIžù�ÿÎÃÓ�蛇M²0€Xlľ°Cö [X0Тùb¸Ðy…_�â�ýK‰¥Îl†;CB¬žkÀ§œéß@çã´mCŸ¶}›häjº™B|™ò,6¡úÈ„!D}¬,VËX<•Õ#ëãÊØªWûp­Ï@Ô‹r,œ]Fy’šÈûæ60K´Kx‡ß•j?`äjádó\ÛNd©5õJ­µ×ÅBÛS�/Ȱ[EÇ´dFÑ Núеø”»ä`ضÂòð½ýÇOªÉ­(¾ûv!/Ÿû–šIåvÚ·(i:b®9akŒÛä™ͨC³J(¢ÎöŽáŒ帨¾º¥|ê¶uD§Æ=PTWïñ(КÅèoç™…¢¢=¶²YÝY›*Õ#ØÐß~UmÓö!b㚸k°ØvÈ¡�57„�æÓrè¸AÕLm!dQKÅ”G+±‰°ô3äí ¤5Mü93Œ¬*Ô)‰‘ XÏE–m·®lW¼Ý ü�> Áâ_Ï£¿ÐUáaUAÈxF˜ûÏñ†’çdNSî”Ý�ª”7×%IïYàö˜ vgõ2 ƒîÀÔÞ"xü3jSYÄ~�çÑ÷L#HHmÉtÙ‡'jëîXp(dY„M5µòõ¹kÎ ¶�ºÕ¨/ÙŠ¬ˆµî‚“X+0ðëï|48~Uàõ·M¾Åñ*+Y€ð–òãNv“m»5‡BœGÉ c{�ÕÜs qÄ-¤’¬ÉyžÅy¦=U�Øù&9£á€°Ã<;<äú´þ¤÷5•ä”ÍKø}1uËŠÕýëš“_�ć�Ü% m9¿¤‚†¡Éô Z�Ô™SV2ˆÖóÚÇ»tN�–-�Q¾zßÊU¡¥¬bÿ{óM/™ eþf�†1�ŸÍ-©Ü>¦­=¢fcõ±J÷qè�d1 ©¥ {Íõ±°6î½4°TZ¬‰b•Êï¬Órb¥Ý² ç… (dy¥±8•>•W…KCÏRßÑÚ¿—2á‰1˧ÂV |IÅæVR*Q‹.U+»n¡IJ=YRñí sž¹ 1z:ÏŽÍVÚ$öê»ÔÜëÆµ>½®ëØW*Y5+ß®kUecùvÐEàÂg’fZ‘»?|Ôs¤_œ˜]CC{ã‰¬ŠºœÆ4‚]Šu¦ì°©Å]2åVZ`ìè&þL&‘J×âÊʲ¤æU]sÕÜÚàÁCݰЪVQ«Ø*Âîw$nÍuºè£U&¥!ÝxØ ì¸¸…·ž–*m‰b[ÂÔoM·=ÅÄw;°ÓJ-PÃáÍãè®2F¨ŸìBªòeY1,ÿfÆ“5ãÊDµë7^e› ÌÓ®¼¢&",Y'îDÊ_°Ù<)^»¬EÕjU®u›pšêzVöÈ»�“(Jœë¢$yvk\ðWž #½Ö}YÌo ½¹T€YLC *<CÁŒ@j.Ÿ™¹!Ÿ]ø#JesQWîÔI, >J»ë:~áìüÃ?óÜ­�¿ac+X-)IJÄ/å@|¶Íí™¶ižÄ]?+“µúÒó ‚¨[ôÈ Z"^:‡%V¯f‡—�E6ëŸZÛù|Ÿ½zä)Œ~ºÀ› x/ˆ‰eÌl¢ŽïÊ�:Çd› ¦r, x1šNO..-Y­�3fLif§Qž¸ÔRÌí5ö–í2ÍØÔ ü1 è�ýûáÿ½Á ŠŽRsØ2U?Cb{°„ìýƒ¶“5�…=»_˜è 8•š}¡Ó{¯ø]�! oÏÁ[´çŽ«ÝžÑæì¼£ÁšÂè€�ÆçöéùäÒžž�àïäüâ—ÑYO Ô:|8¿}wi¿9?;®ˆ�MÉÊ�µ™öŸ7ˆKišÁ P5þb·åòÁ£Ë ¥Ò:?¶»ÐX «�Ò �áî|Ô›«h AK¤“ów ÿ‡RÉ„YòãKðÒ†• a2\¯+àø[çýÅ|¼Î“'ò`XýHKå°s{¸¯_¿€:„}é2º¶3zc(/¯ÏØŠÏÿCŽwãÜ¥q«3Áu÷7éE>�£wo� 2ƒUòù° æ'µÃÄŽ[ÂÔý¤v ¸Ãn ý¥v`Ø£˜Gç„ ícÑÛªûe;©þí?Kú=D�®IßIÅÉsïÅ“ás¯Ó3ƨW͉÷€ ·ïýz—®ñmTò‚1{ÚXóþæ´”k7™®6¡‘Ñ‘‹µ²ÔÛ†‰¯UêíËŽN†Ü+[Ù7Hš‘#pÙØïŸ¹KöHJ�C^_¥÷MgÜR>5ºfüϘ~¥Shúê ]@Ð/ŽnKÎ)¾”ì¤#ß3ÝØžì!®À+ÈŒ.m“Öh¯,6X]òîøê•øÐ+c¢"~ð6Á_Å‹“h�ÞöWýX H†ÛŸNþ1Oáêèèd:ý¤ei˜¬0éŒ+í×ß›.З¯¾W’» ‘ÓÑøìêâäP_•ºr¨Ï\Øê«˜¤³;Ÿ!`)~·²Sã¸ëPêOÿK—]ë.³7âË „®x)ÿIJ°ÆÌæÔfT©bI�J®ŒœT’¶׬üÿ’üIúL@èõ5èêãÓôKfÉçe›ßèÔ¢Ë#œ¤kÝ›;®]…zÓÒ} l(í×o¥ãÖ+YN×0_6 °JºTB%ˆÊîU©!«ˆ†ó`<²ÈÅØ�5ˆ|¹ŒCÁ"qzaôäRÈ]ø%—!§š!;òäÌf¦s£ZÇêe·»Z­\f¬ue2ê^ŒÏF—“Ñ!Zœ�¸‰B®$ü·T$èít ,F‹|6E;C¶Œ›'¿iI¯¡1<Pr¦W,á$&?1Mu°Â>tÛf@ÈXÍÓ Œ'Mx{:O:$äçñõû�7×ðóéÕÕéåõx4��Wpöñò||=þx‰oïàôòøa|yÞŽp¡~'äš)JÜ&œWL ´¢ws_Ì„�®Eó”Í9ÌåO"J¸˜'K¡(¤ HL(–B3mH[~‘¢n£ñoùapøÎg"‘îâµE ÅtÊïæ÷­�«ÐG›#ݵž«LK¦UJœ�UâºyÜË¢ø|‰B=Œ@²u•Oé`*qfÕ‰¢nÑä �®ÓD²F[«®ËšïeCjÚpÐÄwÍ•x'>O$Ó?ÄHbþ3¸�S>R@~zw~¨ô3¶ð’"£(r>Áâ)ºôlàéL‚b¾‚Þýlvt4Ãkh�m¤ë4‰xp(g3à!'¯Œ„dÊúýçxeÊÞ2ÿ¶âÕ£åO7’ 5t =8ü»®¼�Ã'l”±5â÷fi‚âhZ`æÙjÁ#*A‚僸²rOãÉðãçh8 ôˆÁ£Dpî¤Z�?€Wbäƒ3UVMßiAú=sÁSè·pZæ¯ÃÆŸÿˆ‹fQBÛÁ­1ô cƒ•+Ò4�ÍxR®G1KØæ³{¤{±N,bä„nƒ&šîù, )fŽñý̇jho„tÌGÏ»¹¼™ŒÎ=#hg ¬6cý�³Ä±†CW!Ã$f`}8|½®4ÿz6qÁi1lA&7—ʹóvâ�®®>^u yeV *• Ôo)Öþ‰d2Ÿ¤$6…/ÍN),¿¶Ìèì2bXËsaÆBÅ3ꟙOi¤Ä'h®øÕ¶äÜyS“ Ùì¿#söì'œiî͹ôJ -¤r;¾Ë›‹ l‰ òi Þ1AæSjžÄ­¦íü—8= %Ó€hãê;í ãÖò€%óé ^ž–žáWN½Htàe �”r`Ääêsÿ˜Ö¸&jÅ£�[AËC…Ì©•KCDN'“ÑÕu†¦†%®¢Qqí)™&>wJˆ;{¹ zÀq½‘kg_Â}�Ë|È�[Kz¿ÈÚCè?ß<ðËóŠlÙÂ�Yù'ìb½Í*›•²)ÿ�‡Ó;Õõ,uGùsÇ<ö]üf¢]ÌXt+õ5Pi¼c!è»M&öÌÄåš¾ÉYÀÖÎ�¾ëܲ“rÉ—jöÅ+ñ;²;ú®Õ*RÊV´„¶^æAÆbÍBS•q°«ï08~�Ç‚%Æ*Sí¿/~ͨ*B "�Òó/u9¡…sâIo¿zaÈt¾Æû³ c–"ª¼gÖdóGÐÊLëÞoßÁñž>6©,/j½ÞçŠV5bòÁ)¦Â{'÷ýA•7 s”MǸf \Nðî¢Ò¶sÄa�\¶*»ÈØTTÉe\į(ê›Þ7­Jñèváׯ@âj6åzÅq‘n¾ÿòÇ—“Isç\,‹‰=)K£öL5‹Õ¢oçÏ̾¾;8z¼�ãGÉx '_mGEÆ3¶jFƒ¬¯`YO„³Þç�lç§–Ø`ý[·$k ©àšq�Li;†»¬sÞ=fûà…{„yTutìö†;X×ÈÚÿöÓ¬ Dªˆñd‹íÑ¥®WV¸ç=ÜWåÈŠ‚÷ÀÄÛ,c+õýJ…v²eÔ„ïQócW‘궪¦†ëº´ïÄ­i2§�¦• ÏL2(·d/c‡{S’ë~»éh™ðaÇÒ³•еrr4ÜQúȽPDÜ3àØž J�LYÁ}n¬ÿx�ˆ~UD£¬%îűqqöaKO1žæþ´‹4nãÚ9TqíÞ�cÚ<�៑´Æ¼Ø’äKµC’å@f§qõÁV½µ³’Z¥[¾ÕÇlV‡‰ÙÁ)ˆ¥ %•Êc¦&Mu¶•÷вò¹è¹ÕR¹ô¨òi]ñE€[`÷Üõ1í„Ù‚¸6Ž€ÎÆUËÖ6°¤D”j^ƒt–ØãÔ±¤v¡´®{ÎQ±UK?7ÿïáO5ó³àSe­OŸ}èS ¶Ñï¾ý:çÿý£G¢ôèg„bƒ¶õ9ßââîƒÿS‡8p†ý”9mö9íæ'TæÊ¥P¿œlÎbŠ£GsV�=™pì:v)yÍq0÷lû”q–­“͘ð¹P„/nœƒõFu1ºS=Êwuê¤Rˆ±ßjw,›k§k¸ýª:›+Ê’Á�­Âú‡!N䜮¾öÿžŠ4:õ@K$:Ö™ѰÛñ;öA—Ùe¶ÛH¿³Î¹B9Çy…Ý�ÐÔÕ¾�x磷7ß×�Æ`”ÏY@Ê×ì¼Âib?Ën± à�¯¹jî8ç±E´Šlýg|í½;_Ü\�¬£µjŒUÇ2Ñz,É�‰'¢$ä4öÀ,<[«{ T—übxˆeF�œÔ�ˆÀ&$43B¯³,‘/‡ÃÍf3 JÚ�HWÃPÓÉátr>ž-Æ}�Øœ¸‹C&%IÙ‡œ§ írKÀêp€.AÎ�nØŸ®R{™@‰7)Ïx¼ê)‚lCS†l|.³”/ó¬f°B>PÛ&“ј´F 2Y´ÈëÑb²è!“·“ÛŸ¯ïnÉÛÑ|>šÝNÆ r='ç׳‹Éíäz]’Ñìòf2»èæ‚{ØÇ$E @Lަd¾²Û‚±š#ü[&Ìã÷@µx•+¼‰–Æ IXq‰.•  �lBñŒfjiG/¼hxtô �½0÷Áý€SWÁוk0Œ?X·¬í½l›°ÁúÌZ’™¿"ÜYäq¶»&v–B¾l®¥  ®Y‹@µ¤’ ͧڅ€<+G£v$¦ :Õõf±Ìˆjáâþ»÷äT)YDoëd‡Ïš…ɧøà>ò9"ð´.¸LBº•VøS°=‘^*Â}¢³Ä¿ã–>P~¹7$ŒÞ¹ !ò‘GyTñÙsä\„"•dÉB± û(2#Qf¼ï‰®ö2þÀ³íËò!}ò=©6q–aÎ:u‚á_‰,i¼-�›4Ç  ©ÑóF[‚°uªQ6¨ip…òŔnj,ó ¨Jþ›þ0I…7D��„B΄ ‹¾ñY€äÓÉlì¾¾»¼ÏÝÅä_crüâä%€\ ×ðe% ¨£¥!ä嫌GŒ¿2�¥,éV@r¨n�åÑ€ Î`!‹XœIUCkÔ•Q!‰>?v¸¯„pã<2†5iíi¬‹h¿b‘H·­¨‘ÿgaÞ �ôÿ¬Çê FÆr’(Ù MSÌŸ$V� ™—f§È²m‚ا‘9W<†ÔúÀ"¬™MÏöóMY–§1€1e¹çAEî‘€†R­wc}(óÔDœ©X‰‹×ih�\uÊiúŠtÕ—ž©,û�Ü928’KȳD„¾%Ä@í°á(^ý³:‚:p{÷ Îî¦ÓŠÊP§äàéS ¼R³êXOek8VtŒ < ^ŠÝ/° k|Œ3”íõñÇilaÁa±@B´|(+·H•RË\`hCåpãxå.µÐÅ>B-n>€Ю$5.)Mù¤4¥Im¶ /²GApRî×7j�0Æþªàz,Ò¯2•k}v}þšéjΨ/›rªBo•»ÌcSôT'€„UŸ0âsÌø˜{zRZ¦B`˜`ïš�îû“ÝFA�ÌJâXBx*±s–ò¥Æ`QþàÙ¬¡eN V,“^V&tü£ÓCelmPÏg: ¥±%æL DhõÈs KAû”³“Ú.}¾â™"÷ü}�gÑF)Ÿ>­>ÚöÑ7ŸžV¤Ÿce0˼Âq|zæð“ñw™nÑå­•yAo�¦þ¢ßº}ñ7DSÌ6.°O˜xqŠ4=Kúº� ð˜óMûüOý*{ácßIJ¦';ûU-U2}ÂT*cÄ4¬÷o\µT]Øx¥Þcì3ܾºÐDÖ“CÝTÑ;•ÆûûØêaÈ©l‹E‰({;#€ÊûÉÎàP&áFðÖBSe*+6ÿhì‘vÛæñã~ôƒ.‹{ž¨‚BW0¢¯ÐG*(‰f q¯ºm\|ÖÒû=|²Ì|qt?Ù ïŠ°Š‘Ú´˜K–öHÌ=T[ ¸=5AöÌ” Ÿéý¶¾T÷éL ö5³¬‹¯€)-=N F�o»’|æ‡þµzµCm-[[ ×.¤kkñÚ…|m%`ÛH؈翪J`K¸“òô P3 €ËUS8¨ƒBÀÌ�BÀ‡gp{Ç/éÜnšéS7(§uN–ŸÔQ{¬5ŒL«×ÞÛ¾«Pú¾.E‘u0UÊ﹊8fH ×ñ`†…¯=ÝCa(n hÓØûøªí7–ŠA“‹ °½’“ã«õ ˆ†T>qðL‡Ðò˜JZk‘%a¾*òt�] Ȭ¦}Òn×hð©vÖ ­,}VpÁ¿š(8ä'ÅÆõy€¥¤º¥oßròqÂ[ F¯îÕd攢õ-ÉzÖ��]Ö…ûãÏŽã8–�ýêž æøÅ‹æøXÇ€]¯l$©0€{w!s—ø˜ã¥e•ëbö2øÛŸ‹Çv–eà“=D&\ £5d©¤=غ˜âäØÅ ÖÇü}Oãüò@1+§¨+üaºÐ¶ïŸ9CU¯8kìÔ0‹)6‚E§Ê¸4]y=ûÝx· K{뢧þýÑ~µ0¨Ðç%§U UX™ÒÖ¡ƒ=C/x%+�­ç‰ÿýuv ¬.)”Zõ ^d o- �Æ=ãNnÝËÑdz77¡›ßNªÄbð%q4ÂþÓ‘4J$ºs€]¸²ñY˜Q�»Jà=3Šá© UÐ5f�¶é�Úfž©õ+Ý�¨¨5éµ·ªKq¾�µ€¯prP_ðâ’Û øÙVËŸPzõmü5¤ØÆïö¶çåQ¢ÿ#á#“ Æ£óó»«»éèvì^]_ŒÝÉìb|3†ÿf·‡šÚxKm¡!I�0c�:ýöÅâ¢4UIãýº�ÒÞî9 ËWaJ^Ê‚†ý”S¾{ þŠ™^À ‚‘¥9gQ5™Ïî:'Ì Ÿ‰o¡z¨Ó1ý¿üÊž±ßöÈÉ^ ÃîÅ«ðª?¸||È!nÈÅ4ðúi˜%y»>}e×�L˜'ÅÂ5ž3ÿÉÑñšÙÑú«”¾é^¯‡ùÝWov)ª¬âH²šúÆ¿¿ª�¿ûv­Ì}UUÂ|]‡=¤ÃAï,Àð¹oxgEe,P²Ïf¼ž‰øO!P;x¨‡€þD^ÅÅÈ»-Ò„…SIC8„áÕàjØ; ÃmpØ4ÀW×F5Ne>%×f‚L@tû{Ê5-&žJô…7Õ�Á…Í Ø–X¥c|eß<—%Oö�õ¼ÊQÝቧQÅòhÆ9:þîW5FÙÏ’"­ Ù2œð:$²Àa¨^µätTRé¿x1ˆh_lj@ÐeÅ$€Çö/Ï[¬ñéCÕ;”€™É÷ìkÑPŒ¨êÄóa½†ë×/Â×½waOÄQÉÉg6t`¿ÿnCÓ�¿¿7å?¶RºK°¶§“‹:D~š¥9rk`_ÑVÝ¡=ÒùCùâ�qÆÕα%Šº¥›zÓ\"ŸN5â}è¯Kã=î̧{_H¿Ó*Z™ýÓuÇ7lTTtðÐ ©µ ›ÔÀß%i ‹NògõeGx˜Em ýnµíà©m×K W:Ò4«$�&ˆ*X³Ì7¬Kƒ)+2¥aãU4QíŠè–ë)§pÞÕU” T AKî§Ò�­°µ–'‚2\?ó¦¶�A©Ôêà`á,sØþÖÎfjðŽbbr•›×*bªv]- t¢˜W1«ÉÈ¥;j¶˜ùã͉ªØ!´ÑHK`Ð ÓèÏ�q©÷�1PÒÒÞ„oûžœ9³µ„û.ì^»cÝ×v)´OÁ/nØÅ�ã¾ p€¬q2òLôÅãœZeå¼L3YéØ*8.ù!UÑ脼¤Öj~"âu$Úúø®ÿÀµî�l‹C²üÈô�Ì[ìž¶ P>6ôn+ÖÀná`cWÊX‰c �VŸHVÉŠH¨€'šN‡ "$øÚÕ(ŠoÌy„gè&‰T ±à𤮀¸@©Ða‹}à¥%°Ã¦ÚË©,¨ä„çn~4ê2ê$­žK2¿gú³£€Õ%29F"[ÌÕš}ƒM@A¦°(¬uÉÊŠ�A±Ø‰r»cœælGÐ{ͽˆ5ÔJ†¨mÖ¡Ûb'ÝþÅyx†z9<é¾ /ί§ÞÄ:>èA¸s–-FË'[+æ’ñ'hþd " :P# ±¢NÒ£ùx¬ÛºÓ@Ý%Âß3òðæd¼úUÌ©ÆUM=OÜŠz=/ÎxTîT‚vÇ’.NÁñ”yãŠ#‘‡ãª˜…–—ÃEïÅÍàê쌪L5‘ª]…³q” ®e…›%‘\’ýÊŸO¶ ÑZéƒçº†p‡¼Zb7ÿ¿ÿaþÒXJ€ ð¤¿|E^i‹r- x[ðw½j,ýè1àñ&/w±÷[ÿƒ©gŒöPêém48ÄJÝW%N‰8Ïê-°]2÷4¡rµÉ"ë8Ó‰0£¿­Zl[\Ù#‘Û&¥Ø´� x¢HE£ö}ëx¡Ÿ"¨G¢#ïÙκâê8V!2Y­¶Žç ,ähÙn�„…/fô îSž«Þ ºQtŸ Õ¥]ñ1Ê·©» ãd{Шû ÄÃuþ{€Îïî¢Bv»½&ÈóC€{¿·*ú¿~öb�–í¡BBm uKaÁöXî ‡’`PñÛ„@-… Œ*`�ƒÞø60”ŸA#(¿X1»-ûW*í RyC7µeULªh†Êå3Ó¹r¹IÓIh}A>náR˜Zô²!ÙlbèÖô®T1hûáÚ–  {‰:uÈF'ˆ¨µ_F•à�×Þo™®þ›óÓ^88{×—Ý{¤ÄJIîaÆpª Z8¸^Ew],*(Ýk]ÞQZÒ™»wÝ¿ _vûgW=Ó ôÓ–J|6‹© 2h€mtƒW³—Wï`ëVŽvXØâÑ(Ey×mmÜÚ§c²˜Ó¤6™4é$�²0J’à‘e/{«ÂokÐîïõÛí=hÃÏž0¸ Â�…«ÔNÖ=—igÆÂ8Ý@‘Ù+| ÜØ¹íÚI^œŒ}ËÒž<þ³'›Ô_­sú6*¿€á˜ptpô#¼]û��°ol–±Œ[þ÷çU±\²ž³6u0rÇ+än:I"SßaQÆ\("qäkƒÄvð�ÜéÀ¿YšùqG½0è@KnµÌB±‰ í DqŽwcˆÃÏ€ �Ý;,ÉÁ�À‰Ã$ðíÈAÉøùšÓ‘Xèºð«Ä/s�Û@w‡ØS‚�K¦×yždÇýþÝÝ]ÏæÜöâtÕŬ?�ŒÆ—‹q9–7QÀ² RößÂOñ¶Ë  ÔÀ^"Ÿ�}({•2ÜËcâø.õs?Zu ‹½üÎN¡qý,Oýe‘k+ùÃk«Pdv­Á&‹ ‹É¢CHÞM®__Ý\ûÁ|>¸¼žŒp5‡ÑÕåÙäzru‰ßÎapù+¼™\žu€¡¸�»ORº²é“(™Ëå¶`Lc�̈¾g s|ÏwðjѪàöbi„7‚„¥¡Ÿ‘J3dÐ%4�ú¹�ó¥­{¡þÞÞw~ä…‹êG;µ¸ùZÙãöÖ-eûeà/—vÆúòooýJÙÌr×�òíµxk Á›k)²Okäjh¦\Ä4DvÈzÄeF·pÐz¢,¹ õlÑþûpÊy/ý²u²…gÍ‚äÏðÐ>áÙü´Îü, ìM¡êØ6J2'�ƒ€¤-BÀoQKÀT‹u|—ÒqnÑV$dƒ­âÔgÙqu  #²q÷M3!§±Ãpíô–AÁLíð°ðž[‹ÉÆpôüG�ð²—ˆ ùô|¸Dœc@Ì)ò¯¡ÖPÎÆ³Éåù•u>OϬÑÕÍå5<'œ˜àŒÎøÂ¾- 4˜ÜÒ,ä)×y“7k4¸_\Í•¬=˜füC“‹n™qh!üεBG…•€!<†Œo1gN`û!E9“ƒÐY�Ø`ôz|†®qЩI Ú ”²@ˆÃ‚LŒ(Žºé_R¼YpzG�½ÏÜF ûX% _°ˆ¥(³Œ;H7�»ÜœÉ¶]ŸE9L1hT€_H-f™vßQ~z¶­·ô ’?Š �~êPEÛ|!³143Q�ã©À‰ ¤vØÁû"VŒá™sÿ 6(r%¯Pá˜ÊDE¨0Wñ´üv‚~” .ž‘ï9)Ëâc��çhq"`&¢LìØ"zój}Ä!@ õçªÈ“BÈÂNSÌ¿[©Bá§ØwÁ*c§…x,r£ÀpþìÈÊ ó{„þ`î ‹$Nê]Ÿ,ìÿ¼¤»âÃþ¾)M—ÛP†¨'òe]OQ/ñ^î“�g2,€¸¡÷ETÉL ¸Æë¦›ôÒŽ<Æå*NRŒæB’JƧœ9ëÓ@€Âõ� Ù�WPåß � <ÔöŸaMþéñ}T~Ck¨¡E©Ä°¬1�‹ç°�ÞŠÊyñÜ„>™Ñ §÷ây÷ð',¶�@Ñš;°½@d+ÄaÈ/¦ÁÕø%B*Ñv/u`ƒl¸ ¾ùHHË>eÒƒ{Ï;àøŒJÑæ ^¾„&-VkKÿyﳌiö½bè X´BåÊ2Oä™åµ\0ާZ28”¹à�ÄË y95¨Í•Mß4à7Cë-‚͆pxpôÃÍtàÊðbˆzà�(z0wa@×{ÃuƒŽh�þUnÒ"e ‹œ¨)Æ?$óÍmUóMÂ� ‰«pr% \%è=¼ú^£ñÜ¥¶s"�>ŸLÇЦ-ËKN*×j.ÇBøÙßάul/¢Ì_EèkAŒá·-^ X·Ë==è(ÌÛø0”¹ºó8åáÎݶnèT¯ñ�÷[¦óádï3TQŒb³ò“² ÌŽŠäûBYеsì22¨Ú¥ˆ½²}ºš-‹Ü£Ú0(£ÿ(1R#ÌGqk,�?‚¨#1®$L+$¼*–𢆢2QŽK›>G¹[3lqv>‚£5¶9B!ÐâyõXtPd7ͶPP‘F·ÐX§ 2¤›–|‰ô¡Wç³Áõèµ!!Lø­2`ƒì0öê-4IÒFu}xòh¯á„‰A¬wJÚØnšÿXrŸ3Û­S{m);FKR€oiô”‘qñ¹COT§uÊ)JGö¢bT NdH°4/r>\Æ>¢[�y„ßà’ƒM0>f …U–Ï…œYWohä�f$¼ucw<Ÿ_ÍéKÓ8Õ*�zžŒT]ü/+‚9çWÈÄâvtè(pâ>ÄÓÑ£,evÑÉ3¼HÕ«ÖfAŒV«Á»%Ãï®iŒõóZÂr<ŠhyÐvc–EOsát½ŠâŽ!äâÂÝW¤ …‘²ÍoGì΢Zó´T©QB�^;;&�JÍ®^T"R‰|Ñ�Å­Ë'•8THO¶öi²yºƒ9MŠ)»ó#×àmÆŸõ¢|öôÀj‘Ú/'Gl‹1ÔC»™ÒÝŠ3JsŠ1î}sÄýA`‘Uv+ü¨R9óÆr½ˆÊwkjø«&Í[±<“�´ŠŸ3;ÜZŸäí,Vd‘gnöiLK…VUO8„ù·HR¥Êó¤(–HRFm œsI2Iœñ JѪ*[êrÑ9V3m«L8­-U³oÜ»©ðý}å"u5Þ$Dùl7é=�@FfËm2µ>‘Š·‰T®ü4Ô¿M©þ=5ôÕ²jÚþÊ44²§( m•H¡KÓß^y–— §U­ƒF*�£~ëX½¬ÄU"öíè¹tq”ÊÞ/5²¯ lWœ-25“\jxUé¾JIË”¾êÖ@]Ø"^KnÁ»J^nWeFC.3 7ßü'ê«Q!H5¾ßz3öAŸËsîÚ|�Íd­ì“¿@%ßÒ5°•âý „â=aŸ±SKænü•Üò8ee J5±ìQDÔ¤G¾Wâ’˜:XVÕuÒ<$§Åx†žÄö®ñ–Ú%ViR]ÄtIJÎ*z©ÃüŽ~4á©­PÕtÒÏY°z\QÙý­ó̶†Ž÷ÓT„¸hߢ¯vºr:Z‡ÝÆ¥OÖÄe-üûgyS1R®µ^œ°Èhií¦ÈVÚ2k§+3µ¬úÙ2³‚xe ²øÄãå¢:·±€á?½!Zz§ÕRj©„ñ/“kë|0™ÞÌÇM·œÐk|; BÚ)2úIÕ»û5°ŒmÕû¹Ä¿§D,_eJãÙ~éY+�CH»nH¨¯À5Ï_YTëÐI¸ºG¯üU ®V¿PéèÛôc“r›žÕmÇ)Bµ­0v Im–ƒÑèfv3E¿µfWgckq=½Ÿ)ÐZ[E´…¥u<M—ŽE¼aP°ˆåT%)O¼xÑòá6>P¾bÉÉRoN‰xF¦Ì¿kzqLf,ÁEÞMZ þgŸÅ�I0_püu¢ǯ“½ÎÞ�äf„¬½ðhJâ܉ïŸçÙdB[þ¢n/KΛy——'j“ËÀ§QJ§$‹¦€ƒ/(éÆž_êIƒüB“4`Ùkuˆƒ5õ¨V?@�,#Kï‘DŒop)R >�9 "â³e^äƒh¾û(,È.y§p° ÷Ü#È;a3�x\½àÿ� âµgÖ*x4ŒŸÂƒÏÏ�Oí4HãÐ{LI¤\Ûóypp0.-Iý„…!Š\‚F5¹.¿-Ø*YƒÝk�:gI@Óý��&RŸ÷t¬Ü ÑòÄ›¡’�I˜Ñº%ÎÒ‹…ÈA¼ ? „,QM#�äŸÂù‹ŠHyè0øÐò#°ùCÒiä«thzf¦ÈC²[¬³‚Ô³{ªõ{�­�’g† 8»± ê!H£(ɽTV&Ou4“V"6à�1Q£Åd>7D€z\º˜Ì¿P`d 9mY”s ¨!ƒMÅ'r<§æ–Ä¡­y«Aj2¬Ôê9"#íЃ­�r/W0ærÅl÷Þ BQ܈§’CPïn{·ÿuÈÿþK.ŽÛ€þü¸^ÍxJ⊟©H ’õ?É®4+ÓìÁH0ùØÐ`�õp�*à&È*hðê¸�ÕS[_©­+€› « eÀs¹õùs[Ÿ«­+€› PmÀE|LsS†Lf‚ÑI÷RG@£2›9J}uÒ&ö�÷�u†P‹tÆË1 H?A{NèœF4ÒÓΠã�@äLò�å MÀëO9Dòà⺃jœøKè¸F÷Q¬w_¹ƒŒ¸Nâ˜t} »2g™qá;Iñ` 9Òã^ýiÞ¯cÉ®gE5|A ­¦+„>ë_öÈ>rgqÁîMBï–Aá8IYþ&£“Ï€ A*¶Q¶%²pz‚}èî]i)ÌT7î&¼º Öl½è=€Kí!bQª’éIÝaqR2†>t%¨y¦£‘¤U•›:bÛa­ÝÕÁ,@ ŠÁ—MŠ^”�,p`!+ �q$)/ˆ·MKãzÿëå•{>ìÞ¼u‡½óÞ 'Š¿·ýÑsÓU÷·'Šì7)‰Y*Ê_,v4aND€‰Úkà^ÀÒ‘|¿Wl]f³ˆà0ÁTÇ ÿfT�ìÑ}t»äb† Ê�°Z¿¹êKÌNaóK}ŒaÌHyEÛÁ°¹ôÂ�êôFŽQ(ù¸ŠUo‰w@¿ˆ‚Ä AuJ ª‰2_.C½÷Ï åY­3M/I –Þ2Òª2E×°EÑ:2ß®q p[2¾ i: C€ÅÔ|½1c=Üxýº®Â~‚É£|ð¡%ƒ§dØ6,Ùò»·Š¿í69¦� ¤zÉ<^C/ ½”K ·¶ lfj’]°£¯¢dH9¸]*‡B=EÑVíáw\ñÔ�EÎ=oØÁž^˜µuÙØÉŽ¸¡ç &ŸZÆâIóÈP.д¿œ„Ô‹²ø3=ækù 9K(šnâÿ ;ƒ)l™ÂbS™Á‚Ï’ã–¶ÔAß%E•MC�ù!K©S(LÖ~Û ÊeǹtÛ#˜I�Ÿ¿4føšk�o1O2èœ%lY*(Tàº�&[)NTF3O4rôçg�',žxþ]Ç}$Í–)”m•øÓ–c·e"%BÏîSéÙ Ð#—ÉÈA®ÓÜûá‚£81ã,æ8v Íõ,ôÓ�c©#Lg%Óˆ™ »Þ²Ã0ö+ês,§ "›ŽŠ�2e¦¢iY\vp)Jb;beƒÔBV«èö9†Eê ò× ˜–rkY*c „™}’;\Ä8ºç₵øž&,ÆïY0câ²5ñäö…7d;+±td$[cºbe×OimsíR�+ì ù† ùš yiC¾~C^ÚPŽGÖPT¡ü ÊË ò<q– ¶äˆÅœ6T'Š&~=?±ê~ª®5+|ÑZCN›×J`€qÖÞO­“ïÈsÃ�ƒ\�%,X1¼yv¹©Ý*Ñ%߸�§È˜}0iŽÚˆ#r›C®Ú¡Š»è/ϨH߯Ô=Ч[PÃFsñš“m”¦ù@õ_4Ï÷º×nwpjDßRǶ~Íך`Ê¡�˜¥ŒõP�†êM°:.Æ›ú¬Cžq|Éa‹ÞH~º8¼ãrq[õþÂÛrM Ìæ�H›HwÄ<´T»Xx¬1‹._ZÅqáŒÅ4rjÖ¼¼Ö µ¤V/úy=ܵ›Bœ¤nÈæh ò0éßD^®ÕhP6%¸—=š¯¡*ÐÞoý±{Öí_Þ{å&è&Á·’°ôÕ.œ2Y‰‰3Ã}}ª[á‹SSr/o††’(cpœ—bÃbv�c�’硺…M·~ÛY ,ݦDSdJ h¸ÒñÙ+Uá½RGd 3ÛÃÆA<ƦèvøCÌØtï¨W½™xœ�VmoÚHþί‘H…5mz:�šÓ©Žq[+Ä ©¢ÓÉYÛ ¬jﺻë.â¿ßìÚ&„¼¨|€xgæ™™gžY眽;û¦k–³²„KB•Ðûn?­ª$¡NºîwN¢0âwø"Ä*§0{�´ŽYJ¹¢TƒÞÀeŽ@‘'LCïJiêÇ"™á�f†°¥� XŠº UÒ”-YŠ}ñUEVVâ'•Û�’Ê‚)3I…åeˆ’³‚i¢íÉ“¦œNZ�ï4.gEUÄ-�½k?ša¹ðÁyÿ¡ßA˜Q% ¾‰ Bo¼ù½>¤$Ïà]¹—~ì-¢Èçñ8˜ÍãQ¾ö*y£ $zm»ÒB"™Ú¶“Må²ðÏ‹Ð3D5èèø¿“š sBåLQÝ£EB³8aœÈm¬)²‡�qÆ$tOl×µ¹Y†�2ä¤à!Q‰D£ j3ÖÀµÒ÷¢n€.8¶%»s¬¿PN%&7 ʪ’¸8XSbh·K)ŠºÁ5åZ™¸mÊ7·ƒfnÄ”—LY¯ˆY:�МnJ%rE5Ü6Ïqý|[×3Ç,¶‘ ͈ƅCÁ"7t’4EÅ¡…(”N�“~ÉÌÆ¨�?'Œ§y•áuðWÝëßκÛƒŠqýg¬÷yÚA´Î±ÉûÏ¿ç­?v­4(ö}-ÈØÏm’MsbÚrŸ‹ •)A&8)l BfV8‘í_JosTõ²â©‘|-��[æ3ÖÌæÐï}‰J—•ŽÌj“>QÕEºÑ�ÕÕðô¾2 {Þ¨ƒ�ðvNzìÛÈå9ßõÞwÕˆ-‹í?�l,ûˆýn<࿸;»á¡ÉY;Œ?ÅyèéWqÒe°d«JRKu¯cÄñ 8uG¤�8¢aW>MÂñ�Éc&žVJ‹"Æ—S�‹Uç zŠwôiu/»îÁQMSƒèM®®Üpt0ýú#ÞN_.äÊ ÂxäOýpä‡Þ�q<�XãTÛgÏ¢˜Ã£Á¶•#TÛq£íÞéýc•ï`6wç�güê“&ZÕ¬<õŸFÁµ;÷[¶�È::nË;€o.3Bšâ½Ï^Iµ¸À—ú«ëÕJ°‰‰K)ðu§_µ§Óh2õ#û"n_jïñ¿k²9㯾;ò£g;¡<Û_%ýÎÿaË:â°âxœµVmoâ8þί¥+m� ÝíI÷aOwºn/Ú¸$´W©’e, ÉÅNY´ê¿±(¡Ýî‹öú%Å?3óÌãñœÁ0/v¥H— ì¸ —ï.…éR¬EQÀGÆ%/Á^™ïŸi5Ÿó~¼ìvÎŽNá‰_à:ÏÓ5ÏvÎp×1Ï$O ÊPKNÁbü4;çpÃK)ò .ûïÀÖV³euC„]^Á†í ËT’#„�°èƒŠy¡@dç›b-XsØ µ4n�>BÜ5ù\1´fh_à¯Å±0e^*UÈÛí¶ÏL¤ý¼L/Öµ•¼ðÜ!ñCÒÃh�ý,[s)¡äÿV¢Ä<ç;`³9†¸f[ÈK`iÉqOå:Øm)”ÈÒs�ùBmYÉ%R•b^©SûÐ0ácäŠe`9!¸¡'tÃsĸu£¿&³n� püÈ%!LNü‘¹]�ãßÁG×�GžÐ ÿT”:~ Rhy¢ 9o°Èë€dÁc±1æ•¥K9¤ù/3L ^n„Ô•”^‚(k±Š)³ò,©~g!²„ež–lcwnÙa“0fYÆKJþ!ÃYä <‚{¾3Æt¶µEOÖ&ÝΆ•+Ê$eɃ.~b£Û¸cñ æK4Zs*~Y™PV¦òy ¸p5™ù#zã§á™ 4Èß37 Æ&ürÝÒû™ˆ7¨Ä:A¶‘éyÉÊÝ¡NȪÊã|}®+,ÑF¨!^p,Cï´æÌ}`%–¢Êb]'»ax‰MŽ »˜¯XØþ$z-QÀ¿ j y¶¯œÈñ( T¢õö¤~oQ}<®”¹)û tn.ú"GÁô- †!£Oäà ¦¬ÔW“BÙ‰Þµ2­6…5¡|-¹ýµB “_O¡~gþ§ê ^Jáxˆ°ÃÔ.Í`?×3¸~D‚+gHöóP혂 goÙÚÍV8±ö›§¨QØÎnrP»‚C“ò©ÜÑÝAt7%ÍÛz|ìI6wÔsv7«E”Ž¡¹}mŽŽÝ5«¨—˜<©îÀ?‡k˜Æïa0ìvþ¡i€Kµ[xœ�S]oÓ@|ϯX]r IC…�€—˜Ô¥Æ ±C[Š.ÎÚ95¹3wg’õ¿³g§%i%D^|³³3³¹è¾èB¶æ·èV­ªr§E±´àem8럽�ñR¬DYÂ'Ž5x·õwPTó9ö²eû¨Š*^ÃG¥ŠB ['t‰ ¥ÁTrAv‰à—<£Ïþ¦_Q¡$œõúà9Û_±ö{bØ© Ö|RY¨ …0� ê�Û K BB¦ÖåJp™!l„]Ömö$=¢¸ÙS¨¹å„æ„/i—â€ÛZðÒÚÒ¼;=Ýl6=^+í)]œ®”9�Âa'A—ÔÖø©\¡1 ñg%4ùœï€—$&ãs’¸âPx¡‘î¬rb7ZX!‹•Û ×H, a¬óÊ%u/� (+.�ù „ ƒ~&⸠ÓËÑ4…+2ñã4 M`8ŠÏÃ4Å´»?¾�Oa|Þ¤œ¨ nKíô“Há2Ä… ,A<�«F�)1¹ÈÈ—,*^ êjIv D½ÆMÒ�¼±¬ÄZXnë“'¦z-7CoøçÀs!¹ÞÍÜÑ€Á~—)iQZ¸ ®Û-g_„ÿ\Ãg?^úQÌûæw/x7ïwßþ8X¶,¹æ”×{öû˜ùŽ/Z�"ÈÅ–d4‹{h—ަãq09¸‚f1«J2œqƒ Å��Çv€ªlYÙÙ¹ ‚ Žö�@FU:ÿ fÿ´à–ÛŸÍw ôÛ-šÒŸÖ³ê%„1Da’&OÃh·DîÅ£šÚàËÔ�ÐoïÞ'ïñ95ìs” *w«5·K/¸OhÞg¥2uÐŽôžC{ÁöÍîÿ·×wé�úýßß>û]¿cÇÊ‹e½„W„ Îû¸Ú-Ê%E¥±žšÇé=Pd#z:4Œâèæ_µ÷“{:ËûÚºâj¦aèùŽ§éŒžtûŸƒA/cNuSêLýYßÞµMxœ­”aoÓ0†¿÷Wœº/[U’iB1@éÚ°E+-Z:¦ ¡ÈI.ɉÔ¶CZ~=ç,“V�Ð�ȗȾ÷Þ{îâØŸÀ»ÿõÀÄù“É&T#ªµMk“ EŽ:p�K”¨…Å ­¶0R’Bï§Ƭè³3Õì5••u«ùã޳8;={Ÿ*ª©iàZ A ÇßúwP¶iŠ^V�¦qÊK¸Tªd¤år>YR†Ò0H+l…0kDƯ!2…Ϩ ) gÞ);Áx�OÎ�Å^µ°{�ÊBk�=È@ß9î2l,�„Lm›š„Ì:²U_gpñœÇýà¡R+X.Àõªx*aèÊÚÆ¼ñý®ë<ÑÓzJ—~ý 3þ2š‡«8|ÁÄCÆ­¬ÑÐø½%Íݦ{ e"eÎZt 4ˆR#ǬrÄ�&K²œ‚Q…í„Fg““±šÒÖ ì‘�Û~*à‘ ãY Q<†‹YÅSgrm®Ö·¸›ÝÜÌV›(Œa}óõjm¢õŠW`¶º‡ëhµ˜ò¸¸îí:`Lr£Ä¼Ÿ[Œx€P¨$Ó`FeÜš,[Q"”êjÉAƒzKÆ}RÀ¹³©iKVØ~ë�¾\!4:¢‚#$Iøñ"\,ÂE0SA»¤mØ3ƒä*IFG,#‰ÏP²©Ìê6GxklÎi^õþp�¤u{î�‚p›"‹r>Ò¢´î|üöëxw–[u*Ã'’^'pHNý@$¹°âË×ó‡1ýÄgÙþÍÕÅÙóeNøþ3†á»KB™;€ÃKãŸo'Çû ñ%‹¯ê ƒcxœ»Ê¹†cB*[q~iQrêÆìÝLRyÉ9¥)© Jù¥%¥%ñ©‰)©EJ\\“¯2ëŠ*Ø*TäRKj­�2:,ZÞ@™â̪Ôü4 . HÍMJMIIM‰OÊÌK,ªŒw((JMˬpëÒjÒ×R°µUpÍKQÈOS€ qÈä,¦Ô4¿ðxœÝWÛnÛF}߯˜Ä�c"Ù}\ôÁò%âlmáò’‰[‘»ÌîR²Zô¯úý²Îî’Šd6(�'Q{™Ë™3gÈ è7¢ÌáW¾(¹Ìá”OQ3¶ö„�gXªspË�¥ÇZ‹ŠkQ.@I8ÀTpÙƒe†0Ç·¡1BNXͳ)Ÿ H^¡ñ÷g¨�P’þ«†VÈ€-¸…\«EÚXÚŽaX d*G¹´ŠeªªEé\”¥»·³\ÃP‹{�;;Á-E‡Q{6‡R¤šBEó3Ì…-(ËÝK}úBZÝdÖÇôGc,¤”íŽÛ­þõàä ®ò n¤d”û(¢´�""ci‰Ûò>%Ñ�º´5ž’³†›#�Ø¿:¸ÝŠã$žMšÐ‘éØI‚m:�é\jòxQ•Û=vs„¹Òü§wÏßkú‹ 4»!ÊÈ«ë«C6Å7'Lžµâî˜Æ`d—w¼AÆSÕXhé˜dÐ6µOŽÒ5fA̬ Á°±ÑR{ÉÍ– nk~Ðø¹Ú‘¹5ËØÝÝãµ�&h»*A´€O †Ì¹§Œúcž*jÿ”«ûÅÃþ¸Ä{ÿ0ɲð+¬ÿ%&f\hõ#•p֭ȬÑÍêÒ¼ÔJYý¿{¿\¯§â´‹àº¦ ¤Òÿ¹Ÿ‡¦õñ³+DÏ€›4žwp?[¢{sߢn¤1j××qï!ó@1œS—bV*‰PX[›Ý$ bâRSNÒɼt€iç�meSø››#Ó¤•Êjð¦Î¹Eˆ"!W×M²R¼l¤O‘¬W‰ Íc­*¿žS]3«ôª �å~èF“FQÓ«6¡}�i£±×µÒ¡Ok£—C¥JCºBgSüÒØ®ùïÞüu|~zøw—*ãåÝ®G*å¦`�7ÑÁþéÞÇÃÑàìj¸wr2º¸<<üöËëõ{¯!ê‡È�Y’ó6ºßÖs Ï�ôô-éŠ}g µ Åã)¡)�œ¹ó�ìݾb�öL's°Õ* cçߦ‚�u ç=tpsUŸf¬ëqÖy{–¾KY\vÑ»÷A”VÙÌ\ñZí –#ÙW㥼4Õ}MZ‹�±lµG°±Ž^Bv“¸BÙÎÓ^mwšèѼy¹`8SåÌm.g MSÄÚ'¾w1 êX’Y†­³ºVC³�Y¡•ºÒUáú>׎{YÙäîýÀ@[À/Ã1!Nd,¨�D†%|^¿cGÛÖ¿¢¯À7ÖS*EY�ÙÔÍ‘("y Ù3·[¡ž ï­5ç›› NF'ƒþåÞå½áñÃÞL×äÍV묖T‡rƒèóÁat|~5�^Ÿ {—ûÇÛ¯�V_|ü0Ú??;|x‹  €&»옂J³²ï�?¥;»>œu+Kéñ`¬ˆÏw‡Œf»kÔ¯š �¦¿8 ÿ'ß: <€Ën\WŸ¾Kδ™=2ƒÝàu/Ÿ9]]ѰTH®=OªŒvh&Ó¨'N•y]kEX¤] åŒ^¥“ü0&Ÿà[—×gô�°íGZ¥4¶_9n”}—}p„ í&n@ÿ¢DNßk¥éU~ ntÙ—+4ò߬¿ð€Q™bö ¦r³”xœ½Ußo£F~÷_1"•Ζ¸&RÚ—#À%\l°'ç'´†5ÞËÂÒÝ%N®ÊÿÞY°söåšVUÕ'vw¾™ùæ''à‰öI²j£a\Làüýù/0ß0ÎÚnUTÂø¾ÿ~¨ºÕŠÚÅf2:9ÐB� ¸¢â¦Sot‚Ò)+h£h ]S¢½¡à¶¤ÀÏNr ·T*&8·ßÃØ¬�Èšü†žD5y‚FhèELÁš¡úXÐVk uËi [¦7½›�M,w&ÄJDÄ·x[â€èžðFëVýê8ÛíÖ&=S[ÈÊáJ9ÓÐ ¢48C¶=~ÑpªHú{Ç$ƹzÒ"™‚¬�"'[H%)Ê´0d·’iÖT§ ÄZo‰¤h¥dJK¶êôQ¦öÔ0àCæŠ4`¹)„©—n¦§hã.Ì®ãEwn’¸Q)Ä xqä‡YGxûn´„›0òO�bžÐ }l¥á�$™É!-MÂRJ�¬Å@Hµ´`kV`\MÕ‘ŠB%¨l0h©¬™2•TH¯D+œÕLÝ¿¼ Ê5¹§yÍVwu¾Oàø6HR¤ öϓњ5eŽe¸Ggc_<>U´™ŒØzìÇŸ—WA”Œ‘?ôHÓãùÃ_̦ù'7A@äÎ0-’Ö]Íí/DöÏ×a”¥D·jäs7»~îæ”ö'§SÒ©¸Xî¨ ÖÌùBˆ³7é|qQü-æ@ú†â÷øC&ZÌÀ!éIVT�Ë!Gù�c´}ZÀúé(ÎgË(Q®èøi[ªÆn1µè/˜Ú8Ã�Ø],Ÿ•ýœšZåÎÎàŽqÞKÑ•DS(8Q¦µqï ª¥ÛÛÛåøcy8:ÿ¬:Žªnu=þ¥w2Yþä<¼zyYMzï'U÷lÒÓ·§Ã›cÖuÑ_TK¿øÅgË:ÆýÓÞˆ©»üq¥I«îàTß]_vOø¼?`°³Ñ�/†g¶ºÎꫪ?±GF=as¦óU=åý3ÞöY0p蟆¬‡�\O;»êj²gú€™ŸWﺗ7½ê?ìÇ“jyyù?ùê%¸ì�'ã'¬­wÅÖºƒª{96Øv¯¯{ƒS6s£¥‘~=g¨ï5¾pļhÒ탃k€1®ÆÛKÐÔKÐ9îMn{ ä¯7à ß/½ù´zóégËàýéãýÇööµÕ¨wÙ�°ÖuÖ?¿9… ¯E(ãÇ�ÔLºçl¿wÒ?ëåö'CaÝiÖhÜ�\tGÝhm Í¡4ÉpÆÖ³áååðvÙ0|Ú;ëÞ\=v^>ý®º½èŸ\ˆ¼ }b•™2 Ñ÷q�ôl³þh ™žœÜŒF¼ÓsŠõ=,kŠ}à´1´rÜg}ïª%‡9Íÿ>¾ék1¸’gNœ·´ˆÉ¨;ûÆ—«ƒžÞ¸˜L®ÇÏVVÄ�çƒñâÊxx6¹íŽz+i†ãN� úÔ’®‡ãq_¼˜ö5NÔž`á$ÑWèi}÷ÇŸ^nnmmì¬í®oì¿çþ€Ë™^µ·¿ûÇ�µÎÑÎêö†!NP¬‚ o‡£SÛ.Ÿôþz“¤�¡l@6¼1&9¾ck.-ž:]?©n—½ñ¸ºÞTìÍùÈzÛÿ[wtú™sK°e0韙,c¨KÇ­€Ñ”q¬Ôô gXßEæ {…4;‘9E1ؤ?pñj0ó;Õu÷¬šè‚﫳Þm5dš‘3ê=Þ¾KÌ”ðsëßçÕ§·—WÝ·½Ñ§S�>Ü~±±o°–ÄI‚ê&wÓö¡³Qï],‘>¸¹:î�|£lê$I• 6pçÒ|trÑ'&°ê}6ÁƘý³j<Rï`<†¶Ñ“Ñð2ôFa¹;_sÚßsíéPDbs%¼ØßÜxiz&NÙÉ;p ”+—8ÝËJß]öùÇ•› "†3¡�1oŸ Gw  N¨¯ìvö;Gë›ûÈ›ÝýŸ’´13†a¨4ü’i-áT;/o&½ÏÀúäÂàÄãµ\˜GêÉ>¸õ'“Þ`¹Ú<yd ©3žñ]ïTv@•^Ê�ņ/‡'®9o’EnXÝv„“îÈ„Ðæƒ]öÎ&ÕñewðÖ劥€TžAûlS`qÐ=¯þ�Ùjsguÿ'î+Іx��Lkû«��£ƒÃ|y�Š™( ñ§ >¹@{åµÊ^:õ�˜Õ7_üö7Hðã§ –�(e½ ~ÅÞA<Ÿ!ñôfâ;3bÜ+£ aÄ€ÃST¦+x³j$I§�«!ÒG_ÀŽÅüËÕÆ {Œt�`1ܸØ0aâ’òìæܳ­3h]¦íu«‹ …+”’±Èxx3B£Û|OôÎH²•�©pYI_T0àXz$­ê®º5Ykºá¶ÏzOº²4Todû–Ý€0A?cEJŽ 7fº¸!>_WïìB-,&Öy^íì¸^ÝÚÚýáèpge¾aêzŸdhØãð¶ OWÖ67YrarA".`“Ü‘ú1gm°ãQíì>™7^â\–I�è½ï^]£~�Þo|}ôþ»/ùÿ7 °§ˆÄSaçð×_ñÍ7 6 @-¬S ›­Õ�W‡«¯²e3KÖÀªç7R'`ß… `c) 1\À�+‘¶F.ëI"ˆË˜'pà¼!%Èý¡¢V§AФU¥… {É4;¹>ê=«VÏFý·]lÌ'ÕꨋuªŸ8dýîàIõbÔý[ÿÒ~]ëNº ö¤Z»@ïŽ{O0~}ÚuOûòº—|?"äìÉ¿õN.žTëÝA¬Ÿ7ý¹�sÆßÕ“Oq€¿Ù¦ñ6ߘ»“á“ê%:»_-íɼè>ãƒþÀ‡y)›q^õ�¬çÕ¨×{û¤z}38ïŽ4/mN±ôæ“j“uÛ/ì^wmåUúí)`)ýE!ËÓê â±l;[ן°ô5”ÿ´·ü×yïT[ÝÉ;›t‹Ao€¯k[¾íÐa»3ÝöÎí¡Øeµd»f³{C�H?G“›sÜÂÞ“jÈvíñý yð 7:¶Ïâ—§kw#hGX<¸¾ëýÄÆå¶o£Ü"ô4|çfôV¿±Ã·#N†2ƒèû~obœÜ›ÏE©Zûd> áó<³@fmoÈz=Zß8X›Ö7-ñ¦Mu<ê÷0¿{²`YoaeAÈk�ÀôïñeÉe�ó‘S˜<•µ~rÙKú6ùsiÜ¿ê_v‘½Chä]—o?“Óeªp£lˆD£[œ1çõ \¦7k�y.­ZÀdc |T¾H„Zý¶Àq=ê)¨`ÚÁ@Ò„…$o7  ³çìfpbÑœÂ]=í[¸( -v³C0á™Ì‚côTõzq¸³¾»ø:0Ò˜Þ”À\HvÐÀš]Å H¹µðAšOz—w×lhlFŒ^¦]Q‡eHI»I\,-Ü¥˜A_ƒ+ Y`ÁwÇý§xÕ=x©‰QedT¬ ËðXR ÔÉÆL†$ä%w ØæôCFï2Ãyòc‹£amÈ—��‚A`tþô-¦àš ûx`â‰>/{HJ& ‘�+}6µ$쌕FÅ9U$¬^†† x1ž ó e—êöb¥ÆR£�utØúòzKkY®vYÊH¦É“Ù‹J;yÚ›:¯­T{‡¡<À¢ Ö*Æ=é¥_&Ǻ{3A’Mú'ÝK(rö€ŸI_�ÞÊ Ö_à¬?¹ûìY‡ß}c]dWþwä¼ êOÄæ@•Dä;Ž%âr4éIНñóC‘ @ä.ÿ˜þ]}ñbãûMYÇΜNw¸åõT¶¢oŸÔ4Ùú­xØWúЧµ‹{Ÿ…Îî™7mý¾g2Hî{CÁÈ{'«ÁxßHÝ{¿Ü÷-)DæêÖ«? @:«§Ö�6ÖŒ%Ri`6Ž‹³Z¢Š®ÐC2ÀŒÙj“ ±òqŒ[*‹ GÛ˜QÔ‡(·ëŒ«�,Àn1ˆù*Á¬ôYë†\’ÑÙæÎÖæÎÆÑæÎë�ýÍÛ“¾\¨(ñÇoe² þ^6ã*éCS F«I65Ÿ‰"T¦?‚�°íç`´4a¹Òȯîè.žŒï–«5Y¡£ùñpI^P¶Ø%•Ov©¤­¹ à¢:îÊ!’FÆu0l(�§¦- €t6°Ú }y¸µu´·Úyý@§§T³8‹—)jjT¬9vIUZµ¶ßøði� nO7 Ñ  `ƒxý¼dq3Zïs]¡×|Ž›AŸÀiÖ ÒM¼ÜtóÔe{¿!“ uÙ«:û›{G/÷w· 2F`­`¢é(�GO™ Ëÿ”µŽ²îÒº… ST¦ƒ=d€‹Šô#]’6§¡-ÇD”p b»Ò&O‰d¸©)l³RïíŠès˜E«ÉiŸ0¢õÁYµ¶2ÅR»æ¦[ÝiK"ÝÀóM¥éH2œÁ…qùUFÂ;ŠRky¾MD„œ ¹cÂn#”± O04­ÐéÆKª?ŸU½âføˆU¦èïgChD°�ÈZØÂâ3Á¯Mz9‡zÐym¢H„#á+‚+¾ßÜYûhÂa4ƒ¦D"¶¶Ð§Ä BI†ÆÊ%Ií—LÌ•àVðšìôä6À‰ˆ,…‰†æPÇp-ûÅ%ºFmZå ¡ÍzãA—”Ææú&b),)£}“²ßDšÅ\‘Vé„›�R‘Wf]K&bì{¼T†32Aî�e O7«³Ë.©Ðu5R.…Þ©o�럞'Ÿ¹7žäÓñä�žÓÝNÅ3|¸¦¦ps¼9FȈ‹.±í¿Ü > \£Þù�œ×?O�mÌFL–0BÍd‚H9Õ{M €põÜÑÊäW(ä6Û¸ùŒq;ìpZë˜u3 TCÀпXÝÙ‰VCÝ wR è £ˆ²+~åóæ|„ýrÜKŒzL�…Ð{6#¨ É„�O�ñˆÈ³Í€=ã5ß¹ú1"–' L¹·g)S\]Ç�t‚k@,Qu ®îüjvªe…‹¨êj�Vòl§¼X�’ñš‘§¿~?ócœFÀŒ7›„ßÌ%á†u¾€€ L`®e‡Hûp«³iæùÚÞÞÑæA¶ ™W«SÉm™sÙÓ˜(ob©Å+¼öþSƒõÚ¯íº�´f"Çc2EoÉ£,÷–ÅŠöÔ»²ò¿$WVVÒ“äWŒ ‚_›uIœ˜ÓN‰u�{H�þÐ’Áh=òÖé Hãøb«ÖR›K„Ó(ñISéyØÌó/2<‘P�,BÙIÑ3)eV4´Ê_ üA¸®«2P]›VÀÔò-Êç˜âŠÑ¬TãŠØ¬„) õÂ9RŠ6Ó*†’ižúƒýºƒ;²ù£{"œ&Ôæ <1ß ùQI²w7¹@eaÁÈù�â^,‘[ü$¯¿Æµ~{^}ã®Rªöά¢MÁ£zoJOù¤Nø¿*$Í“3ËʼnL �ËÕ*�Ð{*¬´ï%WTb(œ=·uòÇK‘GdO»§Š<ó᧪½ì��Ýzþ<íê@Õ5öÁøÙ›Á§œ î‘k=9^èž�º×,‚4„\°Xwc�˜j?…í¥½ü*{þ¶{£j3ù¤Øê¥7’Ë Iùz­‚¶åË1+ÑÒ¨ä}Êáÿë¿bOAd)@ÑzyxçPÓíà…G‰ìÅÝXï4o¥’ ½��ÊwbK¿¼Ô—èQ~%òå‘Xzª†z-ƾ¼È[œ~2½‚ïâôq ­ŽÀ·•¿QiÔÄx8Õ²ô)£ Ù}ÑC5{ ÁÌz`é {­Uéû°o„Cw�ægEi Æ:†²1ö[ÉJ\ÓÎå,À÷çY»÷�ª¥¤�™\}yÛË…`éA�ãj­ñL¼m%gϘýäyòùR�i1$Ä02ͰJj"3ÛQìÂ=Ž23hFlQÕ[PBR¤Ðuì;>!žL­I~)N_èH“Ù£Bî™ Ù[ít6öwü̃ŠG„& ‘Ø ŒàAÌÉš’án™T‘‘`h,RÌèIìH1Ëç¾FmÌY¬} æC;ÉjJ°×RêÝ$¾çdÆ'?v6v6‰ l¯îíg[‹�'Ï׊¼ÉöêþŸÖwØ¡JzoEn*¬öTh Õ³º:U€ùñ;S•8Â2É㈑œœX ÝbÚTâÍàŒä#™ïå…À&°S®…!>•ÊÓ8]'‚N)Nœ“€è3Àò 7Y ‹w¼rBÀyÌ.P ³�œ“©œŠ‰ÄY7#Vk´m¬D¨,ÜDt¬ïªÿ> -Ê2;ÛH-Ñý�Û[µï`þDkëŠð!¯£ÀÎj,Ìû5IÀÊä­pÄI§Ð®H˜q’ ÏH"¡?# ˆ9F°Ç›¦pèEŽ7Q #òB$ж©{½±ªSO á"}¨÷�zè¿õFC÷Ù!hW•¤ñ5k1¢EnZ¼…›•O‘û˵‰+ Tö™Bqhg/<Ñ`=ic'óì`ï>öí¡¨òxmÞƒ¥ßΈ3ßÀ}qp@Õ*×P5°2°²´ û´h�Њ…|1 ùío§b!ß:Ü%V‰òÎN:O�ž§’Ž›™ —Áö­˜Ð0“X-ÅIJ6ÀÑ~Ky½N“E”ŠSxãë¡›Ôkæ¨ E�‰% ¿Á0Õë#û†éUI¥ôjD\”µüÓ[# GH^Gn[!®O9;uŠ“aÚËBOù¤@õÿp Ëc­v<Î<§óË¡¸öÎ"VðV~’lßjÁ£¯ƒ|–¿çàœz«EêÄ"%e¥:pe”Þx¾a<9}öÌ#Ø0¶þx‡Å0¹±ø™U0%ÞR« €_Jd/)‘hZãA„u¨O*5nÌKH º™Ì÷1„1 MZQçdG˜¼#’¼oÒS5üV›å½Ü£Ñð³äp7=:†³`�Y£Bññ�í¥Ÿ1†=ƒ›™¹æÖ«]äêŸ@–âb@dý5ë¥ zÁ°å¨&'9S~M<‘ÑŽÇô¶wgQŽ�.AèRÀþM�ƺDê×d€�¡Í¡:�bâkJk�úô»­Yå@»âûSâšÙ}¿¶Áj¡Çlx4àd¸Ö>Ö7•T|qHªêÕþîážpÚ7Ò½¡½µ­Úö5èŽz"w ÒzׇVÃP9KgŸÚêHy.ÜÕÇßý4u ~Æö¬¥BÃÊg4`ÅcÄâ {My Š:­„m'¾"ÒË{-³Lï°½ˆ›ÎA!¥�¤òÝqRäV¡1©~µ˜:ÖÓdý–x~aÍ„”îÐÈF’“�5ácˆô|X^a¾`XÁÐNN�¡œ…ñx)Ž«`)²­ÖÒE,íô4O…@Ô!„óÕhE0\%§Ž��U*¯ínïírhä`Z–¾°'-O]×L–fò—¤jÁù¼Y1{Z’0IÑß aIR—®éÀ … - M2Ëtsì$aDÔ5Ã…1«%Sûõ)Õ=èeœœ‘¥j²7N‘á``? ÖË⬠ÈÈÍ]"ý˜�€…Ç õòÇ� Áb0œKÚÙ›Á°%¹Jó�ï¦Çv3Xò=Ø?{5Q'm Q[«ʪLñó“dÊøI›9EüFvË”„ôÄ?XP¬LrÀ"w½Œ–�È&Jý\z²MIt¥bœP†R…h7uˆ@ÁJ!ZvÛ�-ÒÈwZ¿êê$“]#Ú3[ÝNïG-*ßDJŠ”°<|0CArŠTxhâÚ€’ÂWÇdàLÁÊ1e ™ ìù¸0£f„“ªmcâ`s{�ÆHÙõÎLDü ŒhæC›& Ú?åˆ&Òµw / B¦«LS!àÒ&¡¡Ä„CËSÎÎ2§ø¬%K¤U[Ø5™ä~ƒ-ŠfÇÊ´ SF’¾câ`:–b1ÆW‰7 R3¤+ú�ñŒÛ21ÎFA”‹¨ˆÑQIE÷©‡Àw �Yð¾;?ím¬o¼<ÒaºƒÀu⹬ð™DÂl�4‰B¦°œ€Èš{” ˜<Ï’Ps)žôçŒ0Ä–BEBbL�õ%U“& õÒaþœ™J¿tˆA)ÉØ<Ø�pãlâÕä£Ù„6™½¾¬{´”4a9² ßv>›„L3¨âAäáºFLt¬iOmÝá‰IÔ2]0³öˆê�¥;Ö`w¤Ôœ#Ì| ³ˆ¸‹|y™Ùµv*Ù¥‡L ¹ q”O[A("ÞbS¿b®_ÑœåfQÀVçÖà $T> VàñjI]-|�D™I=µ›Ô4¨eK(ß]Ó€EùL,ŸŽ%Ê5½#ÓÒ•ÍÖîæÖV×^#|È™;ÔÀ¢¿ÀšÍ8„í£˜[êãÒ*GÙm�P‰a[›6æ…ªˆHðºOÉ€L�I¡\¾-,¯À¡Šóºn£›ëvz#§®ÚH¬lšî¨—€[óZŸñ“Wð–~+Rë¾jÈGa×X1«2Pø1Ea$ïm2ÄWq=¦¡‚”@1ð¤äE-r?Sï`8Ƕ‡biV"n¤ŠcM”÷9 !|çÀÂ1qÝC‚„âìgÕWÿµôåo~=…‹"P%A~ú”*ÆÁ¹d”/–— ¹i%ÉFÕ‡ÁF¤ \%@$zøê¿¾üÍóß|ûí׿a  �U©BªÄ”¨Ñ!—¨œR%~½õ�‚7¤�oÎI£›Df¹(€±Ü´WÔ)®?\ñ¦5‰úéˆÌǧB‹_¤Ÿ‚”9Ï«/¼ÌªWír~iŸ˜mç5�L°<=±`¹"uV“ EÐëREÀhv§t�U©°ÄÓ;Ôj¦œ qóˆÕMPéÔZ(0$† –Kº IH¼#oq]§â8üP×ih,ÊÂò6\z]�Ñ›¤?6Ì€éùJÎoD†¼ÛRj�Btœ¤",˜Zíí" H¼ˆIë-1š!yfÑd/TéL£vaá¹â P\›®.C‡ÚÒ ® l9- â`s„ùaiMSÐsÀ÷KYnbP�ÆÉ™ÏɃQ!EP—=ŽâåÄÄrµG©¾f�­(O(@Äh^5rC¿ò>_ôÍÀb¥ÂÌ„K—e+ôN+¥tŒsŅ뻜˜�Æ2yy0àA4ýõWSDý%D=E²‰¨Õ¹ññøéÈ ÞH”iÿ¾��ä×öWé G§•dò¦ñM€KÙ뤙I{ZgÀú[fJ7}bµOž Q´,}f¼l‹Ä|�‰‡Ù¯t3yD Lý.=‹›ɸ¸èãt«¸ØÎ;A¶Œ�¶µ·¿ù=…¼Ò’uº³—¿<è¬v6×´m&‘ǤЎEME7E¦Æí!eWÃ\2$Í¥+¼k}Çf7}“cÃØŒWŸóáS؈eÿ°º¿cÉ+1dc�‚]Öä GÚž°Vü—¼˜nf¸‘²šRx³˜qhã,O¤�k`RàzÀjÓ:bÅ÷¬öèûÍýÎáê’LþG^ù»>XºZi Ý™â¬÷ð˜;Èk«m¼6¼W×þT´"ª±êÈg3È£òM® ;¢Æ”m¸³•üQ`kºö5GÈ®i/<˜0­ß`l™ûX( Œ¥Æ"]—ARé¤Dû[»k«[‘TK.@,Lò™TšH³V5zS&­ï‰/ˈE½áB’ß7<_†3Ýl$‡M³hìâxª‹™#•=ñ¨bÒ‘-¾«¼â›ÑäY½fª,ä³Ù,æ!¦ÙP©#Ìý:ƒ*ÄÚR w«¨72>9ö! Ÿ8¨sÕ€„f±Sa'ŸÅpŸ@:Q­�†…e¤~¦3 .HÒ÷MéÒµ¯>1ú‚(™œ–¶78ªƒmš£Æ×ÜJ†ÐfKN ÖàŽ†tÞp Î2Ô|A¥Ž5ÀuKo¡Ó.ÇÍÆÀ®“¡òÕM3åoÿ.îûǯR=[É‹²¶Z=[¬ƒƒ¹„¹q‹½a:JF•õѾòTuœ¨ äÏúžŠ\?ê«;”õìì­®ã|8Ôc 2\oúúðSé,èAgÝàÜÆMb”jº‘Yw·[šQ²¥¢›�9Ø ŒP›Eƒ+e¦ÓüA…^ 3÷Ϊû»[ß«m“Zñ®s<u[±øˆ‡6™ÑÚi†oh@ä™Êži�D08q€Kò i#Zp_<ÈÜO©5»‹D]{›_S¿%9:ò%)ZuŽoZ|À0 ¸*�]OM}#¡¦ä‡«vÔE©¿$ÚdìðZDäæCÙâ¼3 '…Ò²9Š§î‰µ?Æ ’Éâ«í=õõUÕP‰=£p\„>¾dZÁ76Øžêºâ-¥ñõ.yÖŠŒöèpLšÇ—4Þ)³K%WjmúD;XsÁ¸-RL`)Ÿðè´¦Ö@‚5-ôR™ZüLR¡bãÅî:Z9͹Ð]rnxÇì²iδ®cPENÐiðìVUäµq4Hƒíʼn6¤O¬=7$ù�òk”ŒÔÇ~Kˆø�KcÇÂ|†K‡‰6wTZŠ–’…,nûY î ÁÁ‘Mw°rM–‹³î•¬5Jôž£|Mjráä!�²/�….ÉÖpÙŠÁqçN?btƒVž½1Îà4wn€)�óƒ—ÆÃÆŠ’r†Ã½P�ü�5¡ªÖVˆíRÜšz¼6Ä "©3 Œ[lŠE{9•LßUº€Fe%£Ï,9zy§Ø‰‰r»èÕ1t@S#[p�S8c´Ñ_O§¨Â`”NH‚«Ù,Wp¶z-³FÜ(óó9zž‘YI$­,6œ²üµ%L("Õzi\Ò#üd^ÑXe|yò”°Iú„§YÌKÀë}¹ÏÍêdŽÈälm²XPTt @ø_¡ôêEÁ>±$Œò[_Vtƒ5;=…ÞMô›mÝ™1+Lô2›É­²;!àÉ‘mv3Æ$¬Å;l•üã1üÓjí’à î#¼̬Œ-�ÄH9}ÁÞ‡6�Qç:²Eð¹G\=|ÈqShÒèIß:¶hŠ3`´(­ÞÚ¡K±�¾_»;¿íèbÀôôyQ»¼zƒëDŽAQ®FVCÌ,¥F¬ƒ¤\�j«?¸y/‡×ËF”"㙼S³1Ÿß£Q�™‘ÃâÌ‚šç yfpfÒ�zt\n3™sêL[ë`��™­Ç’°ip¨r³Ù¸Y#íb½È\Í bÆ :Üœ´…!Ð7…“Ã&�q°x�um6M¹—´åF“i{*‚ío¼ÜØßØYÛh)56žÎGX•h3Áú\ó¨RÅ g˜Å&ˆF­ d·2Ô `”\í«³„2n…†sôšÄH‰¼š´³P8 ¦wײþèBôC."×¹…ƒÖöA„–Óܵ«¤ƒŸŽô`ºlû%奵²ýµõ×@ºç‰ê‰ù Ljþ“«u�Ã×QC&©a�¥õ/Ðçík½Þ¥ÜHaµ*`[V€‹àN®¼­¤4²î­+š³±)HW�\%tøJ‰k†å9o¥¡`…Fç/½â·Ù÷³·™�“ð¡8'¹¶qäÞ}œxŽí€J,X„Ì—R�cÐq©Ï,VVƒIn¨�j ½ãG¿Ž‰¼íMî9a;›¹æÚìŠ�E]ÃæÎËÝyÈõ*ïÿèT=òŸnSéd=8õe³±æ ûþxló±àDY.ÃåŒýÛ–¯ÇIÍ4²©ˆ–(M�)>G’“e"„¼Ý'ñ»†‹íc$%9ë7Ådº—×]¬¨Ta 3õÑ–Z¹�;ÉéJ{vQSÇh —ÉuP¦v›$p‹SïÔüÁ0™ÕÖåêРˆ�züè_ Kšˆð= †Í7{…öøÜ�娄Öï/LWÃ\mh:"Å‚ó!íB Á À¶˜¯‰ïàH’}yЙÇ5M_� 7¯<ï!Æ& ’02•@pÍ bdâ0‚¯l¾Š¥ªÝ˜VvÛ¤M£�¦veNÍÉu†Ëa[Ç ÑAF‘©im*¬=ç…ˆi¿PóªÙBÙÀ3©â·²¹) À$ú X Ê£Á´Ó¥Vóòª?p&¥Ë†¹“u¤¢fW@(ic™N�iÂÖBRËLHâ«õ°VÞ‹íŒCÁž,¥BR…€)ŒA·=o ÿŒUK(&ÇìL‘xc­‡pN¹£$¢F¢SëÙ2žæwñj–͆šöEÛ�a²ˆï¸0,uv°Ý«AJò0²Àšö Én:9ö.ŸÀÇ7 ÷hÝ[¢LCè‘ ®‰'uK‹„ åéEB˜wro¬'æ™wدP¨©ÀÄ4åH`¦qŽO+¨!äë Ó©D>8}°Ðk EÈk“,çHëSsÓÙåèlgíµjºë�¦“z&Ï <£f,›Ž“cî–³"k°âìb&UÝAÁ–4®cáú$‡œäÁ™|õt†�oP‘±Îá ð:ô†j–½Û¬³3…Ã=“³Ære¡‰›�Ô % Ý  }h–¤(îp7¬adÂØ)Új3™õ-ïmxŸ×Ùn×Hº3­Äj̺HŠ´·îLWË̈@'V¦™ÀñÒ’6:„•O „EvE~ÞVë�Pĉ©›¯.ø³Ý¤ºI"ñ½Á*ñÒúÐT5âÒ1õ5ÕÐTÂ}ª“\÷'Ù~}±Uû‘¥fc¿*Šˆš¥üf’Äô¦wRÄCÇßê½'[4ï¼~ŽB‚mŸñÚø˜íÛj£»#¤éþæï7ö­Õ �W5_«¥7RÇR«9�X_7ÇclFØ%Ù£‘GéÓ¶@ý¼,]ô‘.*Åѱ_)æë‘´¥:ñ"`÷z'=¢(Jä9ш §¹x ß–‡‰Ý{ß;±ÃüÕ'V…2~ƒ¥ÏÜÀLb*ý´¤ÞSñ\*kÊßH›’”?„›Ù€ó\Y=˜½-Ñ|…³uìT3iµvÔ�ñÌ'°+_uB +ƒ­Û›iGÊüI±‘Q£u$Í|8¦—:6o"ÑÈ  ó<%"=µzUC$­¨%9>™µ!(ÆZivx~kõ':æ™Ð¼ŸBhh­  mÐ} ¸ÎøÀ‹hmVc—¯ªlÞö$ħšÖ„°M2Pƒ5ò£höš>Š6lˆ–HÍq*‡Ê—Î œ6¢˜œÑLÞRÄ†ë £æ3!VÒ`}~1Œš'c´k˜§—áææZ›åܭܳf!ƒŒD}7¡?Hh‚<½7ïL$¾eà\~%ÿ.AÒtiMMœH«µ„#/(Üà{bdu(ׇrê»c{êËÌ5uæ:À`¸z5­"}íl™f#£èRd -(H€öšP»y§wuMïô4VHìLƒkÜxuôbóE¡ºç*ùæˆ(3¾ŽûǶYLiZ°Ë)l7ÿ,™ˆ–6‡3Õ ¨­N”·œG2ƒËþf ÜMÌÞܾ 2«�©ñ£Í�°$úÇä/áí¡� ‹Ã,:ýlE˜*#QyQÝÔ´„»¦oûoûœ íw—‡£óýµò¢ÌIg‹‡Ú¾u1¬TäK±¥Cжe¿a!øˆ‰/û’˜4!ÃoÊìc„-\§ ð�Ÿ¶t*05ïeÇéàŽ¨ËºÀÅnd÷¨Ä¢>kQÛ„cÊ)(µÐÕ6ÅöÞpáˆõ äÁ3v«¾™U3ªˆQ|Ò"„D+"”Ç="³F—ãþ¹Z똥cì¬2ÌKóY]œÆ0ËÅDµÎ1¦ bw5 ¢ÍþùpsÃÓ-Ú§«¢o°2Ÿ`<2sK ?óuQ†�#³y´±¿¿ëÖl�-Å•¶ÜØ0˽FsrÚŒÀ�Ñ T?£ê¯®/Q?™TBíX†ëÊm”i’ÁýOñek…P¾Z¿2á0nËû•Ž�š*qc<…gt”;…=oéw~_ßš& k5IÈtŸ�qg×kÄy2EðƒS¡ C§ RIÛ’qb®²˜"mOçòT¡mO¦æ‰Ò _rJøŒKµà#¶lÑŒfš²…MKËh=q:΀b±yN{z¸D¸MY„¼0“ï©z#ÆŽÖ‰•¸%Â’Ç(Ì´ /î%WÆH¥ÿ�¦~—SèpöOâ²äúÕƒyt/жr…ý+N1p»±]·<^«\Npî&”Ê¢aû~N6:od3gª—\»zÄ -Z' ozfYbÔãa�q–ÁìMìÌלqUÎíhy¦uX˜�T×z’ó‰¶Féï@˜f´’˜¢9§Ê‰n,á±7ä R•Ê}™W—§'¦ñdÆ´·4_XÕèʻȢúyõ‡Xב(ô¨;>2Ñò‡õ44S‡CÞ̤u+Ö {�J.P0]­yCV˜ �Ī€C׸µ¦ðÏÃjª_Ê#yRýRQJw`éåtœP•7ܾ|2Nø 4õ¶¹3‚’^OçÜ͵pïOÏ¤ÅÆ}ÄÐ…FÞ)¿ÔœƒÖ.“�êbŸºõ^ý2\ÛXš±áLòá£XZ „µk+U 7𶻥H×ÉåA{�`†<ÿb0Ü3ƒÛ³Ê`†mZb1ã^šêÓéÇ?m ~k÷•¦¿ß½¶MÈðZ2`K#&‘­ @5€‰²a�­»Në Bª-fðd2š�š-¥´¿´Ôzƒÿ ¶·è£Ž¦�{LÛŸ­wsgïйQ˜B°É—7…w,È»‚¦J®t¶Žƒf,i(­<öéÑ‘7Ö!C¯\ze<  ]Ýé“å“ëk´˜†¡³:®j{`åfî­Ëþñíå n í-î¹(}æ´ r%Ì.ɼeÚèÜò-?öm¬P×ò±oC*“!�gÇ;Àh8œ‘®¸ù˜¸YbeÍ<ëeë§þÁàÓû¬`[ xø/6wV÷9F%Ú)‡Xï÷»ƒ¨$Â(×]Q r™$J é}EÞ¢™>ô}xÿcÞùèõJÖ¸Ôýˆ•®|ì.õÞ Õ‹o1SŸ"Ýt‰È‡Î¯1xÉ${È­V`¡ì'\œÁqæ‘lRÂŽ(å7ƒ•†—\ •ÑpþÉÏ–©KMÔ9¡=äõªÃÎ˧ßåQëd†`_¾°>¡èO�rZ•#ß2EuL ^®ïÖawù´Ó×fð¡ ì=H¤¾Ô%“ë»5¯¼]cJ![ÄWKËïÖwnØ’Ý�ΤŒ¦ÃeÈ«\²—ËÉH¥RtŠC°©FX«¯«þù¡ »«Îå‘0GzÛÞªWëˆfx ¢e�žÑ`I5çõLZ7™/|†g ç x9ñß¿·?®¯Ÿ0Ÿêf›Ï—ÿÒ}×ÕÏ~ßþõ‡ú<¤ÏüÎØ_§öãÔ ýÇ…vq¡'.üí ûÂß>Á'ú|™@ÆçË×6,?¾‰?¿õŸ“+ŸapâÃ]écltÓŽýnï_ÙçÈ®àö†´©÷%¹�$6Ö,ă�bw¿0)mcõï­~œý–jý°‰Ï¾øÚþúâ;ûñ¥ÿ°ßÕ£ÿóåw6/?.}a7'gúü¯c¯âD¸‹Ÿx20›�ÃæûÄ®p³àû÷ï<€¥{�`7<ßÿ€(àþ'úýß/Z$ô´`z¾ß» hpÁ§ €nïоÿ�‹_/ú~”à›3,‚ÒÉøþîµ¶Œ?ï_œ»ð‰o=·ßÿ&Þý\-ø:$Ç‚§@ãjÁ,’C÷ÒìõÝ¢ïoïiµè�ÀFÂ-á‹ï<ð墼�î¾J×…, ¤òýCH^ß»Ý?ˆVì`PùzH}Ï÷÷Æ•übA`å¸aB³ñÂÔŠ³ÑŒG()ùñqj/Õ²¡ä�˜ b£õj²VÑ/)”­oüh÷+Ý ‹fp:3è’ã^¬85c¨m7+K>RòÔ†X‰„lJÎŒ÷±Ëâm/Ô¯aÕk¤sÂð#ª*5ïL ×X�s7E‘úi’l}�;Îbwlî„ӯĘ£%¶ªƒ ¥óÚ!X$/ÅéÁކ@LÝsÆ¢ EO˜uí‰�c;*c:¶«Ž*ÅŸ ˜Ü«ŒèrJãàTNT2»«ÏWôøÊ癞ÈÆ…~í$_  §»[³É)n6/]0'?!ÅB—,æ©v˜×Q:Ë’颅tÁ”ÑZæï´=œjFò€vä<¼ëµ•H…«¯3Cj¿×<FÚÃî ³d,*!€k#ú«çŽžKY®6üþt.YÝI• OªÕµKµÍ`-þÛ³g�oó“Ïž}ÞÈ¥û©­4ñ? ó‹±�°R$—`mü¸jW@ ¯gJލNr£$>€c¸ ×Lv•ž…=é%v6êž«zìÏHVD”Œ R#í{¬Ï#E0–‚`¤_±¾ZÞ[Î!Þ":8_¹æ§‹à¼Fc-VÆâ¬Èž¼ ¿CódÏãE2Zî¹7x Í[`¤ t_oȈóò%«4j @»’žlU¤´ŒxáÒ$Ì5=Üy†¨ U›ÈC&ÍJ(H7¹$bQ¤­oWú‘2‘æ�à^#ÕXëB­äH«ß1($­Ô›Ûôg• }TÖ¡×:Í×kVqhd†™U·’¹ ¼±÷XÄCÅ2k*,³bTƒùàjñFˆÔÅ·ÀŸkÄ¡*ÅØ,S·L»ÕÑQ£ƒôŠë>½C&Cß GÒ·Yó=³â›Ëßxöw>ãï«ß™Áaá¼ßÛ7Ö1¨þ^qb&kPE{ÿž3/GY» 1ÕiaSÖ©ÊÖ¦x2G1Sí²AL3ûBYrlÔr¹Bw�¥Í*ËDZR+kñ—‰dìP�7ÑÂêr,ìyî�Ôuð³ì�ÀŠÂ*\©‹¡ÀM(u C©¯ñÿŽC¯dâÈ¥Uì�ÃŽ5Y® zɰ×å5*31°Fò9=˜j±üŠ›À E»Þ äÉm+ç 8QKo# ­�ýDqB4@³z(ÞÜÉuÐÿ² Öš‘ ÇS7èŠ!•„N W³dVÊm¤›®�⇠EÑJèeÇ©5Äâ0‡x®ë(ºµöI_¬—zT9eQX'Ç(97³Ó¸5‚ÂVÐ!B°ºÆ úqsJs†²5.çtë,­Ec×UÖFôT}fµÕ/ªµE'-‚ÒŒ7¡—Ç�x…¼jqº¨­AY“*¹S>¼fDMí«¦ÌzœôÇ<‚¨ü¯äét®¦Š¶„µÿ‚øñ‘‹Èç ¼à6–»¢†(1½[PnTå©Ã.ÔI~*ÉÆ:?kì̱ÏÜD²mÙs´Ø§äÖ¼“&ÓZPJ6I’i!^DRé*<«Û:0¦†«t˜$,zwËôhêD\‡}XEyŽZWÝ%ï3Ê!œl‹FÇ –ÞZÏ}KW»®CÛ]ßÐå¥:G1ký~¸;³›³³úÈPVo‚�C Q€ýÔBÙâU5¢J,æàÇ2V+FËeµöÄî�vyü’þC|¤ƒæº—D¯søhý=~`ÖöL15ÏTä.�ëG/~¢g'§Áè/aˆœÁ’$f¦²«õ5§)%[^‡÷óÎRhœ$-Ådæ›X&#Õ¸3–o¥™³¾D‹Á×ù©ƒ�ÙB^*(mm"ó´Ÿ•¤S~�l`V¢ä�ÜQ±ÖÂb˜Þ‘ùQ9/Ò-†­Å v4Á&nº ÷ÒÈcøF§iVï2 ŽN™ù›&�‡A¥3©Ô‚ä)úfÚÑmß yî X¬«|se!Æš†æ28m¥bŸšu#oífôkÓ}ä²I8­,4&ÐOyùým dŽð†Ð˜oÙµè´!`0êÚh é]bV›”Õòº³úÊÅucAÏõd’9LÞŒ0ŸL‘ bÎï) €Üj‰' .ØÉxéKŠQ[(#LµU€1RVµ÷ÔîÐ 2qç‚[ç:,ÔÞ�—k#òÕÎ!ÎÓáw_� EžHcuO'‚ooo—§ªø|€ÿ‘ ËŸtõ¼BªVS'$î›å˕]\ŠgÁ°ŽŸó…h°%Àæú,ÂhÕSªí�²}uIÁ9 ¦âÌ…Oln ¬ßN »"½U¨Äd.ŽÜŠRm„í† Oß5ùÚ,ï èµÎ€õ„¬¬½½_\fI¢š�ŠÕ¶ØˆtDxѱb8™7xiy:ÎÏœ ótšW¦M7g0�h@W”¼Rª�â~Ç”œ2�œœ¼ËØtq Zþƒ‰:hBÀ}§ÏJº*6„Tœp…(ƒîÜ¡Nƒá¦dÔÕ ðˆÅ@Ûþ|FNÆáÏáãëÉþ²Ÿ-�=e¡Jn$n>2"HëT[bRËÚCŽÇèŒ+`OùJPGq7Vs6;Óþô£5±uÛÔ²ë9Œª"[“ížA΀t:­ÿŒßüò76ô|€ºÒšÚ¬C¦-ÚhÖ¿ó wõ€Öz~‹Ï�jjKȤ£ÚmÀ`™\¬‘!J¢Œº¬~¶²bß-_^¾»²š`¯¶†tÔIx;×j·µ‡G~2„â-adN›©vû;�# ~ø ”…++7¹Âm·\½@¬Ëk9¶¦iíMè–ÁG:×´Fe˜‰b†a<ÇHÐ6¢1IÕ´®œ¢à@¼1è)€Ú¥Á�õy,T­à¯00^Ïwê2‹ é]8ÐýêÚ`1ÒVD’ê\îËh‰Ðj¯­zº��TŸ-?ÇLŽ1e¯I†Í's¬æ ·` Ç f³� f5ç+ŠL£ 1Öúºz±[Ú©¥ÙÃlR¤_#¤Q‘™ ‰›RjÄ?/æïËÉ{Öb ð’¼ïLñQÖ’çØ�FIŒœŒ"ÌÎ~„h£cƒ‰q½}kçþ\þC‚b ð™Tµí4¤<0;E•{~•;ή—ê©ï$òЬe°É '†1…²Kð$À ³Dñ¥[„Ó‹žñ†2‡ë½²vÍ$AiâÛÉÛÝóî€YØGB¿”$ÎÈ&aDI1§]ç#œ‚sÜOàNS‚øQ o¼ü—1�àT½f® 7ʧñj×®—»_„a�W^o;C÷Íu5Í ^G[[ßo»]ÄRkÍrå˜;—V¡F£Ö–ÍD‘¯Tª©÷×›>þ©\}ï±áy—DYO9Æg$)Øp/·AOvCÝIÀ>{jU÷V—rÅZX•Y",Ðr;×|Í`F—æ„}¹è8-¾Ea H£ÉTá&[ ‹ è(àû!'ƒ`×W;«/V1è÷”7—Èø×ÇExeËdJa—±ýg"W·ö^¯¾Øàª`.ºÚÜYß OaþW+óÜ\˜-IÊØË5$É8½�23Ê×YiQ½äF¹¹o}·›ST9ØA.à%yU»¹„Þ(ÏŠn†ÜÆ3À7µ³¥DâPùªÞZ¤flË�*MdÕ"qq”¢,1#pfiix„Úݧ–ܸ¦]×YŸã åKÚ04ΖU£j×T™±û¤Â ë»�ZËóªÍW;»ûñ„Mƒ>ãÕ1yã<ç0]·âk°C[utØ?ì�GÀZª+×"í.&´–0�–$'�¾Êé½*`TÛ⿹™†øÿGòÅMæN»ËñÏb Üþ׿šÁ;Ùš“;œø¢¹2#ƒya¦æ5D)‹„4÷�óî9EŽrñ’ ŸZÚ*s+¤kiiõ/ÌÁ¦[¥kÄtRÌ“ T|.�qå´ôîþOåPb$œQt“1tꢋZS+ºªTÔÌ�&ÚdW ƒHvŸFÃg(Y.Ÿn3a7‡Ù´a³ êƒe²¯e 2`Âä©ÃÓ’\Kö½×>«¤óŸT~|k¹;¾¾'ƒkæÁ㡵?€ñÜVÒ„Âë�ÕõEM8±IFO¡Jë[aÔÝòh�‚Yë3@‘«‚!Šx³ùÁšÔ§–“>ðâ<呎r/åJLæ…É,˜ƒŽÑtcØ;Ñ�/z\NÄpöpZ‚‚Sh÷û ©ºƒ#Ò^èoF‰’½cÆÚRoÙš ë1,rËsgíìol|¿¹ñ)ÖMËl+Ü…#@WËÐÈ *• kK 6—’ÙiµOo�šq>o�B¢nûûŒÓ„Óòß¶kÛôòÉXû& 7Z³˜×K�uÉQ`íºr¾ÒŠiœÚ[Ãûa_Kå¥ëm?]Ïg�Ií~Êè¢û ÖExÑW/ц�³µÁ&.,XfW%_ÓFµ}×ß[Bèqtz@f™Ð˜\®º³«¥®ùž&нÑz‡Šiøš¹}sM]ŸE¥yƒ¾==Ìí2ºÀ�§ (GË0=¹bÁz™û-8ž‚óÂsèÃ�Èfüœ„©3=ÝÏ”yÁÈ¥ÌkŠ»ÝÝ…õ“ÓŒî䔣V¬4�²wÎãeÇ— X d™ôŽ�ˆ”rcÉf5•X&­\5±¸X®HúÖ ¹C‘åÃ2Æ%|žœúr ¢p†šCãJá>2ãͤqbo=ÿ/¿sño­4^oÌnæXÛ• à’Žg “#‹ä§o248‚0éo´éM©‘u-]*åñtrCŒDròr8|ëF�þ²£¤UžDž¹Ó(‡ŽîtÚG‹’ÖLÁП‹½»M+)c9…préÞë€Ç6wd}ÛŽ ©ó‡Ðޏ’Ï$?¬þhx¬à¾ h>” ]2©GŠÚ´ð,ÎnÈPØ íbQÓáBøÇ±(êY•Y0§(œÞ¥Ü¢ƒLhõ]š%Å€Í9rŒÁ¡iͨÛ§þQ%9–Åå-Y|Z(Ù€¡ÕVj•V±v£C!q€Ê‰ ƒ�ÀzÍ¥ BnÕmèn½ã5ƒŽèDÌC‚tž Ç pk£_˜8€ÅÍZ´áZ˜–�ޤ!“þI„pîG±Ë7׺ ȶެŸ%õ ¾*˜#‚‚ÈM}¹—l~‹ŽätrrRpÙÙ”œk»[\بf×G¯7œZð›GJð²V–xÉâ‚G ¼äÊyök;ížþE*#¿“x¨Á�R(j¥;úø5È�tœo!Ù?&!mÞåê5�3ax—Ý¢5b·ŠÒa¢h™=ˆÝSdÙ’à©T`vóp�8ËÀs—v4¡¢}X¨�}!ù™úä#{ŸT¿ùB«º#ˆ<¤;É—_ÙŸçäàüõ�ýur×å�¯¾±?޹™ó ÷³|Áh×7#ÎÿùQš¯} És;í*œ/¼æëoK3ز"èYõÕWÜ óÆ|‹ ™ç¼!^Šg¢ƒ§Â¨™fØ¡’�KΘ֣ÞSœëÏ<òj3{£f¸|°ÇälãáB Zi™É°Á]ºÃ:´nÎ…l=Ç…¾úö[§Hâ¨JÓÚWÊܾÓÅ@N0¦»B𡦠ÍÅ<�iAÑ Íàó ýju{{uš?Ï»ˆö„Mà9øt@É…2€¹¼¡êÜhqs�Ì·¨Ø Fmp÷÷]H–üs ¯´,ü/¼T:ñCÈ5V¡Î/Õ#©Gã‹ðÁ¢† 8õ+Eo·ß`4.ö@ÞYâ'pg÷6Á XõzÅ�nú›0iåûŒÍY1 lV³%vãÈì‹eu‹~‹GË¿ZþÊQ&k‰±Â¯ñÙÄ3øòú¥K¹l»0¡>k1æw�O-N ðåwF,Q$iz‹"g.¿ãx¦Š§pää·v®*pš:cóG4f‹äˆÑŠÝýhAŸ†ß¸Ð`þ0(O.§‹H>Z,J‚œñ DÍ/CW…uRhÆÆHÎ …TäÁ¤º·Ã¦¹àjIOÖ0F#l!Ò !8ãjQÎ�¹�1ˆ)Í>†L¬¡êT2±q�«Á~ý'® Ú\ãî�C7»æÀ?CÊl“©Í5 ¯cdØ6ž%±T6¬Ý¹�ò®îéaµ§”º®Û�ß鬉RΤ¢íkû[ýëâÀ"“9I=齈ª0Íg‰%¨ ¾ä�2­¾Ž¯_rá &‚d:�Ãmn³jPŸéJp]nùV6 [~îÒ~ÝS_·{?%ž�S8D 4:ÄÅÑ$ "3¬¨Ê“cqµz*!µvv¢Ä?óµÈ+•ò]‡�üå<1>ŽobÜ%xJî¹®kÆyŠ|_á#ÐFĉBáíúÍzÆA*Î…—Î�ÌQ6¸1ë…8 ûwQ¹ùcpN‚PÞzæœT*lౌïÑÎá6}9†€¤HnqZ|áøÖ7 ¢ÉÔ˜�ùVà еº—V5‘XŸ£4ˆ,,ˆšnA²K9š"Ö0k\‘$:šsw�àa4‰¬”Ëî5ÆFLR�,u‰ØÁ(¦n£áþöÞk[®Ííu7Ty‘'¿8-ã'˜9¢mÕ†íÔ¶Œ�â€Cµt3ÀÜÑVS[�´@æÓ>»—^œCe˜ ë^©¡½ÎŒÂWY(ãzÜÀš²ê/íï˦ÑäÓd0Ä,ªùqÃs«X3äs¦/ëdÙó@•˜¼^Lš§°LÚyù'�Æîð³I«°³Ýó2ÅB»í·ü7¥‰Qñ�ÃöÓûÜ,$Vº8M×¹q=ž †Ç訣P.lN#CÕZÂ`°7Äj>PûÄÂ^å�m�JÁ­Šðk€8éa©éöÖSÑÜðZÇq°7x×'%j‡sÊ2¢1¥�ñ$ö˜,,ºp¯VÞkÀ ÓI:F4 ´{ð#û]þ¶ZÚê )Æá~BRdÌw�Õ—¸v‚ò@F'J{ݪ4§:˜W"�¡¦âD…Ív‚ÙžF5¥}¯\2¦Ž åø’x™á0*ê—ÍÇ#ö`ƒ¤?ÄŒyÞøW¶úÇ#Ê+WÖнÑÊ�r{§+ëå…¶ú {KK.¦v<ØØ>R‡Ø¡¥¨æ¸¹–u¤jøÉõ<¸SSióêâ(Úu¬D')ìe+B€ßëÀ@´¹VŽŒ€<ñ6Ó³åáÎLMø çLÌB�x^€v›ŽZ»üm�zXHÉ@êH )Ûºâ!�3-À~†ÜÒY^°p#"uâѧ«‚4ñG‰«cÂ?—Ý(uñÔéU2Lš¯³V.îRÏ$$ Ë%SµíR:”"Š£ÿ*(³Fø�Ô°Ü ¶>3Õ r"BÜC6ßÚLª 7üQ~ˆÈ ¶Aì½ÜØX×%—µ š“ Í)ã®…|Ò0&«ýîu£ÓˆâQÙô×]‹BBŠ3›‘…m@ åÇDl �’x‘®•è"ñ1îД§CÙªO µ‚µùhiåyj`¼|u�^Þ¾[7Öi)Q�š‹/ xNh¢ :ô´tæy¿Tr/î0¢Ì>[Ý&Ý9pq¸³Ntls=“öŒé’׸Ú;|Áܯ¹Ð™×DþŠV¬QðÂõ —Ø‘q2¯ù¡Hö 4ˆÐg!¡�ñB:/çÑ`Ý€ð~?¨MÎ…¶ñ‡àÔ¬È÷ÁÑ>ËGØKS=ÿÖ{s.~^åI´Ÿ¶é iûzck®'Ÿjâ}mÔ¦…‹lozPZ'lìì½ú9§_O<’þ|k1V”>¸vC¹ie¦­K›$uÙÞîë�7�Õ±Flâ×rÇ~ŽÞ"-®°muè,^ª¿d6GoðôfLfãv ³„v‚\Ý@"ìýÿéŸ>ÿêË/¿þŽ@â ú�ÅѺì §'„Ò­ÒY‚O2‡È½ü T´l)¾”'m…”ö®[Œ–j�¨e;€�UH …�¥å“‹+ ¡5ŸÉ¿b47ë<ÞëYkü/Ñdl¡úíwî·¢ºiÜ⸫ØZüØÃÜÍ`éósýùjiùâòÚ`‚K“ŸÐ =�£ðž¹óõancF2žm2‚ôõ”BÒ;®¾E>”@ìë,V9_.=¦%£Uç™À¯-Ι¼ÞV{›H“@EØf‘+náõï†-£ˆÒÄqG‘È$È"ߟký¦¨«íâcÁ¦ÂÅz(ƒ¡huJ.é^ÅCìúúâ:#Ñ•`MÙÏÌ™m±ê0ï¿­óö“ â?ë„Òëµ£­ÝµúÔ|é´´@t9DgÌöë˜5À3°³õ²öz³Ò͓Ơ–ƒ�\Uéj$(®ï²Ü VóÒ+à¢o¥6í¢ Q‘ƒ™…"EÀ>ÐÒÎî=õ¤ΑÆsmškl³C`; Rdé>ùFºxƒQ_öt§ ø¦LÑô‘!µ‚jÞâÚ†LÓŸ'ºýL’­¡çâ�GÁèŒujC£Ñ–³Ã-”M|(‡dÕmè˜LŽè¸-“¥¡g"U×êðŒW„¾„ÙŒT² 7#Mæõo7Ðl®³öR‘„µCÍ€.*œN2m}$:(€SSAˆ8 �öVwÖ~. kwUl«QëÀAL|C(ß\;n“SPL¼ !‚ S´»!;:Ψ‡)ì]Åä¤Ðþé€)v= 0m³òϯgY”Bç1_É<=Ø[]ÛІ€…>û~s¿sÈñœ—»[*Ú–=\e ÌŽdypEnsÔ¿*,jq�R' Ië@ÖŸe7þUÂ=Þ‘LjT[á—y•Ìž”üZþë ÷×jêéLÀ7ð󘨘€ëÿ’õhøóëWM )qg†Ct­$�±öz‘�ô™ÕcÚ|aeÒfË»e‘çÑÚó‹Lí¯0h~ÀuÑ"ô´kí¥ å�ÔàÐoYÞ5i­é%‹‡d@Ù½}"Ĺ' ò ‰tÀN£Œ`ª‚—Àf—QT»oæ÷Vª�4™HŠGÔö!Gé†'Ë�,÷‡+“¯üuò”>ü½†Æ°Ü/òªïÑ� §öÃã3�Ý`Ü ñ–ís%÷Yh1öGaP™jýùXø¾OŒTý*(¸ Ò[ÿçà‚€·†{êô<¾#ŠŸMîÓÐ4rW°pðôµÃƒÔåÔ¤-ª&µ{À-Ç ‘àJ79¸±TX Ã}°¬žé3¯\ú0r_K#½´†Ä?ξ.õfU ̈mk�³1�–§@Ôå÷t‘¤©Û4ézѧël®óõ1V0d\U@cq]™/;ŒƒÿßÓ�Õ”ÂJÏFßÖ£¦òŒ€2D < ™1øe4Ú�¹NzÕj�™ŸÅì>ñÓÑÁî?�§AS‚®¥ÍKÛ�ålìÑîïΰ‡š.î,ÿTÈÃm™QSžíò_/®Ý>à…‡Æf³j‚¬uDmÓscm‹d³ƒš˜’ΈR®2Ä`é /šÈ`ê_n­óŠ*£Ö7V_(Ë—ÛO´d¹uDV†gnO°j¦ªÆˆ€’Êà¸íI©qy$âKáÐÜ*/x[�ÒD¾¢¡–7at ¯^§öoLf%“QŸ'yÀöÜìÐB9Îî/Á-—gaƒ™ë?T]M}µ¬9nJìÖ…xªÙì;²ŒHõHf«Nœ.³çzºY”÷¼ÜÄM"°\IÖd3ÖåuŠ&U\‡Ò´ÿ§V©„1jÊo!DjÛ¹YÌ–wT¡ÞÝ U¤ o‘†ÃGM]Lè³ö _‡ï(Â#ç°¿}¸µj?9(J …ñ;N°7øMc u}zößý`a*¼�ƒºç8D7ÇVurÚ½=ÆUÁº‹,* žt¯é_Ëk ‘¢Áés5 U;ä¬oÍå¤ã':`\|ÿ*o,¤ùÞÎ+K9iEH.ã+�¹›÷ƲãH¤‹Ë�è ‡u-P½N�ð Î¾É¦µÄ¹:“‰¤÷¸`¡¯Z 7¦h¹Yï¬Ùæ[Ö00áŠ!:!p:Ü%0Vê쬀ÃN™á´c6;äÊæ’ØMz1™96ÙÏ–*³0ntÁNć†Ú�¿8©u†#�uõ7µ[ÝNïÇ#9• jÄæ·g,4ExeþžÕ¸x‡=W�ïg*`˜›5aÅ�ae‰Ñ›Övšúm�׉ÉF’ÉxĵÝ`'Vcò>­†ÑÚ"4ÞòãŒÜjá�º½ŒÕ¥¶ôgàZÔ¾�ir>¢ ¢µ3AÍŸ\±ùï©@UEñ5Ƹl*ظ³¿ºCzqŸÓ&€ØJ.–‘°ýzfÄÄ"k¥+»V tfàÔE¤ŠFÔ'Uâ1¸©õ‰Ù�”Ì0Nظpv¨.0|T›Õo–�xÁò›¨fZ ð€j«š†mÔžÕzž¥SV&riÙG j8åÂa,ƒ$!Ìê LS@GqªÇ¬ìr¾²wv†-0Ÿ-³ö8.‘glÉàBrD%o¯®íïZ™” °ä˜:C¾¡»æ�N½îeõ'ñMÄ¿â8ßWþrú:¨)o¥)5%x¿d;I~LÀõ-tp`²[@MéO7í~L!)½ù©~Zµ!7*©Ö;q­�æ^’Þ|›^�\ý1i%ˆ™Éšn wo¶)qúc÷}[¥X!$_ý¥ûÞµŠ[.l�¾A\7hr�"–úä›-X»òÙ’‰˜ÝŸÓ’+w´ Å WœhZ®Ehx÷åÙ:[5»ŒP—ˆù_„l\ 9ç™VbN{²á0P:Mb”Ñ’å'±x…mñ„$c*N‚8lRál) �~¬K~htb@VA$'»ŒU`Ažì©yªÛóY⢇ºR+�oÔáÿ$ÆHÚŒW‚@l‘¬Ù¤Esæ]±„’À¡LmT§Ñ’Ái7>ë”U™BÑÁA`ÄDZ+½ÁÊ»¯–ÿí饸LVb,Õ ýÏ3'̱*„ã§ ,å¾uéî IO«�cÁÇvç3ähŤl-È)ºÚá�w½mBÞ4о¶·ˆ��m̦ùXKÓú÷J³;¬2! üä’q¨²R0'—&Ÿ'J‚ò”&"¨èí¬çœª£SAxÅ’žWØþ+«ÛW *ý0o,Èw>Dt“ ’¹¤)ÁG7©-*�¾ð–Žø ¹FïêH0C;°LâEV¹jðI‘b³y�åÖè�ÀCïÛ fó4>•b`ÀGU Ñ‹anO¬‡À;ÃÓéÖàmZ›?ØXÝ_{½±ó*Åý`ëLEsŽ�Ʊ…ã!c²‚-$œ'S­—ö¥¥8ã…ž¬{+úWÒ¤º,J.%L‚œM`É{˜ ‹.x`ÕÕ�­@�o¡+‡Èô©²æèNª(,.^IVžÏe¾Ú�9"âù=?Œ¦ºfÀ„—\Ò1q{”¨ïm³ó«ç²Yî…9z±zÀ]/ŽQ3™À.À OṂó Ùbý sçS‹wÅŒµr}Û»;rF™³þÃê/7WÖ6DTOŠ`ªêw4¡R–ç_ýº:¬‘§žm| ŒŠ{Sôõî�Ù.‘Hqó”3îwk�ý­ß?©~·ºÕùýÊïÜDåo`©Ò[�_¨Û‚¶Vg‹àNš±áùV ךö” â=ׄꮭ@žç1Òg�†HZò¦×ïbFÿ~™úHÁäwê¿ý{ApŒûÊÑXå0(ŸÖÚG;Z¢dö-CŸô ©쌄kyž¸¤$Q‚ �ÜÊuÖ;1;�Cÿ’”Ї W@ƒl‹ÌE3¿;¸èŸM~ÿë´h�cÑ«:Wd/Ä– ³]iyõFR½’88í³µ+:qÓ¶5ŸûmPߣ-r6ÃÿnȸPÞR¾ hÍ<ÉÚdbÅ"«%4þp¿¹±&Ʊ'üÃå“ó>½z­u§ å<üeù¢èñÙƒ$’µ¡`¬»×}Qš�ê]ñÞ>V¼jE9L‰iûŸBÂmî šb5�YH (ߘ-¦ÀûƒŒ ‰ÒFê„Æt‘â ²5�×^V-£ÿï&†�#@WÄ�8i%�I·$Ví:¾k¯a–H“Ün#ÄØEÇ¢JÕIå ²NÁ2ºžK«(‘%VM�Ùã’ßȇRäHô«:†ñ] ø¦t T}H´S×t2‡é©d@¥£CÐ nm�“³‡Ôę̈õ-¿Ð'l½ÀÌGÍi²AòsàÌŽZèH½4b󦫕l-K s\<"ÍJžxA—0"‰Hê$åDAó¿™i5l ¢º{{Ü4yàf¢yI´VÞœ_på©·¿cqÙóá_§ ÃXèJªI�n5†}¦jÅôŽc‹eÍ/½¸ 3à"Lä(ÄœÝ`– ceNÖA$¢Ð´ÏPØå6½4Á—gr‰]ÛÅ,¦÷ØN Õ;`Eƒî Y^b8åÑ(ôv})ÀXœ±­Z�c¢6àì…ѧå¼tWK.á5îW=Ã4`Üý6%ýqí»ÄrT.ÄPCKX‹ë1´zÙ\ZtºŽç"©eй�‰”gjdì³`^p‚Ötvl6ž åbç/ŸVü2ýõ•þúŠHé#PÆ :6Êøo¸újËrðùþ§ß}µµÚ™}q\Ð2Œíe“³\[[zÖ æºÕ=®|Ž�z¹V¥mØ7¾þÊ'WÀÆW%çà¿óþ+h¾÷þ´—#KX¾¡w@HP’“jã§²?µ¶½^}k‰¥2¯â ŠÌ¯kBÕú^¸q®Êø­•‡'ÓïÒMü®ãÞúK_´qr4Ïf¶4@¤X£Ý÷5Bþ’¿Ùq‘ùÔ§ŠtðLëœ`(ZtƒÉøŸê÷’™ XF æQmßxÿ$e‹tiæ�¼³j–•’…‚—‰Ác:ޏ?‰{!ðÛ/ïía¢VŒ*;ö.œ]âA¸‘—J3=dô&2ÈÊi{õOÞ­!?#5x ´‘Ÿx€˜¢0�ot¢Õ‹tó1 /Ô …ÀQn7¸‚àŽb®ÌÖKò\””~ŒÜXdmSP$&óA$”�ò¸™¨¬Wꎑ––a¥©©l�iR×n<›¹ êf6·5Öè¼$`?·�ÙMŽk’y£â)q� 5ÀMXÍ;£‘nü¹ ïS{È届‘Æ~ýÏ„‚[qƒlâvÅY`{P€j�âi‚ ‰œ†‚Þ[Uixç§½�™¤¯¾†|êÁüBu3ZƒI�ì¤k’’�¯dÆÌzËî7ÕÒW_~Q½¯¾úí¿UWW¤f¨÷’^XúŽŽ©ï«/¿*Üy<¶oÎÉ,û øå7é+-.æÉ�*%ª¥[þÊÞUÓÕxÛõ,€v?¨´{¶l) •¡i²¥kÅÝb7p¡¼?­¾â¤Ð=2¼L;;_#Pkžfçë:œ‹52À嬈SLê„ ½‰W¡S1 ¾t<Îç¸0]ø�{¨†ææ{É%3&Éæ ê©ÄoÀ}žÉ‹Ö˜Þ/ƒÔs:᫜W'Ù�©Ò"×'$;¦ö¸žû«¼Ôãó¿Ûÿ` ÌÃôF•©âíÞ¿Ò¾òJ»WcËq§U¦Êˆ˜yÆÄÿýú(ÿù÷x[³!KŠû1à¸dܾãÖfjB�“_èØ�R_Í�g*Ëv‚ËÀ‡\ Ër‡:œäÄ•îåtyÎÞ’Ü;�’ sRŠÇƒúRA�JDî°£°é Ý¿4<˜c@´Øµ„€ æy¾v³Ý ¢‡­›Ees0VŽ•®GÜùf¬ -ƒ Ü¥T˜‚ÿqvAäŠ<ɵƲ$�F)i¤ØO*d‰:SêJ8ã1W¡»Ü›bôEÝòŽÕtË¥Wü½Ø5 8µ",EÃN‡€ä‰жär£0Ñ¢ •y˜#W½®v®³y$æh=?zVýrÒŸ `˜_êÑýÿU? A…"|JG ]üemÌmˆøþ˜~ðgõ—Ãóa+¢Ÿ‚úô5'YÓ8“z¥&s žWJ,ÖGÍ…DòÎ%J@é’6Æ Jß9!ÿ|¶i°Eæš:öãßÇ}W÷ex»É6q+H웕ßÏ6ñø|¶É÷à ™Lþy\ãóY¢Œµ5  %Ou?©ù%ívH,‹wλsÁft–¶qA½±ŒpRÌAÑ�xg'±—Ÿç4ð=—"¥k¾_ÄY÷a(Eƒ nñœâv=Ð I1Ô÷MÔ¯ñF\GÂp(éàsÕ$KŒ pó dY B‚f]Ú7çnÄ”1c< ðX�žx±˜Ô¤Ÿ©z}þZ®¦O&¿B9~o‹»œI«üW_AÈ*ƒR¦(¡¥pý{¿/îñQÚt¥8ÇòqW:ÇíñŽ ö[£¨ï…ÒfµMM2\°Çx?ëŠ=[¢„ûŒ�W 4ãAjøgDžŽ^ãwìs.õOn(7Fñêç­ät$™Åù•Rñ¢Ã-§f`¢„^”rat.1î¹E8IU*)ªDMQpé‹@ˆC.9~N°u’¼(‚S{È|¢ÀîŽ1ÅFGMîþ4j4 ¾c©m¾õ§”\æ–DÕËYi”S¶Rª2X’÷ûu•8Ë‘ùÀº™ÙjA?IÈ\8Hjü¼^¦Ft«º$Ùå‘ ¶c×µ bƹtá*N T#NâØbùüWr>ŒÆ¥ïä™Öb”hÆÝ�.ÍΉ»P Ííô\º§‘“�k¡>·úRè#Û›­ W^ÛårX÷Þ¨¼áŸŒ�Ønв¦ÀŽ/öÅj‡ªTÖ¦ÀÞ\©É§Àü†ðíjiH`TïÇŽ€œ¾«YÌY/àm2Ö4‰h_w…Úãµ·½ÞuºJ„Qí¨&+j:¡p�Šòš[ºã· �f ¾Š“8Û¤ŠÐjGaxDU£LŽuém^òƒ6øÁbËùÈxŒ JÈ�ŽT$Ñ@ÅëÍu땲¹®C¢ ¾”f•…4âña"è[AϪ®Â(T»'i\½D¶R´îãkœ±‹BãïÞäd9÷¬�xCu@g2íêxÜãÁ¤±[�%ÁDØ6_ø%Û÷BØx˜%“„ˆ¨Â¡qÌÉÝþ�{{®/àë¾ z=Q*D’Ú*Dû=îÇãt§1íϾáôEÿXg_eݾ9‘¨i²�<ŸˆlBÿtJù”cßH¦#_1é)Õ—ëiÆt$¨…1�ÑÕ¼²�ÁL/…ég͵Œ»åRäW:ci˜°Oæ~“°­Û éÎ#ËÓâšÆ“xèßÄÐa@²˜T ¡ðhý¬ ~M@ÐmÁ¡Z+CÕ«ð:™7¶wÿ¸y´¾¹OÇ�ÝýŸ’¬”ˆ‘ÒH1)Ac)g»!�Ô†]åâÖ‰Þ4’3éáÞÕð/ô¿²t”Á›à°®~°0H‘9÷6ö*7%FÂe¹ŒW„úó”!†}_aù4RUXÃÌ~6§PµÅÇ"ß6ÿ»Rèû�—á°üÓèšjŠ[–Ob›ª^—™˜ §þD´«�åGñÝdý¯ª"ž2Òvx”(· 3›<¦ÂP (Ú"D±BiÍ%;_nIÐ×9z­£þ¯”Ô|óà ½FùŸ“ŸMÎ>„°µø† >ÅækèH“1v!“S&ECÜC ‰Z©$ÞqÉüü�Hš£ró� …Ú$ûs@G@D‚–î‰h8`wj?@{ ܳÞ婬gœ”ݵÅÎw�´Ø]³Ôð"g�ñZîš±/*B¹½ÊYÔ>2µÈÁ^ ¶`Vt/[NW¸NTúÀp±¹"h¦×÷º´²¶—0ÛÀÔ-øM¯i óQïçý‡Ff!½…΄÷„ô-î!r£Íû~agZ"š®¸Ï&LaDxBI\‡Üãl”tmXÀ¿’ ?«6Î[¦áwéSE”ºtp9·!…Ÿâ>ÓnNyU†#Æ­£P V¥iwzÕç5Ü…ri É…{„î쫹Aqêx2Ê”O˜ï“[˜ƒHÖk¼Œ”%$À/¡ð”CHr@õsÆr(ÓKeA›Å\ä.{ºŒìƒ.‚õ»+Ìú‰lb¬Ý’*€|`ü–dV�I 4 —30©[ 9 …}‹Ñ©ZF;ÞÚ(Õe°³t°&Áæ)Ü3Á°MïÉz iOP*Îè6@ôßPH··¼Sdï1×dC0ç"�íÕ�û”—¯ ˆD_œ¹*K9¹„ aGÙ*âíšµþ/É�¦3ß<Øð¨—“lìve ®Ü´âeµfú¯³A�n/òõ“uÀëX·‘Ö{l ëú¤3Á‡·€Ú6eê©jp‹ÿ5v*yÒ3™Œr�!×™Ý\e1G£,9‘‡Â2%:ˆ”K�ô11·P;B42líh¶›³Ü¾ã)JóIýwüZ[RLW­Y¢xø”7—n >åm÷ˆ¾Yêµ ¦7d‘á$ëÚ@¯Ã Ë_?¼kXf˜?¯–¿.Á}pø¯s¬ëó Äi2©%ê’áo½¸ ¼"ªkÏ3ÃßMHÙ ¦µB­Ë ¬|4>G¦ÈzÙ¹d"µ\F‹^‘\ú —‘%Î$ÇØ{†�ȱ°ªµ³9Ñ“3I$ªSñ™/+,´ôGTÖÒ”\ÊÁ@´\fnÕzÅÌF…{,L‚®�pÒm&T Öšò °K\Ù  ©b<~n�Ä1Ñ¡^Í/zy¿@ËZ�hMÆS9R©\ xs JÈŽ,-²ìbd=—êq¬A2tø,5 X$oŽ%ŠRï1Õ‘u.±†EQhGž-îá!m\‡ÿEÚPSÝãÊ5i ¼¥ó“ 2.lºæ[äxêÄ!—¬E…Gveƒª@0Ñ[J?܃ÕÚm׬õ¥öä›kO�"Zª“ƒÿlíÙ"ÚZ¼IÙ=ìÈU¶[´ú¶²+v_ƒGÊŽ± £Çhüju›‹‡;_ZˆóSk†-âWxÖµ2º0,�¯1¥rJ3]ê“aÛH×¶Žé.feç'ú´è„­¾˜. J/{^¤Z{ˆŒ ÊõéŸQ¬"v% 5Q§¦Ž $'09ïÌ×z˜;œùH…�væP�ß98ÚÞØ~avöÑÁÚn™6pÙŠ¤E2„]I;ú5Wé:¤´ ò1™mD2ðáºsü<%³h �"M; &8çaÆî‚?ÿ'=M^ìîþé_%<Ót ëÓc«ºë`,€EW¡þ’šW²ö~äÕò1-Ä…@)ã›ËÈ´Âi9Ér±ªæ‡ò�ð\-ʳ/2 …Ãû2ªæx”Ë ø³TîM9‰ÆÐü!¸æ����çlA#ÉÌçÒJš™ù¯ ÚÈìU«œlxE2t½ý‹ôþêagÕrD'ª…º S¤u:˜µh�þ·faœü×q ÞXv³�­ž÷¬a•¼µÈ=aÌj0PÚS©ẗ$ÄÚ �’°70žPNIî%!§ä÷Hûqª�„P�ÖêÉ�º"dy_óµfî ™$½åÖþäÒ²÷ˆ «Ý¬ÊÞþEÔ¢ú$j+>€Rªb•bö)Ô›¸øpÄç0±’�ü"êÓþ›ò5"Kñåâ#6McZz:ßMˆòq"Õ3¸5zÇ�7y%Šw}³ÏÉaÊü©òQúú÷› è´ñÿUwî=q&Ùÿߟ¢ãU'±GöLF› m`›lÓ hÆ3ÒHC3 �…�/óé÷÷<§N½hÀN´Òhlw¿]oÕ©s¿6¹C­¹QCº�n�àé³Û.d’Û&é¤g.ŸÚÚ^›LjHº�¾#P]Þ}õD©Kù‰ÜL0Ô 5Þ Çaå"Y‰ÎgO—ÁÛ YŸ�(é6ì°‹ÿ;­lYÐ-Þœ›�Ç¡„k”ãŽáûÝ`sfŽ�•ï‡äøÈÃz]…`¼i3�õ²ÿø :U¶ µ''änéB}x£�Ïö¾g¼h%UÉ÷EhcßRÙ½é.Ë-åeV…w"t–²š<\o-'{q.$®:þaT§-OšC˜J8¿Þ0´¡ N÷ ÏÔKjb¸*mT÷óp‡¦Š¿ôïT@×lžR;ï­ªD;8¹PÛ�¨äж•Ïfÿ*ÿEçâÅ×ì&ß;# ká6 ÊMPîêÿ+¿@^Èó3µD’óûkŠ:ÀY¸çÚØ£µ¸=Ì)Æv±N§Bo'ñ8bºò¤½ÓâE¤6N^{H˜û iÃX/“Ê€®<4ñˆ‡a&söxÙØÛPkó2®î§=.àÄÌôå™–ÑPDÜòŠœîc1׊·Î~ÁzÊ[®ÂÅNúƒªOª ‘ÅÒ´„]ÿnz.ž(�è†ßàWº½já� ·ÒóÑëE\LZÓìˆO*îTç\7Sã®bpúÞæxô«èÌJci˜ÜÕܶ³�™ ؾ郵°žÊy‘<ܽ³GA·ÿkX3€ªé‰æ�”†Ì8ªØMÜF+väª8@ƒ�VŸ@ÓÌ;áÑ×7š�® ÏØ w¤kûsmáj”ûz€›sk‚]pÑM†I2+£ÝÕy›–^É$ƒb I‰Œãg{[꬞F^´TŸ–Åø§Â_¥ EG~7V—�¹‹ «…Ц¬>Úh�ÍKh÷lñ̇k4¤qª¨|:A!Ò1{ ²¥#L‹ôÖl 1í–Æ…ÃVélÚÜl©Ò?iŸ€:§,`ÈC¨< Ȉ�ϪÆCwª‹;æd`þxö�åôæ÷ïÙ�âœNöûˆAÎn,­•œoúšî*¹M訫ӟüOêZXŽ&[tÁEðã"á U%f/1ty¸Oº ÈV+W´½æ›¨m!Di!BCpb½«›¿ü*¿ËÎËÍݽŒÿº¾µ7y¹¾3øíAûÁÎß_�¤nï½þ¼¶÷˫ᯣáxuùÙu?ÙÛÛS>±º··Ìc€¦ÜM_ê‚æˆ I(_XO>r™,_ƒÜ=L*6�yaSß—#m]è~éºý|Š‹ÂPäCK)cÊ.KÚ´Ç‚FëŽ5g< È³Zóß™…âí•×çè’¡›ñ¾IZ}ó“â™r-EÁXÐ]žïŽW&è—´¢Cß æÜaùÚz­°ÀBép� ç¸)4ÅŒÒHž}½˜yt`L?-ܠв}š³âu’¾¨Ò­ÇN?c9$ˆÔX†"MÏÝãHŒ‚y¦ WIÙ#›!”$Õ�Ÿœ±ùT²tó•…÷h9ñ¼/ïJËݳl{T6DÅù“H¥’Zo0>(….IÝF2TþF¢·¶Äò¿µÚ!Šî_Q£t}M#j�h²Y<᯹ä¯nÈAƒÉðÅ­ªBEŸ–ûe`ƒ j>ã®ì�›{ERգ૷ c§a*NÑ1G)Ž Ä™pÉÌÀË–\s±f} !~¢ôw竇YönžùÿÙ€oHNBÈQ”%(¼Ôvë‰$ÔlU©¯ÑÕ‹F‹¿‡Ú×÷ȵø{XzüESA·¹û_QLk�oÍš d ±Ï´&5T™›ìˆ£×ö#²ˆµkè{>£¥D-*#‹yEêj� ê*êÍñï±Ûi­•Û‹»;¥ÉJiȨöu15ÌÏ^‰þ·S¢Æ›“ÔšYNpT�Hè­‹¤R¢ tYaϧ¦¨xpÉJûÚRŠº›eë-¹–.5Ø|SÚsœÊ¢-'ªБáç½s-ã9|ñÞjFcƒ´/ õ»‘Ü4P›Ê'gÿb§óñ¬±ðgÊmY.xÎÕ²Ú�/Wývº—ÛË (Úâh8)‡J-Øçˆ!˜3"¡nÉ©“ÕB”ºé›,×ߺìo®¯Cv<ãz(Y;ì-ŠàjÔýXøzóúAgãÍgà´(Ky¶½Û›»[7ŸÐ�ë^¬Q»r³ k„ldéP¾xå!Ñ`Õ©Oy¿Ó3ñÝ]xÝtDÑú»ÏûË¡ýCnÕNMBh—bâÎ-vãÂý[ÌHUYá1U⣣§ž 1%ì]‚[(ü­&�´ž’Ö®Žàá‹…Ú#C÷†74ŰW‹#Chó‘dXå–_͸‚ª]m¼ËÀç�3ÊØá@‰í²ósŸ T´É¸Š�Õ�Aë˜ ‚˜qLËÍî²-è#3/£]ÃùY$oªÅÞǵ}Ôm»^¨@ùãñ�adhü™!Çw1Ѱ¤ÍÃ{õ&ø#à+ªùhfk8ù—‰A:'gÒã"Ói0Bï -/"o‘ä\Hì&%£ cÔ«ÌPkh.�ã•»'�­?òA…b…’•‚ÿö“=�šŸ¼Ü^®îT•£Šo#»["VÌA½i|b©ŠŸ¿M3–Ü”Èj¶|©íÊÖ.š…R"�²”QÖƒ-D†uo61sv{³‰øª]¤´4Xd4q½8ãEŠ4'uo«\ìòÀC‰ j‘Ñ'Ò 9uU¦J•vc¿Ê£Ã«î-®\)Ìhg0BCØšbŒñV!£E‹Pèùæx2n¬Å�h8‰à¬©à¥#w©Â:¾ªÙÒZíqQƒ‹µ`3„@J¤33ƒ�Kí¬ýæ¬R»Ú óº†||�lG»Ù’c:ûxL¾Vit…Sb^Î¥k¼|îß¿`kÂÄ†Û Op’Û á m÷jf÷rzò‘®oû 8䮽ä„g‹r—uuyÆ’xõ¤T‹UÚóÀILvlÚñ{Ò_1˜’IðÁÝ}“¶6u;!%´ó^ÿç%¼þž�zx½ÐÌíÏܵ­£ç¸íŸHø/d”ŒÇäƒ8kè-Õašúp³�‘#MƒÓå}¾¦ä)Þ¢š •Ë8nIUØË ]$]ÀaŠÚ�¾4‚ƒ¹ñûL¯øxamC«�gG)1P7G£áO›ÛÖZî\``â£JËí�›3Ì,Y0Z¡Og èAÛ¶1%À[´Á²ÄõÂÓ÷×øÒšä�d,Y ¥¿Çëªï{s@lògÝVÀmÜ@àÇñ;LÓÇ ÙÇèbµ¶¡(ö ð¯sœÛJ±ðo—dü‘ÒÃÅá;'îùg*5·°A$ªËíØÅH²ò©&®_�ɽpÜæÆ lø]/ Ë7pZã8—ò3*íû¯†JR,[Վ燮”õ´ @ô·ž�£>U`RfE¨5JóÍœN§”q¢vÀ™uÛRN[3çg™©´yNÏ’mGû2÷/�¾P—AôWú Çãv‚°O÷•È÷zJ>‹ÔŒ¼c.ªs’^¨þÞöúÆzhqÏ××F¨Ü̧™²7êÉJ'd–e±F÷ÖTJ9e ï Â`ÊùgÚ‚Å»� ì —˜t,ùSQß9C=t–![¼£]µ¢š-p¨ÓÏèpÜ<ÊÕ�Oÿ™�x½ž“{ƒh(ëq½Ó™¹7QÐ�3‰¿YLjk…ß?C9f´¯þöcVïbz ãÔOGéÅê,G�M¼çq¯Μ}Jr” ·r?å^ÛÉ.,úHO“¹¬¡?{ÊgɲT~zCl¬b`‡"ÑÑçÞq£Ê oŠRBôäêÚd¸~É ©\©Ž¢£3’þM>!`ó‚ª-k€ÌðwàpÄË‹}i&’ ^U{[f71BÑG^&Qó aæ•{+¶g½áý½·�7ÇkìÍúž<•Ë•ë·Ä BŽZ#Hœiôfåœ×OL^ñ‹Ê ½¼Û÷ôaâFïyak¨vœŒí±³x¡€¡ ®® =³œÛZ_-UHÕ‰ïÉU�{}¤ìÝuòÖ䪀Äù ÍÑ®âü£UßÈ&Eº —ªc0ä��ΙbJÄ“ùò ;¨—BFÁךpÁÌ Õšë3f³#�¤3Ûc•ðH¾Èz½:º�ŸðܼƒI)G�‘¾µs™–TÀAÌøí”™ë”V0–HŒ�8çš 2»•1YG²Žµ‡×Ä)þæ«Z„kÌaŽÛÓÖo/÷tC=ð‡Ü{öç–4YÛØRǰ–íÑ ýŸr“_ÀÒ¦ƒ¸Õ’m@‚µð̱Œ%£Hß¼Æ÷w?M`ÎYçh™Ûd£çñ`nÊ‚¨«ŸmØ#l$«°SthÐu8oe7éú�÷ $áÁâ&`µù’¶»º{Úâ*5™«}³˜Ö¿Î÷Ô¾; LÖÐuÎ 1Êc«{?ý:ø ÍYÞ_ Ú,¨½ýãA»c@¼¯õ¯컓±‰÷Á©2Õ„ùÒ6>5bptr†·³I`£S€J]†�Ö1Ar`‹c°[×\<ј=¶Ü.ov�Jp›ÊË wGAEDŸ2a–ª Î/‚º‰ÏU–0åPä}†¢/Xñ�6¾v2gÚ�¼Æ°îô¯ÐWu¼´i rtümXTI ‚¯ (B¨qÈý×Ó˺®f;Ô,²Êà7ÅLêâ÷ÑW>­ ohð’ᣧȩ»“‹GGð”ªxÔWŠì7^9¦ç7 G® ®Ì@Ê[#HyÁ·D‘zù ’¤WL ãÆ:ˆ²÷r}m[SƒçT§Í“tŠƒ)w�c¿=&åôüà-ê$ ݘ?ˆh›Tb¯dIH…#¹îê#ßS8Í; Gï³ËÚö¤‘*ç­!oš|À|«ÞQoñ £Á�XùXÁËŠF �©öÄ&~{-E…3ž*ݳvkª[µÄ y0ÃOH5Ö²à×»!w¦Æzß Ü}8,«àžKa}ƒ¼ Â>ÛÈÒ^a'¸·®X8öX†¢ß.§KÝÊðÀqYu�ž"y¸2lÁý¨½�¢¥áŒ(QSÖʲQ¿©š½éÃì,‚À�^,úõôC‰Ä¥ŒU3“N�ƒ÷�ìõȘ—ÔÇqbÓ« Ùöùí‡S"Ï`“æq°g>«Nœ�Ÿ_”Û&¶ìö“<¸¾6øïÿ< ìsKšÇâpè=Z(Ffsq²Éåë�Î56ßÍŽøßÒÁ>Q±Ö_/pD²»žäOöÓ?¿¿c þ—?©Ío[ú¯GÇÌ.âåºõüG~ßúÓ¤õííw7[/{ò9ü“úUî«ùàèðøÝÉ•Éq ÿ£øOZŸ°Š¿¹š°x8¾¼»}×!š ¬em«x2;�4V¼A#àÓ¢ƒškkÔ2dŠU¨ËQRÚU¨s5&·fï ×JÓk+x0xÜKœQç M k‰ ?…<90ë5š _^ºÔ;à=GRž“4£ vXâìëzÝ ZXƒ”qÒæ}2UîëéŸÎÎ�_|[}söùñ`…‰äÊ¿ÞÙC—ˈ]lªøA€â{޼ªP ûÖD—$Çz⨗ˆŽòèÍÊ)ÃH¬ òº6Åé7¥‘ëÕÈr?Ëw}Lv"ã/×)œÐë*nú¢ W¶â–ùßç½­`}+2©WÖWœ¨lSkW,)äŽ Œ"—>¸Þ”ç’/ë¤0EÎÒ�õWê¾õ4yÔB­@·Ù)Ò ýê/Xð&(„d ôk-uÊÚN¶¶…~¿ñSýü)ÖA‹{g¦Ðœ«9»Cº^·±³Ò|ÿsŸ¾?¸ë¹ùé¥s?èloÎö‰å~Íí+Ýî®×v¼yû�íõ¶ï\š°-¥Š|¸8¥Y•0÷Ûx"?ëÿªx?)U2^žI²´ÈðwÒ™LD5&_ðÝï´XÖ6í"½’YnK[’K´›�ÌNø�/ �Ú\ž6y›ýöˬ–ÕÖ–J möÓþ¹ó\É�\V¨E*Çþà7· ‰ø¡•²ÎÀÓY첓¾QË‚‹Dz&a’Ëpô ,þRÜo{ –=À²'°üEìàæ‹Yyþ"š_C0J&RéÍñÑEÉ‘Ô ìjÈ×¶÷X—Œ=^»�&ßD—ž>FSÜŒÌU½©�–âd$¡Ö?e‚, ÆçŠ.'3Ô° àà5iO4”j`—ž;ãaSÏ)vj»‰Z«_öÆLšÏ5!T°óóñéÅi+âèð& 2{L'�Ì¥P±!]]ùM £ÞvØÚUL �†gEÂU÷5ÆÈCK¥9†¡Ý{…LÓ2ëÒZ‘Iõy Á'A�íz·:Ë©Ù&ÅÙê�ê’NÝnˆã"l=áÃ[j‚IbO3åü Êòš­ã°‹ý44Pnl¨;üz.ÌÓ ¨Ô�Ç+€‰MÅÒj›1í"äp¸ð%¬kZü¼Hù7ç Ë9g¤ Ñ1sÇm=颛-¬®mM^J;Y(’q9”ñã½³ûèá,IN�>g»såzC1Ó‰‹ÐPb% kÌÈ¡ zžŒ¾<ÿƒ§¶¾„¶ß0•›$n¥œDçîXÖóŃ^Í(X Ör ®Ì>ÌÓÃvON_¨.xŒà‹åuåú±ºÑ* «”·±•ÞHÝÖö±Ÿ± ä{HV^Ýé¾r]AöÌ~—3ó¢"퀕‚\؎Ѧ�¨X‡ø˜°Ž+Þ™Cg¯Ÿ w\‡uü¸Á¹Êjrð%n”¼®§üäÄg‰¼' £é¥8ý½�tI�£;ñt¼Éöp¼³5Ü^‡§ƒ{p¨¨Õó°Sßà;ljLôÔÙ߯ë­¬¢™ŠÅe¼ñ±Q!] l¤µÑv3‚Ü::.§W$Ö©¡éÁ=�ªŸ)¯Ï9�D¢ EˆåÙb{’|‰}êÇ-öÈŠ’1È’òÆXe`(—Úëë6¢ ¹N`q¼Š÷½Þ?ÉÐ7ŽŸ�ñæ�§²ÿzzb¿˜– :h1ªGžÁWJ%% ÂÒi!Vë,*³b­ù Àe‰xØTÀHÒ»ššDz™>½ÖŒhß–¥uãBo_êÆîh²¾7n¿X›„pl®5Ìá0•*si¢Ë¡Ò™ä™J|�ƒ×0ôb̘ºO&Å^¯ËÀ‚ÂÎnpહ‚tÝ̱֛}Ô5æï¹ëŠ�Ê—¹‡ÙôSÏ<Õýϳïþë»gOÿ-QCx®Ie”­Nss0/ þmn£ æÐž¢af¡ÔšÃÑ�œ¶Üòi¤ä6»L^��?™ò¹*ŽÄ´í2lò´sHw¡ u»:» ‡ÉÙg9xùÙ>S¡.nOh% æÒRdC¶\ ™û:µ×Æø„ñË¡+¢²®Dd+Ihe´6ïnñ;pé)N¥2^‡±«‡æÇèQ,hZ-ÝLÏãúü Ÿzn~'Ýi)XVŠ#6€´Lô:Òg»x‡pÕõªúXÆfXUU"5I¸Õýü à¥íéd/�_ ¤?ÂýwSàLb¾Ëxœ­[ioãÆ–ýî_Qã¼XŠ–×KIc�AÇvw¯7¸�I€ °JdIªÉâ°HËzƒ™ß>çÞZHʲlóɲĺuë.çnÅgb“år­*1gª–:S©x§dÝTJ|Ô¶>:ºZi+R“4¹*j‘á;+î +t^fŠ~PéH”™, ¬7•(+S‹Ï üã·É"Va}½R¹Ð…¨ÔBUªH”¨MÜ«þøM©ÙˆO´îÏ“U]—öÍtºÙl&þ… NLµœŽ3#±Y�š�78†œgjrtô.0jW¦ÉR1W´­,¶Â,ˆ ð—e4—ÂÖ²V'vðæèh(þŠñX ‡$•‡oÄ[‘*«+:“—��i4ôïÄ/{“d&Y¿|ãÖu2!Ž”0/ªŒLsY2Yˆ¦LLNlܨÊjS´ÔLaëªIj|éi^$ß%Že‰î…ISAŒu¶Å‰ÈÆTkÞŠÉÜ‚Ìf¥ku�¬T²¾Îeµ¤Zý©Ò‚‚*ºz�$æÆ¬­_wuÅ´O Òñ d«ªñB&ÄNЬ¬ùdG !£a]¥¬j-3°ß³']Ó�¶Y.K62¯”\Ó°pYà+Zl�ꕬÉßȹ¹QNŸ»={&œ9YúüL¼-dmr¶é�ʇ—ÁéD=|#~ÅaÄûF§j$Ž÷¬?l¢8¾�&+Y¾˜¬ê<€§ŸÅÿòùËÕùŸøÔÝJØR%z¡û,n!› ^'·¦©yGXoYf:aYŠ“Ú@`7*nÓÁI©!£‡À¶ ’˜ �CJ&+ÿ¤ Ã"‹$³™ˆ«În¿ÉmÆ®ºRYzsµ’7Ú4©ƒì5É4–Y’5 ñb!nu%H)a;SÀ!u½"–MIÿK"öÇïgï;Ï�Ú$&ëˆÐm?Á9§áW;½M—ãvÙ¸�W���ož@•8QUÀ¦àBcKLµÏÃ~Æc좙‘˜a8aWN>½Åd8ÏÄyXäÁãΓG÷»Û=Çî[<«Šë<W*ÔÒÀ?‚/¾àˆUÿ„¬¬cÈ¡k"aÉ©)zIG?n%vÙ'lL¤U=cK* $Û”¥©ê}§˜ÐÁ¯t�©¹¬Ž„¸W L–:Ó.d®‚»ÛELÑ!sžxÏÒRð(�‘ð{þÎ& h¬8Y«íÜÈŠÂQÒØÁAÒg•\.I<ÄYíÏ$r@Ší(ý ‰K½\ÕcxI²©¶ˆ‹ÛîZpžç°p+ ÷ÆS‚ŠöJÈœä&•Ù@$+�–*Í’A¶®–Ò[oGð_]ZýOEQ'38‰½óàGµ¨Ç$²ŠY–™^dˆ©*xÀZ(‹e#—Š0¬‚™‰B&ëNÝÖ¬úOºÐ„æ´Ó¼©kxÂñ\p8TøP„ܲա7Õ*½Øvå‡8_/¢ÂÉÏ^¼zÕÂǘÓÌ'�ñ´\­¥ L½ßOµµ�²SH“ þ±Dsd¶Lñ®³v)š‰·H-ºÕ‘þwê|œµM…¬†ŃpJæÃüQŽÂ'sÛHlã¸<ž|ì¨)½{ÄO8Þñi?B‘T ãw)­?�£KFy»öÜ^, S©wÄ+gÈ阪[Ê tp¶×°8{tئao#D"k@²>t@>'wÃ>Þ äÀöxˆŽI¹ hÆg�ü³ñ¥€w™BíìácÒ (ÊÕä@g9Œk3fÞaO©:¨¹·NÔ8ý÷ì}kf !�¼Sƒžðxœ+" ñ(9ò¥¨Ä¡b׺:a MÉ¡8îûèZ<òéÒqàðX캫ñ Ø…eæÑM Þ*#^d€zÎâÉú¸{²Xùñ·{©ãn!K©¼Õù£áú›Êp@ñ�_¥¾sú¾¿…q|ÇM�o%œu4êéR�æé÷MïQ¨GÇqÚ…!ßâO�V±R¨¶n dÀ˜RÅ=íJS¬ÿ£8‹Msñ5C¹K Ê€hŸÕÆkËRÀ¥±OâæÀÍFs[ö¢�x–ŸZXíHÆ70iæãÈMeÈsÔ#ÊÉ ¨º2Wåß®�æ‚¥Ô¢@mí@ùø}…R¡†«CO¶�N €/WçÎ9²1˜`cçÑþ6¨!4…J—Ê·Ün˦v³«ÖPM’4åÖu�éá¾Üd]Wî‰ê¤¹UÚfl|Š¢nÖ.ΈóiÛ�æø:¹C a‰ï©G=�í‰Û» òlõâÄT}'é¸Ç DÐŽZZ’ÎÅ(Cêy”ZN¨û1þ·¢ÉçªúyK¢k) u[‹%x,Gû GüóS½v5¯"ÌJSºbÀ‡˜3Ø‚f%Û6 bãQ?ngôí?t¿4ç|&,keǵX¡ÿ²÷ªàÜ uI*At¥J¤ùÀó-%A?ýýI9ÐOßM�B²{È`èrC=òA‘»„÷ôr�¼œ.àkVøâ‚)@ü²¬½009S(Œ8yã±£³å�·t_§Ä³ŸÜº3͠ű¦>.Œ¡q|ó|<4^ä˜tžE=A7RÆt­%>|Ïd/¬Ñ=²Î Éþ2峨š¤+Ô1![dj²Z—q¥E±h(�v#˜vˆèò?Çq¿Ç¿à8˜7ìò´<ôHg,1]¬ô\×ÝÛ�G„-@7•©âL§Åw<« ÈP®¡Jø\¯¡3ÛdÕ �Ê£‡ÔÆ(—Lø.'û´HD€·t-‚r—DzdÃKE) •Ù]ؘ…¶û5·¿¹��ì{Æ‘ Ï`!/·]^œ:ã�òe ˜x´2®˜8¢BZÍøWüÆï³fG) E4Š'`Û·|ˆ•s>ÿßéô"QÒÝWCNF„” �¬ØÓݲ6ó B`F]ù“˜rû—ŽÏ€Íbžm:§ôR.¯o´Ú�ŠTõ£GÒU–¡+f]nˆÒœ0;²¸þm�Gž©»˜ õ1Œ·ƒYZÁl—¦D; ðKå²ý7¨P‘¡óŽ×^3è{á¾ ¤g#AzJ9YUJ®Ddd¶­pÞ&‰*ë€Ô’G\Ú:M�Äd2 W!¦»jehåø­Ý´Œ¯ç¸ŽSp˶>'Zè àA¡HÂPÏGç’Nl³ø�»„œákªzÝ–n7îXÓƒ.Á§ZsƒÓW*QšRæ‹ú^fÖˆNdE‰?¡ÂêšêGºüKGè·ñïxš«j©®WÓW¯_ýø ¡H]¿xùãë~z9¸ƒ1¨»¯!uÉŒJ)kírLé¯ÄÀŸòz}í¶ëzÚ¤ª¿÷ té\J  xÍ�S?’˜QŒ t)}™q„ýýùóÐ/ ·¢N~÷awð£��Žh­dbœP²CßR ì1Lå29_Å w¹ ^ì\ á!7XÔá Ê‘LüîÙqÑŒ¯¡æp€îê“-U¯ÚÍp£b>Ñu¤xˆöjí}ê`™µ-ª‘ˆæC}aÙ %Ý\‡„’�£¤ïgüŒáØÙòqÕ4‘=îŽZÆøeœÚ¸_`c½@ÿñÐhDwT×L”%.cjÖïßÂ}9jáÎ&�›ý=?¶¨´®¶ l©�GÊ nª¹¶â€nÓ®]¯Ý•µÂŸ¡‡7¾1}ÒÑÃ9èÓ5/lK$¦Üÿó9…úºB•ûD®AYJxÆßº½zZz@Tj©v#¾–ʲ”dF¦!ïîTÐ5Ω³FÇ•PÝîô1œ!÷íƒô<{|ô~C„Œ*pIHL­4N²Øt(ÿòrÞ•§¿Òa‰jÀ¦6ÜxAZC Y•Ô÷bE‚Úg”E«ÆZVˆ-…ƒ±$a…SU µ¯Ìö‘óŠM+¹Q· J'IÕØ&õi²¯­ÍV`P*·[¹Â§½¢÷C]0õ—µú¹õÛŽ(©÷Œœ¶­\ù²3ßa>pvòí§Ÿ½�X+�¢«–eè’yŽOz·p}¸Y¹·dlƒÆÛ°»WÍ‚×9:W¿œuü–nz*K?x›�U±Ù· ²gÄ ñ„ ›_\˜ ZV[?¢tåÆí¼-Ô{ÚÁH'fòn¥ºÙ »-Ñ"«�$"ìZÏëÜÛ$¤²²™]uMǨú³ߦáãŸ�Ž8ùÏ&ÓŒ‡¾<¾BµµÖqœA}]Ô6K¾ËNõй¡ù�Ô•Á±©sŒ`Lz˜ð£•ʤ»[ìnW'îæ,=Úí)»—>�­v²4åï–·¥â-e“c¸ÃZCâhÊ ®¬1�….úŸá=‹g¾sÀ@üÁ7 ¹—¶¥®Ž s†� »J¬k æt­‰²,]tëzº¨Æ1ƒºçQö™sl‚ЋxìjšÑ-tòCS2<;’þƾºM‡Ìt{ƒgÛBæ:á÷Iè¶2ç�Ò-×,ø*tÈ,¼Œ'|iz®Ls£™,�î:·7`uñžYœcò@§6!.¸‰³Òþä‹“ u£¹#ʪÙioP¯êù‹OjVáùÁ¡Œó‹��¤g•7ð (¥JÇô Í–»HC—“n}¨l–á-~§Êäg'ajãT£rà~\|UéÐ|`lÛšj—nWߟ©?ó¸ݾ=˜�‘»ffiº³žoÿñžò¯Ö‘èRò»!¨ì�¿%¦5Y‡<àca\“dÑÝ¿Ì5âÞ6Bj}w1‰¯²ébÝ}ï­×–ýµäÁUj.Ãõ˜GÃÈ‚í�eã&¨\íÌ¥Õ ¸ˆo‹Ñ­H²¨—ÏŸfQ/ŸÇþçé�9ÈÁh¨×Îoü¥´ôUÄ ÜkÆs³ðÓ€ï½~kx~Ã]sSâjû‹»÷cƒ‚\Â`Qg’ïp0v8V¬{ /¾+Èðü{HjÕ^üNbÿ'¼·Ä…dNƒÉÐÂù¶µ5¿ÃX«åλ?þ=¹ol°stw`a®³ýï_>�ÿÏôg.“é~7q+¦Té&€:ßâ”}æK _s„·H–O<ßÞ©ºTÔ-r/Ž…{ N’6öGsùÞæv·äŸ0»1�¿3”D×§¿^^ž¾º>;ÿö�«/_ݰðŒtˆ±HfMC�l¯*¤W'¥©Ã-àÎÌ)ä>qB­ìÀÐcùS‡«�Ór¶GÝ�ز;½˜žž=”h9±sgŽÂ‡ÊyC1{èûÓSñ)½P3·?¾¾~ý Â✹Ê_¿rìG]4·â{1üåÛYI˜q¸v{=à€BÒžU¹öŠË,Ä×Ïïùþ~ù÷pÚ¯þM=)hZ$FÌ\KwÅ!¼¡ëÔüáümÜø²áÄŒgt\׳P]8R¡G%JºûL<§è¾‰lkƒ†íÑ;bc’Ë8¡ÍÇI*íj¬7Ûfœ�€ÆµN·ãŒÿ§kEÍU<>óÚ)Œ÷ÿ÷>¿à xœÍ\]sÛF²}÷¯˜+WÙ¤BR¶ãĶjïfeKv|c9*ÉŽ³åÚA`H",�D?ä·ßÓÝ3À HÅRönÝ}I,˜�žžîÓ§{澺ÊVÑ…®ÔX�Q²ŠÊ{÷~IMZäûê@eébY_iú¯ŠòDÍ#S«OÑ:£?âbU&­‹j¤.SÓDY¶VinÊ´Ò‰š­Õ§4OŠ«{ÇÔþH¾oè�z©WzÍ2­¤�|ž.šŠLîÝ;kʲ¨j5/*Û¬Òó¹Žk£êB¥«²*.µjL4K³´^�Ô¬©U^ÈeSie–Å5¤5u¥>ê:J3ŒéµŽjzá]jê ^|øxzt6Y%Cþ8á÷ >½_�`ŽüôÑäŽ{»»¯‹¸1»»ê°ˆ/ÔuœæéX&hð³z•¥%ó¯KÌ*KóÅ=¥vÕ1�6Í1ôH]FUTk…9ÄÚ 'zã-`T„qeX 3Šã¢JÐMx:I´¹¨‹rªL©ãtžÆQ�å‘o’D%ýMïš}Ùò€L�ÞèMz?4+�×,þEÍÓ¬ýùŒ>¶�1mú~Ä�IOŠ2Ó—:SW2i–2ÖO¥þH5y† Ñ÷U�)Ì«b¥’îã¥ÆôRÌR­t”óJFuÅK×=á‘Û®Õ@/&Xú o šãPZs"#Q+,Å*ýBº³«^SÇY:»NãYdt’VûüÑëôZe:ºÀWõ²÷†4û®(ðëRC< G_—¤žøWc´úõðÍùÙ‡ƒGç?þ||D]½ÍçöCÕĤTÜħ*­iyåð?’¯\š “誊n}~4y>yü�Á²®K³¿··À�šÙj¯\^DÚèjÏî˽JcÔF›½:Zì]ò‡Cêýe³Àò]kþÿÝ£·k05¦AsxHBÑFM]b¥ ”£Õ/’D®*ýO¼\“.ÒÞ5q¥ñk´Š®I袊ê 3…ŒFýễ}ŸNÚ!>¿ãŸ»!΢¤ÝVª˜cÍ�á²îÃD½‚ewšû{†VÀˆñÁ¶Ò%¬ÈôCÕè)?š¾Ž2ƒ“ÂÏŠ"#ÝìFûôŽ£}êF‹_fPo]­ƒAwƒ]›¹‘¡u ñ'Õ�•ásRäú´ÍVN!ä ìƒé¶\ŒÑ,Ä(² Æ“‚VMžwË$]×:Ox*5ÃÖEG6,ÉJ¾öd[e“‘é¨ XbÚ²Akï`ØŒŠ›ÊtÓF�îé:Þ‹2ˆ*‡*]b^×cym̯MGl�©Üt¯1ÕžYÂRî‘É0{‰žGMVïÁèë‰|0Ù˜¾µ�¤¯ØÕLÊ `*sÊàYÄl5¥pâIÔYx¯Mrg,ÂU‘`·ÀV�DV0ó㤊 ZìÈÙ&ß·©ÁçûO¿}v'¥ÂûÃa0¥3ø[íé³ëI,÷ Û¥#·õPÍôœ¾‚w/û M„lpžÖ)T3ÊŒØrų²m—Ykr¾h¶˜?ûËË(¾`ä@Ó¯«"ƒò-«¢Y,ÕtVÑ9|E\gS6ðË¢¾ÐkÃsÚ®>¼Œ­(y5eQ�ÔQɪ�¥ñÅHaêE–�Ôd2Ÿ^M¼,£$hÏ*õ¾4 åƒ Áܱ+VæJÖYHÀvDAp™5ŒF]ÃI¨±Ç` „xi1Ê/x3¬;+ùÓ»¹"¼ßSrê€ãµã�æ$•:2céZ- Û;–ë2Å«ø·…(4†gßÝm ϾÛ:†:¾$é%0U ¬ÙªIdúëÅ앯˜¯a§ÀÓwìÿ÷o�Á³?‹ž1:x¹ÞW¯¢U9ÓY†ÝXÕÿ_àÞ#ý~úäo‹p.5øWèÔÔX=}uòVC¯Šj-f=×õUQ]Ú†ßXTQ¹dÕÓGbÏn9P¸‡ Öì1Mr×¹}h{BÈ·ŠÖ´-V‘€xR;,/{-Óê½lϨZŽåþÝ©ÿG¶=èU)Úí:@{ß¾¸›êâ}^6’øA¦¯Õñ:þrQ¨¿DøãoQªÓI¼$)³s‚œÏ4Å1ðÖwa£¯±�1Íy2í0�9 4[™7ï?šZ—¼ü`A²¢�F{y»}<ý¾ñ ÜZ–ê'~OýEÞÿÛ¢™Í´÷ÍVþT“a©àT|gî|²³SrÝ~(‹ªM£>µ¦˜�1‚#Ú­Ùe„±¯Ž²” m�ý˜kŠ. \­ÈO,tºü—MšYY]eäNÌøÑäÉ#8 àX'Öq?wû)†õØaDkìÕð±à­õ£x·©#lÉŸ¶!m‘Ljêè«*²â&ãjM [QdÖŒLÌ%Q|æ+øÝ03ÞïÙ±6§Ù9ëØJ­�h; o£¤Ó;ðoûü•@½JÃÒÀ\ÂîÀ x1¶nòáwðŠðcõWõxòHtúnàïûæóOÏ¡èc«® ˜%æ`8ŠÇÝTÐ-zS¼@ŒDÆàˆ5cDZ`à+ÔÏ2;‚wN÷}Õ`p¡ú1Æbdrq.«s^�ìߣÐÁy�¸^ž 2ÕçõÐÊŽ‡P?ÁËáÈ\œïá¾þ‘˜¦¦L"Ù*¬$—4¡�ï·�Øm¼°]ÈÆ[î ×”Ä_y¦ ?Ñ qk#Ž:JD ¢»ÿâýôî†ñ�jB÷ŠTÈ�ö(iï��Õº¨»Õ‹µÑh­ô¶I÷̦¡WF¡†×ÿÍl Ú‹sò �&>$X¤¯ 2Ž3C‡ý{_½Í)”‚¢Õ5MjG—䫪(Û±AÅ¥ ‰$§ÈK `Ƈ¾–°2¦ ¶'/ÔÑééϧ{‘be`êÌœÓ7çü�ù!­h”oyƒA #ñ Á5ŒÉìfi”;<¢ ZNäÝ2ª—ýÕ󕶤¥^@±,VZ@OV�õi<•m Š ·Á´(‚æ½�†CkÛùgužŠCÃòÅYAA þ¥×ÔЀR-БúxúnDÔ0CãºJ�Þ‚X¹‡NÌR9díÈ�yqA·Ë%èúJlpJ¶P�I4ãLÏkõ[JŒ*f°«—h¬�…†‡+SÅ$a–²;¯ ùà4�®Tp¢<̆‡ˆ p\Èò`“·ZÅYÛ®ûPü[š#†i§ñˆqà_¯A'±íûØÌAdòý*…ÇB�hçIU”Ñ‚6„×Ì ‹£„&´Á‡S+= 6�ÐežVXë?˦u0øÏ÷ŸÜ1fÁûÃ}R�°5‹²D¿‹ïî'ð~Ðf,äE>Þ"ì«ã–üã7­À:mK Âr€D­’a5%Ä2�‚±ÍâEððV•Þt3¾&ÍêTЇ~êÑäñ‹àãë«LæC[.j’(æÐ‘gÇ4]NƬ&‹õ Úº*y…{RüöÉ“»E OžX)ò攈–ǹ�ï î¦ xÈÁ…ŠÚÈ‘7”£aÌJæ$•ØÕdF¶þ¶?±è¥ðÐK@B„Y9!ôÃΫÅ@½\[iBÖ)Ó¹‚hÛµ_c¸ì6¸Bž0jK͉iTzHT ç�B¾©"/ÅÚÑR‡6¿Òr!?ÜŠÎbÂ/üOÎNŒu§_24MŠ¢ Óep²õÙv¨gð ê5âê5#.‰K|~!h‘ýÐKø$Dâ+r.ªô:ÕíN­Í»ŸRCW)#ÀŒrNu™%Ã9ô"73%'†PZMÓ«u3 Â…¼¶ôy´šaØr›KÒ 4<Û§Ó.VgžãSEO]õ<͵sºä%YÀœ b–W ∵º4#ÓΩ˜Ãƒç‡oOè‡q Õ¦ïàŽ§�ÚL”Ý­YLë¸� õM (°kÑÒªå'Ä��®�ÃÒ»aÊÇ�•d9ô€Ž,(B ­L¸­„»'ŠyÌs´y©‡Yý�µû! ñús£/ß]ž0J�Mؘ)|Öš5j“ȱÄ>¨"žlÐ(‘êì—72h}�à™<8Ù´eQ¥_ Ò9ð U€‡�²MI_-)YæqZ¢Ã2àv) 4ãYDÂEÕ…÷EØæÛ;ï‘ià”¢iÒšIR†÷­0!Л5„ªž>¿~úœ…ÿýÓëšió¥‘pUKöJK% ¬òZv� ÂKë{zójÃÒ[Ò4ý°ôû0,=†"§ÀÖD©üÜy²&¼;KËíÄ2,B5–×Ç«(�¼ã†ä 1Ž/O[+ÒÃÄ¿ÀÎÌ×âb¸Ò™j* 5† �M»± ŸöÚ`£�¨�†bÝú.˜Sò"bNoè×èš•ýoy di±àTš°­Û)M§ÕxlMþ1Ÿ�hm›,q»Ã6Ìy ÝšY&D¡èÄ'n+eš­Xï.Køıkce9±ù°óúoyì:³"™þŒmˆßÕ™$pß#ô4Ó}õù§¼¸Ê·ª%ó/iu±×¶í°ÆãgàG~¼u\ND.ÿɃ@¸cÓÈošVy#.Êu(2K}€^ñ¢\²à¼²B×ÌéÙϰönUô‡b‡«ÛˆÅ‹]®–ˆ©ÉͯÖÐt#ä5)ƒ•x0@žd…¯Ì–,œRÈÒHÌÕÚGAÜD T¡�;KNÈüã�=4¾¿+6‘Ú Ed¼_Ùð¸´(Ín›Ñ±·øÐ!Ç€Æ)3"°ÓŽT,¹ƒxªªà8°£ï LðøÏ ðrÓ~I#gÙˆöá�¸`…Ø7÷Q—PôŒ ³Â­^†À@¹jà´¡—™G׳©=ù»�, ‡-ú=——äp¥öù¹kšpÞEH}c÷°ûé!…Öoy 6 …Wuè-žß„£Ã’‡mÀ%ïùD†"j¹X¯ÉØ%dŠ;ç¦e‰˜ËÔ¤\¾¬¿�Ëî&.Ú Pˆ«ÚWG¹¡Ú+&sþÑé1Ð$Þ:¶M]•¨µgæÙÕ1mB˜—ˆ�? M lâAMÒº$Lùs~DUëáǧþñÙ¡íŽÖwlŠŠÈE?Ý /"ÀQ:ÊoNX«�m{h�„^¹mÚ},Ü__?ÿþPô—Pæ~mÇ1^Û"�â]ˆå˜ñG_óFçºjƒ‚@•¶5{k¢Ù_Ü2{f-ÿ‹G=š?€HÍ{s`vXEWT×ÌøÅ%EÉ úÂÍ4§HJÆV`Ñb»:ùî«<•u¡xÝVÚ‰dM%N1uBI�¶2KňyËX$¾ÐɆf·ùð4/mÑ!9e—x t1/®C»&Ë[€=j): O�ˆtT™Î¢ë-N.¨€¢ä¸r‚t’EÒ²O¹LrBa¬€xEû=x#ÓÒc0yGÞDÂX û#rM® z{0”š€¦®ù�JC¼¹ºŒ²¦çôI²Dq¼ËB²Lh@+ÒTñ’i!’8�ÆLMS™¡ÅØ·¤Múû»cÓþm7§M¾neI�9Áe„Ì}EvÑãõN];ÄHrkYÔ‘%bá|s}]�ɃµÕ ­ª„[ŽzìÌ¥P¹"ä1Ï([Ç­bÿ�¦ÜTÏ4¿,.d £(]i·p#fÃŒ sûÔrÉ=â2‚©µFÒYI/¯²í}®´áòW^…Ø âÄ“¡_–³i„�%ãD 21f"�¦ª ¨üz*‹IŽ`›wœ‡Ý±ÒçG& uÃ£Ž—ßW;ØÃTªµ3n<€p¾ÁÄè’È&*Ŭ´ ©-[׿ú^÷��'�Æ2’“ÔL*© ί 9Ÿ–�Û92ˆa”:ýŠ^K`¾}[´­1d½]{”½HÔçû�ïHQãý!Eâ½!~êÔŸè`Ÿ ™7_¾è0�ý&ÕÕºM7º"këa¥C‘½uf?+ŒTrîÒ�ÖNÝ2½Ý·SO{\€¥&¡ —Ш�#¢çÇ1y½�IPZdŠ{Fémç†Èȵsn•¿#WX¬sæix‚€¬4A­ÙVs¸Ú6 Î#%¸hC÷M7f‰’çkí%·mm¹¤L2®÷´Š�%¹W[ÁãhóŠþr)àæq¥+X³õ¨ØþËi¾„«¬ù£~÷ʇ€ò Q[5 e�>‘0¸�šØPîôèàðøHÆ9— îp^…]NQ͸<¶ð.Jjv'$DKŠ´Ra´0¶¯WÄØÍoYÃ,�Uˆ­´iÓœBÖÛ·àb¹îÒ¯ÓD—:O“ã£= Äg!±o)_9ÈÁ`GÙ, uèc §�hÉq·üùs€_·ñX#ñì«#ýHö‹Œ}è$'û¦u§Žž'óÂlõÈæiøKʻȤ׷n‰Œûo >¤É9vU<è-`Rà]ð€zJª•cð…€²çà‹“Âßš]Ëg— Ùj⨇ÈÓ=û]›–ûþÙp›½íjˆ\þ†ËjôXŠ~‰8Y).Cc�ÁsX_F©YTK¥Š�‰Ô�éøã¶‘Ç.§4rTq·ê!*¡ ÕšÆ,Ü?J4&Ãç;z£ö¾HXè²=Z†Fu·ÊëçTx­Þ5|ù' ÏÚ2šÒ-ZÙÌÒðl½bFé —R�2|ŸKž“ I,;�QÞÍù½x> ‚ÄÔÄ=J¦0®X¡ËY3·[‘mW¢ÝÑíñ `Z¾“ì›Ë´ªé¨–Ëòsuè- o;©KüãÀîm~IÇQ8 ÿ€~"1ª·%ßHÖõz#ظª r\${É4x\û¶ŸýÍó_ÿÍEi7´ÅGŠöbIN4`?ÈF ž'i\Ÿ—uÕúýoÿœßÿÖ�ùÙŠ;óPÞ’Jût–ÁéÕ¬¨(gá\è!q2Ö0¶…€sâ«íg¨ÊTëÎUæ9éÍ^’v‹í¹NØn3�î�…0T¦È´£´oïâ[¶PQ¼‘yÓ Ž/„úFï+åd2]æJ0Ì =‘›¯ZM±I» ¾\QýN¦Ï-|8·{ë£é@ŶñÕSìJ–Å&(¤Â�Îæc �ùXIP<Ô,±¹¶C ¸¡TÒµÔ²”ÖÊ$â�&zJmf3Vƒ·÷ÕOàQÕ+j0fwö�j÷Â'õ¤cåEŽÞz»ún:r“ �ÑpøhF¥Á쥺a[µnCmwFPN†lŸ°=l*žpÀÛÃ}ßnò/£ ³×o‡êx%¡ÝÂZ©Šr hæ“ò.ç¥�ȽP™^šôˆ'›«TpÊd¨(BS7µ Šºf‰1¹Ã¬�cÏu¹dýf½Šìî÷Î2�yÙ¥çEÂìöÝ9f¿[/ë�æ["ÈC«ë³^‹#ø.Dt5— ¾/L·fûÀÆq]ecѾq”Õ㨪=ö({ †é�a/N»uiŒ¸*Œ¹½)™@“¬ìÜc™í^UQZ®úz©%Šã0–�âãò=¦ŠÎ›yTW5y¼¤ tП*‰m–›Ó9î?œŽK9ð¸i³åµ_ âkÓr*GNaÍt}E~µ“�ßùÁ¶Aßr4ö`øöß¼v夜ì²ÿ�´wGj×>»Ã›½Ç ‹,=ÛÙîá�§WÚÄChÕ:Î6 PÞ¶¨Çé‚7F/©ëªºýw ½×†ÒnàpSDÿ  y�Þ,b¤º"¨¯Û˜¼EŸ·,[ì£Ï'~ŒÁL«°è~è¯)üó±Yç±²©XÞ#ÄãWE¸³n«æÃºÜ ¸a“U"®Žk“TŒ~YÑ&L¸V8gìÄÇ÷EÛ3ý’Ä´¿;‰…|�0„Í-Ñkaóö¹l U@9»³WИ•¦(—šØùØq-5¹É•@+³ƒoà>J>õ¨ Gþ¼%IöýÙ²è�n¡'61%1�4¿-çˆ9rs¼7—ÞF¹åFT΄}ž‰(kVåÚvÅ55 ]Ÿ·-ÙÛäAQj[l„¦àÌ–d»|¾�ýTiØÐ¤‰É÷‚š®Hû“]V¼ÈÂȨ6W¶uÄ#WÚ:kÜžá1Ð�ÀBù%’~hˈ°_‚ Þ¾²�‹:ø3úB�]œf ÑäÔP¯<È%âåži›VÀÎש¨kšÉjEÀ“•ÍoÛl7çäl¼ìwf·É{^q™Ð«(6–péU/øõnñ’iNOÔ~DV"ÉÁ¡ð[ŸÀéD~ÍÅ̶¼Æ¦†³QA&¸ ëÂJÎgÞ©�W{Ù$MGœ„Ju–e e†mÙƒs†*¹KhVYuܵ?qü~øöìäÝÁß!ðKuá=†2ðÑ�Aú8i÷<K•ô/gƃSÇZö;kÔÝSM>øõ�-ŸÊšȱ”‘êŽÜÈ}òB°�9¥S‹þÇ6kîI]‹=žv¬Ê†Ñ-·é•}«Ò‹JŠ‘œ½tEh·Ë�¹“�·ì*¸ÿPZ,1ñ,ˆlFl†„ M85¥m&×Ü €¾o5ÖK}¶¹šJ˜òîŒóÁ1;Ÿ�{!ß3ìƒ,ºƒ)Èa¦rÍŒmväç  Ìö§ ¾ÿŠ@XOö* æ“4Õþê�>>¥Ó¢fÝÅlJ–Ïzß~Hýº»4g+\tU[Û~<-àÙR qw¸ý­(5-bá…q™µ¤‡ÄÕ;‹À÷]fÈBg[reLs€“"�ßkOŸÓ2ÐAF Éûí‰){–¶|Q-¢Ü^ ä[�Íãr‹ã*håĵº‰ft5¤¾8IUÐq™¬Wß!éT¹s´Ò—¾ô"[î¾3/NÖŠîRý,wuØ(¸�ÔL(ùíNØa|GŸÜâ7¦7û�éŽ2SO–õ*³®äØC~óÚ‚Ç#>"ù9èïÖðÿó9KÉæ£Þ`èB×ÍL+ ˜©b¨6«n¼Üg1à:)¿´ŠÚŸe{_å˜ó¾c®‹n‘ÓÈejÛ‹9üÛ ¤{WaÿR¬ÚÁá›ÓƒÃ·Gï?H&Êæ-ƒ‹¥|õ}H ]´(ìå Ô_ÎäÓa”É‹a¤ÑÑSÉÈÞç3ò«`]šoÄoêä‡aת;Àj¯N”žQX#žP"5BÚ¦»O±Í‡ßH@ˆpÚ2"<Ø.µW9ù‡!%ˆ˜še:¯Ïã¨4çu±€±ÇCÜ2‹·ùùÌÝe!™=2˜¾:ýæŒù挠Ùt¸©G�œyGe씜·éì › L�§Y>±u›Þ‘¨Ž2ãëÙ²zvµêîl½–CÅÒUPÏáß³&ºÍ¼¥w(¾H¯ K§¶8×%°¥ÕS�4±†ãŽ—\µFGÝ%Æo¯L�¯'º ?fb‚‰E{ëÓÁßß¼?CNåÝEJmaDç]Ä2„´Î¥â6|o"¾–ü´Nº½Kˆñ<ôÄØM£•$“÷òß’Eš¨¨R[æ5Ðt쉶R7¾‘-F)[�4´úhYÿ6i -»5rcôX'ÒB1tÁZµ rRJn{åì-Y—( nS禨œ¼" �Ù4O­ùñÉG¾‡‚þýÞ^¯hÕ�½ã‹)Ú‹AÝ5 »êš,¥Ä…~Qïá9Þ@¢Ôë¡0Ït;F,·oDä+Îèæ@l·´*r9a¹+·sH|„ýK Ãqº%Íþøçe‘±‚xœ�WïoÛÈý®¿bî\äâT¤Î¾ H‹œí¶’8U|Í×\’KiOË]bwiY-Ú¿½ovIëGTôZ‘Eíμy3ófxBóÞeôIl´05½+é&“›†Œ T[#i#Ô:-…—ÔX­íšÂRÒçZ¡´–5•½Ò5)ãƒë« ¬ñ÷/øñæíUÞÖ§ì$`û|HhM�³-yÛ»Jæ“ÉìU¶mqÂS)Ù…ð¾o%< À��4µ4•’>Z)Öºe ­¥“£Q �¯â7ÿøóí»ëÎrm+¡ ˜¿5•$—"½ NzOÅep:û¤Lm×Ù~{Wà6Yx"AAºV¡éeÑXŠÓ)Y7ùâÎ_âù¤¼œœÐmÇÑÓÙó X°´Ž§Sˆ­õ�DUÁ¿*µ„³x\y¶|ûYؽÏÿoÀÖDh'E½É†hháD·Tˆ“¤yTΚVš�ÓM J˜h³´aI–Mýtv†(ðÏàfâˆ5À^E)ür²ÏݬTf6PÍGvcɪò ¼;õY¤jºìQs­ú;”+|Åq~–¡š]ZÓ¨EÞiôÄýË<Ÿ><�Jçë%«ÞJnJ ÖñS~2!‚¿,㻯ŠÅ‹G$4©u’ÏÖâ:ëCɲËÐ�5çbÛ(ÍŠÊŽ`,I4½s5àL�áP-Ñk.5¢;V€Ü_Ð%D LœEÑ |/æ,ýk–'¯#-{Q±& kÕlÀ+¦uߎ¹âžFWÜ"K­}d´•‰©1¨8L�¯ahgXp¯1”"Ë‚õ:Ïsxuý"ŠçsB�çVúÙÕã~bŽýxz„"Hâ_Z™Ó'F±ï TÂü'ûÛßN§0bŒ¬&Ð! ±2è©G™Œ3;1±=(È2Vû”fÈš¡¥×&æ‹•ì€ðíìÐÊûÈç—Þ8TÅTÃÜh›k§ˆ!<ÕÛ £«à™JÇêáèémbæÉï¤é(Û|ʼ+#ÇŒðüPŒpëpšZ>Ε[�-9M5{¼N÷= ø!„�ª–˜~b,.¶ýÀ¶#‘\[‹~¿¶>°v€çÙ)O%ì)øàFèÚV«TÕZu žè³Œø/g Ë¢ŸchïÒ\àÝš¯±jD±á,ïm+SúiüÞK‹4[N�§©Ãx‡¶Àå)�ŠÈÖÓ Ôu¢ZAz¹¸ŸÇ?.›´VØ?§±wA»ð°5�„�u v¯n>~xûæçbw1¢GáƒJŠ]²¬V¹^<ÞŒ3q˘¶v…m“çïRK Û ²:í.Ij§Ï (7*›–!ÅyÛ‡®GŠ�Ð%Éί–ý—I9ö#Êuš†r ɳñ8n±d5«fìuVÊÚ³âG\I7b÷k$>N Èí�åØ4Ð5SÞà‚"X¡ÿ¯ú5L˜½ÝæxApo+lÿ ]ñJ°à ð^ ýþ¾uDɆFÔvñ å£Ô¯¯®øñOÛÚæ^Y 5qb>–ÖË´xð:bëÚÞÏòãM¢Ñ¨|u=ŸßÎ_¡8ܣťíuܽ`B%4ÏxÇë�Kßð“Å3�Fu%pÀšá�L÷±qb§¸ëÇúÜÚÊé�Q{0PБ˜ >ÿ�ÝKIœ¹ñ}�_{’oÄòWéx®bVr…" ?Ÿdx@‹nw>úü·³{нhО›&Úâ?\F'‡o0Žvbˆ9:|gì»4»ÎQ Ù{Æ.°³6 ~4á9Ä)ÉENå†;(n�ŧ·ó‡ùõû«ëùõüu§ž°©w~?˜¼i†×E+ÓŽ»”ºÃûÞƒ×aFpÊTº¯“ó¦G¿tÂ'¥�ïY‘Úåv=J5ùbxÕE¾ãê:®6‘(B�mÇÂÉœ]Ð2„Î_Ìf ´(ó1,޹u¬Ð‘°çÏl¶V+ågœýû±ÏÖxµ[fèûl8r²M‹Ÿpèÿ‡�RÛrÖr´n†©âgÈ£ÇÛúœl¦“¯—xÍô“¶ë’<´£xœÍU]OÛ0}ﯸ+UÁö†Öi”�„•mÝ[ã&7­‡cG¶CVMûï»¶“Ð@éÛÃúÔØ×çžsâ{²Už3™ .¦˜¡F™ dJC%rvƒ~î%w5¿z½½GÎtêæºY§q÷F#c™¶ó[ *‚#¸,,W’‰#˜­Ð®¨•Uà«àÛ,TEpÌ [La±†3V X¢dÆ—óŒ ì ]1»r0 BE©™ÛWÁYRY(´ºå)¦ÃVcÅ…“0Ùô€‚€Œ÷aÔh#cthÁ’›ÐpQraG\vË£ Üâ.®¾ ®8­†•A¥ô�)X‚f©¢VκDð´m0}¶°Í¾;møN¯)ß)å…¯xQƒ6¹\mÀ-7%#Bv-Â%CAÕÒš?I( na¾™ã¥•²sB*wqó[)šDóEÃÈwðé ¸äáB�Ž.j9x‹‚¸œ«%„ÿŽFQˆu—Ò¿ßãɇ/Ÿ†pvqr9„ÙûéÅÙ=N¦ÓËiÔúMd™Ã-%š#¿á ôöëgwú‡Íc�ýWÍŠ‡ƒþë}âµB¾\Y"uŒ†ÓÝUÚ¢´Ö#xOô8¨V(A—R:£HoÅeª*ªÏUJ/Ö]C%Åx£QÅSò˜0h‡$REp�Ö©=ð±;t{iÈeŽ@ÊÉ3CFç[€z@Ï/?“]-ùÐsAÖ@|?Ìb>\[]b|×nÕÀ É´AW¨sNkVÑ"ðíðÐM5M‚¿<ñÙa ”)¶Iõ¨šÚHu¯û¶æ4ËÆu?VþR‡XÝàPëØÈÑñ›“³óÉÛØÝW–ïÇ;²6³6Ó*‡¸®ŒÚ)swu÷ø0k1/¼é‚à=úb=Ï„"T­ýa"… ¤�LAçÒN0µ/¥ÎÙmZ×n{ð¨„Zqø ]ö4 wùºUÂg·ýŸKèFñ6t¡¬ }€�à¼'ì�—êQ.ñÜÐjyüæ|òurî(5Æ }0§ßKã?)d@Ø‘ËnL#Tš“y2Ìll, ¦&ï¿…ºOçØçJNƒJL,#®m”5|B*�OOOÃüú„Ïf³ø.üBº4«3¬ùÔ4PÐ&¢�»�±ý�&.k¦N‚ý>(»"¤ð‘�Ü™°ÀÖrg�%˳ êùÝk|¹¼˜xœµX[oÛ8~ׯ Ú‡Æ�í´ó’í½OƒI/h2Ó.Š"¢%Ú""‰‘²ãô¿ïwIIvœî"À¾$6}x®ß¹ñ¡xeê¥^u­®WbSVòZµâŸ‡ŸþH’‡=�tÚÔb©KÕÿ~Eß@ïi+ ½*Ê­Èâ¥E©¦Bֹ謲BŠ¢«d=k•Ìé§�n`®ëäÛÕ§Ö4ªu[q®­û~T8רӓUÏ7úZ7*×rnÚÕ };‰ÄW%ˆ'ÉÒ´•tóAÉ�.KQ™ ×nù™.©²lM%\¡¬]Æ?ØÓäXœ-AŸÎf#/üúôíÙù›g©�íª«TíÈMkÖ:WùÔËKÍ¿ÀB/EmœX鵪O{Aâ�g) S©Bf™iY=gÄ×׿‰…„*¹nUæ ”·�ÊFÌ–¦ƒ£Á¦U�¥¬·Â,™sÓª%~¨35O|Ê{a‘¡VöÁ4BZ‘+›µz"]óõ;¤ë¥ö~!5>’è�¶�ž™ª�Wò®[圃ÖëôŠ岫ཆâ䣲Pì(•#\—Åatˆr�щ„®�NTr �•¹X˲ÌsVziÊÒl :¹V[D:²ô ‡ #Û\µê¯vå“=Š\!^ú�4ÄE×9x'Qa8Õ€ß�$p¸R5pv}7�é\Ó¹ñïÉÇâ÷hGŸ—ѲäG¸lä°èS¸‰\Ôû ”[pçTm|sqïvˆ®9‰ÔL¿^/v*Â[D%¥k'p:ýúùýùŸÓ9% ¸ã�‚ÃY‚«�/;:Ž¿‡9g²&Ì4²µ”dÚ% #‡<'®™Ê ï¬UÛëâÕÛe='dVôT|’® ü#vëTõÈþLù¸P I”Fš£_&i@$ J$�¡¾*�•:/Æ¡"HsmªÅ·¯¿¿ü~ts½@bUPÕnÒki{澟»RÙt–SñZ-u"ŽHœ¦�„pá½ÉU ‹)õjY©X:*:�_zÓ üÙÈ– 6÷Àãœa&gð½Cy€ëÃÍ•þe�èÿ”­–5]x�ZUéŽZû3+L}÷Å� —c\¼PÔ�k¥0þ”â•¶¦Ò+9À 8÷žÿ¬%Ý®‹FÀmL¬-ÅœýÈŒu­�–%”"â…Dú4[f-%6…ª‡`’ó–êÛòZA«ýHáàP¢ Ê—À×NÉëÀŽj‹Øç6Æòvy‘e„§�¯Ȋ(µ@¹W7 ‘Ã+ondÕ”ê4ynkÝ4ÊýãZ]•jé~PÑ> ¿ûD™ûÚë£×CÉìËÑPFQ�^nÉ}²+‘ñ_ä¶äD.µ¢€u–Ý]¨äuœ‹B•åÐð7ž|Ôž H9“™ÒžÜä«™%ÂÉÄ·‘¼•|ªs³IÑŒúʪrMuí-p¥rH5™Å�›2 µÔ˜ü/º Ô³®î0ÁÌÖO&I¤šösÕ/€ƒÚ¦+JYEF ºm=zU‹† £¼,¶q”A¸ÈŒ¯OžDSÖì™ç;|µODO,$Ê:@'ù¤ÚJ;‡vO¹Ž8X¥|ã¹ ¯n…òŠf„ �CéE·Z�Ó+æ›æ<ç±4d$Çe`0]½`ÇW£!¸·gß{S#32ՋܱŠ£" ) \»Ž@æ ®X÷*ýÌ¥‡øñöžÙ4$Plî/ãx±ÓßÃ!2j\²¦(ÀGɬÇ ;f<sõ:Ž…KŠp�j3W¤�g>qÉç½Ù’YϹ+ŒJcÅåI¨ªq[Xzƒkºê�+à‰›À0”Ù®iL°æÛ»ÏxóbdýUö4nz¸zªûºzä[öõ;ã^ù)­÷t?¸ý|”ÚþöÚÐŒÙÀ5 Ù\ü}%L6F×Nµ‰¢¿–cƒ±Å'ç½ã‡jE£kB¶§—ž]¨Ø»u?Nh…\«Ý© �½»b{‰~ J¸V3*Ð\dT ô `Ílƒݴ oŒÇ²RNÇitišsxߟOùûg,‡ÃÁKãРwhüшŒ»» ƒ{�îÂÉ„Ç4¯§÷;{9t”`ÏX·sŸ�uëÝöŽFdÿÝ’Æa}za æ1Ï™Ž¨|hA§×*Ž¦Î¸aD¢dåMh/\5êïÒ,Û·Ù =X#'„_+âlcêG#4Y ‚ mÊ.ŒÊ÷É´>³8Ï.xÕ9§U§O´aýA¦½Ú]Rœ®T\Nh¦ÿ„]¢Žô÷‡…%� éÐ/�éY^¢¾s!ŒºjáÇÔ�TôµÑ[k×OaêFeaé+¯DÊ`Š_vÇë¨'û4n·žÕ­%åÛŽaçMÉ-ÞA騠lh>£XƒXR6<šÞ3BÑ‹Î¦Ö ’ñˆZ DÛJ~]°Ž`L­�zÆ~ãÂÖâueØv ¢ýà ¢ÌÿÞƒh¯4›ð1 º7ÀÃÚÿ§L¬ž(õæìÍO}��È[PóÄþ¥�A¢h¢Q&œF³pJ‹m×ÐJ9Ÿ‹/”tpHÜßñ„"›+§2Ø<Ý}¡Zêц‹p·_€Q£�/æýãùðòBK0ßO, —LäÄ‘\~†ç—€mú¹N¥î½¬»%°åQ–“°²RŒEXw°ß¤%‚Ua¡à�][êoÀ2¶,Â+ŸØÂteNèhû¡7k¦aÄ?вô¥¹•µõë2R�N>«ùs ‘ri-ŒãÂòÑ{_˜7¬ç7e=‰ì‘+ùÿÛT�K3i6\:<ܳ{ó-Ög Ê­r¶oAt®Ö:Û{ßâ¢MPkUâ Ýæ³FÒ›§3PA¥¤|‘s£Ü`™´ ª½Å·ù|Îíe,îŒN�Ï9–—Þc¿8Ëòxˆm½ ˆQÀŠÓ~±‡â(|ÑúB$¡Þ¿•M'ÆSÇ9J?˜t2§‚H‹ïãLqKgR`Ùúj‡.VsA+á+¼,É�„L\ =U"ÜÒô@¥úGƒ^Õ=žÌßÖabCLï³þ‘$õh¦ ÂP*æñ!lYR͇ÍÊ¿œ‚6V4aÛì„^ ]buÚ5ÏBõÚU ww¬ù@?”· ê3d·%5á8>Èx‘�ˆW#¨²öcÈžœ�§�ÜÙG¡ó ²õêŠôé—³×—ïžÝ<}÷æì·w—Ͼ=úùÅå›gßSzdú„¦ îe|-7"õ�p„"Öí z¯(u¦]¹§adÆì^…uI½´” 廿ïÙQBí¤Ö “”§¸Ý¶´R?óéŸÑ¬ヹo'Šl‚d<ó[@å¼Çó¾¤xœ¥VËrÛ: Ýç+0“E“ŒäHJâ:ʲ]ÜMwÝw –8¦H ÅøÑ;ý÷‚zØ’»wníñXAààåÇøjö‡‚4ÐÞY„Æ5%‘[À7ÜHʘ (É?vªb©…�ÔÂìððxs“q€o€_æ›Âšw-ÂÜ(cS¸}‰^¢UôÖª+´…Ô)ðÏ_ݹfŠr@Íߪ7µ6Ú¥'µƒHmÉɃu6d庵À‡9ãH»æ²¸Äe¶ì<ÈŒdU¦=Vð…õ£¤èÄsàZ*ç�š:lJdîžùÐði¡÷ãxCEkß�™ˆ­,Ê�äÎÔLQ½ï„5 !ufÆ9S¥�Ì4�ù¹¼·Rì¤pe ¯ËVÒó·¶XTLàeþ’È¿§4±ÐFÓ„¥FržìĉV£»JB>ýßje¤–”r jB…d‡ïâHPÀmį8 :G?G÷“L´ó ·1£ŸVTGá³b?VSLOçuCmî.Øù•3xLá_TÔ¬@Ff‡Êx�gËI§hž²>'ïVÝ}ª­¬¤“[úQIÍ�?é‡ä¶\Ôºøtœcreš1`–îÚ4Œ4<'|˜´QÍP–jBV›þ18=^«Õã ºÔ`—ú+¹Ô_3Å©�{&'NërêܤÙ.p°fK×êñ¨Á—y·îJéz‰ŸµaÃyãÚ}¢)é<¾‹Ôuþ>÷?>À÷6`RTµ£™·„§�—GÏÄøÆdñBÕüºijÔS[ZË=ƒ†±°^gy¿u‡v½[™QŸ’Uá)S˜žáÁñx‘xËã‹¶¾¶sT!*YpTa´H¼Ž#(ã©‹ñâ¹s±LÎI¯x:WÄ�¢ ÜO®ùZûf´á˜sÞ~/õÇcÃ!ml…j`õ‹çCþDßžÓÓîÃt+¹iHŒXë‡)û€#éSþ”/Yêx߇‚rc±ë5î)²þòöDZrAñ¤¾‚�üGAtðk×çFø”0ï_W¯+l1'–&,2MoÀCåH—§;ÖJ^"{Â3Õébå�Íæ�©w:!MÕí¸ÅKäpÔnè°ãn9³QX"}Žq‡šÆ¸Š¦0ƒ6ç÷\^ª4s·9&6Îr›ý1T_#¥Ô@Ûfh µW÷®Ô q+‹6�í?·[lMȦVxH»á‹Ù.ÖÆ¸6ÿ3åÕo˜°C²¾Yxœ�TM�Û6½óW d]^«‡Š…í$›MÑ¢I±hR¤€,(q,MM‘?¬Eÿ{‡”ï¡@s°9oÞ̼§ º“'tð^š(5ü½è$™^6ø�[Eg¨µô~Wð»@Ac±ßR×€wuŽ¥Ü�?7Hv…¶�-ž¥äó¶¾—æY¸wx¤/Åþ“µ4j[¦ûýûLäv¦´-¹ü^ˆ™!y�p¸¤ÀÛõÖS°îóMBïïÊÍf õ¨Hn¬kÊt*/O7½³ÁÖV/s¨þ â—k¡ÐScPA°à�_«X#„¡ÂVžÉFö(ŸÈ(;@¦{-> WO7ÓPRý冖êö ç3jl¤  ­=‰ÔÍQ3v¾<ü†~üðñíãÿ56¿[Bô虀î(kܱZýŠ#£Êú»ÕŠCÏFGþMd=�d}"Ó@g§¯à­ôc¯ASÓ†Ó?¸sk ¡®£úÄ+Iì¥)ëg~¢G¨­9R�¬4æû6vÒÜ:”*‡Ó8’Fÿùf1ŸÒq™`l}Êyµ¦ž'H¡ûÞº�‰+¾Ÿ°ûÞs³‹EJ‰š Y“z}Å c"Vš ^C©Ö|rÖ†'΋s`–ˆ¿ F[©„xͳà™ø%3ûÉ!ÞxE>8ªb*ëSö™óâ}×Ìfj‚§Î�)Q‘‘nL[¾(z ÞÂh#Ôl2¼­�ƒrzàA�í’$Þ±%´LqWÌ¢`MYöטÁ¥ÿâ¤ò]žÑùÄ©Ø €«cÿ3·’ªÁ”¨–ú–Y¤k_>³÷KžIìŒßýp1ú# F6I2L;D_ðšThwÅ�ß¿HOØH»"ÍSØ–’�üZóÉðŠÎ¨Çõ×Îmø““¥ïÙbl¹šU85Oá;/ %SJÚ¯¶àx« O¸ìÛ“DvÁLz¹�G�Ò3ŽÕI³ /iÈ.Öy[ßÃR+ïÿøåÝæSËdˆ*’V™›˜ÈNú»�Íd…޼OS™­'Ä[御hÉYÙÿÓ5úø¾„xœ­UÁnÜ8 ½ë+ä’ãì5·b(r+6º@QÌÈ2m ‘-U”Æ1Šý÷%mÙžNÓA÷4‰H‘|�OÏ7ò“­ê+ù!¸è´³“÷.DùíÆç#üWˆ›Ÿ& ñ±5(½j@VN§úˆrh�n¥«ela½º”·%ŒŽN8ª]ñù:çËm£Ç‡Ãa˜#{åýa­°œÞÝI`™*QŽr°�z†°ç©+ Òô­) MúBghº|`úfšÀpR ­:—ÂŽªÑì ¥uúyÉAzªy/Ÿ¢Š äcç-0\¨v2ÑÒ_{JøjÐkx�¿Á�‹#C¢ÜmQ�‰m*÷ÚuóçÜ}ÇÛÓ²¬7q/±Èc\3Ó4Ø ‰-X{)Û#ÅŽSì8ÅHÆf²´êeBÆnX B­40‡ Ñi£"ÈÓ`�˜¦ÀI„�ðßOåî³°…Kѧ¸—™Â9eGd£u$(â_·¤îŠ CÕP�p\`Í÷�ë¨,­�@ÚÔ#ÍCEi%�ê#¯‰·8ÂF‰ª ¸¢*Ý™6è�”!+Àç輄Y‘¸¤_íúÚ4)0Ó¯ñ¿£ kðQz瓟]5ÓŠˆïb"¨˜ø¾V4¯jƪ�ßXìDÍ ¿�³Âçl�wh"�¥S¾KnC•Ýe.)ldmòÅE§eªëÉ~W|Œlý5`³hù=!¯à%ÎÇ-ƒô¸ZÈ… '쮌Ê̸s«"7¨àl4äÕ†¤"½‡½|ŒÂgo#I‡ŠùW.oC;1Ž@EÃiGÿdkÀÓÝeKi FÁÊ<+cfî�»i„ȼ›À»£®ÑàÿÄðܦغ&úŸ¿Þôx&ØYAÛóÍRWŠQ}ï�°~Új:Ggª©@l%‘|oÏw±Ê”êøšÈëéåñÇ!_¦'YŽbûôü./USls½FÄôÆHB+|†EUqk2„ãh7sú™d{«a��òo{õ<é<ËM™þ±¿Vxœ}TÁj#1 ½û+9l MæÞS ]Ê eYè²4ŽG“1ñØÆ–3;”þûÊöLšBÛÛÄÒ{zz’²‚ŸÎ< Mð² üý<ð÷««³ˆr¶Óû$ig¡ÓÏ“Ÿó#F3ÈhuôFN$äÈ90öh!è}Oke´:h»æ’’uJ%¯±P‚ë€z£ ‡è¥Â+p¡Â}À3�ã'M ëp‚Žórd›…gÝ[�*+Þñƒ EÌ’vIZk ïzºˆˆp°$õüÖš7:Ò壋ݡpG A·¬g7AD¢EÑv½~ï ×ûÄ?éŒ_¹a�¶5Úâ% ûÅÀ7—еCŠ-vœÁz³& rÚ@Îîtà4Xˆ«[5.¢G¥;Í}æ·Ìø�?56‘kB—ÂŽ0jcuy[±ZÁ÷rð¯ávÞ–42)ˉBÜ,#Ŧ•$›œ¸)|RTDž‘´®HÎb?§ZÏï_Rr‡…±öõ¸Là‹JKîY©ÛDnà•Tp_ƒ¼œB<ÉÉdþ‡r_>¸#ï_ò–¯Žœ3[H¤�¦z3sÕøxòzY5rpCÝÏ êcd|âR| 9Ò»H¼C¿ïîÏ“D@ï¢&¦²Â:ÎÇ‘�¬Øðn™Êôê,Ù±´Ëo˶´: "3Í‘‹’c·�hêaŒÚ¶nä:ó¸†Ü㵸‰V{�ôBA;zÍ·Û,LJ;-mµf“„¸5„ÁrL?]•ÿlÇ!+b϶/z"¯›fÇM­\þÔ6.ì›Ö©Øð =×�Í µéi0—� TKŸ nžÖ™1ñ‰æó�º #xœ\»e@TQÛ6:  ” J·t Ò!ÒJ©tww÷Ò%�’ÒÒ1¤”tw34 s¶Ïw¾ó¾Ïù1³×®µWÜ÷3k‡«(Éâb‘a�@ \y9©Ï P|z†lš› q� ŠËgÙ÷(óì`ÍBBQªŽÅF¡û4&rŸA oF(à+t Ø�ܸ@ }CH("vÌlþ$œÀt”Ór�°_þû €Ò3I€ƒO]奟î£a<#b8sÐqªÉKI¨z.fyøªöޅ希›O’cbbþ¸~ŽõìuP.s ë7æ¼¼×Y‘AÛ�ìßÖz�¥Ñ Ù ÌI—3É�p¼bÁ¤}GFÛf+C<Ι&2ÐÎÇ�ÿžÓšÖº˜�|ÿ²s?"üfb­ó�jà;ügÿÙ4Žx=\†eå¸ÌŒSB±�lD•_^©²—ÏA ÿp¸û–8a·O~4/Ú�j>€a�ªˆ +×½ÃùÝUЇvN”ÿ© ŠÞ:ïfµÙ� cüâ=á§üd±þa·ù8îTˆSÙr›ÔxìÛCŸk³akŽ9ýß:€�Ä𠤆äWEøÉÍÈ'{£ÿ:§ÈódŠ ^%<Ä�úßÏ�ØÈ!cyì}úßí¡¹58ï�¦¢ÒüO Ad6(?µ6 Ž7ú»ÿuâíª ]¯x3螘[”†b�p3ë»zÚú¯jèK>m@wkÿûzŒpˆõÏ›§”Á�hÿk�U$@èH ¥¯t�óC؉óOz-ß%Z,ôŽÒú r…5¶gÇÇ߆ùø;= ,:°,�˜6¦3¥´fR¸ªU}½™Íòz‰ñ_=c~ÙQÑúô,ü�Àkµ/Ãô`0qư6Ͷ´? ´0OD.¡›•Fž@ˆÇ¹sµfë/YTª¨(·�í&“²Ó]�|9ÑA¡ýÚ)*þWOeˆ€ÔÆòãj¿edˆÓì¨äÈLù.çïO•^*${™Ub9(Q"‡ êƒAhä™#‚Âæµüp=t~ù€±:ŒÛæ°¾VŸUP\¤×åŠ&Ûôå¿Æ»umçUÀçH~ýðífŠ{¶xlAAÁûyÿÛþ~–øà§Wç‹x«ð±ž’‹#ñomõñþǾè=�xâ€ÿõÐy”%bdû‹éVî2ˆö€pU«ÐN,C^2n/Ï\f¬ݺƒ›ôPí_φÍ7u?`�#ïyÒ‹&®ZæªÝ_ªG¾ß Öóü¯ZÑAÈ%½S03û÷‚‚Iß³cáçp°<ÆÆÁIl§á»ÎXê€Êƒ¹#÷9å�ÝÆÕ§{÷Ø–Ëítnû³ WýÜ×=P“ñ'®=|ÿ+Vñ7 ªÜV¬(´�åúu R¦44\qk†8OÒ‹c«¬éå»,y H(ÞÒð8r2„¬ªûËZš’È}F¯§qý®CúßÑ•Ï:þÑ›åNU˜;T¿²]ÄXzô{x@ù©×Âw´P'™l6$ *iiŽ¡—¿¹»7á%C™k1!O/ÚXß[/âk›èÿØï0Á ÎöÔlcÕþ‘Aú·‚˜˜\^ž²�’’’·'äÐù¨¸òd‘"¶ÆoÖa»õ Ôà΀�î'˜9 îî|Œµ²!=ÐM4|Õ'ê•™,¬ÿ7Gxù|¦Bˆw_–Vj�w=è#Z/ëfõrœ*OTÊGõä ÿ$npg}£—ðÈ©dÏïv\®}‰5”lWzvu³i Yg}•tâJ”ƒªsv…6h³ÿ,ÅZÓ=2H ·å#'ç·7…�dá�óEíM›ÔÎkþ¶|�!9ýñ¨Ë¥ ÈekéÇ/bE�ÿd™Š�'nµÝž9èï¥O’|‚àµ_JT‹£RË�×–;Ù0z-Ÿ-§\àÑ4Á&¬GÛ.jjñ„Ä‘0qU«Çššçº…Tã"g=âEsvõç÷ØÑOpÉx Çr%.ö§#ÝfªýîXW ~IaºR¶ ïÿÄúùS0h(š¾(éÞ2ÅÒg‹¢Jx½^ ƒè)[¦Õ�í¡EV¼+™~I®DìšEŠý££Íû#ú!¥¥»Ú½ÿªJDó3?‹Š•­$Ìhjâˆû3”ÝÜ=¥kÈ^�Íœymµ'Üx¦BGVF#"/—9Â…»» ÃT«’ç¤8M�âï�ŒÇüÂ’¼¾¹TžYÇÝôéÁ«‚K­,Óµ}w¾üíûóÇy£ÓTË}ävÖ�²v^ùø€ùü¹{‹<�°û¡áõñr?ên®k:£š"?’åÿ¥ ëïßÐß9nãbÍ–¾¨ûëÅG,¨èººéƒ«¨Æb„Âlþ¬ñ—¡ôý6ï¯aa“Ifi/™”¾Ÿb5ªb•î¦oüO¨TŒ_žÛS2 Õq²<˜“Õ÷†ý¶žŽŠÓì WVK“K¹ý´?»q> 5>Y±OýOÇÐP;ÝLTžáá™.7ÃâèIÄý1mf©\1O–[NÚ®¹šŸ·ùŒ–¨ËðƒtiŠÜ*Æ@kúÂr Rööœç;Ãlo•Ô*ðrK´¹� êo<’ñâz�è€.ò¼d{ßâ^ñ-<Üd(™R-ýê—Ñ“T¡Kît>;z© €ì?` Ã_rmö��€iz·âwmR`|¢^–ß$®u?úa]š3HŒa¥k�UY±X‡ôˆ?à °I^ZâýüÙXþ£°¤ŒÌë´´�Ÿ¥ƒjäóó°þ1�R§ŽÑ*¾ÇG#4 |‚ ¸ 4ÎdÓ䲺“fQäÜ�{t»“¡æ‹€ó€Äîõd¢HÞ««óµxœÛ-&·žv¹ ¹Y÷uæcÅhu|{Œ#øn±ž#³‡Œ7�Sï@<ד•´ûcò> ƒ BÎñõ>J¸vgr´EÞ0g…*…Þ€Íј=ÈÛ‚X@Ó# ÜÒ~g=*±�>`´,Ð^°JH4‘“§}8TŠ,]�û(¬UÊ^ hð ¡¥ÅÍgL,¶ä•¨ƒµœøßœ.9JúðÙ-Œœ@«òMúê£H¸¿ffê ¹**(PÐg„|„î­BÁ¢Þ® LL)ÀLS¸èT›ÌLB!öíMØb¿$dÿ×b|݋ϫg¥OK÷›amôòjõ«j„Hô²£”zܵiίE%rÍ;É7 ŽR„ÖQÃÉÊÆ♂.q[[/§'. â23y��Ý�ζ8éå™™šÝ_¼ÿPÎDÓX´;5ÀÍ`8œ~ÙÓÓƒ‚O I^õ×½ÆÉ“ ãÛÚÖ)zðù^üE®ÌX‹l:½Ÿê~˜úüTåò €++q(¦‡QsKYòšXÝ,šBPrÓX¹Ä8Þ¹òÊË÷�óÞ¿ÕÀЛ•S•¶ÞgyNq“46Æ(%SÏ(ÒJZÃÎ)(ñöäîU9·ã:kEzJL2Á”F"­Zðî8Òà�Fdg‹êœ>ÞKc?*f6¢•©¹qâ4:®Ó)¤þúúÖ¦t\H®Yÿ×Ëw·«ð¶ÐJ«écïU\Å VúG·¯OßÕÑŠ‹GÞÜøe Tg!ŠtK­™h×¹01Ãà§§Œ¬¥OpÉW¾C-]%êL¯�ý@Kî‹SeÄòºKVû3ÝY+•z÷ý°ÔN«t™OÕ¨�„„˜ï]Ÿ¡mhÑf¦dœž¾+kÐ>¢,µÎ•˨‹`²ºNøË°3øbÿmñÂ!šÊñ«ìžßE%1èÓeóÛyV¬ÍEM �—ïóø@º~Ÿ¿l~ Ÿ™¯ w;jæ,ëMè®6!t2Æ^_¶šû…ÝÙùiã5þwdį̂AµËÑRSŠC»V/€¨óµ”ÿ˜Á›û{øA•�&Ôÿ |˜_œÇvtu¨åÓÉ !¢xBJúÜdåbï¥ÜÇ¡-X?šÿÙ8-þ³žÚ ¡ýüÀ^cÄ ƒï—ûíŸXƒ¿>'¢Q•£Q-t�nzOe¿”¥(M:±£þd³�@ç„ ¬±Æ't¯w@ÄÚÿ$éMªŽd ö§½yŠù<&Ïë\ÿëW^|â�Ü +A�úN¡Oõ…L4Ùhà“0R1_|½f×�­O"ö›T ý4�ÃTLô>v÷¿Æ�÷« ™}­�w-ñîktòûGUÿaâN5_uU;o™��mYs†|¼l632Ô@­L:0‚Sq�K䉢*¬¨fëôÞ£:‡qÊ—îA';´˜�5?WŒÏîîw¾#�ÐÒ…Ï*™¯—¯€Oã�_Fô|íªéÕ!?˜¬ê· '/³íܯ¤"Åõ¦À÷ÿ¤Ãƪ…²WW»6&’À=à¬sìX[j�Sj.;{�Áÿª$ÙÛ³�y v�}=…6Ò®½yÿžl¢ÙÍñÐ <Mí!½Ò88è?l¤ò�o^?< ½™ÅÆY_žƒÆÔpg—�Òÿ†ºÆ¤vÛi«ß §ç°ñv£�ÎÂÂ�¸œ} $S¡ÛÁ"ÍDI¯ñ§^…Óà`¡®•’¼ê©•¥ùiµAÍß u!éÉæÝÙ¢1J"ˆßmWÞqÂB 2ÅÚrvwW«5�¥BTol^ǧz~ßö™øedA/OD0yeéÄ ˜�F†cvèÁcc´ijæÈb5øµ“Y¦‡êÇcb*N/½B6×.u�Iûûåù£fjJÿmð'ŸÕÓç_„˜�+ÉW($õç÷ƒT³!uÐÌÐD‰vý!6 tkÓ:¥”®Úëô�ͧYK­ŠÅ‘a–9®´q’]�¢¶H¥æ§¨êæ£ë�¡ÛÔÜèyUéïó]òO3ë*9q@%K%}t›®5ݬc«ÚïÒwÙíô¹N’oÀKè†*\dÑq)a¥¥ÏcØD¸r„¬;UѣЙk¨1�÷wˆM šª ¤Ò�焸1N­¯Á¡Ѫ.¯¬¼VËuÚ´ÿm9Ž­¥.U¨†x¸>±_2II¿ªÒØ%¥Yøô¹ØyÙ‡'Þêü`Yo¬àÜ+žkmÈãµ¶¹))cΨ‰Þ²3nÙú:4 Ë0jåDØ 11±Ô(FžÚ#¹„N;x ÕdÕÕyÝD/úPþ3jÈy݆B¸>¾ ÀÄuŸWÊó‰n�E»xÒ2'ód¡ÂbÅæ´È_¤¶IoÊ÷‚[~ÌâJ<®Yüšc£ï'~x\‘6 ø&ýh#Þ\i\«5†Úö‰ê†3sE)‹tñè}SÚßß&üúm33„q¡á Ó¼Jõv»ÅçõtYôÝñµaµšù‡ ršUº…t½Ý{eU¿»„¨qKwnÔç»fÅj/5Ï*>[O+ž]M¥!ááêéëÕ¯oËžïžæÙøŽÒ†O“—�¬ÍW6ýÉj°,_¡©…�&¥©«µ"ÚZö§"Ǽ¾„øz$“ÍL7Á”’ºûü5Ët�Ô¤ï±þy4g AçµýT]!«¤„/?]î;íjfG‹” ã©Á 0H>aÌ-«< Ç–OYa¯úªƒw:¦¼‘ö1Ò�#€µ1 †’�â²A#’}¼ÕËå5]‰N'@Ð ¬¬ Á•¦àéã³¹½�þж‚‚‚šê<ÿÜ´2“˜Xö—b%§» +¥±áõî0ù�[ö͆þ*lœ@Åó¤#Wxí}_g/_†ß v�Õ5d]ÝsÒëØNLÛ[áf¥ç‚o)÷p@7>ùx-M»�¶<†µ/[–?ã‹ä¥v˜Jµu€Æ~H>Íö»Ò X¿œ5°èÅ­¼CFcö/k·ì<ÌVÄ}òÏ6ÓúcÈE3Ä‘'1Õb¼Žáœ[|Kz_ÁÜD”B,½[dõ³t1΋۲|<ãË_bD¹õ¢m\™QŸèI@¥�fE  Ž êÃZo¸»RvRÚ½Ä4tDd˜Ë;.K¼�%…ÿÉÃÖ6xLÙâ"7�EÍdùy4þq`R抢q Ù>™TŠJ´HÛ®‚Ï´Ÿ†Q9»?å_gÛŠ4è}¤#YÂÇp}ýìß6³ƒ·×õv-c¿æ*ªS‡|½6 fÝp0èˆlÄ8´ëéï:PjjjåÁcÒí��$8¡CæÏÆÜ([–•Ôe0ö¬¬,bø9têÁ³X º´p0J9½º U‹NãeÉakk†öí·æÀƒ•ÞSZ®¶‹^îO·°Ã""#MF³E—[RD}®~YM2~þò¥ªU@õHJ9Òäâä#Zþ8;矃ðSš°ñ´¯ˆ‰ÏºØÞ�‚%Dò½hÚ׉k­‹¢xË%©ÉCé j>�عá|._�|) OW‘�),_Š/v?uuui¶zrÃFs`k�Á1.S%!Ko½mQ÷]¨%”ľ¢ ø§¼¾Rd|‰Œ’?þmÜRê'§é–¾RF`IJ+_nÆðŽ£D­vÕÂk÷tÅ/^®�A)LìšÚ¢²Ô”öU*�19 {\=ÁÉÑ®ÉmFm;ÛÉ«z1½ò½gå–æÙt v ¿wæ-rò´j‹R‡Ígª�‰„lí€7Ã[ЍbÆš,R4ꀬonÇÓ!Ol�&«í#÷^ ¿Yp(…HÜOV†"ÁíR¾«†�'K®_ˆ³½vXög«úJ¾À[á2Ê  �ˆ$�Ç ×£…†�b[…)æç½ÄjZwIÐɱéîUÿ{†É¢áµQñYýÏ·œ²÷jaw,l�É�—øø*¯G ä‘î%}‘ÓU-€ÇMˆ§“å2íw$†#/&•�û(RÚ=N„A7ûef¦0.ó¿šs›Ñü;Õ~÷Jæ#¾1:ö{Ÿ^?.:€×¨ý¥>½¢òsÅñX ;äåç{Ý¡s^Þ?\‹;²<Ϭþ¦¿…¥û?†~EË·"+)9Æ]ÙÞÙ©¶[älmk³ZnöŠÞµ'²\wî…Jÿ¬R-#£ÙÍdy¡nîÐ}b$‡I˜ ;1¬�á²b0ßQå�uµaíü(8o첨 #1Ù¨Ó�KMùÄ*!#ƒºØ‘VI¢$ä'Á² FÌïn¬X™Ú¾íR'”m2 ˆÒ|ý6o*‘9¯OÇ€r|k3ó °ý|öKrþ�I/­ÂÑ€–»w=öÅ |±‘×:ÃM:F½y ;Uˆ?t¿xÔ£`Ë2‡½¿HàDPaü®~ˆßø§n/ã@îïÃT îN·­5îóû=Þ…tÄÇ÷Úuurýawùx{Ï©04¡Ô{šñ c’ˆ®‡‚I9›«WŠ¢cn¿Æ+ÔaO®PßphÚ»MCà½Ä^áLÞ–�o3û—š\HÅýE³SCÏ,!§à¹» sYª¹e¸ ±s«Å'�÷Yßï~‘8î[ïDáf¼Á4Õ¿Ð㎕Æç‚ õ½&("l3•Ãc¨¾*_¸ªVÔ¾kûo-O“.›vüÞÆMíÉK‰Î!NÖÑÎG¿§+6ߨ†Ó8{×O&v¼"ûº�<“ô0ž896{Ø•ü=bZ;b�‡,Žú�8aþ[ó!žœR•ЦE@VüÃ’>{õ:p–? -–ÉZ 8ZŲJ“V¿}'žõ(ªföîLPÖÓÚû“gÙ²·É_ZF³� à3àt‹ý'LlQÚ:T7™«YGÕ�ª#¬M™9øé˜×ý“päCnô!Exl;·aõM;Y=m¾ Ï ™‹Oá2ŸÈ v§<Ksê°Øhž0 /ܲÂñª0¥€™%Ò´œÙ!M»ÂØ©QB/�k7�n¶„û^…Ô Î–Vÿ X!G2$f€�t4¢sAYsžá`ˆÚ ;42ŒvL�kÌÓ´ãg ™6‹˜—„­â•ödÅkÂkßOÞ I:€�á£C)–ü‰ËÄ=ØwF!‘l%iè›-5V·^¯¼»Ý¢‹¡>NÆOçZw:n­¥p�IP0M–š²»+»ñ¨^¸xxóG˜ÌË1P¶ç ÁË—i"^¨'í÷σŸâINþvÞœ-#î]t€ØT=Äg48yûÆã˜âîb6ciîóX™‘y2*zÍ.(H¸¸þ³õ‚-"..î}¹?0U’ø‘RͽÁ7¥ì¡Òýá;1²~ñØQ°ýS¾ŒïÍúýáJ[_Å�[Ê@"Ó/ëiåL!“³‡}ºC£Ùµ/â‡<ÒÒÒžoåã½q4„¬øžÛ¼Lô¼YÀK—Ê÷ôÅYd}ÛùªzùM¦t¤= Ëmú¡’|‚”””@±RV¥ŠŽUº¿šFtcƒúº²n‘8 `_™4c·ðã€L‡Ï�V˜éiäpha‡A³Â-{ÏâPd¥^ËD�›ÎÄ›³x³üEÀÏ’r£¯}ôrfº¿Fº÷ê>ŠŠˆìÎVQ_¯ø€Ÿ`àt,.¶hO³¹îO…+e ÁÜKLLì.U+»Ùˆk¶y¿%ŠÜ®�Ñ‚ÁÁÑä)cõGzSe³Ÿt3gϺçL¥Øø ú(Ñ”¡”¢ /ͨtoÜò_øÍ ï¶Äü(_:pŸ–s Rq°Å’é£SZõ%)LEyçãÂî�[µ{ïM+^ýÌÊÇ»Vî÷íËŽî]ßÓ’Í´±kš(*jlî:þ!ÔÙÃüLLLa&}1ðá•{½?¼f ¹YÎ×0$ÌÕµ}•Xé¹QB L×î/eÊš�mA9I‹q.gãQg�Óè›�š@ú;.½Dî}«ó£s –ô$õ pã™¶^€´b»÷Å ?‚ýC¹…ž¿eý%ˆ÷ãÖ[hê;}¼�ƒ2j¶^AÎgëF§þ•}YÓlÁàˆÕËP˜ ÝåATý 11\ÜãXW&ŠD"—óÖ;Í4ÿd‡£úG)O„1=�‚Å-¹º5:QOq”:éÛîþj�4ÆKW1¿éUú©~ŒK°Àbÿy>²IÒàù83Y8ç…¹†¬IoF=\Vü¨³Z`vöqî«9^úGJ�)Wdz Ö¾7uñK­y›¾mnVò…¤Îø‰Á–Žñ>-½¬Ü�±1ÐØ˜¼Ž6�’‹ÆÑ`¢h^1ŸéÐù|®MV:$Û3Ì/ëÛ]C_Ô0.>{!±ŸÈ—Ôèl“‹ö+-;†Sò*Èôy=.p¦p�t57|Eú>8Лtý…( ÐLgìóHwX:f: é§гMẸÁ¡ŽX^϶›­H-ü(×½}¢rMtÙIÈ8ÈÆމ¹^Ѱâ„UÊ¿â™ø‚.tòç'ºçßÌ{¾­·ž�´Ø.ÖSë{†g ‹Lúž.5•-Ýà‰pEä@“ÚoŸ¬o6� ¼ÌiâoÅËþùåvÀ'äÑ^ýþÚÖkOƒ™Á÷Ðødüo*›¨Õm`Ûçv; ‡œï;Ç‚ºÕ}RÊãveÎٌ…¢,Â�‚-d¯ò_¦}�÷¼ai"X"ø˜óErv¶’··)iÚÏÒÁùZ�‰…v¿§’aÏ›~¢Ððó7Lybw >"\(Aµ£ÛU)“µ0Žrý‡°ó¹ÝÝ`À€|õ9&ýƒ¸=Q³ ¨”fF}ò0îÖÿ÷ïkFÆAƒµ2sSsóõíí�ØXþÒRç³-–‰É7ËX)ôáZ}.S§‰0¦ƒcçæ¾˜š™¥œ•;Q>8L¡ Àä>|hÊ¥�7¹È^ Ëé¯ô>=EEE”|aÂQœ<½¼Ü™«Ö|M×Í�!*à{C"(Ø$— ¥ªJÿãGgÀ¿_๹�¶¶·_RPÈu‡^ÎÕÙä ýúù³Õ!½¬ã] Âã Bxô×ÜÁÙEM»ÁJYÓS �jÃÕWd^P0¡®Še5V ô´ò€¬ÔÛ뙉<36Ô kuE†Žÿ» >ÞJ:!º¿F~²t÷ŽÄTTr¸xýÆÉ©¥ªÿèdï<‘Ž8ZÉnC¥ö€�zã¼%'C!¯­M¶ AŠ/y¸œ5­*]˜¯µ ´²²°œ­O �Œ¼Ôþ­iûqáG$Ä'ãwù¢g¤õ«&-´áöI�§�Z¦ÿ¤äø÷¼ÖÕ6.'²‚ ‘‘ÜßÒòôÏ®€óŸ¥˜�ƒðÑ 5ODõǜ禱ÊÇ»b7#ZŸìÇ-{‚L úùü™gü³Æýç#š-“tÜT«b¤®cˆØl—*]GÆ�ã�Ò‹\ê ¹ˉ¢Áµ°¸‘;J¡¸Ø¢JŽ/l*°Bt}ðä¹LôlN |‹DxSŸ>•ïŽêë¦ïÙ×_ZÇ#yoÝÊ5þJ]óÆ*9N§fJÊØjj| žÂX†³«F/P£¿œHêëY°ãá¯ÓИüó;ÌVÁ³d—¬Œ²ÜIY<ÉOÖ”ïÐùÙTªg8§mM}håÇß6•Õ°_Q±)ùQL…ÎÙ.1«ÛøyˆæÎ·»˜žÕ»Ùõ´î€›÷ô-�t�õëì±LK)^Ø[ýRøQ8iØG7ä¬Ñ‡ÒìÞ¨¢ 1ŠkŸt¢D+ê 2ï¢i>Ó"ÃSà "ª{2:1h�IùÔTß´£"ëíŽB-|Î1‰òé ľ àzx9FÙJUc`SëGdy¹­RV„ÅßÔ0Ýž.ž�î û´L}ê,2HC<1‹˜Œ˜:„�ãÚØÐŒa!„a¿ø5¼/†²{¢p¨zñÈü^멸|ÙXgÄß¿ �w6¢Ož>ÿ¹vI¬¢’S9é>91ñ± úœú1ŒQ‘LãÞ¿¿&bŒ�›3Åsbbcm üttC‚ztoßâêÍÂÏÏÝØ–,y…&gܲ\`#€B $È®³ 3nX”W=1qeˆRœs\¨· §—O6g'*Ó‰|°JWrwîåu«Y¡^®N~{K…ž�\g3ûZì‘ØãU„ýMC£—ïZ]¥Ö›•W“µ´üö¦-$N/¿�¸-«íð4ªk?bCŸHÇÃ/.zw×ÃJ}‚ƒÞ� ÐÐB^’¼’y§B‰ÔuЇÕ-Î…íXˆÉ X¸¹ ‚ø"$Oá.Ïûé®E~‡UmÏ[4£«ç]»¤œ> Yàø´Â¼} ªìúǬ/ ü/ ¥×üüY:)‰i^·;¨Ò22­¾”z6ÕDn ¯H�‘)ƒ‰&‡r[[ò!)Uç´¢B‚8L6 dÌ¿9štÄôÂ&åyÂaˆG‰Ù NŸé-r>&ZRº4y|:»`nñ[^_ë”Ä¡øÞîwÏe6(EöÖº¿Tm~³ùr™LÂ]ÿ ½�]·1àð�Â|Ô¿°ãݧïU>ÞÂr‚” ?¤žB߬i·œ´{ÄìrjžŠu‡­*Žf‹j6M¬‡Ô:®£Ú·7µ¼±kX–øü¹Fv²Ø€‡oà{”gV¦pw¼…ü\"÷ÀÈT—™é<¿Põû¢Jê¡‘ ¸^Û¹8 ~ŽkpžfU©ºÁ¿ê­,αè|ä·7 ãK9)R†MycdO'ÄÇÆÆâEäå–“®ü§?*¬L/ÿþÓ�L↠îns̰y·)O3¡ÿ±.5u�ë¡îÅÎ�¾d°Ñâ–-ƒdJ¤‰ñ†î*¹×í×ÐTŠÉ'¶|äbb+ž^^É©wNܳ’ë°®¶=ÂÞj§ó†hÆ�аžS!wÛÈÓëºOeê\òò°ÆæŒwªªˆÎ?OòÇ:×¢‹� ~*±ŒÕE~sŒK©F7Z—w}7ã• *ã¿DˆlžÜ¥ÅmÇ%»‡Ú-ªÇåäøiMàáân‡3eîO•(id$ut¼Ëö=“gTÎþË”ãÛõÜÖô2„HZš|¹ò…^K =MnLã·*:zNÇR]ÓG%A0¯ 7|ÃÈ•}‰éã®Éqöà�Çš*Öþ7îִQy***SSÓWšC6ÂÂfúúú© Œ ƒ±:ÛàElþWIü¤q¶î[åºc“ ®xOzC�ÅÊþ:|Þ¢4ð'âèÉ9|jßÝx à¼áeªån/žž’"ëî^ÿ1 �±Y>”�¾žî˜'¬àö/<'‰2,$¸\á6Åì²²j”>-:M$Ñϱ›ŠçÇ%ú�£ô}¼tip†cz·&jéUqE¼¹ó¥>EÃv>Ω}åxcÙÕ»h½ÔÆ*<‚Õ+ÖëyÕx³ê™2¸´jî kƒ›[sO�†Ê�YaB›ÓÉKFcå�Q¤Öwt ½ˆÎMžuZ¡Iªøõó—ðð„Åí'ýœ&mÕ‘I«ÛWovGÊæm�£ö…í¹÷‚6K�çã-XZüØÞûúŠ89§òZ±"¯!£<öKšT~Nü«·´¾ã¯���½îö,ª5)^ú`yLaâàlµ]�­ø`£ôÕ·÷Êžq­ÒjózpN&f§¦Ê9:þ:oѪÝiôÜ$¤2X¹mkñ:€¼q?¼áò(ÙkÉ@~ì]¾k[:ÒHC¨Õû/ECfõSn÷CÍjñ‘ ‡vyKKˤ:qñHà†õ¥¦Ä�÷íÙýî­?és‚î çŽÇDák8§�c ‚›W®Ôʧ\¯³ä¿°X&„®´™%™‰ŠŠRæ�khà¹Y .(*šhóñÈËÏgSõÇ*bœ#1ø¬?Åã=‰h·¾ÍÏ”Go‰pæ#ô‘âÒÎ5 |÷¶‡šƒŸA¬qjœ:{Ø kËf¬)zŽ¡å¼7&%-=^ø‘�@dêì4š˜„c¥�Ì(›/þK=: ’+& Ú¥Ž ûø*üWX¡õ¿œÑ[°˜Fåx£&2#Ñf0§¿=›|q‚Ÿk‘@¿?ݹ'´Zh/1¹æ™©�ßL†{°¯D{¿>ÄGEµ�?ïÖízáö×bµ~õò2Ýs¤ÚÙò.&þ¢AìÓïOêÂݬÁ)Z}‡?\X%^_ýþš÷î$ãƒGÒ½®+ò„Îs€¸–ïËó<þ^%2È qZàåØõ½?ø¶›)rÄìcÈÿ5gó,Om³T'ºðr±‹'Cˆ[b±È˜Fæ~Ñ*Q"v»eâ~¼ÔûÛjR¯ÇGI‰³ºšioìG[äxë§üЯu³Þ§sP$ˆRè7O)Ž”””ãŽB|‡ _ `À„!XÑŽÛYPyxTÔÚ8¥ÚlhÕvþŒ—œÏðO“çå´kV{Ë-TuIâo�~ïa¶XU~QVãÏ™ ñؼÞÏŠ&ç×KÖðÔÖ& ”HR‚µœ¨óxžvÜÀ rŸ‰m&08çæfÒÔÂÂe3š_ ÷Ù³\‰à;)Yjöº 0ïç”�NÃÛ+ZÚ«ï§¼) HΫh™|—øŽ³.Jì.Á"ÌžÞÞZ i€à‚ˆ$�á5]wx,xÏH§±Ã@NŽHyåQ..^â"È쇕�zÙp1ì&"lÕD�€–¶óëW´ÄÄÄ/;]Áï‘J ¥¯-t�à>/la�®'k¯é(MXߦâñòãG|~6Þ„Á4òæ&'­&‹þe…>äÁmg$K§PøéÜ‘:ŸƒþØH7&¶ #çþA ]ñ �)žtŒX8_�NgÆDÛnû�' “Ýñö¶+E�Ýâw×ËriMq á+¨—ã‹ãR4Ó.QCmÂ]%¦¿³d)ð–ÙûðNÕ1 ¾¹î9ÝÈÉb[N!j§;&©Ð®Ûh»^1é� d7hÏüù“™Ês e篓Ô¨^«ÙRGos¨—Ó“è1�~ÂÍ/ú kŸÅfâ�È×Ìk¬W¤±—1·EÙlü‰¢V4iU"QF£˜åü¼(Z]‹Ñö!-®®õ”�­� [ÑÅ8áFrýUB7fjŸCn ’w ö˧gg¯……32%!ÊQ$œß_邌<tNâÏy{z)ŒÁw8µ;µñZ¸>îÉ͖ϓڢ¹Ä[ò ÷�­Ÿ$“×»«(ªºr?§rMŒLÌVPˆ²LV…í;{ù�&ueŒZ²g›lÇéHí_=ÄܼK~Zës‹ î3=nÔÚl‚ó]Q©“ÌC Eñåe“×o1cšOá¬<ø¯fF‹WÉ´¸†ó~ßé4¾giðÚ”òß,ጧԾOžæa«oÅOýŒ·�¯®¸óZAÆ%'ÿÑqîëë+NíÂˉ 4àqÚèLþÃã» &ÏÓÜ/ dbÈíøf7?ÄváwÈ?ºé§Ã3©[Єú·éܲ «{øœ>&ûö\¯";f :{�`P^øúövxi©&ûŒó¨AÿÖÀË�z…К# ý;55qâ�«Y7úúÙRÒÓÛ9�õFÿåõøK’1è—¸¸xk«üÒ­q%,dÚ=âöü/£—/Ebªª|,[–G“ÊHϨF2%ß¿+r†.o;§rö0�']‹Å¡ú³”µ´�øŠ¢ÜÜÝò’éÊÀŸ "N¯¯ØØ2NO¹\õ·æËõX”§¥ r³NLK“¶·§.�ª¢ÞÖíãÆß`þhjÚ8ô:DcC%±ªo«ÈúþÞxCÜvõÑ-Éá|¶ÅÃaÅ F©]gÍÀÚD²²²ò7S0ñþ¸%‚¼_EÜT©%×ÍÏ·?°Ù7‘¸íÿÆOò‹¦ƒ@1+äѼaæ–ùqþsþÊxôÊx×\¤´LÔ{e¸»× éÀ>ZH[ç:Á[Á…—’†=޼W%'h¾c\X8 L%‰{#:gdF\è…£®ÍêC´~ã´ËºŽ…ïßš´*ï Ë™J·CÒG`V jìI‡ýç¸(lÂdæ2d�íˆoƒ›ƒ2¶ge t±³Ëêø±1cQ‰;ôÕFJ�§‹î¶go]Ÿ(<Qrgõج�¦ÖS¸³µJ…Á» ~Õ—(¯<´q …ÒVn ±&ЯD3¥¥¥¿Eåûö�dJÇ|Ò>úèþãI|¹nþ&,ÿìt«ûn€¾h·õ.p0á·ŸÇ·V»sTöHv½Tâ̧e>Ó‚ñ8wˆŸK ÉÇ'¬tÑÑˆŽ£I’6 óDPµÔzCsñ×°ñpÕH�RdŒ#ztrÈnwÉCî^!÷Kj̪þŽìNÈB•3,çŸñº’Ðósr�•f·TƒþÎBYpöÊx6”ÂäDÝ'4Ý÷O/dôS-7Ð’ÉÓSùŒôÿp_xûÅJU£ËØ%©Ù…ˆÕÕªŒç% ê1h–\%²Ä�G›o"È©P%�‹é¤©>”MSÖj~=ùl¹"W,Ë}5lç{[Öü¶U”�ÞqFšöӺЧõ˜½*\Páôj ”y»ÞA«§/Çq3 ïQ-RÊú:o[äv Žx¼'Û…4,ÝDäý[Gn·¨‹Þ÷<¼ n7Þ›ØFCG?Ûɨ´w¦b1UÄ‹¬¹²…rE)r½©�-ËAâog\–ÚÃç{Eù¹û �OÞâ‘{MM�œ²r•ëÝ»Âs$,§LoFån©ï²dAÎ>g²9(ŠÜ¶…÷€3Ó“>¯ß¹…Uè4>‰ƒŒß¾¾É¤Ûõ'%|ÜØ–Ú¹QÔ~NYˆuG A„„Qyw=vçpöZòòUD´MÀq ¤,þ�ó™˜ÿp¦‚�œßÞÝ@n_¨ºÚéßb`))+«= A稙 �±J=¶«c7Ï[¦b"õ óátÃ:mAï ëjQí!¬ØhQé`ŒO®‡sDò @úÂvÇ7�Ì%ÍÈÓiÒg̙޷´$‚@øfFâ[àSé·H!éÀàØ¨(ö…:5GŽ‚fJ–!&–ó5, ô‰))0ÝWÕ°oßžµXG" ‰‘#H›ˆpæeî�\ðÓÑÖÒ!°*¿RŽXG¥Þjþ�Ì[³X‰òßÛ2q´’Rè-� «þC[‰òrr?…Ö™Ù\÷y¿~û6“TF˦1RàuΧ!J]j]v¦GÿJºst”{Öþp®íéÓó«E‡ÄœåëãeÒ7æ8±¢Wšá)¦Š ââ9QQ8¼Še®Ñ2o"Œ_²ÿäÎà7™(ÀTHçUS }§"±¨6LÌð¿éo‹µwªÔÊ’}o·yî.ö4J¯; 'jÌÞóü-VÜG~};šlZv’#ñ} …`ÇZç›*K±lŸ•ê—}¥Ô³S4ð*—�9Ú±^¾Îž¾“*g¸¸õ3 §Cnª@«~üãããKÀV-¹¨¨äpé–²XY6‹H—~²·'¨ˆL_ß›õ°L¯„ëUHNáÇçóaþ7&ph#�ø2011;''k?LÖÍŸ•ù~HÉ5wÞê�ÀFKéB?ù·„!/o_}¡˜Æ_}¬ÿÐ í¨vÓWQ¡éì”\‰z¤ÈžÐÛâkTÀn`É) ÂD}üòÀ©ÉÈïO £c?$¥·õQ%$$Øuê£þÁo•Où‡VW=“ëãn…¯!o “Ø™�ÔXÍ´þ¬®B{6²›+k�{±ÔË5iYÛ2ï°Ÿ=ë¤ÙÅÅë9†ãïê¯yß4rþ½7€ûœJ¤ç½ZŒ°ËÄR}ah9T% ~ŠW¨8‘Û­/y?JS�à5ä"pÚ(rÓø‚ ÎAýGs¿¾×¬!™â—2ú6”'§ð>ê—¯Y.NVsÚ|®yÌÿ’ø?ÞÂ>[± »û+ò/+Wh_J£c�ñ óéè'ZðeÀ¯¯=*íÁ�®"""Ï01iEEÛ‰žcG¯lªÕúˆ†¸f}ú‡ü¼Ô¬]½R· XÎ� aϾ”œÚdùÝ?W¯¥˜–‚W GWÂE§~ÿ~c»x…^ªíE¬+ýÉÞK¬Õä\ˆ�T“³´d¼¡sh»$Â�Ü>*µ69˜!,(Î!LÒÙì…FLk»Ñ!ýŒ†��7é¯QPQQI:N7®ÛÑHëÔø?Kuß~#×AÑר0ð j4÷™š3–àóáÕ`,ût¥^¸ý&HÇ¡MmI%E>!UÈ�Õná·ÑV<=“«hËr×ÎðǽñM#_¿�ꢢH_?d{ÏL‹‘‹kÌ Ï@bzãè(zÜ’žƒ£«mwÎÖ+U![.á5«Æÿyi—ñòpäá~² m[&¹|;Oû[Ú3¾îÝl¤][9±ïDw¦1¹®Æ2X¤ÜrðãxSæž@7XÀ�{ö{t› Á3;’GçUÃ\“õ!—š¿?ŸN={ŸuEë��«óm> øH�€•6ŸZŸkûf²÷~—ÏèÖÛj…ö&бÖ‰^»áfpT}Ø¡JW`´ž.eèAæ²hϺ•Ï Xõ.êâF² �¦×›¹{†…e ìŒ<Ô€ùqɦ,þ•©åKÿÓ›ØUudçLÅÛ/I\=45;@| {y‘&)ÙÜœ®ûÞí«µ×­m¦ì—û‘6•×Ö‹dõ’ßÎ.“ó5f¤¢Þ"ìõÏÊϨ38� =ÐNÃ\§Í?S¥jØÒŸúhß¾�nŠ­Ìcþº±¹Ì.|µ¨.Fð¨¨äfµ?ÿªo÷þtÊ3lìͺYýÍEè6p_QC¥H9S@#éUó£ZÎÙÞ“ jæ-:¨S&&¦ûôòÍùZƒ<�†J‘ÓŽîLÁ/¼jû[w—÷ÄŠé¼F�ˆ Øi½ÝbèɧÜ.;‡X9 ÐERþcn <*NN×oªèòeCì½£9!¸¼ ÝÃùÚˆs@¸…,_ЏVD¸W¯Y×Ö×;a9¥Í?Ñ^{…)¬IêÞwZî'úÑüÏË>¿ ùÙ¾�ü ƒðÛ-¼ª�ÿC)ÌE=Ue�˜žjñ˜"«©J=òüq9[[öÒR‚W¯¤–›Ý²Ö ;MyàS_”«mf+­9ä›7Ê>&H½{ŠÈ‹ËÊÒ+Am™ð¦—>*Ë ö•ª‰ë%züûm‚ÅóIõ�*­fW/îàð˜iënó–÷ÀæÄ’þë—ªM e$Û•=›àåuÜÿ] °OGD=í� 5t]& N ;×%Ñ—Y :¹HHcÊ?¾»ízójjã)í*Á{;rÜÓ¸IE°,¼³#Ûïë¥tM éû›ö¦Ä*øsŽÚ/ÚT#=³ü_w† }’yYYÆÆ ß½æçOIM8;;ËZ¿ÜŸ.T¯x÷ª÷�¾>Uuuqÿ ³C�ššÀÓÒšš$2ùñ‡‡¶À€)É„–ñ«ÔšÿüRüü`¦Âåv¥¯/¬ÖbÔ€)ÀÖ‰ŠfÅÅá+0¾¼½½M6,õ¯EÊÈ}ýb©c—ía€ñtñºŠ"{‘-ê“ÇŒU]]í 4Ô¾¾¾ÿþnŽ$ò“r½�°x¿š£[ò¿·cŽètSª8RÔØDu¬Í‡_ßÿVÚâ1—|àö†²»|¯žíï�-Þü¼ôdÌãyNñ3Ó?QCŠ«‹‹ÙiJaÏ�ä?~„ÕŽ&'kƒ'‹•7+[îÆ[®) ÛŸú\JèêRˆ#Né®—Þüí©P¹ìüÝ(Êhýr¯èŽ4»Ü_õºœF†‡eü{I¶”òí'„Ãrs�9)hcóÚåb7ôCá?=9˜Ä¶·ØPÕhSŠùoÈŒŒŒp(…~æì”�)ïëcJíïçËû\EðâÅúÍý½óZ‡Q¹:ÛúºÑoÛÿ‡®·ªª ÿ…�tHKw§ R‚ Ý! tHww‰”4HIww) !Ý%‚€À¡ûÜuÞÿ½w曹Ÿ3ˆ°÷Ú{=¿ZëÙÛÅÏâ>8yD@‚º¾¹©q\§=tíí pX ìô)¥ ÓÖ¢pÛÅ¢„„öýçOF>¾O`¦€07c)²²²Â—•.õ4µ×ש3þyz¢8XRJŠ–MKûŠ×nvƒýr*0K-¿m¿'Ò6ØÌF[çx…ÏÌh¢b‘ö€ �ˆ"\l²§°Š…ÏžÝZÇ"k©cë1(”Ãâ Ù"“n`™bÒÓI½/Ä’ñ“Šºs,..‚šÀ±¸ð‘Íþ½ºwOê8’¿|Ñwx××é}Q Wo5–Î26¦oh‡ÿÜ×WÉ“•+'¿NÎÏeS-A•GFEÑÒÐ’˜ø&ùÝ]B�½+d‰v�šD£ò Ä¡9Ù+LøôéS(þѯ3R ßÄ1aê…G%Öž—‹»ÀAšNæÁƒ¿z!€Q½ðÄL*é}­®»¤‚‡Æ>ÿˆäã¼ÑcGËÿ�?6™){1#éôc·øuf­î)�M+ÿ77鵟�×â`Çœ�.ÓçSÀ‰™Í”F xþÉ Óòǽ<òc€u&½Îlâñ»€÷�ÿHvvôƒêÅRÞ±˜™å�å<˜@^“à þGXÊÐnûåG˜õqÇvai—àãX“ÝR±�¢ ö“_£˜Š èÈš5yÁ(à�zO0é›fóBÅj�¯2Ç� ´¡Rè;™—w,~$2ZZ ÚŽ_�éÕŽe·º^­-z�‹ï®|ûQebDöX~Òiù1{ìíà€á°²ð¶ÌbaiÉ|"Gœá‘3qcÃô™˜'ÞååeJ+ÑÆí…ø÷Qå"ÔJF11C|’4ô" ”Î9Òð �$ˆ[ŠLù,ø¶ÔûZb½u{±Ì4P›Ù ¨ÚÜÂB8èqoŠ^VN.cÛëú„–˨íÝ9Y?Ï÷ÑÑãj@,Sr˜/DEéiiƒ–™éé‘D²«Vþù¢ǸPká|0ÇépÙ’Ì�)`CÅW¯‚´Ë�ÊÐDM6Ù¼oÔÚZ{\y´_n “¥ëôã•+)&ö —J É’ð#ׯ$^ò{g'ÈRï÷Ÿ?æw—ÿ••·÷Í¿ùC6öö"ûúdªÔrÄ¢¢™]¶Öf.Okêê‚ëëù]\]{ë8êêëk­~’%A,)/b¹v¥Œ¹9�96ð§hIŠ¡\FXõs€Þzn/(ð(ý¨QPÂ:¼0‰¹ Íkó°(Iæìe54øÈ9íoIè’Ð)�%]™8SþüMùlÑ~©F™óþ àÁˆ» à&ÿ~|áú‡£¾†K)êq$ïî.‹åèß²Åì·çžÇâ›T6ãããï§‹Ÿ«f½ˆ F4lwGÎ÷·‘1˜–’é”÷hqIBÒxÈÒºÑ×ÕØøÆ™[ë'›m¹Op!•Úa£iÍ2%üër+SSøwWÇ óUFftYÄ}.e�[  Q~Âܵ÷ÑÆ°ãšÛûŠ 7j š¼?F.ñÇ;tô÷ú6™†×\¿¦ù7ŽŽ¢ã)—iDEI22ô¾��UMBzêÂ[ju±ØÙÙŸ“Û+«ªr½¬(ËÓ×£jðÌ�¤¦¦Ž”¥“UPxY2Ý €M!8ÜÞô_p4ãMw 1ØýÙ KƾP¹,qb�*C^‚V@ÏÜZC««”§ë³Žó_ÊNÎÎÒòò¨Þûað®] !HáåäÁð`›u‹$1í£iÜiì}ìßÖ$3O5;šÜBkqÞF媜ÆåL75s ®ÈéãÇPãêá¿Ó%£{/]ZÚÚÌïoŠš¼¼¼z—–È«ªª"£ZqS-{y°Î½Åq¨%F.¤IHHèI`æ×V›’ž'<Äœú=~’H¨çõÌyÚ¶*T&Äý?~<ùñƒue÷Õ«–9 È ÉÈúŽƒÀ,ÉÚÒ SÄj—¨fÙVÇõíÏì¡Ê�ÉšâïágzÌ­ £ñ"îZ±�w§åæ^=¤!,¼ BÙÞLafÉÛÕ.¿ÑQqžEž<ë:»¿Ç2ìî¢ÈQgÚ'§,KpTÁLJ[QNWê%à�úòÞ,Àï´¿ÜÆß¿ñ‘‘¥.±ކ¥­¢µ1ƒc#‚ÞÅÅ Ât(šEósd$89™ð) (:ö•œ””Á_P†@Yi’œL)PÙ²vFë®ïŽ�ïܽT€Ú;Ÿýy}þo™Àìåîx6ôf7³Í8ÜLéö÷$²®*LíP:χ_ è>÷¼ZõàuÚ� •*™ÎXqÀ(›©ù¤ ‡Ú³uû:GŒL§Ú˜³±Ó“‘$õù9YÙLÛØétñÏž‹¡±}e3³ME%%ùã5qçûëW ¶óÄì:U›n¾Q²ûwø1ƒZ"¨`qòqäæWGÿV •••S eÖK2ŸX¨' H漂ÂTB!² ¹ ææ¡‹;”Ïž9ýþŽOs‹¼ü·¼½7UèäâÂwGúäɦçÉÝs+ì‚y ¾ØåRLnvöŒ$RKRffÜÚZçgz,ww÷¢‘ïï4t_–ÏJ•ðG:g«¢5êcSPPnßÜÞ’ûY½(!²³·u”•�M›¢4zÁ‡%›åYþ^Ô$[�K°RÑÖ§8rzÖ××ßö/Ìqž½€‡£B…ªÈøxâgÁoN=•� ¼Æ'e¹²tý<-~w‚sz! Ð åbˆ ÉÞ.ß¼aQ s+�µùZ\\àuöG>×ÇË �˜”„´yï£>€8€btÈöýL)íŒ 6˜!eËÖ]Ñ;“±$úü½Ùr4ïK_*gghdlÇ]‹?µIù,p¸$ÆÆ9+‚&&ë|›[[X’Ò|¿—ßGqw¥G_f~åy܉‹OLßåç3�Mƒ\ªp [$„1¾„ì7"Ððóãàãç[¥576vùÝÁŸ%$gu‹´_2lTš�×ׇCÏΞC^�úJII:•ª¦±\«“‡‡\¦`eËâ`çþ‰j|’d{kk¤TË| ²aûk©LAéI-ùÜáááþùy03x1ƒïjh}ýü”[Óïnnà^&ëš»8/ÂÅÅe蟂¨–Ä·Ëayšlµï§è>­�‰û�oÕ\¯'俌(x•̳·ÜÌ à>°£ ¤r´Ôë¸Ý/á{]³qÖd3‹1!~õŒ�à j¥¶ÁQìãtú·õ’\ÿè«õj—ÖgXýyà@€ã�ªm6fsD!«ªªu�Á3Û¼I%8 )ß;tA»…2Ìfð}^‹¡¼Óʼnвž³IžÛqS+l„¢î‡}¥êÜ…^gVð_xO£]eˆª  0~òΓ,V[£ ™/ãó.œ.ñ‘‘¤ºýýœNò Wû×Ö9Ã£Ü �%%h¾¾¼æ5|Õ+-5æƒÒösÍŽÔb?3›³²ú:�ò�úKá»$ÊP+;—‡kxM>nG'EžQ{Sð>¸}€ZÌÎë‹2éáÛ!´V¾'©Æ}“ðÒ ÿ¼ÊxÕìÒ{�/Ë ÙAäþ‰3êÞç{AÙ¢æ{“ùòÐß©·KÑÛó›ÈS„™<à3hxj„IåC èdå×¶‘½UÆè¯ï„dî.+{yèXR”€Ë8S¸~C‘� áÃ4ªFÙSÀÜ-à¶d¼ZŽ>FºPno½¿¿ö!O£,öÓXÆ×gŸ«#³å&ûë�\–4èlwSbº=Ìëyb:–% èœì�œWõÛ ƒZsx+RN“¡K¢¡¥E�„ÝóƒèiOâ�ëáâ"£­M|UØ FõBDÄ|³/¬¦©ièáîŠ×m_s¼ßìáö¢¦¾¾¿@.&ï ‹ÓÕ‘ˆf*wMö0ÀÛàÔa†\Ã!h¼é¼Ûשּׂ_rooôvÊ»-4üÀ 8Zñò=ûi‘rqÁèë§Ÿü¦…×uç0þ5ú42šZÙKfb:]¤¨6Ñ_Vü¦õŠEj½«Óùö‚ttt5–£©ÞÓÍŸ•�ï1-aTŠ´)# �¥ñdŸã‘yQôGLTM¹>žO‹Bë­«uÊ®uÞóC ÷Ÿ´Hú¢ûeáLbV¤£›’G'ÍL¢¥Q¶Ñe:—Š@'êZœWñÖ ¹Å!Ÿ>¥s>"l˜3CMH…]üovHà«IV) q�¨jNN1ò†*�H·š’ÀpppÜ´ïGw\ש@raC>{Ó9“å)p«_sð¢ðbgÒSL�V¢©�.�V0I¸Ô_âƒQ?G-èN$£ÜÂé9’'?—ës“Œ 2µp;‰�ä)×ý�öbK3¤KÑÒ��ó1·£:’ò "äpø¼ýNHO™&KK­U€Òª««_oô“-àáÖá…ð�WsáPÃ^f+•~å¯ÃÓ¦¦¦” õåû“~Ü7,O¯·$?–¥,¸ú6kósdѯ$ KÂ&•—�ûÀ4H œ ²Ù“-j-šG¹ âUóæþ›‘—5ß”Ðauû¥ˆvŽV“Ùÿ¾nD!©†äY ¦Ã.œÈ¤ˆç«þ…*i™jÿí A³`Ûg�!æì8q>Ù�ݗFÓ¨½ !0¨aZ=U:òsÈxM–§Ñ"«ˆ2i÷>_ëª]�œUKøÙ©ÑGÉoSU˜J jðq]ldL�TßluK›=1Zzâ‡cW¢Ãï$‘Ì+a‹G¾@�›n�Ú~äý<ŸÂ‰•õjŒ�‚”48UÍ›¶‘К$EPPÄ «K§àŸOö¦^Û(bÊ•LËTÏYb«·cP‚ÛýYitïÎT�Op|D¶9"òèÜ7� Ç«¢amuíSL¬ÿ ¿–œ•«¨Í¶ÖóÉpî@7æÐåéÖ|ð8\ÛÛ»»¾?cu{ôßþó±Ó8!ËT–˜˜xsk°3æk¦™"ƒ¹À‡­Õì¥å4œ÷³l�ËK÷ŽæLóÑTlRA;ô+*vG�Ï•{,ùÆsJ:¦?´Äœ·”((7nu�®»þ Ã8½ËíAS»¸ÔCt/<Ñ™^³ÁÖÚ.\ÓÅ—;¬'66Z]ÙÝòØš‘Ð�î&$P¯¶S]aW­ÏÚØƒ%¼7ò¦Ë@ò¯¹Ucâw‘’ÇòTº =¹¯¯/Ïǽ7zuÈøøøDU¼ÕÓñðñƒ•SM�ï9�B{�oÁ£¦V£¯&ÎxnK ò÷O6Ÿƒ¹GòÌ'¾©é/ /±ÈŸÆ²¾-Þ,J3êô™Ò­û� rd(Þ�‚»SàéÛÿ¨4co²Œ ²ëƒêuá´#�,N6z¯'«á={[ZÊR¡[1±•ƒÃÎp3ROZ õÄ«Dã ™êžÄº“««°ÆÆgBŽáÛ§;¶ßGF¤GS 㲃à`jÉøRñ»G‹Ó&]“Ý¢ÕÔÃØ¯aé×É©©ñ«íW2Eß|&Þ%žþh%J6W¢SùÉ«F}¤¸â{ËÈLA{! eXl“þQ<ú¨ÿxÏRÊQØâË,Bå„BOBÜ.ó#ö+Üž*Oˆ¬ì³—ff©.€žƒWz\]]á.RÀv Pˆ¬·wÛ¢›ªšÚp_æT…¿J¶ÈìáEYÔ°è5«"v oœûúìdåsòä+ÌWVVjÞO™¶»SB¡Otk6H€C-xEØŸðgoªÚº%é Ýb’èË0éyŠR”ÜêÅêêj­i/¢a« г³sº�#UVVÖ$8�ªÔÑZ'pÖ`ˆ´‚+‘ ¹A‰÷û#p‡�s¾Ø×¶x¼¿uÏš¢ë2–ÎÛⲃñ)9Ũ»l;�Jœ\!þYoޏÈ-� Æ÷Ÿlöóú\ò�žtveð[É®h¤ˆãß_E-nÄ„„`2àTÍs_ •שּׁ JK¿ïiQ©E¼xñ‚†“;,,L¾Ú\¿É. �>�°0YXØÍB››(å›ÛÛFV°¬ç>63“ß•{ž®­aOÖ[ãÚ­´´Î4Ú101Ñ23“ˆdãDE½RT0>‚þ*FOåªJ¦ÊÊ.Ù !ˆ"}�34£8¼8�Ç‘/PæBn µ¤š'YyyŒ¯E†÷s¼"pÚÊ—‹)–E¬ŸgÖ�ú~ª€Uäãû�&{ƒÞ)õ^Ñ~ž“3ÛÐxÊå<8½­ŠÔ¬�ý)±†åúT1‚B¹Î6= €Í#ÌÍ�bTÿi!(ˆo+[¤1¬°I¿Cj8Þ¾ÅéI¸ÙÆÁ2ø¶\‰ê?¼§3Á¢U11„"n6lyêôÔȨM÷×ÐЀ@ŒÚ>JËÈ j NfV3¿ØŸ}?]Œ]ݶ¸´Ô·¼L¤iÅZNNÎódQ ý…#2 zÂ�ÿ|ã×A9203o.evý$q–„ÚÿšÖa­ø ­�û›³¸#K ‰Éƒ EeåòËm È—ç¶á;£ióUF`¾@g»çdÉÀíP Z¨µ@Ï•LHa×)ø0]¼rI¯ÿŸ&žŸ‡*"wÜŸ:Þ‘ùøñcÙã¼e9øê×yâZ‘²õ×’’íµÎ÷õC“S…ùï×»8;:ÄP(ííìœnN‰èC^~<÷CFn`i„-ÖïB<ä6„}A÷7Ú6;?ÜÚGÒ Øœ3 ‡Z"¨©y 3oíÏfF&¨ÉžýyM›¤èiïáê$c‘)Dïô÷×s€ýÂoaõYXHv{q××_ üÔõŠ™ã ðŠ_^é²!$Ïž:ß]Rvz#-.¶¢cÙá$EJ[Y ꦆ%'&ÑxxxH+)}ûq*SÕ-F.pÃOG_è¼3âX¬Ðrô�áÝ:zzB®Ä_ñ¢H[ÚÛAá8Ë ¼]¾¡PŠÈqy}¹¼ø¦¯a£á<Ûr»¿Z§FŠ%ãÇIfz-ÝíïW~OV举,0Ð òqä} �5Ç]˜½Mö¢NÆô�Ñp¥Á‘9 ¯)įgëPz>_[ë‰Í\ þŸ^â É%ÔˤqnÆ ÁÄ{ ±ÉQ#�lAHhe°1÷=æÍ~yµÒ`Wêbàƒ?ƒ$&Ú×מ×[1éüÖñ‹QcègOt]oan"ÅÏn_Úó­h’€Ñ‡†Žõ�g·'}ú4Â9�–œ"ìjEÏ÷ /3;%E«˜wbi‰ÞÔä¸>þsÀÇËk3Ûc­h"ï»ïxÀrã`sl¤<­òÌ]Y1,$ÛÏÊzVxú¶X¥÷£rb.7GWÒT\Lìd„½Út±Î2øÀäb°x€ÇPQ¢e®4‹Ûó¿ðþÀ_¿~}{¸Ä=¯ªªÒm²#ëº?U.ÕdŽŸÚr莄>ùGË?�˨¨¨t[œh%®7�XR”ì–¢|z`ÿš×³³ãÎv'¶ozîf7ó¸í“L¾o(ºÝÍm¦ºšxöBôÌļîUòPdt´ ÏÊï?¶¢’�Ù.Ísìãg](äbgo»]ou%ùùå9ÑÇø\jZ¾Sª}NðÁÇ_¯üózç÷ïü¼<]« K=–§—MÊEG £tââ„óÕë\íû››8•Ú1±±*ôHRP•†æfýfä~žÄÑ�7e3“¯a‚§g˜o4¿†êW†ëWi”mYe‹ÿ?÷-uSå@­9xb_¡�†¦þæÄÄWCƒƒeö>z¬Ó315}Ö‘ñõ¥sXÃâÞÖczBGL)B*†Ð)¥9ó°ehÀÔ|–¯U.¥’uM B^øù9V¡}8öP›EÒçÏXqòp3¦È ’jþ¬MmLÔPrM·j¶ˆ]ÒÆXF}ÇDÀÙÎñ$äÞÏ,í—›6‡¨Fë¾ÿø1Ž�™øéSô€lÿóÌo¹fX"ù55x­­¨¸¹ô 2¼Vcº\¿fÜ%®ÂGfO«1ëg’UXW�–¦t\ Œ�…êrliWÏ¿¬œ“ªäúw[ýwKêõ„ö>ŠÝñºÝS‰·Y쎯²‚o36Ù±Šûÿ·Å؉‹²n���=UüÚ6ë…3 J‹û¿pnH¥6f¶ªN¥Ñ#á¬#ËÊ›ðk’WÓNê)êeü¯Ä:LaûN& O±�å ·Ó6|né|_zäOuIN9ýS¼¾·Á̽¶ê^ÂJê¬L»¶FPÜߟOÍò%aeí£þ´Ôh2ÉÜoàµà £]¡«N?_8Àk;Ç!+'g6ûM«ƒóHv�€ xrò�aÇX!Ãó �£ÞþB²çæDæ ¡2*?Jùû#üÀt®077§;Zií?—x,ÃRc>ˆñ{ww»ý¸ËÇ…0U±&æï€ê;Mʘ´Íí üá»^ €R*�KÍJ˜Ò¨†�³’¯_iDD¾ˆZAo|®Ž°£dƒ R}ìS„»t9†Ö:}Ø+º[O„xÿþýK.½¸¸hÖŒÂÂÂÒ ¹íïk| -…|d|`!Øí¨�[Ø„ï#Èçªe‹íNä" ÕøÝ¹�åˆS îÂáãç‡.ÙV›Ýœ2’ò½k™º`dd„4á®D6ÑlþÇ�'zz)·\ 6Ç™øßPÏUèØ,"pç�+“Ï}�)X8æ!í`ù ªŠsxxHCÃ>ô–€��ˆ6…-R­î@T‚ùg¥”$|ãÂ)RËéª÷>ÞÞ¨+«FSµï¬wéøùc2õ\ÄÃ`h¸æqãzííì�~•6ù ìlN;» uÝïŽaùŸþ½¹Ï±Ô;Ò·ä#CDü’j6Vç}o˜}ùÏà%S²Lå\Ïì, +ë_S½¶‰ÝÛÑÂëk99¥ÂË×ÊìÆ9CÕ´Àb€=eçá!„Œ*8|q`6iÓÇŸãºÄB£-î«H ¹7g$­‚NÎνYÂë)f <ï‚Öë­©åü‰ª€%7õÁy£Çì}ã=ó'‚O±9Ëuuu›ûû}~»Þ�TÖÆ,g¢|ZÝÀ„нð„4Ù/‡ÂÅópÑõ…ëŸgó‚ÅMgsÿÌM,éã��� RÁ6û¼LrYq¨Äbåbˆ{QÈÈÈHyò$AfEhÔ·8½4«ªªª¨ªÊÛ†›&'/_œMàþ¿»ÀÌ „¸ycаÙòù©—ÙŸaŃ’£užï|0")§.ã1Žnú° pÕ^tÀxQ±‘FNgj°†ÚxIKr¤’õLß6ÉPßaæ)ûŠóÕ¢Nuùáy…Af^¥”Íà»ë: ·Æï)Ô¬µÂªK òµ„vµ±Ñö|“ÿƒ·"A…aë§9Æj鯬ð}DJMR~CâfJ©nÿ8Z‚) ­ övŠÇkV*±È^§[e¥Á¥fIé7ìéÜß–K_Ir<ÝÞÜ çÙz|¸Kç³ÄØmåÁ:==MÌÈøp.õùËí@{žn^ÚG®µîëêÂBzí*Ã(`þRñìôà"Q„ ÊÎ`TO¬l#…“={{!D"±»R^ÎNÌ©º° ë {¸Z±Ss£îæÐd#úôéÓÆ‘ƒˆ^68Ô¯éé¸�Oe²¾¾bð½êÛƒêm fòúS`xá½I¨8‡aUð õêîÔ ð á#Ï8«Ñ#;Z�ê�¨õÜ {,²ªšÈí$b×–´|£^R¬›™pȬ¯'ìë#(ÿjÌ©/ gl,ô!Äx¢ozš2'çH�¬@‰¢XÉŸ™È^§l¾)ŒFùŒ’rswüsddäÖ¥³‡Gˆ|¡´¶öåÛp4ÆŠŠw5M©®­Ï(‰€¤u², .ÓÑ$&¿u_£nÔïÃb`,êY\|ÒìvÆPRŽ™AÈl˜ íSòÛÍ�ë¯ÅÅZ-âÂ&/Vÿü48™�,‡ß‚›ÝÜÍUIøË—@ÐMløÀïôpç‚uù¡Å÷†-8m«æ^`Ðì@*é,õ’U‰ì{Žê­ö)•‘Gæ7%%¶5¯]q¸£ñ8ŠM�|D2×Kb2ªZÂßj´×i¬ø ²Ó³áêWŒŽ‰© ¡Áç}uµ=¬V`ÔÞ¤Ûê‚]½M¢ö%ÅסjÇïÿoS>¢YÄ¡TñžË.ô[LALMA2"ÂŒ³ùï,nUýŸ ¯¬¯¤sþ�-äåÒú9½¶ÃF´2h&¬äÂ1òXCgÏ#WÃe¨oȰ6©†ŒÈW{kK›X&uµöPzæfñÑp¹è$MZD‡Ô$Þ5¼Óô �‰¾æ¶U�þäI~¯^ƒÝ"ÁÙŸŸ„†Uy4„¼¼i¥å44´ ãND�*ÃÁϬӱuÈ$OŸÂ——> ¤Îºn}¡ÞQ¼_éPNãÚÜN�Œ^nlnV�•ENãλÎïeÑ3qqá£óì錦®ÀÀÄ„îMÑž†GØIÊÊÊæ�ÛrrzÚ_ÙèAxö ÒÕÕ_ºã±�«P¢^?9ql{üÓ·{Ï(ç¹=§Lù2ÿ͘‰ž8 ¡“ŸBë¤Qgû]²tÒŠŠè ~ŒûYYZ¢8::202fˆur ›<„ååÑN�?· n–ž;öÓ÷w|ïlªšl¯�º““ ¿|°ûLÒóTOOÏÉåÛýíì¡™°?÷%0úS»^^^‘Q^ïÏÙØ4踸¨nnnöfÊ §®¶��#jjNø†müý%7Ax¢I3 ee ãWúÌȦD¾G6dšÅÉZö4΃E:„Ç„GAA!Ôgjm=\IGGÀ{…ÊÊ”�Oø¶·¶Ò±³S,. ;¹¸„¦¤Ä6£I "þH¤íháR»-=dµPÁîxãÏŸ«Œ}G2Û™íY8/2TK"ä YS4'——Ëg®4µo¶ ê©R\ÄŽDö»ÊµÎûéy¥ŠQeo™]‡’Î5ôærsüÖm…Pq|n/zA,R®l0‹þÔúœ�€×Ð0+kr§·ÄŸ ™?ÄvÞÁôÇ'KRÑK$dTTKûj¬â}ͯ²VVL¾~d~ŽPrø+$Ú^;ÿVdñ¾ØÊÏU04££?>ý–«].„ܲwß®˜˜zõS©®uSSe_è J,µ$>@̀ϱ_dM�Óù¿åLªµý*”•/Àk£ZOœÆ <•:9j?.x�ïÞ€ï‘|ÿ®± �ßa]ó¿³?JIL œ-×Ùn^÷/?‡"h¶=ÎÍÍ­”ëªè¸ŒÌÜÄÜl„àÖø?Üj ç Ú“Ã{é>þÕXXZêÇqww_9Tq£2)ŸíB�.QS�sc{;„ŒßJªLsk,ƒŸ)¡zÖŒÞ�ŒŒÜ3?Oˆâ¥º:~<•8Fœ|H2³Ú~xDPø¨^ˆˆlýþ‘lªGœ)ø'M£¶�á¹¹ÜÆ2âNNœ¤Q÷öîÞ¯u.x‡É ¨©á.,S‹‹‹�øõHøÝr<Åðñôñ9©Ö.¡z{P8Y¢Æ�ŒÜÐ3o¢¦àâ‚¡š+ñ6¼·!,O6 ød‘윘šÎNʯQS@qp.ñ¢HÞo\¼°N›¿ü¿ 3vÿ5Ì¿,ˆˆÕd{^Wç¢$‰!±6ñó'Šžžð6-¼(Ùšº:>—nV÷aYºðæfc �++n#T½Ÿ.NQÿnâÞ´©ïëÛ¡ô3:“Íqÿ6 ý(h\<ó¼ÙÁ?9?LH’�%ÕþãOq…R [©ýíå¿�ôôˆ¯3b Í-óµÌ“íÜ…~1ØŸ$$$Šª¸EE)€—ë—8¹t0}Çó'D‘aëèn� ÁÛ³"–,�s“æÓ+Þ¥«c.œÉÌ–õåó}´’WÓ�piü˜fíwf÷Kv¡Ì©Ôx©yƒh¶uÐËh»ß¬Ø1çQ_|1‡,ó6þnPÚkÜÏÿÀ ,-åŸ|j¹•öã�n“]Æ•²ÖÖVϳŸBœæq&�Æ µÃtttˆaÁòï±á;.;J ¦Ãæv? ù2­ŠÆäå=ÎDZ™_Kã¢öó´xNé:fh·Ö�R®S,c³£ljjzáqôLâv�¾†³V7–öex é8Í›1 ¹¡Fíî› ×]C‡©¡�wÇÛg’Ào¡‹Ö¹PãU‹ƒ¹J§�e�|ÙÙÙ9íŒ�e ŸAo»m¸ÈŒ^©§äæ&dee)QSKÀÀÙœŸôGc3Aœ [r*|ƒ¿o^û€ Bêç)Ò~~,¸ªxÉÇ,a×p†M€¿@¦þËͼ~}ՍߨáµïÍ€7™TTT8]ìkuÜüÉ4”Šn6K¶_ú·³Ã#" »ZóÁ…B�ßévûÓgegŸŒrÃBââ°¹ ["_%£»tw9mÒ�~dÏæÙDÂzö"ð6gK-™…Ãñvòx±žÚb¸kðnÈy„&ÛàïÔýKK¨ÍŽëCçMR¢§ÑVq­Ç³Ï‰ 1ML¨á[O@ “’®&¿i)› D)+#•'/hír|""ì죎.rËæT½ëÝøKÍŽØáøÒeštllðhóZt ã�céV70[ÃŒdcããAii eP-MÍЬ,1Uä’"ËÏJŠ:9ÿ߆+.VóWo§7ÿýûÁÛ6è*8Ø}inÎ7`΋ˆ”ù’.© /âq$«ÕRÕZ eCíê’x¿*‚G—D*''×â#ô…_*}‰kåÄŸä`Î~ $×?˜BÈÁ|¹ ¶±·´A†+m»7„t,Š 89îvóñ ÎsNû3zwÅÖöAõ Ó‰õ3Shx77àeQ4ü}¦ OŽÖ¾/WfmLl v÷õJåúëG††ÊÚàõ™³àÑØ\/ìD@ê”ò²4½Dƒñ|«!kÂjÈsâ·NÊœ8?«?®ú‹jJWV`•#µ`A2[¾xè µgñ Jzø¹ý€8íŽ;ï]ˆH[Â_+ññx-;í»q£MÊ ñÝH ;´W2ݰ+±TÞ‡Êk1dº*òhÛ±!"+Ÿ#{wÙ_†PŽW©}Ý_Ø>ެÏ.š�ȸá[¾ÑI·l¹;¾ºÐêêÑøL”nË¡¨iÞÍaí«lÿw.“æ·{�µKñŸPEÁ\:!=tFÕÜó�<"þë’d¤.ÑÛwŸzÚ p!áE�} )Uëÿ�±·øÜÞïq n¯($"ôÌr5ÛA'$x½g¢´ .®o“ñ˜Jc;;Åý¯ƒLî翌1$™Õø>66°Ùñ¡a_e¨H<0½¼þÎxj…W‡¨2Z�ð`ß]�T2stä^‘-™vÕ$ø¯K/Š8ðtPR—FÍ껑ŸkÙXàþ`Š>Ûu©øÖR«ƒüÎ3ÊÀÖDzÇîë¾Ë,Ù¶’ØŠé`#šîÛ}vO//è’Çz—ñ:ª[~ü>È�cé¼/7Äå|®Æßl?ù=žm`Ùu´†­ ¢›g±¸¸˜axB£BKóS‰æƒjƽ£6K8oôïë9b|;ÿZ/2z–çÓ‘90K IÌ*,¯k+ú-‘£’è…Îfͨ®Ãò$²�ðF7=,ÙÌÁÄà!ë\¿!ðï¾hânûOã~雃˜~ruE¢É$ñ(ˆ�Y_µyÕ ¾%Y𭦱‘¨#ö©¼#04,F øi+S¢+ áYщ©íGT­4�è²™ÆîG½&OÈJ»‡öIÈL_¦ã#Ò‰'åªbÌñפ¤¤` õ¨oÒ\i˜ÆÑêÕó´"·bÏ�9AÄa�|Kö`!…É\ÿÊù�"?››Ú}Êáà}ÃîÒ%Î÷¤˜ü×jHÏšÙ ⋈›¶â5ØÙEo¨G‰$t›¤ÊÆH²nº­'rH%²›î®Ž_V7ßg�놆K5YÉhìµCdü„Q™€Âçg…ÛÁ“³¤ï:â�{IZ ¹^ä3üï‹d4@!†qŒöÅo’xçpš½4@iˆ‚B/ã¾jiwât…[ÔÊÕ�•íD¤¶;ÄBVºŒ�N«ñ¤ï˜™™ç3üõÓöõ©3„sLœ`�(6-ä¡0üÔ™æ1½¶ÿépü‚cy{_oºÉÑ“üÑDÙ`ÀDrâ†_˜â±C²hû�>,!™ö>ñ§S©�~W� t¢/X"@Îhn^…ÎÆïybz¾?«ü¸�ÿefn®Rñß©B4Tœg,¥**c–ï”l:È{FF¢Sè¿ag¨ ²³³K[6èÇm 7ÁKZZ³þq÷béƒàSáE­< ‰™Õ ¦Úɽl.ÿø á³é쀠�&ørAɉµõÒ2: iaÏìj`n5†þ]¦)È|Þê•ð†×à­ÑçäŒTâmªØ:H“‡’f^~±ŒÜxÉÖ(²K ÓBLóß´¢‹Ì¨g†fóW,–¦Ý66ðµës#ë›™&æwˆxaæ)hJ0õWÚAM¶:¿óuãvn,¦  �1žÐJ« ˬF¤Þøqo�^!þ©æŸÞ�QLÝŸº/ÙR4^âÍFk²eYô™Bòþ§‘5•á;@9& Õ’ö§=jmÏú‘ÚÖ£\P1:ÒýM1bGh"²ç«“; H ïkÚOÇ0®“:RÙ’6Nw²žß¿Yž[A×L8ˆ·ïŽ»… srrÜŠ«çŠÚæ¬edd�+yMÞ$F}þì3ã4‹N6ùE¦”2ºŸ?ý·tL5zßÖ!¤p‡Tµû€+ŸY÷*+ Yºÿ–õžñ¥ ª¨¤;ñï''2yDîþ‡X>ÂHâ£[ßS7+H4#TH~³T²=…†…ÃËL}ò±P!AØOÀ‘£Z—dò�·‡48{z¤ø; \êýZ„ Ü'*2ÌàÉÆÑ$)Ž�#Òü7óÉ7Å×Üþë*t×éùµ»ÐéAË,|Ó>ñ×ÖôúwŸ�‹––¢S¶ŸÕÞ)¨ÞÃüÿ:ž«Û“ø>Æà>Œ&Àv×�ˆ¯f²ÒÒ¢ÏÏ#:®¬IQ§‹_—öDGG—’• •óø“1ý¾¯ëñÖÖn½+»Ù6Àfßï÷NQÿÚZ yut™²™˜ìCÏø8Ž#ìwŽw'1G9Zd)ô¿!"H²áÁL2Çšj\E‚|Sñ?6š‰&%¦çz ”=Gm¬ëËÓÖŽ�Ù´©@-k t„�/r+þã—tå±[ìQò\ý8°SfA£�-YÇœâËS1¢iúÃ.µ+ü±„zs˜ò¯BܘãRqŸË¯S6Ò öËÄH¨XxË£¢h¹¿ ܘâÈØR¾ÔÒ~!�‡ß@ŽAä7A×Ö½2åM/¹k½Ê•Ær†7ì©M—WC&kQi×'õb7ÿý&]EÝöY4õf~tó¦l’§بÜNÊò&èÿW�’{vÞYÌòÆ #†fkF !�#pvnFIÉàµ�/š}÷/æ¸hÄùý‘Ôâm¯èò¢ó ªÎųU•H=�ß)—ó€Ö­îëNy…>ïzÕ$�Ä G5Xøcæ°ùQk·³È#†¹Ë–úW­ÅrYU1Õa“Þ,×€|X·ïÁ˜£"ûX�J3C$©Ù¦RÜSzm–ÇÜPQ]]‡¹.ñÿÓ €.%#CúQä± �ëÞZ§¡iþP*?ÿ4Ý]i}=¿±ç& y±¼3LÔç’£ú¡QŸˆ€°ªT]Ô¦ƒMŠ)Yš)Yý%^ãlƦ«[�ãÜ›sÀüT—Nƒús°�m”<6—ˆµÏá$Qº_Õï`»È…8lüö×G¢ºIq—saÁµ’D.ÑžQs¿d æŽèK·çµ.i®ŽÚ�õí˜ýŽ;¯÷tYdîLíÝlØ‚­`QKòïŸ(¡*Ây€Ø‘‡ö1¥A,_4'Y vÜløu¸„«,xtÐRxsaõOñ`@Ô±äû¨²œ61¬Þ”³±±Á& ¡Ì&‰Î€)Ùìô÷UÎÏ µ£ëÑÑo㟞þ*Ûd‹’¥ÃòOëÑ‚3 S?H¦z>ÈòbÆ Î³ê£·Ýº4: �z©aÆ­ 8Vˆ';â»Xž¯ÂÈÈ5õYµs0*q:»åžŠð‹€ðÍfÔ51<Œ–ë[6x11!q‡%!!!ð†ói¸|_ô !‰G2yÕ§………7ôxnuÜŸH ÿ=Æ‰Ý ^ýój=EèkiŽ¡Dö�0F&ÍL=–vÎ�$ÃÎ&Œƒc)Ðò"2a7÷~bm�ê¸ãL5e~;Ó±+¨¹¹yð£ ð'¾ó2œY€]ž¸(œï‰üD”xþˆÐ³¾ÞݧË՟ˆXCZQYÙËsŸ†ÎG a!£�bæÛ5š$ÔäÞ8e ƒþBÕô[¾„I óK®­jlÝÔÞ¡QJgþ.G¨£'îèE¥6Ì">“Ä –§ZƸa2AðÇʬƒZ#eé*Œ;Ûô«’KõŸQRBç  —�)() <7Œ‚r#SPPHÇÐ|»3¶··çÜPÈ S-) §ÿ¯°‘Nˆ`+Ç2 ±“¢2a ùÿ*0y{¸Ò®½S{‹Ù)�™Új¥ œ~¦d¨�--W–¤¨„̪Mm@Z1ë­~~»„÷ß^«YÔÇŸü`ÎE'(zŠjºvÍ-V�V€,ÏXÍ òß,·oQH^ U‰Ë³Š›rëÊ�4ã�æŒjÖ Ò ïý5=±=#~æ8ò¾‘íkñÚv' “»#�˜S_³ýšJÔÝ®²ñÇB­gºô 3^§Ï՟ΗÏyŒÜì2ÃÝ)))y_m‘Õ ¤_½sádÁ üϾz¬Ï"“JónòùiäFƒ²»õd€^RœTë§ù.g=Õt§<2˜ZD7�†:¾Aë?3àâ‡Õ¿’L/6“í¼Vc¤ð—2fÐÈ܃ §¡1ÿnNާc\ú*”iF£þ`�ì¿ „1ØàñóVª÷T³$!s5¼ý=Æ_û™¹X™®ÞÚ2.�\>{ôø�òõ÷¢–Ì}}ÖTá6äɓ͎+7w÷!eîNMú ‚˜Axß �Qæ@EEEô¹*#eþ,·Ïi&þdñññˆ·þ ð1 u+2r•ÄËë2¸´N:¢�AàŠQp9ð£ ÛºòGrVaî ]'‘Ã:Df8ÍÜø„JòêF8HSä‰Ì1ªéÜZþ‚�(:9ç[a(ìÅJþœ+ä½€œ¢²O�LhÝ·^ÒÏs}¹ìX{´1Іƒ˜Éaw•ùan¤¶¶¶—K¶Wj¯¢Âå }­–•÷®I¾ØgÓØ„›!?Hü½Ð?BËú‹7 K!‚1®³cþë]ùÿÈûúiœ¼’‘<’ýâ,­ì$ÜBþ¯M-•U‹ùhcüµÕ•ò‚'% ¹‘ê²-jˆ�Û7aZZDoq˜Ý÷í¡¸òoœg/ð677ÓÅ}ïYR´è—~»3©¤.Ö[¿m‚y÷µ8o×¼ûA°†HÃÊJGÞèÇ´¬´wÍ|Ð÷«¶¶VÚÌ,Ÿ†>P—£èäA3ìš›µi–�‚¦�²C�¸KP8±�© ‡ �Õ»N//Ð2q˜/ýÏ'ŒÆ! 26a>L»¤¬òž~,Ù"/šfº^“'Љ”J?$HÀÕúå¿e¸SkÔÖ06ñ¿Ay[¢:X ÿ/ì¾–”dºqèèètœ�‹^ÖO|›­,õ‚o–ÎUlƒÏŽážz[Ȇäc‘ëW ¥PË·+jÏÉý_}áƒÀ9\Ö>2àh��¦Ö=ÿ”%o¼ “øˆcCÛ|±ÖcÞÞÈÎÎùSHeÃ�,UÉ%ã}ûrí®œŽxÊÌ××·Æ´ÞT=‘#~½W˜€GJŠ^«kè@V« Œ“ïù”|5sk{Þ±M»ø &=óªëÃÜœ<�üí\ (mÁ‰]Jÿñ1L�60¢˜¾µùI/èµ_¥äëפÑSåÏ4Qð4]ÍæÚºº¾DÚ—<@Ö!Rff´æææ‰YY±cc*�ð¯aÀJ‚èUÒ8šK+Ÿ@ÇÝ}OàÝù÷±rþ ñ‹7õÚšô„&Å2„å†ZÝ:qtçûÏŸDï�iþÇX—šBb¬•îÓÎKŸ#I¹œ­Ý]­R`“ Ô쩾³°ÉË£AFA™Šô‚B¡ÒÍ«NÆÈÖ,6õø!èd0¾Œ \Šà†sÞw|d36ÏÕ�ã±þçêú·º¿’ôÔ3<ÚÚ“õlé©uÅR‰ñfS/Áûe6>ïàcÞ'—ÑA‚ü"šD>ÁІ ßA�Ž~÷=Ò£K2ˆÄŸQ‹±†$*^ /”ÎÊ}‹õ±Z’ÿ~MÂVŒÞG€gÞÀWA_Q"ÿ_I~iFÝÊÝájq—�„¶ÀàŠ888̼Š Ë̤€¿Æ`rò�ñ3ãjXLÌ1ÿ”  †”" Án,ª´Q¦––()Jƒ¶�ƘI‘}†ÿ±Dñ,($Âùß>ŠØ�ÁQ—ãÖP/OCÈ ‡ÈÂÿbï½£šjßvÁº•^Ai"Hi‚Ò{¤ƒ€R¥K'A)J•Ž ]z¤wADzIé( Mz‡$ûìàûΚ™?ÎZóûfͬ³ÎçÂã~ÊÝ®ûºöNvº¶ž”}�³òåh0kšŠûÚª1ÐÉ:…Ey«ÔmJùßÉ©d_G¬ÔèI¼¯_*aÀÁ¡°3ë‰*ÐÂméí;~àºeöæàØŒ§¤ö ½žÎ#––Èqî�7¯C£æÛƒŸë ñ«ßðKIá¾o:¤ ïdsç<ÐÎVp_Ñ„:�éfóÉÉ*'ç÷ ÒR·æÔ×Xø½€Ë²&ÂÀâââöd’õ�¹�$CH“ɹ5 '‡©¶ƒûãu0VÞ Ø–ãË R¨¸ÖreÓ¾"‘Ôibí¸›}DE&û8—°ÖŒ¸ÝÑÅ(ê’FÊwØÃ;ìÔÜ…óÙê=ù}� Í�_Çv{G6dñ«„æº5_±Ö‰þhF£��ïL­ðSröu§¦æ*z—™H)GJ@&„<ëš2Ê6Èë ã•ÁW0®N¢¶GÏÑùUWºÜüg{pÊY0‚dÑ ÷ºA¢" øíõ‹Htº�?I$ìà?Ø¿Åã€Ö9„g?`pfÜ£áíîŸOÛ�ïü*ý¿Þ¤Ex ö..޼ºáÍxKäyssÖõludƒ‡ýñÙÙÞ¤~éÙ/”›É®/@âmÏT>†õ6í[Y¼Šd³—pËïÎ#¤Ž!)WÛq:îsž¶Õ–¶°*ŸAY‹íù/MGj*á…µ¬Vhä)-³ËtD™9Dóô.¾„å/°;�ºL^¹zU[@OÎ$T`øMѤƒêQ>½â`åÜWÚ…AÐô‹^1¯‹%Û›"E³[øç” Ü%È…5¨!Ò‡ ­WCµ±P]®zK&úšd�ÕÁ«tIßÝ¢ôJ²fSâEãòIéïÇ�ð‚ª·‡ Õ°4šzÔ3rɆŒƒ„~½ñÅ÷ªzN{w/ ‡¾kQõþÛ&o-‚Ÿ9°öì¬ââĘ[—Þ5x¬­¥É6ãÔXš¸çïêr½â²$o (^¨khû³¹ú.55…ÒÅ}PäÓ§¡�Ç"U­tÁÁÁßûúdíì¸]Ýܲ�Ã%·-´“Æÿ…�¼m‡·à¯˜?bë °¿>-vK«|e®^‘¡¨}QÄ™To%„gr††L¤¤¤Üìyû>’Ð/ïC¦§�I)Yûäß@B:öËÛóIÛðÌ]~ÍM‰·l¾åÆ~+€±¦“_Ãß=àµ*A1ã ½À8Æ,WÛ¥½•»S¬·òÃué\m­ç·dþú ¯ »�•‹¸ˆ™šîH~0~yú:ULËZ§T¯k/Ë“°6ˆ 3‹¿üxM ÅÓྊ'..žn™ûÀؾþþ.//C²ç³áÌ�‡³€ `LF†sÊå wŸèHaÿž×ùD²ãÄþx!Òt|¸Âà*CHp\Õ že±1ZÊÛL†ÑÞîÀ™Pßt¸1Ó(òQ›ë@9Ûôz\÷6zo�Œ�Ïûà÷dpÜmtsu}—¤ï¶‘�”úç�ÊÀ)ˆ¡æ‰Þ¥Dö›ç‰£›Jøþ^6|ÜwQ«¥…i¨ö "ð½�µw`”¾.˜‚“·¦J(• ­Œ& È8|h„f£¡£;¦¥¥�¡D\ú?jõuÛÜ^ð·Y´EZÇüÏ'>T*_?1ÌòR ¼­!íĽ œ‚8î_nL&œ²iñQŽÔ®'Áà1ý/L\FFtvv¶acYvö RbÖ¢N@úÚß^‚¹…˜T=“¢Tkè)ÍølBò²þè‡à68ѸÇÚ¸&‚埧K�Ê Ä•ò?;Ŧ13ÈËaÞ¦m�3Ú—–ë ÷<1@à6(Ú?‹{¿säö�±…ý¦bØ ×+ÖðW0|¶dAì»w³�Ns-½ðéMu+ÑåÃæOïE†±^/<3E36ÏÒ0¿æÆÉ5` WýÄ·¯|ü<ø UœJxb(qŽS�QÌ ^�ÏÄm@ò›ÎÔ}Ÿ¹ÿ #"">ñ³â] ~q»t`Jç/);чŽÁ€Œ­ÕÍÑàõ‹ yÇGÑ¥¾š‡@Û‘$áÔ¸ßúXiX×ÂtB�R·•vê=GR÷?CC1)?^ÞQkŇÙ¶l飶ϲ²®×�|e ¾PGJsÕ€šy" ¥w:án~î®_M“™µT¹LL)Ëî¶ŒöÖ6åZ§½fçÄFGâqú=Ü£šW²Ãë®k!_©ã;(Y ,’šãÇH­\^�‰‰©,;dI\o ½ËKà;24/ˆÊe “´ÿÝœ;øÃ#,ë–ÏyŒA¾¸[ÐÓ6—c{|§búØÈ2�­³ÍÈã¢A¼/á!~�ªç’º]|vsÜã¹õp« R>’–V9Sà.ä"R.lÈá°·XÞ:Èbäùuß.ü‹¾Um§vó²”TÅ´Êc�o‚kdµ!º2}Ãùs`Q6¥“!’r‘þ)"Œ¸S7�·)�ªâÏ1^ãH´¯FFEé-ÆiÍ ‹‰õPD!Ÿ^—€¢ÚãV+ì�´©ýží1}{w÷Ê念~²"%~†*TþLßÙÔONè«#£ î¯;¨¬/Ð7ßéŽsôVþ¶'W"§yÜ@K TÕÖ‡C™à¸:’›"ât Ö¦ô$ÑãÛ ß{ú©¸ŠŠ»!Ž Gu¶DgÕFOˆˆç�‘è �Ô6G¿Úk×h¢y–m£$oÌU瞨ùWaää®ê÷¨¤?ÿàê˜xœ¯óó¦ÕåO¶­w à²Þ]C¢ÅÑÜŒž�ød9Ñ x�jœ³ï8L×Ýryý:t�¸çpÏhX.fÙ»gÉuzþ~i¦xàñhiN}¼í2- Ÿ Ûøç¦�Ó¯Î�_sWÀmá.¦á §|5_kUšÑîwž~;['aä·ürdƦR=æ=õ×Òôï Û js="Åîü‘1œ-Ñ¿XµÒ ²"IZ.ÿüê=µä0©úìÉ„Ã'‹Š‡ú ÄE3¿ûSzͰÒ £Ö=ñ¢'· –ó@b €ü Öùtzñ~.¤Q99¹›v¶­nEÒJ½à|'üëà*é™-£�Y¥· ›~,o�o•ƒi¹S,y·°U�vŽp¬öá3±¢DɻǬ?ÃíêžÍÑJÍ:-Iß"s ³ô«ÿL›q�¹ëV’f¬éªvŒ{lùÊ€g³f,¶ÞÔX¿÷¼ž,@˜Ykû®è4o ñÞž™7 €WGYéE@û>ÈZôsUì&p™ ACnDT²é�â�£ÉW˜ùÐ]áp£ØåÞšÝXǸb%ywuzìÑ´X¿@pzµwµ;‰iäõñÆ9ž†/Wý™>³’mj—ÅèÀú)¼Mü‡aÍuåjp?ìÔä޳χÌväMÌsA\@;�œ´0™à {ÂZñäÞmk`þ¬åjWá±ÛË«¡€f;€ð…êQ€x~7[pïy=æŠÙ±Ó ¢½5mÛ³c44©µÄ�^và šåèZeר8Nèó;Ô,^<%~—;ˆ"ß‹ç“m'‚¼#Éâøô«\¨ƒiæ¯Ö?ö¯il÷G½RÃÎæ7 ¸·v.ì ü jíUA©8»É§Ñ€‹X�c/�Áð͆yr£5Ç�? ÑFÇN»ƒÔmJí°)æQQø¾Æäv¾¸ \ƒxsu .ïí~Ë&T§&øÐZ¦ÑâTà¦.o¤w‘ˆÛÛB&=ô˺#„"P_žÈ. ¸¿^ò�ÙhÍb_ð @°µ°‡Ÿ¨¡€Xscs‘t`ž_3ÇAhœÔÒ ÚÉYa{à‡âêç ‹Én­IrÂsšg”Çù^DŸŽ‘N«MSéšG‘Äî‹IÌ>'JÌSwçÙ¶5ý.!ÏÙ¾´57r³[Üzî õèñ7„Çú˜ }vY‰Iã=sJ“3àÕ>hÇDÚÿÉ1ž0'€¶°Ä…õ9ɺ¢ ,þüÐÁj`˜•")mMUSXR•¾)uG¯õ´b£ôŒ÷Šv]Ì Œz&b-™Yˆ®ÖùÃWF¼AŒoÃÚhd·/ìç5SÌ =D°yÔXWU úƒ�ܬE0)W}v¨Â~¬ä§º¬ÉŸÁ|$¾«‰B¢µþèÉ0¨M´¨7^å’÷Àï�ÖÎÂZ²`4*d³ÕÒ¶‹VÞ óî§ ýRìP¿Nîìß-LóùÒú¬œ}MÙXo£T�$X-ŸÈ÷>ÝÊ7vI)„¯ –iZž ú¯JŠ­‡‰/=û5òÝë‹SÚbǘKôžH9ÃÈz(U>'Ó½wgŠ¡[Ä·ÇO  R3o~sã+6ðÑ4³Â3ø�(2!3¥E_÷%Àz÷•V€X? p²Æ°ê}ÔgÔpä²W´äö³Êóû?™=FdûøÑZ˜Ò¥m ¯Ç¤�˜}«¡óã{ÙÝ1€¨Q�#Åv£Q­ÍÇÚíÙaÏ?ÖÇJ4û²I¿@Ïù«E‘x Ž ¸ÂÁî=Oà2jm´¸žì! ùªEW &ÍuÖjmðØmst¿9{È‹ÍÓ_f6.©8Þ”éú}¨Ac4óñPdÚ‰¢ÅXrœ|WÜUåz¡ÒOYÈ~CxÒgoþì`hc‹—ˆá‚µÅ_ndòYJÚ?ìOáNâj'ÊoùD}j¿Q#óš |ÍúŠþõKDŸ›žv’nÊ¢ëuW–H“}#ÜPIøˆÂd@5[Ÿd»®SˆÁîûïl�µ0i¯¶3` u€é ß%E•ÔÜ<ÅŸs7Å,¸Û*¤ò÷˜Å&[õ=�†6­Žc­éC0¾!‹�]Údb7�°6oï™ÍÜ¿÷ m,Åf+½•ŽgA ,€5Îêòø…!È(ÌN?ÎíÄúJ°ÅÑ SŸB3»·t�QD\/”k jYêUË›*äË[˜ùû¿í#ßXyÅ.©Ôø¢Ë€¾÷-V)±wíìæ·}…à«È®–‰gJ ’yf£œð?sSL®±sÎ=¿Ì¸×Iæ„@¨U>Vb@Í �L&q+‰žÂ…˜J�V¦HWË›—§jð‰Ud.v‚ñsŸ9¦Äø¶’ÃÄʬκäaªA°Ãã¹3·gõ0v9á”­Lkƒ©S“°‚w1É\u4íé7¯,©“}xOë{jÓŸ­…³')­î="ÊaJLÌ—¦<4¼~œ‹M¹1`Ÿ+G HK6¸Û*�i³s)€Ñ] ÊÁñŸÝñúpåJ AD>€6U:°e˥șÂx ¾[¾ÃbwtðšvZó%–úô0ðh?½PŸÔøh ¹a4£¼}NrýÇ5)j¹VKåDƒ'bm{l¾±¡Ê.´y0669ô£0sÅ;WRé¾}õ–ãŒ-�>¿dŒ0ÐÐØXO¦•DÇgj<¥}Ï0˜3\ Š,ùñxX iE(_ø ¡:týäßÁúŸ¬Eß+v´Y™Ñò°þ7®2ô¬ ‰ú,ÛÄM¿YL‰u{´¿1ûaç:ܪu\î¤#{8ÃWùØkrîgäÿ5¼Åˆn ­›ñ0è½DÅ=~�qÁýO,»JL+éÕìY½±aYjqÊ·>Ä–eÞÊf?�QäY¬I¿»¤Ðv´éA1ë>3¦ª)Òbp¹i§Àxn‘òWÈ™Ãå•rò5Û®mâF�̺Ôx±ù~B5M: ÎÓ©J.åüV¬âXÿÈÓ® òwÊFù¼ØÞ ódŽÄ@º™Ôm+ÙñØÍtéØmUy#Ü¡ƒ?^õV}¡Ö.0ÿ„Š�²6¨q‚ñÑvGŸ÷óJ¬F­òif& Áß*7½«Ei£—HeÐø-�"-‘x·¤Ø&J£& �hjhúиפc•"v®ýE„tõÅÑ�XÙ­2ÀÜá¶WÅ#áCû{ÕS¸_sëd�ÈÄÄĽù!®›m ÌŠ€HOs$u^û5wäðÕ(a�ƒŒ¶^P˜$™ŠHõqIº©ãš—ä~rssën�ˆ•¨¥¶|×y‰BOô› L]„@hÈM>l>ºþ6Lú^*©»±Óè+B2œÿéx Ù ª8,.-w=óòò¸kx{µ--oŠõ0ü8¥¢MR�u‡ÁO´³?†„ŠE•£ö8ÞSñ_#HÆÏ™¼ ÖSåÝœ/wìà“ˆAï\RŠ *ö¸ÂÂ%�É8fØÿ8.0Rà‰¶ ªú˜e “~Ù‡ú9¼•G¡LúSBCcCƒUÊ-‘ëk5À“*- "`i—‚êêê§µ¹¹Áõ §= ®N¤E]qô`‹_îGæï=.ê ÞkjxQTCÓµ*,ï÷‚TË5‡2ºQŽ ¹™€Äj¶Æ¦ækÌX¸µøQ*‚. ä |]Ù‚‚£‘ þµ«dZÍq’nÀú¤¢ G5 Hw-¹ŽÒñ­ BŽ–°°pÿiý=aásÌáü‚�ó lˆVÉ¿�åÀú™›ŸF½LCS×Q?l‹çØeTØRšËºÄÀ3Å#1,EÛçP»m†Œ»m&¾n¬®<Ã>ú)µ¨|¦=òå.— ÇÚ—øaâæCXÛÀlˆÎ ˜�Þ»M lllLûØ?<“öˆ’ßcç~ü¸_܈‡»ë!ÖÒ�¯¶?4 ˜ ±ãë©Fñb#%î>³…R1 ,µ<ˆ˜îõWèÚƒõçvžÈ­”‹æÇÁíR¿h°ö�Ú¯7VdXüé�ùB+ŠÄÚ#-×mù¥– [=§™°v)é[–k'_BéÀX¾Æ«‘Lû¶K蓽z|­ñW‡�v¤À³J°¶¦¦‡Ÿ<¹Ê(Ù%{CK yY3íUÀ y&ó+ec|î(!…™%¬V9·¢=>Ii w¸ÿ±¬Rv½~DùLáÔlÌÒÉO¶Üæµé]Á¸JÏ~WÖWûsrb¦ÜVçˆuy‚£9&ÎO¿2rçíË àWö‰–ÅäÝÙ·J-×;È{ÇurÔz¼~3.ƒ½úÞx€<pÛ²˜´VWU}¯¼Ñ÷ý àÎ"‹ Ó_\‹¡òçÀ¢�dzÁç²[W�³g|­ò'Þwé\'`¸„wÛW–ç�oUûì-¹Ü Œno%z­—T›n ]?Îî<…go{æÓ`k´Ž›â"®{Þ¿ÎgÝýÉ¿;¦²&+4ÔPÝÍ’º©Y°þ@Þ—2gG‘ &VÀÈÈÈ0ÙPyÇ®7X‹òõV¢FFÚ?Àû¼€¶s˜1yV8Ø`—�©‘lZ¾ãµ·eºg¯ÆÝ¿™Ï2_ÂSç\ÌyÙš­×Ê&#@Î%¾?çu±J¸'¦Û¼M­3~ëmï'öŽr#ΙlØ8¹.úl)ªJ?é»ÛšSøm:$Ja�lw1“Êìn~ra!¡‚·£Ø‡¸êâ•`aàÛ¹/ƒ&Ãe„å‘ÊN"Ýüú6¦gëU}­R¦u¾b¶×á~÷3=%—àG­JéôZ‡iϺ…¬êZÈ{‡¦Ôe£½ÔžßÁ!Íb8`7D©úï{|ÒyÆû“ªŽak’€óL“ß\ññÞû ¬CHJjª0ëÇ×më(h—’)Œßƒå¿? ƒ÷ü1øbRî¾?Ï+µ'“ØnÂ]5ÛEóðYoßj‰Á‹Øêoñ£½¦ç4¦gùÄ­ŸîÀÝt(s�Džõô2í „|«ÌÛtþ͹Åå¼c–(0sÐÿ¨©¹÷BU-êö›«´t_TîÆN؃ åU0Ѻ°¾Þ@MM ÒÌ=VУëÕåªòÏŽFÃ6º;^ÆÃ6ñÙ,´T�ÞæÒK 3ˆQñ þk04éÖþŸf1py‹Óÿ£–St©ÎY¬MäÇWþ_ÞÀÔýz¤<ØÅPïÒ€}_¾¿™ÆÃ˜G>•ôS1�ÃO!´«÷$³n08p˜·LMM­?Óo¿}KŸ&m}ïà§ ï¼µ¼qÖÂ7î±Ô°¹xI©ƒßÊ ñvoö|W©gë‰&ªr»óNù|'ø5ÃL,_.¦*§4úˆK„f4K²x´¦D�—7T½õþüÛ‡Ò³RÊÏövøfE5lÄßMøêvÇÈb|.±F¸Æ¦––ÌM�-Ùýƒwù÷sjOÖ5Ãä—óêÒ›�ÌʳV †JG£G·½,R|(‡4‹XUw¤væL’ú´<]iRðeÔ×7˜l_ ?+Ä«œùûWÀàÊ—Y­R˜’9ÆßÒ¤ŸNÅþ5©|J #Ú €ú0)–S©ËWq'.’K™e…ƒoáSnÅ!“&/ç´:Òw;(››žÄä© {îWT($&Pì:ÒŒ3°ìn‰ç}ÛuN±‹‹‹¤%…«ìšt;çZô¢Lfx}x®ùýßvÂßç<ÚªH]ü#–5CKËò’ªOÞH Ä�€V6×ë�ÈHLÐè“þІ£½p�ÕÁè"û¹d6.)*­9ÅÐÓˆ‹�ËQ…Y¢¹O$rÙx§ZÖ=ë· zD•–*•¬#{>^Yìå”ØÈ¨ìÏø©ÜøûÛÁ-…²‘¼V'¸Hƒ˜ n‘²d¦«Ñ´çää°¯�^†À@ËëÒï€wi à´%×t‡\¼¬WH\¶14¢¯à1KýÚn½WÃÏÎÚ‡ºgÿmMó=U³g_ãl}w%»{�v¯º¡Á8!lÕÜ'Æ2«û-äSC?o©D§' ®8¾,(ŪŸ.ï YŽåïâ´yñâ" ÛAœJiFJ§/;Îh 5¿Û ¢¤Z+Šžé«þ“p7í ý ݵ¾îm ¿I§ïý†®ŸÇ[¯hh¼ëkw†ŸŒ´ 4ÞÉÃ0SzçŸ]{E. ™{*‰Ëõ�Q–X¾Ëe|FŸßb/Y¦äI æ\‡¢ûå´U·” ÜèbÑãØŸ\«K!6áõå-ítz6zòR½âÏ{ø`>uÈEÞ7+‘›oL�ìxøñüg>u¶ê†p-,"U…˜–~ã³É݈‰=,5Àx+ƒÄ=g/ ¿XkWR‡od=ÓT·[û¸xfwÕ%°Xdû#&íÈÚ«EB§gbDósî—g:J–“fGîPdìŸ æWî°›„äíÃY¯ŽAufþ” ¯Ë¼Rê’ˆ“ݽ¥³ÃBï7&$yP’ôùlû¿Ï}1”)GJhjªÜV@é÷³%ü¸óF#ò€ &:£r>cçÜ�Q>B mÞá"Ôª ¿èv0ûpè8]¬¯Þèã)à&€à¾@u-úþ=(ZÀö»½wñxÉÍ` ÖN7HÕ D¿ÏÓß‘ ˆè dý‘JC àöe‚ÛÃyB� N¹ rNM·Õ ðÐAKuøü<ÉÏ¿]¹®�õvý"‘vßD:Ncx)ìÎðë¬èÎà13À"â¯üjÅ¿ƒ±— W!µQK³~ÈÔ9wß=2èEZF€Ÿ˜�„ã˜ç-; ªH›@(c5F¬kt>é„=äB€›øTb~…ì£6—! É0½*²ìØ- Äÿé•®pÚ˜ÝE ;0ê #cÖL©££#0êó†Mg†šžGK)9í¸P7¬>ºÀ7}™+M�ø�QÅ9¢i¦9¨ª¥ƒ‰€/añüÁ—4‹ÛiÚGùñØKñ-Ç,Ñ€ ”-šÞ0i6ª&E þ5“äÿØílñ¼BŽ [@�†IÛ Àî9T¸mtˆŽá!ø5¸¸Í­ö’3hpn¬ð%<‹ (Ö �œœæEÀQJœ¿3ÛÕ`™ÍЄD@žmwå�À(IŸ}N}€€=Ñ“ ±ÐCŸ@‡¹!8& p ï>ÚTBÉ7Ÿæ9{ÁÝøvbIŠ[6ˆŸK<.C ì\®z²ººú“4³Œpb×C Ç£¥ið�uEÇÈ ÀB@¨Ù§� xäxˆS h³žeëD< ãùJ¢‰q2‚ a„tûE‡Á@ódÜDv7’S‚ííŸÆ0Tuf8 ŸS-‚ó €+„)pñšƒ ›¦ýÕ®á)§V“‘�Õ`†å Š Ç[Ëð~u¤Q¥2¡»]>4àVÏ<¾‚Ë,È‚wŠBÄÛñ� Q¹"s~‚²v…){83/ªLäÐA/€¡¡!ÃÄ$ìNÍzoŒ /Ë�ÄÊÌç* Ðu̧îÊÝp!2 `ÒB“¬½azýÞ�#„@ð¯ àï1¬„,•WsqjFä“9Ö^AyÀ ˜K@Á+€Õfx÷Û'gZͤí9³‘§ ±P4F 1TTP 34õ?x\†·6ÂSÞQ|]œ™÷Ó2ŸCKÈ—÷ —°˜³„n«:ªKKËÀÐVýÜ;Ûµl âÊF¸ìNòi½Ás0^UÞ|˜"1Hö#ÉΓç¶7̸ö‘‘nø,½üæ¨Ôfߣ�•½¢Mô ØVHÈŒ5#Îs¤ˆ Ïâ Õ»(Àœ!$¶[ô1#.ÙV‰�<%œÝE¬P•)Î{·=¤—Á·“Û&¦¬ ^ަ±`X’M¯ÓA» „!hüø5-§W³{!#úLP …áfÈ´ÃC˜»%ynÍ2Ûi±e¦,íy7ï�¨F9Ú¥èWã.3à'„ýÛ¨jš@Ê“±êžܦ)b곚Ûf4÷xѹÏ4]Üó>iaYyr^»E¤@»Dótµ=½>¾Çõ¾fÓþ›@щ/qOð¹(ˆ�½ ‚wûªÀèÒß”€´#¾Ý¿7‚WØ´„ÀŽWÜÓŠoÒ]›ÓõqÅ� —÷M5ØRhÃÙ‹%:öá`~¦Sê&n¶Ç„@,¤ (WêèM½‹S–Ïæ)^Þ´Çð–‡gÐį¶óá'�¹LøÂÀ¨õõud >qÝ�Û³£­ñ¶6ifB€ �¥W�µÀ£l½g¯É¼õÃ.ºÏ�ËMo&*,‰õ „7DZOÔ Â–{(€WG ¿[N ]|ï4 'E4`Òö<0@³OÒ”ì&îš'¥æ: ø»E" {Ñxâ¥ÐP…EzÞ(çŠä‹©»ìq¥Ð!Óa€�ɶÖ"qœïÖÈòM7·OÁm/7&s+*î–‘Úis'ŒlÎ4â3y�ša¥“±[÷£üæ–åªíúK9ã‚­qžü°ÔÔÈFõ.¹úlÖ/×q( Jì©ÎÓ¶½>|owy߸�FQºú‚:ã„<†�uk€ŒPEü{àrŸ�±à2s(HTs«Öuâá±1ßÙ§ñ ûñC5R7§P®)€d¼hŸãJ ªö*Λ‹î¨5$¾@©½˜ f¤edRí䥈ñŸŸ˜ý~„i;ª±H ÇU·f'”›h®ùïßßœM͹ZkꤜÚFFE‰á¾©\Øûð žüèB݉߃m¼¤>*Xa¨`YFÐ?,£] íÓHGGèÈ|„Obÿ³× ö³óº�X�uÔð” 9…„NEZ˜„Ú®§¹ Þ™¹Ö�·y¨V~0‹¿$¸v‡B·A—;÷y†ÿ½¡Šf77…þê‡ÉX–guBš«íjöwÏ‹Òî&Ô&UÜ–_‹ÝKêµ=;Ü0fFñ“ ¤Ö~¼E�!Ab šmz Ld{-±ûQ@q}Ò‹Çr¶—~(~�rù·éE¤B‘Z²›%`'w+î#.t(Uà“ÄvÅÑŒ'‹>±ðÏ@âkœ*,…-�õÁ\)eº0ésŸÉÀ¤Uzñär(£ü÷úEªŸ^+»\4YfÒ¸ÿ;Õ‰Hì“£¦ŽŽo�”% eW»~=zÜ»¼çj"2Tò�+eK‚!EÄÉv~~ø9Ã%Xs´=Âê½¼ž<çå2²FàIW“�û³$ñ(†ÚïÚ6æhn�öÑc2ê§£»hðé@Œ ¹ÑÞÖÚâú­/LÚ÷,¬­ëÁ^ŸàÔº¸³¸™ø=”ýÞüòòî¯UJÆfô.-z_¡*Š]öô9 €¿1eØâÜP?îC{,[ ¯·œm’5eø[.H°Ç9 Ïݲ8mý*è–ó•Y$ ×c8pBù›Ç˲‹~¢áhË2ª¢‘Ï9æã„dc(µÆ UÜ%.=TGWßþÛ'/¯èÅ[ÑK|û+Ze4E=17ªüÏ+÷ß®]^eØßKµfŠ¢Î¯ü~�S âÐå ð{¾¨½#›iÉÜ5 ÇŒÃ]r5Cå•Þ6—˹ÐÿùXÄ‘:T�Üvpao?cÄ=1bQ”ci=¿ðžaGý;}c剉þ¼ã‚Í íåØÁC8ÎÁø×´Ä¼x:Ž» §>ÙãŸPÛœ¬°T˜_X·ÅÂïb¤’z ¯©Áúî�{È\Ÿà¢H “VèêiÓ�”DÖ8NÕ�zÎù{Î�‚jHÒjaÓR$ÈžËô jÖBÄníðF–*E n0¿bæÀ´âà"ç.fO‚5ÍþÁâzžŽ45`>ÒöÅmÝ`þ£"ÛIòs¿‰‘l¶ÃÙ�Ñ3;µ¸O ”1pO"5¤J“L} oûgމzv¹Ä.×ÑÞ=iºÚuÁOb „ˆR€îŽ1½ÝñÝwîÔnŸÎI´M!3³o?„ÓáËæ’‰kÿÿ E¶²ÃÃ¥ÀhÜI¹ð4+¾}ûæ>(yDð %Ç2áFŒ×L�þ¬Ô½{t^Ž„æyP˱öZßEã¢d°_!’¸¿ »Ÿo,¬ûÑqªÆÖÙùÓí£€RR$Qúr+·Þ­¸˜eï/ð“Ý}Ü Kà“ù&=¶í©ã/—êì -芪J ÄÄõGýSƒ¡Jå’Š ç̱8TÚj,×VHLI$XN6zòé_¤µL—<=(‡3go›ðö†�ù7Bã&î¡nØ·©:_U¨7¾³½“h­ôô˜:éX:¾Y†ã'sGƒ zÐXT7¢W’(ô~g¸È°�Ü>ü-/LË[^AA†™åŠì/iHK+¦�!|€�ß„XÓeýK´ÎΚàIȈ»¤ÁÆ�MFÊh{Š( I_NIyÊ¢ŠÌþ Í€!‚²1ć4Y9×йó�&eÀök– ¼:«îgŒ!Jj« ÅqË ' ñ©Q‡&+xîW@“3¬&ÈGg_Ü×ÈÒ3&êù²'�émE«$»êÏ̤ÚÖÙÞ¤sÏé+Àù*Y¨"h��¥Ð?/ùl!®•üøhÞz¸1E|\ïÖ˜ :�õSý°ƒóë'(múŸÙ õ©Þº %÷ÌB‚Z`¡ýcóR¢Q4¸vÖǾ`�즉³€EFDž6Åç°|(e€9SCÃXoÍk§Èÿ³—’'‹qÆr}a],pc ÉÝùGñ<¹ðY™Ž´µÐ[ž>¡Lã;ôvë™ñísð�Û½y�kX �ëÏ¡ Ž£êS~þe4wªW°�t{âFo؈s>wtÄ6!UÀoÔ>ëì›Ú ž�qÑ+©cE¥~$¾æ%Í×´�§O%h·<�æ³£Ð_kXóç*Ó&y{ûÑá^±ß� ù¥½“!{Í æZØy1Ú8|(†²=ËÔmʱօýnv‡¼‹3cfæýq?:D¿ƒ_f+æÚËk÷øM,2°©yý¿à8«çuù¤øP;S5ŽÈÙfßú”dÊV³u—�þXrä.I&®öÔÁA5jaÈ9™2j�3®;È'Òñó1 .B÷s£¢F³j”ÝLô玿‰ÔAƒÍÎ&ǽMçâºEíœÌ”:/íN ÷AÛjc…º�¦>®q@¦Mk–ã¾�Ñ–ù'ÝwS³Ÿ/Eq�BË3�dñéXÿM�U«Òk%PBˆ¨„Z�ÜL>#iXiî Nwìå~u¹$OXåéš~+“Ý&œ�JßóèÓ��� -oó”èáEK>û.ĈӤ$­º…Lâxþµz§«¾ˆ3|cª6Ôø¤•ÿHJõ]ðNe7ÿ£ÁÔ�gŸ¯Y¿‚ø�\ šÅbÿÎb»¥²G—±§ã­�å ü–xÌ.x4YËUù�œÇ´ú¿Õ83žn–>Ý´€ Iˆ}uägøí*Íøn5Ä£pMfãGÅM”}âI£Š'±ßxb-’³È  ”ò‰ºç| j5Y IOô‚JÏ3DúI±ž Í©Ê <0ãISwóЪEfÜuꢬ +~á19¥8ˆ3� C8kÌPï:Âd§céݶ _£üqÚ(¯¬ì ËtoÌÈiU|îä¢>=»Ëzi­ˆÅd™~öÚ‘ªñBêöqãÂbbÊ Yll,5¼(߯Üe@µcm”>ÔTqbcY2¡uíɉ¯‰/­ASR˜T•˜2•˶ß!âT*Ôåv&úðÝÃ$W»ðƦA:öøIoV~ÜmÓ&¥ÈÈ=›&ª»ä<ûœˆ§¡Žø:‘AéêíH8N«jâÙ�·häÙûôCB·×àŒ­i–vºXÆq­ºLæ}âXYsÄ7žŠ§étŸ�¯ð_‡x•ƒƒƒüæc·85D ª¬º5ËwüÍà‡ÏáߊæIxSû×ÂÍÐOWòá*IéÓ ç3^@‰‰‰ééCé ßg-!!!�Z£þDfÐXIýmwÊíµâò5¨²éô™²üöRçP½|­WOûÁµÑ> _ÒÓô±(ÔÕ¨Z]nö.—g‹×Ïk%Ø«I©71íÊ©î+àÙœ[5å÷tI_w¨fƒfÂhé9¯9åÁz¿ò÷e“– g‡‰¢8º\Â&åÚkrÎ%&�[ãsÐ&û+Ë4®wi?˜Ïïaú²K§‹aúò*®È 2GËRÖïÞ`úÔw›‡›k:еóâ²¼?÷‹É7wœðp�Œ×šÌ¶oç«»EЉé� 1¯{«¬S^®ŒCõ%a[cDDgÆàà’s{‚ �£âÆqß5-…½¯Ïû†u¨`Ô)�°¼¥fpBŠû*aŒ„¬´5^–4—o©>÷ótëáy†ÝÀeYfËÞéÎ&‹jk“Ä3˜%}tqDª¥•ñ²'.�LW–GÞ|þøÃƱŠw&Y÷„îß‚²Ÿ8¼„ Pß+�õÙr«JÞýõYŸu^Ï9Ú€‚4¨æ§�q·¯¤9­¦ (5l<`rI¤5 ª/3rÿ:y7Éb5§á¤¹¥ãôq—\óaE×5$Nvn¾;€ø…É%)´³VùLÓIΟ3ʈìP‹çŸ'²õüµî¥ò®UßFãÐò™–B—²‡‡#çZõú·ÈÁüëK®¨÷¸^å±›V¹I“WÜä”þЛ„]™¾*kƒNÞ¯‰Ê¹awð´š+'†ÃêÿmtSŸ~*C‚#ËLä.¹=ÓOçZüv¾] OÁ¿yïÓ¶aåF2yNGeçE|�”�ýKü §çÈÍýðvâí~œîVx°Kù›`<ðçO ÿƒÑµ­-¾µƒ+÷ü!4 )á�èT#Ü–ÐÀ`0·IŸûÍeŒUß!PûÇ(îGžG· Âg­š¯]ù³ªÑëru3Œ)apÙ£_xÜâarÍ-ŸÝª!~¥(£ÇÙË ñ²Ë…Xoƒdþ°ÔB¾à(Á9(Ü&wœp­†0>Û3$si•û‹¯ŽÇ•Ô²Ï.¹ûý¦\ó=:»½ßÑoƒ[¹A…�„ߟ}† &›Ñ54¤9¼.Éò¥ý¼gRÑ’V‡`Z1S­ç…9Ppw{X{мå_¾2î4±{·s!RÈ*Wᚊ¢Çœº‹•k1U~‹ˆŠâNS§ÇøLE þlÝ"H¿Øuǖʵb˱kåî�&tóD½aÁ¬(œNAKd›µº¯Š®ý1–‘Ñ»Ð[im“m&%!QRQq—©R>"2ÒÖËË‹éH'(ë/Ž8N{åBÙKw`8¸ç¾)²©Ëù‹…®RP¶ï×ú—­ÈµÆv‹BUmó�['™T]ûåËÃâØ{Õg†öâªÏ]”Ϋ…ÑÊ@ËqæôÂû¼Ç¨õ½“¦²æCCǹ–´Æ�Ê’Øã…†ÃI{(áëWórÝNœh€ÇÝšlTcÏ4ï$åpëÞIJ=l�ŠQlZfÎsõÇQ±õ’_³yH[�ÑÇç§.tŠúp¢ö­†ß£÷ªy+ƒ vöF²–zë—D�)z^‚ÖÆ0ªg2ÆÛÚP²Èï°ðˆIá€;™�GϘ¿´t*$4Ô£“VåýóôŸ/ © 5Út§½k+‰Y_3,·îȽ[üÁQ!¿úp]Úƒ„RONã,æÖXÌ›‘äsÿ19Nº'¤¤ü†Âó_‘aq Ø»²™Lz´§±†ÏrŒ8== Ã^…za•÷ž‚m_ÊX^í5IJ÷Úúº¥˜4í÷%Wè·T½©™4¶¿67oÖÐ{RÂ¥?Ûddd~…ñì¯äD»Žëp¤{=bÁn‡æWÿòäÚ¤Õ¥ë৉¼%”&9Í!�÷‰ßÜš·G$Ó!c˜üû£ó+2ýY¸ùÉ�5ÿƒŠhUÜ×z{ª׃Ø3�¥}îeµõ~0‘W_Ã¾Š„[˜€Ÿö­*ÄýÍðññƒZÎÔþÄÌ㌒¯¼ÿæÌ-wÇ¡Éê$õu7(¡VsX0-GÍ[.GºoXÐóħ‹R¢¥„ ›íLä ?Û 1YPNîÿöЈ PÌG ÙC�ì\ØüFS‚s6DŸ?Ò�F3öj×0}뱜ÿªŠYʘ±ŽjôÖÙéY+èë�ÝËÈ8¼áNW:ž"œ4R \¦+7 ± ª¼”L!yyy!èÏÆ ¥ÁaUÇõót ³²�jÔvàC'«:‡Ùôôب·spˆ· ±þlàÿ[0cX Ý!¥zæÖŸ6Uúy[—'(I0ŠUª›§VVVªjSd\üæÆççç…òÑ •¯Ü —Ós%Sð8Ñz½W®##3U%5Þ|4[| ’Lb^Û¬žHrQçyÄ�Æ?3ã&H¬q÷âÕ48ÖC@-5¤´ø2ëƒÒ4Ø-+ƃ££^“��º²â–––X‚òàêëyµÒlç.¡}Ó۰ݾNyÚ!Ä-epg Ä ú¡,B\Dÿ¾ à‰Qí‹QÁA �lßyAözbMkJ„E¢Cõ‘ñ�üÙYS:â0¨777#ÓwuêˆIe>ýúkµšÚÞÁìÑìýh7Ÿ¨k’EV¿Ü0œñ^NÚ‚½Ú•{C—¤Œ+_m5í•�N×ÏÝù¶uÁhØ&Û*ƒ/µ¿(??´ó¡ k\wA~~þG$'NPÂÔlË#£Ú€&+™GÄÄÄ"+§ëåúp¸ÅUm3³Œ…«“öå¼Ýïà…ü[g>KFE=⟥ŋÊ=DpÞî \”ǧ™€ ‡‰��Óö Ê:4®ss®@ˆÁž•RÆE\Z¶4o6™2ÝÏÄ�ŠÀœ4,§û*?™7;𶝲6âW塚ÁNcƒH¯*îN›VBÎÒÔ•™gD²é4ŸÃ…Ý÷l¹u�YïŒXë„M®áŸ¡¸~·‚ƒžÃ?273Ú!/Ÿ{zÜÔ·òºvû¯¹±úeì[º;~—"# hËþ˜áöËö%dèÕ®¬�/.W¡°&¡ áYÇšh|\çù`ü|ÙÎÄëçs«©áØ€Y3Ìšþd¥uxË~.Ÿ­�Í›�k"BBS_4(]y�â Ø �¥w§7Þ¶å}ò)1D¾Í÷ÙS5ÄÈ‹KI½K($2êñ'-2Ñ{þf7oÞ´žR¾ ÅWhèœV<ÜûÍ‹$¤76Û½ÜærÕÿ¨qntm´ø`à n´êÞðbê~šúzÍÔ dO�G/o-gRoÂþÞÔKUz÷×mq–¹9}IymŽ9sß÷ktTÔÚÊp3sé:kÛ¦»F1WÛÚ†LfØß |àU â±š 꼕Ué–:ÎQ8�ï‰6¸¾¨_Jl0¼+‘Ô»z6¶è•âô »Ñ¢SDþéÍТzg]B2ˆ òœýë¨3]»vÇrÇh".–6Í$,!aœ(GEÑa^ ?OÞí”Tqwh1™þ~fë4Ö†Õ¼Ô*»Qî™—SŽÂÍ–ÒP^4èñÕ¨š·Ó»oÿ¬ôKKLT‡eönÛžD®6×Áþ¾��M�òÃïã8xü:KÅÛ°? ŒZŸ1…^dIjE[ó×iŠÌzå˜úít¦Ø¶š4¾\%3;~ÕÐØx�Pî'çÔŠy9Ñ¢ç{FjâÚ�Á}Íé÷7¹·d=»ìž&ž®²móó™»'~ýUfP«z‡Ã‘˜Jˆ¿¶˜ÙX_t8Ïѵ GìɸíD¥[Ï©GåDIÀq Ä¡ú¡ý¨”ƒ§å�E¹3ËË5æ@ì‘Äñí{yO±G­f‡yÅ=ýv¾1ˆÜV¾ŠÇ&ßÛùaëŒéÊ'§r¨½Æ«ç»êBÞWÎGB½÷êȪûD¾è½�:÷p'çóÊr/´ÓÀòî96(—–†ï7ÎaŒËŒfóݧj�Æ7¦’à'I¸Nëç*$qç†åj¿³Ù¸õW±>Ç?CŸÿáʳ–«²E²7ú ŠpõœPKÇ(çÙre ÆcŠ—\E¡ŠK—x˜"¹c9¯Eë;®Ÿµ¼�øöí[Oe ¸*{Ã¥æÿüdX4»JMAµG`ºÿáµÈ¨DpÕïNs4WŽ»D�£=2O­½Ûëq²”´^Ù{G‡M@ÜÉÏd|Ç| >°6¬À2 mý9:n¹`œæ»™Q�›w…Z°½ò†¹Î‰Ÿ|‹6¯e‹'µ_ý°T‘-ûÉí<¡¢'/÷¼­?OtzšG/(Dî&;ï:4t)gnÍÛíPÞÍ2ž5D-¬œ,gŒï J•½¨—+--õÙò�+†²ÓŠÐd“÷çD>3ÓÄòf§¹Óõ:âž›(Ü! )!ç±²/íÁëUë]Œ»ÑŒ"wÇ%2¿ï}`äkà�OÎ+Tds�µNÈgxiâ¹k¯á×÷{Ÿ¢­S.!a§/B‘­loŒ7…cl›÷G,]Mžk …€Vr» ßg>�øXZ]ýØ~¼ì±¾~RCCC•U7•‡‡G!ô¢££c˜ì ¨åëÊÈt,~X€:”߃rˆ§Q\¼¸6ÿåµÏ^¿ˆÇöüCf /Mߣʹѧà±–2Íclɰ-‘`™ñ’õÍ[ØU¾®î�ž£yïY—ü[�`½•³¸³§ñ q,ÚA_¾†.{ïú›•­·¬çÄŽÝx#ûtA» ž‹6A#"›Î0²Ñ‰zy¾¦mꃃfáõ¹a¨lÒNWS6jjä&¨íìRºJCÃVÀæ뉅Ø´%1·ì-\×F€¬¢yûÕ` ’™?Žt„ÚBÚÔ}§3iðÜÄtl£}íP^˜·ÔУâNý"\�³ûèÐJ1Sfœxéî—p„BLƒI»ÅéHùÉn´ÓÎɬ0M| 9??%¤|¡ªæj¬T¿¸¾þõ7iv�à�£‚uJâU#ʘ] ­aåv.Bò 02y9‘±:ÿeÎv0=4¡?j„Sî�<µáçß¼f×40š� í…RÞ­‹¹~øQaÆCÞñÎZÎzªï€øã_‰”‡Ó;iC=ecæ¬îpãl#Â�Q�Çq|´Z §ù t¦xe×Ç÷y¼]Ý={BJ$‰´1Ž]O~²œ3‹Ùl[xx: oÁÁ7Q×üÓäE#pb…a3zò Œ\ªÎDZ Ci ÏVC=8OòQ;ÛU”ýL&‹ïs0º!æºø˜É:¬Þ­±úÅ 1Ä.´™ð…Ÿü0 “¿²è]v•ƒcÐÍ&žz2_óx�çÂùÚ!}ï??¡7vnöñ=Z_J‚ç¶´ˆ¬7Hþî7*Âéüz·$g·LõWˆ ‰AÆKxÓ N—èŽÏ‹¾ ¼ý(V«ûúµ“=ˆ]ö˜o w�9õâ¶II¤»ý¤8Wep }Ò[Ϩ\^jƒJ\®w�ñ›)¥478\d‚ÖÆMq¥´”¬Ú{ïQ©ÑQ@óÁØ��©£’oßBìw�-+ä!oŒí.-sÇØüüõšµ­êVhkcÙÍñþÑ?e°àǘ›•%$�>.Œ¨Ú'%ZÅz&%´Î41Ÿýƒ¢÷| y¿ûVG~u¦¢F«Mk˜Úñ¾ø .êì*~ea¡�×�É̳U»¸óœG ×§sJ^C÷D:^1•²T8ø�Y²ACRòLè¾×í%rZõ[SBp¬kF«ñÆF/í[¶:„ôÛv ;i¹¾Ë_åi—‡dä<=ïîüü¬bæ*#?~¼óñ™jk»NAÁ<<4$î:3Ý7-ôRêÄvŽ&™ccÁ)Rðzª·ÏîPi~ãoÚè–Ûó›K%Ç#… ¹f^G Eóœ¾G^Â�YB(³¢fš ffR9È^é™ÉÈ„&(p¥]Ó¾%éíë¹H™¬qªO%o˜˜ …¢áU.é†Fâ ªKXøS]=•};ðØÅ^ˆ˜HCìdkôgœŸ|¡¤ ó—é[:/–¶ÛôSG¡„¶>‚ð1 VKÞé»'wOZ8º¸">m�wè²Å¾³M “ýÕ7E\´ÎÑvÌ}Ƨ2ûaЖ±û|õǘ%úù—éeÜubP¤ËF¥ÏFÅ]¡ÀCéHð/JVKâ®=ýu W$Áù#.´ç.ÿóŒ÷÷IúŸ’üó ûûÄú_ôïŠÿÑ WÄRþ £/ÿý»â4èßÿ£Aÿ·ÿ× Üÿ~®—V¼§dRý™iL•ÿçeTC‡ËûáòZÿ³—×f’€ßÏ;-6g‡ ‡:ðG ÔÐ+úåͧ§þ¥Ï30¥P³�eÄà’>ÁæÞ,½äÙ'Ûcƒ6¦ÿé¸ñü’©Z©Âçþ5¿[q%¿Õ)�»ÌnrVhoCð× iÞ³ã.0ý|{=Âÿ_{èÿÝä"øÏGÿ»ôÿ/©ÝΦ…*<žÃ€ð~¶÷.à(¶SÅ~â{u›FC€ª©©.Í–²¯žÖ]7 7,5Cjq땜éU��n†ÇÈMÐèʾc¿0qäQ¹%è' bc TBlûÓ?è=éó^Ž÷nýí6û²µÿ‰]ÿ CÿkÀлþßÿÛõÿÉ ÿ�]¿á–-šYîƒÞ½§ø?ÔZ �÷w2袯ˆ�h†w¢ógÆk¼Ë%2†û?\¶ñ?CQ¶€cÑWg[Ém‘ ,ë d�aW”µ6¾�4EL¸®Íî]²—ê-F&jáÉ\as"h¥•½;ߘg$}ëJˆ"ÕÔJí†P?܇G&M|»¡Ta†+ÂÊ¢Þõ>'Kú ñ^}Öÿëg›‰­ä÷ÊR²¢œzßY¸ïÔDµÖðÔúº+�úÌ¥'žÅ=7y½øñ&“ä­Io¸öˆEä5j­ÿÓÔ$Ñ$:Óè^öЬÚ9o™G�héèÆþYÙÖÿø)‘$ÞÐT_CzqÃÉüÙ1gì.l.žI„B0í?£©Œ��/««óNô­ÙÝs�O‹sùw†õ»·|»¥–ƒ‰jã¿zùÈ÷¿§Ú‹TYS<8aûâ}†Z-æx!"à*°:ÛÜ(ä<ÏÊ»¶¾î¾=UžÖšñ#çe”]U€cïjÓ€‹‘º¿ãÝ„QѲá/öÖUàÿhê¿ãÙ~¿ÿq\«¥ÏVèR¥FUÔ(bÔªRjÕŒ=JÍ ÔJíª––5kS;±÷ŽVíÚbj‡X±Gò}<úz~·_ÿjäF’ë:ç>Î9ו‰V=Óü_?vXóe»'ƒÌ+ œàÎsØÙ¿¶>ã´| „»¼R*Aæ�ø©–·'jíãi‚3úKæiä.Õvuv®ÔP…똛¯1��ñ^zæ´»6�C)55Õ×E-þ±Á�…˜"¹#Í‘¢¦þM“Ýþk…z†g¤ÿÓÅ´ì©^f“sWÂwa8Ý*fBÄŽ §÷Éà2œèü ü8Ÿ ªåìü¹-,­`uȦ³¥e0³Ç_ûMtÓ‘uñõt�††�;³ÒÏQBrëη«ÇÖ×{Ç'‘‡°³�¸�mŒYÜuØß!–0SXfRß‹&¼ž®{Çòt7ŒÕsÁî #Šs¨Êß°õÑúÏ÷L#§³lF¯ò™ò§OGÇCÑÅCCèé%rõÉÞ¯+ê+AwC»*Öõ/Uº£îÉç—:#+\—ùƒä2Ì‹Yæ…³HüàMõ¨¥Ó-�®šœ»¾nF³æƒwõ`J•&1äR §wCWòŽŽ½È¦gg…z0MÍx§§§øSçYÇ©­"¿wg=L2~ÊÂdEZ–~j¶SmŒ¾°�Ÿ»Kïï€Ó^爚aÝúÓ°/!RŽDÿ&ê3›>Ý…ÆnG„銺:–¯wD ‚i}3¿þ¹ùé# LÁÅÎ%%õ"ˆ*<Œ.P¿v-SÂ�¦Ë¹ízF9<¬Ú ìñ† ð1$�„8ó—€S·ÛO¾(Hc·f‚:wî_®ü÷ çônØ#¸k—¦;äÓW{8Oæ[b*aI&2¹ û4¤ù¯`ØH“µæ-A\O뎲â#XaUÜÜ]»;*kî‹íݰ0œ»s49Þ�Žx%áƒ}"í› Ûl<ïIÂÔ .I5°Ôw™j»Î<Ε˜”uXgWÒ²VHv ýu÷æ ù¯Z^^!p8 ™ÆÜ’±²²uϷɦ^zb· x-aø°þtÍ_�£ãcñø´oßPÀ– ªª„lL‹ZUŠbUŸ®µ@´#3.¾%ZÝn·9ßÑߟÑ"l¿ï£T…8*br¾ [ÿè�÷ºÁ»{9íµ™Þø·Å½,½ÑSÓÓ{ƒ*üvÝq¼;DâZ�YDÄ(¡ǧdX�HAÔÌQ�ýòa»L…ß™×4ç+˜ ZhJ\ÉŒðhøó.Sñ>ÞÇ×7å ñ`Æ»n­¨~an.ckËyù¬%�…ÃÜÝ÷ÄÿóñúAE\êÒ3óÍG饼«Ý×Çž×jUãüê—z’8C[7ìßXX»­hükjÌP��½›8Þó€÷Fc™*ÊöýJU€Móvquê“ÌTKY�Rá³#3 Y?ºwOÖŸDL[\ZÊùøñ`ooÔ»¯îHæ°Gïxwè#"c¿|‹EüT­�óÂú!Ý' c“~¼ùù3mš¶ïÂçË3¾zXÅp·CæÍÏ—€g º|O¼|%>IÉ*(p£§¿•Dr¦BÌvE Û&‹›‡ÇÄÔt†ék¾”·+�M6K)þÑSë´,Å;.h×Úhξþ(túŽSƒÉÃh¥ éW6'“ªRgÿ@&—áUQ3Wë>]YYÓЫÀv´›¸Oüÿà|í+ÒÍ©vñ‡1SRRòúû…XÜ{J^‹±z–•†“§F#�§¤ÜÛŸtâááQ)Æ�êØ•~þüy|€!Oeƒ]K3™n&âvvvøxë³²äCoñ’ÎN&%—677ૉöE'›\C¹�È�ÿa «÷ú“³M2Éió£””F`%57?¿z”޾~Þ\K0:?_)ȼ£¥E.66¶lÜ�д,þ@nš·ª¸Ø †ÞÇ�NBWÊ3Ì8¡¶ê6_ÿß,À¸¥A!‡×š·ƒÈÚ¦?¹^·Ð»z9õ6œ:KP}ØHì]Cå=’˜_ÂühPÝÄ�÷#È8:²:Á¹â ²}ê`Ƥ²=<+�`ʘ óò–ø–’w|ü”pf‘ ,Ô¶ÿjØ%¶|¬¬l²y¸¹FX�\Wd•Σö÷Îmš©^>$³…&S&‹Éaª#">P]@LáwÐ4Ì*�ýß2Û+^Ïùô$]üäÙÕãØdñÌõRIV “3-°7ý¹Iþ•cãøpOâ’Ýt�ûD�;úú�îk…Õ]sζ{x¬ùìÀ½ µw#ŠCåæ�;èÝ]°qgòùs? †®°îa玱å}Òh°Uí…Ïϵ¬žô®Xµ«œ! Ϫ"6¦Ñ`£Íÿx(MØk×ViÙDdéI“1Ãb±]==±Ã>±Ÿ®=Px$.>>.fôøuRþ·ƒ\¡==Ø<ÍÌ ›>Ö0‰ÏšæÓ#¢û¤cµ]{8‹‹3¡ID@@ƒ…„‡�ûíñ€GçXF6}Š~~"þÜ´€ j¦£§‡®´ÅSËÊÆÏÑùçUTOû“π蠪‰’ûÕfJæX°££)ØC6NŸôMŸNa€s'i´‡ÇwZ¥L [ÈgYF)|ý<D7c'GìæPò#�sA"Goø{Á€¼€@öà¶yñÅá¬gUGdzñBq1Z;¡RþÛƒöÝ*m-AËÛA·ø¾óËylŒq´[¾üný§ÁXMM�0ò<�”î´yµ®£ÇkR§C,÷oPl6�¾6RXG:^[Ð rGÄÿz�ÂïM©/^€OÝV"AøIÍf @Ø;Þ¼u+gu(WC¿9ÞßÒŸ?Z�€ MÍ««¨ %95�ã“ðî©óèéhiEñ5îjÙÈ58NŠ,»†„„¸­ôß“"þ2,µxÁZé�Cö›(éÙØŠ /¨KɃ3–má,è{rA'!!—�ŽŽVW2Á·àªûÒ{O"ÍÞÙÜ_¯K)�^XÇØ8Y·Øtš° 6€y +^RÓ­kÂ]ª“È»_o²H¹l´«+ŸêŽ�¯ŒoEÒË™��ì~~ ¤-\þð%cPÆÖµ0ƒ{I=9++o]ÍirxÀ§»ïÑ¢ÍñüuåÆö»qg æ ç“Ñóß�ÝjL#;ÀùÚüîžDÿ¿ÊÊA´lJ#¿.¡È fú[ Üü¢*QÂÞ½çY”Tí´ÿqIÿòƒ÷BãRO¦É¿;Ç×=ê gfžÂ{FHç4¾×.CÒÔ"ÔÅóå¤ÊÃÇ÷êË—/àÌÉîá|ÄÁ¡è,oŸ»3ÞªØpC?uµL15fxçæMv¥�ÐP{zŠšÚÚÕQÜ0�]ÂÞûëó{½'Lú�oõ$B»�ê{¬‡Ï8<|çéí "% ‘{3‹ŠŠZ¬“Õêå6}[Ûq{¸êÀ+K®¸V&á´ŽM)ƒÇ·À̰qTˆîÌÞË«[¡/&«HÕ“C�HŸÄÙ²íŸ}»I×k%,nvE$v# M œí©WZ´^>[ä÷ÿ7pÓJ¯÷R = ¬† q¹o”95 9­5x¼·FãFf¼™Ÿx€p‡JÈ ~~~;ÛÛšÀ?ºÂÍ/m™­•yê…?¹îýÎŒ�F•¾iÿ×Ýwí“ÊÍTŒððlØôY–HwÍ¡ié—£“ÊÒÌT>Ž é]¾@Mm "]€d›IþîµÎ¿�b:B|Cª‡À•£NOOAPLè±@‡þxêÖí˜bÒ ÞOý&øöO‚Ð^®Ì™ÒÆÆ˜À?23'‡7�GÒ‹}wêµ([tŒuò(ÒâÁ£Fßô½û ¼p²uDÛÁ(‹‹!ëZCz††11òñáf¦Øë×þñN  «Ûºà;=z¶]ßÄÒYÔ´Dmè·w�r�¤j]è�¯ÁÕõ5:d,™V•ôœ <™ßû�f*µvƒ$ÎL $S[tx�Qjº¯�d555 ` ]hðÔÔ”•`‚¥ Rgû åmZ^ÃÛœŒ=Bq4J÷îe8Âÿ¹øcg6ç¥`Š2霼B4l+@X%õ½6°Vñ&#(Or§ºß<~¶fFv™Ñ$þö ¦`Rû߀!+4©×eeeõZNAV¸,*�ö]˜¨ý+Ë =ìqòZµ˜ŸŸO©>ØÄmJ^›�ã52”*6ÃøcÈ'Ýú›7WÙhmn‡BK {—Ì¢Ph´;𷪼z]V _c5'µØM@ :â‘|öý0|ÃÍ‘UNd}¿‘ÁDœïϰOê yß§ÉD%ίZE Çà�¬ó9uÚ÷Ïè^2™$4÷Œê=Q—ÊÆ·›øýh*º}ð7t'4 ¯gÊå­Ö](”àç_¥ç Ø´êñDmíÂ4Ї†ÕÉ0Œ`JàµöLlXxsØ5Ù£ÞdJÚÙßþÖ:¤m]>¹}µ¹ÿ×§·Qæ¶í½&.·i!ÿýgûŸš½¶@1-sáõáÖÈÖ#E&è  ª3‚“¿¶©i*77w"òóôT…ø ïN''KêOß©š·x#M>um<ÛŸ¬;Ù¨¯Dnš:âšLü�]Ø·I«õ‘J‘¨Ë]ŸJ›ê\7óÝÊožöºCzƒ7â™Ü ©ÂÔ å6Áq|&ø¨n u�UâB 1úƒ'®»gú¬BÍ ´ÓãäOÝíä…hu¨½G2-ÆU,cöð 䌤gýºKìtôþá<ª»#ràg¬HïF¹óF’ð\$àÙÍÝUX×a¨Wð�T+Øz›IPLžÒ‡ºÃ¢¬CsSCîmlÈîÄžÖûì´SÊãN‹m-—D¥+ ­�DU qëu|Áÿ<ædù ËOD•¤vŽêº¸ k.!!íPþ‘ù™ Éû÷Ÿ“H¤ããµb ã6J:Ét¨ýH¢]U¥û@1\ÇÊ*°g°¡V§Òºê¢¶/�F“©;øûIÇj�$⛟ÿ•Ÿ¿ñm»¼lð&ë“®ìÉóò|ŽÅоRÖÇFVÙ̹âœm +Såã´½ÁK˜ÒRÍÊJlYÜ-tA�¼'ë87›ªãó¥ ‹ÚŒ�2�]¾}/Õ3¿IÒ1‚åÖíÂa7%næ�+Ó—Œ‰‰ánq¶�û%$/‹’• ý¿ºÁì[R½á04ÚnnˆºÕ‡j©’üM‡?F �–}$Ÿ>5ŒŒvc-W=�¯E B^·‘ªs_gÔáÌ>už}QPòÖBqTì„É8Ïß þ R§ø;Ç-,¬ hp—ñ`]u_¾üMkÿgX€÷€²¾ �¾m FpIúè#¨MM‰>§Îy”2¤R¼« ß×¾_âp$ø^}KÑñ±!L'¦ÓÐøGÔñóEHW”Þ×®”üz/ j¦P�£T9Õ/ë#´å¬ÓÖBÊl›ßñ3Ò[�Gˆ*5^UKK+‘&¸ÂVsj[åÂFLR}½ƒšÈ?¶>¸ñ^¨q ›µ@9уm²Ä¼Å®oŠ´o†ˆº”xªíÜW„q¬c)Ñ?³ñBt/‰I#ðX|©Ï7¾^w%Às^Ó}6ÿ†ì!*^^=~³‰ód¤öób'ê¸Ù×µG‡œ‚ZC2$}j¿Ó70?bÏÅ-ƒ•é%Üœ³�l> ¥S[©3²c±hP{ò50—I?-¦)íyÆžT‹=`�@순tXh�Ï]·béŠ"?V‡° +dÈ=l™bvæÈíˆÌxq…¡C€Æ—4ΪDØcßúbCr|c•PäTÌ©é,L4ëÝ貃Ä+g9ƒÊ$OÖÀ ‡èj|L8ùØ0-SÓð®îî§T<<Ý0�ï¸7ó ôKhê‡^u`ÝWßZÛoÈ.!vù!G­@ï ¤}š�“X<‚áp4ÌØ8½!YÕ*.û‘øB‰p{N÷ƒyáñ§FM/dÉÑÕ�ìÑ�ì¡­Ú ¥JÔZ´¨ÞzÚ¤|#ûÜ•ÙjE·Ö=Wi¦§Àzï33ÄçÖŠåP4�Ê%k1xXmÀ±(b[ùo^½šþþ=ÅÙ_Œ­;c·�cs�uùàP”D²uj\âãòñ‚ššÖÿ(T­Ë]Ê„kËÍ3DÚôU/nâ.ÏÍmk<üq³QO0¡w­ï¹”¬x@˜¢ÊÅeÐý�›ŽgtÙMqŽçBwøõÈ1‚°úî91V3·�¦¯óOå´™X¢ò 'ʪªØ€@EwðÝ»¯ªþ¿ð*ÚX¡ºx±^ù”¼¸¼l˜pÌ4¹/AÊ»ª@ žUS‰*jØQv/{1>>®¬¬<ˆ#“�€�©Âº€ž–®®®Á*ÇI€ïºR ¦µ=ä' eÍ0˜ÁÉ“¶«-ìN.€P+V¸‹B¡v×ËûR÷¬xéYX2ýj ÍM`¸µÄ#"¢ ÝÝÀ37X £¢~¹–±ÜIº®W¬»svÐí,vÓs³‘‰ÜÅ#è홟ö)Ab ’IR«„B¹ùø4^y *GÇJ.”LÙ0&®È(ù™íëÍÄuõtjiÊ6Áá(ËòqËÛáCMüÁÓ÷êÍãÿ©zhOª°±b|…·¿³úl!'J]–A=à+}ÎyCØfh˜HÚ+öïìëSïŽç�V—ÎOLdj¸¨0•�…¥Pç£ ˜¯=¬Ô²ã*x0)QÀüCΫ@o­íík½›:ÀK“ˆidÛãÝUðˆïMFÆ|,Z¯þ�£ÑÆ ad6¹ÚwÛåh˜½† NW¼Ã ï�eºI¿dÿ÷÷pq²�jk¡>IWÝMó¿¿ÿÐÍ9'l¤ õ=fŸö=˜öÚ·I)–x{âО&`þ£éN6p¢>[þ£I/O²¯2DE0¯îxwxÍ]ÄæäÚŸvóÛo!›÷kŒšŒÃsr„ 91ßoC ›Œ£tª¼«»<.=6ÁIä¶6˜ UQñ2{äù¢¾ L¢Vù›Üý…èÅâ?ƒªP*¨msÇ9—±3¬†n!>äòÅaj®f à!7Áb»�S‰u@NÆôŒ°G')!h‘ j9CMŠÿúÈ|á·›WfÜFsª':éÃô[ü¦ÀÂ�^Ý1UÔ~äqm}½=`CþWd,’!mG ­¯ÿýׯ_ Ðâàä\Ûéæψֺ.ssp¼¨žr =OIi’ŠCº…•£eû.RH±~½’I%Åâ¥øÊ^À‚¼¼;JJƒÐQãtttÿ—£c!{§u¹ÝLƒçÎÎŽ–»{eèhåY¹w5JÖ,íŸVà„–Œ;I55ÿ…ž‰‘é&ùÙ¢ðb×›¯I9ž=%PU…ì$šàá~ýbCíìÄçxz�ŸŠ9ª�q£ìtÑ‹1K%==ªàQ|¥ÿª 4‘Ùc¹W6¿�‰½»½dÆë±Ô«cg—ÿǺœ Â4Œ'ñU­¤zo IV¤¶:@k€ùKqìó'¹~ RŽrqu5j,ªªa¼Tãæ ämªþ¦6¼s’M5œÍw«XÑõ±RÃ*‡öÕbÿ34»]k=X4ìõ:]Y±wÔB¾?ù\a¢²½A¾ëiê廀}²ªžÔ�i¾Õ�”/2IyU)Ãäá'ò‰rà B<1qw¯r”Û­õ²Úd…’`¡þä¦ÅÍ9»‰S¬ . d÷}Môn¹×bžWÔ¤»¸¨k¢£�žú™°sg9ó˾J¹Hwé½äÔø�ý›…”;�ÆÕ¢ Uƒ¬P¢\Nfµƒ;´vÿ_^¼I;'ö„€ð9j¹l ¢óW7=h ’é F'ÇðàU’?ãÿ|®C¿uû†ê€Cæœ�LÊëySܰ²ô'!skËÙÄ›P9Qëš o=�î#°x9q�ÚÜÜY@¥v)õQÄMu«w–�Ĭß[çÓ{‰ÈšÂKJÜÆeää(@EDòf_ ²G·aº0p}>½¨íÚQ†�WX¨]ÁÈÏ]|c…B®�f}ü{õŸC&Û¸\zöî]ÍFÓ ,ÁáÑÅq¹¹¹ÄRÜþúx&xHÏëx5·Ò¼… X¯OO#FÛÙü 7žît+*â4SÐ'}¢ šåû÷ïïNØççä´U®MZ r~º�^ÙÛ|'"ê~ýÂmßz*GϼŸvöýýcwùùùã©�yÎé"1 â-§­Í”gQšWZZ:ZØŸäWøã9`FÆÆÉ%1'mwʸ¢ï¹i#Í<ÅÃßÌNŸWÚÙ5ˆ%-á¬íµÔÜ+ F,èèép‡r lm™3ikÁŽï¯'®ˆÜËžsA—¾t¿-=æ ‚ÅŽ rš”!î—]. ›ÊLa0zT‰!lB#ëC‰ñQë±mø¨äEëà—îK|&ßfå!ÿ¤(Ûe¯CÝZÇ·{Í”ty²ŒŒŒ _ž4>¯µ¿‰+Ïÿ6zUÕo¾¾Þ>>222À¾ïÎx¼´û À»´‹|öd•û‘•;#ãñq¢½ðnÛ(!3¢ÎA¡çs«nèô­mKFP�L7�•÷ìýQcä½ýò‡úmè{…JŒ¤U¦›úšaó‹ñwØÇ{ «µ1GÕãë€OÕM¨”_ ‚!~Z{Æ¡²ò4³¹w»´-Ñ Š_VPúµ¹G%Iuc§iÌ¡ ø_6êáV¨‹þðáë“äézdµW/à)+Ý×F†ÂÏN‰}kGËi›ŸÚrË�c·5pÌÁH®µ…åQ È;__“¤œâêóf¸%ŠfKÁƒC�×.H½…ÆÔ»y«Xÿ™LBKƔ茠z¹YfÅ(©ßy€ÃI¤}Bˆ�{âÐpé[˜šgµá�cåé¹»äQÅ~ÌKOר7W !ˆÊfŒ|s)Þ‡ß8_™²�ËJÄsÇÞdè2„e}è0þDM£·§‡åæâÑœ‰tðŒ(·­"[1EÞáΜxܲâGäÞ ¼»·„Iº÷ wééבåøÞá À¿ƒ~Ö‹ï#ÎF1%ö-Ö&ùÎ%š6ø6·œ±9Yu¸žvÅLV¯—ãü�Õ“ÔþØßJË/’T4¶î˜¾v™GBÀ$çmP›wD´Ô´F²Žª�fXRþâ$ÆaiH˜ÜÓ]Xì”ú׉—KKÓ±Éå�”ݺ䘢aíYW0zSœ[Ks1Þ\;žOqÌ”ètû¸k JDܿψ…‰ÄøÀ¶³ (Ù†ºwáÔ£ÃÍC³ñ(<«Y!7�lœ&³ÞR‡éü:„bw¶JòV3öYH8÷°»/€íD¹jÜ þ nÅ6»Ø(·í¯Åèéôº4Õ¥5¶‹^d¸ƒæ/nÐÚG¤Jdó¤}ê¨u:c¢nPÈÁÍסWÄ«ò"sêfù9(æª$ï%ļ2„6+°f[«ç-�Õ$â†-}UÉÝÕ­ch˜wÞµ¨‹3Æváó¥#vª}ì»"¡“¾‘Ù×´j½ã‹^öot’ ‡ß£ÁŽcÓÌ)o[„¬ÿÜöÝúIÝÕÑ¡ñüù‡¶6ܪ=ØW7ñ;Z,Ü0ª°ýæ±9Ss¬�K‰œUcE¿æ2½\l¶%éÕ!c´^¹T·ÔÕ±##»‡Þyùùßr[Ë{J—XYY�¤!m£Ñ‹sQ„fSí̘S«¤%³(î‚«÷ X�"àg‹Œ"äŸ[OíHŽjjÜÅ%ÜüœF \ÊeÌZã­!a_55J׆éh0Ý7¿‰¾bÅJçÚ‘X¿.9�uw)ë|OCOýrËÞWÑ™OfR»-(‹æU&*¤«P¯¸l¾¸¬tgõÃÙòÿ̉„…Û }||*p¿HEÖp.ÓÒŽØ7ŽÌåää*áXî³£>=ÅÇ  |aõŒÖ‹Ÿ@¾ä5k2^-Þ,ÍÉÅzUk=Ê7¼õ¸÷̇H:»hB9ê°*´w^^ùEE¯ªjœ›¬I=9€ÍðÔìÈôD=4¿æíw øƒÇ·â©cÛÕ¼ŠÜ�§ß[v�z&<^GË0+¹À-›�ƒÇ®Ãܳ§Â¶ÁóJÛ& ¦þFAÑN0Þã¿}?º§Pmv¨ì¨z’€*Bi½}py§,VÓ[uu ]xe0 ¦ûÖ%?Dñ&|jl;Qß#¿¡ì¸Ó6¦;ÑŸ�ò¨¬È¸O6-®@õ‰‰é�šù7‚:Í™™ÁUÇ_ßη�­Dß²7î$°Zî}R+ýr ùË� òzê)ÚVáý 'M‡óç~}hdp‚¼20‚÷¿…<¾¥®Ô£½{²·6ú`3…RŽZ˜3·úœRS,Ú†D¼–Xùe“Ѹ„)ªÒåÈþÄÍÝ¡WõÀ„cþŠ°ËØË=|±¤wª�E´E8·vÛÉÍžˆDxFÉ·düŸS,¨�çˆýÔEˆçºPßv}ö=fé[Žß JèÌ£&FÊ,/W0Ãc—0V�ï <~wÙÙZ¢.S1"Ž}Ú¥›ˆýþ8^#°ïºL‹å)n¸�Æ¢>¾˜={üpþñÌAÓ¨µxFFFTØÅSˆ"¶È®ÇgOR?rÐlòûO þ ò4ü’tv. ‰úö6™R'§4‘ÍÖ¡nôh[µ~ÃíӣǢðs¶¶uõõi…  ¬õ¡Ž¿ÝލQ�J4(|²÷:³ºÌ}i­âïjÚ½ý¶³S£éªj—E/Ìù¬t›[¥@gcöqXk¶Rl|—¨kúZùV5x$ëY‚”äïA¾úžtv¢6sS½”Á�n3Ò}ºˆ¥7ð‰)ïý¼¦&S@*««;!ƒ¥4¢tÌÌpx›gÏžE¬¯§žÚU6ù’¼áÎ?±w›ì¯ÞÖ„¡²²2¾«¬ª ÏŸ??)l +˜ämî#*ztD½e°²[Þ_à·n󉂂ñß,ÂÁVÃóEþåÐ�˜ëß©UÚùaxnƈëpøe‡@¾g=ùmÉu`_ÚQÕƒi™Ó�“ËäæîÒ‰LC'ŸºeDéHgê—j0}ªŸ?"l×`Oó½ðZÂÁøwÊ´ÚPòup[k¹¼ß`;¯ázj©�œñÌ�h#éx�IÐò3 ÍØ•ìG‹°à‘ÿÓÓÓ!¾åß:Æêê}º2<ÜÜ^&“IΚÆÿe4¦øÂ�I#wM{‚».�?Ö¢©ét-,,"Df)©/t[ VÛý­Íí±ùù:¦GÑ�Ã<“?kÌbú¶ ´mºlvmü·[gÈ�“UG±£¨† �Ó|Á¶Ia܉2„/L£D/�/?)WöÓK¸›Sc²v[Ù¤cî8þꮨQÂ"—2D=jÓX= ™–‡o©ìÂÇgl�]KÔU<·h5œÄ‘i“äÿEî_]Љ”-ôôÂ]NNŨdÜáÉááK­j~\lR�RïìÝû÷ŸC n^^BBzKm˨&„/‡ x  ³Às”¼÷ïf±ïmÊ”œå¨ï]ÄZ_/ÅÖÝz,áîk¼ÎQkŠOª±eù°õ÷£…ºµ†Cs®øÁ!�ºÞçk{ï•;Ã…¦ûûÓQmÊ-!P,2‡J_Î*.IrJêækTiD¡¸�Åž:°Ð'&:  ëiš§î¸ÅZ°sû+WÝç ¶—÷û(XV¦s–¯Ô’ wøyÓ`çÌrL¢¦Á,B˜Ìißl^¯l¶IN‘P›®t]~Å«_’>ÝÞE0íµVX¸¡¢¤¤©¡¡‘´ì·; x¼’á•qd_;?æùjŽ…Šaì¯òÛµ�¸j>huÖF Á¡–•ÁÒj‡ñûJLÒ÷ˀϙ1×òÀÛͧ%òyÐÇÑIÙñ�ie£z]«zd±ï×ï/ó ˆ‘¶0rìœÀMy´×;/OxäæÕÅxk´Ç»OSʼ(´ktühgwsªäîþ"®ÛP+ëoâùÅO–ÝÈäꇷoè¸×(p�…s¹iå)`[;x¤ž•¥,Ñ�¥†O«ÿ¯×}†éݽúþŠ«¯úÆ#‹*’0IÄAàÎ÷°wÕ”#Ä8¡[t—†g#3ÀA‘î;[=I¢ö[³÷vˆWVJj‰{{£Í³?¥DHͳ²;ûÛ7‰š�ž]2«OúcnÎ…§Vƒ>Ý®Tnso3÷È ‰›ß«WbÚ'= ‡ñ�É>+ÃíF�®¬T:VSª««·þ–W ª˜k¡³ºMûéÓ'‰;öHQº#ê}my»˜Ë®® lôé”ô‚¢‰VÛ%bŠŽð²üª*C«‘ë…­]B6h Ÿü_ŠÑf¦‘.]. Ÿ“¢…kœ?ªÆÆ ‰ž6öŸÞüŸ‹ à‹ÊÇõb=Ã6÷á¯wW±@R*Cîe}žç ó~PxdÓp¯5©÷xð½IáÏŽŽGŽ`­ÌºÜ>ü«‡3¸í‡y¯î4š·Ü÷r8ýy/Ã%ûKñ?à™�·ª/©„�d4w¢—Ýç¶÷ÃE_y:†ÊÄ�fb½ßò)òò/NXLÝŠtðÌñn‰¼Å_[6Ïô2QÐ*ŠYpw.ˆÎêåeËÌi¤R™¾$TL@²º×µß9òyÅÆò8>íÚ#QQQð[vêPÏ­²³¹‹ø4Îq’íµv?&å±Ìù£O0–�ÄÛ¶“òž¹ÑæB)“K˜VÇ]‘öú{{G9ê¼ï'“§Ob˜n œ¬­Ù£_�5êS‚(„‡ Ú¤ùþøøñ¯AÙoŽyÒ† “• X2®ªRp+¹D {©G|•ñû›m2Cëá@ÎS÷£eõ\8PV;³©‡níìêXh%æ¼JxÌ$‚8ÕjѰÛZºq.±gg ?g®K,±à”«–IØ%/„n¡Û²&¿©H�>ÖŠ[„!O,çÙZmgÍÏeÄr›fæXÀüMÃë\®µ¹ÉÁ¸4êÌ?)ÅB3{_âÂìp¥#‘`ˆ#øäÿü¤XãÈ™KæÃO\M…~ffä�‘ñ—"Ÿùì›Ó¼7<�Ž�íÆ¶¶^%5™?¸¿¸¶–»œêºxØ×ÙÞ®(÷¼¾š¥¦^¥´´GF½dÜò•Ò·Ì… YÙĽ£å² ÔlˆÇ|#6ñ¦ÜIX|CßúZÜÌzŽèå.žâ@mgg hiÛÓ)H ¦Å{–/(íìíiÛj¤å@Ãá S-R±dµð®¤.˜Hžü†¦µÉua¹»ŸO5Õ—þ£í0yH.Ð ¬%á^ôøîì]éïïŸËu$AÞ2þ›TNòw?»ú~¼Ü¦^‰ÿ¦È:–…Ö×÷FŒJŠbïIò$Ï4zÿ»é´G°¨ÏmP¨+µÛ/7Qiµ»ÕB×ÝÒ"W�ÜïfáP ½Å……cÙȇŠJƒ€Éùû×ì§ê¢a˜clñîŒ÷æjÕ$bmäò�œ‘*mÊÌMò@~Àû«¸îM©1¿/Í©ÅõIõÕšäÙü3Õ]!'E`Û¬��Óvê0ßE¨9±[›0}êÑ Ò®þòeÇÀÁú`Í·)SÓ¾<ÙO9Eξ_ò9«d·YN³WÔ‰©ºpQœLø2XÐ'ã´ÇÊœ©�²ïÙØ˜Ü '‹³~먥ý`¼hi#2Úf5îçÿ,ýË�hÛèå—HHýCùŸz{Ckçé—«õ[„Ú»r3Ò‚N¾~ÜQ¡�”ªM$'â¹Y$·²=6õ== R¾bvþÕ3û�rÓŽÇ 'õÝÃê=õÚÍ‹{lðc­c*D3P¿Ù„ž�}-uºÕÂÍÍ=^õ\SSã¿ML#ƒ×ÿTãüÐ0´î¿[E÷ “Ðw[¯!Œ‚òÑÕ¢ØÕ’ÅŽ\ɤ3 Å~¶�ÇõbSR¦÷ ŽGŽæ€&¢¤ÖkwŠ}3°éœUïô°€8l_>�¿TòÚžˆa�©Ó¥”ÓÎí‰b/4Ã� ôÀQA¹_­¹ 1Hø…t·³I=¬"TcR©7ê42Ûïn§‰IoãåU—:­À˜ U5UlE~t¥Ã* 0šD\¹�©ýÞ?]A8ìÛQ$æôJ ´Ê«ìW¹Åbì�™u}ðã[¡¿ Êç`”–b_MíH…Ù­ÇíFüøP©Iõ‡Ü7�—þO b r¦þgîà$gÝfÓ »ÒÜÜxÿÝâü|¦"›¯ãnÔFÍ<­yë=;ØnôZ/mwû¿ùÖÕÁl°VÌnX¡D±·?À›²¶çÛí›ßŸ»LG^W“RŒ«sG/õ¦ Õ’DÜmS$<¨¡+ÝÁÏÅÔ ½'Lï踵þ|mä”{ÿyw¦8ÀüÑ#Ût…ð¯ƒWøÒcbb„X…ÄÅ ø¶>Ü¿¯Às7Ý,>©©ù= `èç}2þûó2v\†\qrÝ ˜£½Uõ½É_úqný¤,žΞc‹î4¬rÈ¿¼ã“½jD_n«6 Ï!(“ ,•íU} RËžP7²Uo;áÿC:&Ñ9 Ī4!} ‡íˆ}÷þ•ͫɼ½ÊÓ)hŠäÑƂU-b¯™¾þwöÕ$§¦oìV––y<Éù´¬’�”¸ùùõ¬­sž?^L:NðgF€˜÷!`}É'¸xv›gw•¼6¤¢ætúÊ dû^>„²™L ©`®Âû‡è=ì ßl€æìq‹o¢FRå‰lÕ„…†3!‡q2õ¦”-nã‹zyùùJàpk¯í‰³s¡jrxx8k5þÁcHÔý˜9þ«Sù¸0âtx?×YÅ2æ@à«/­©Áö…³â�{Øe¬KÑ £Ç.u'-**#S1Iî}¶J±–¬¬)új:,vcsoø?²êÃrpnh´ÓZçîré}ÍåúóÉ{•Ħ©)cÀÈïEGGC-~_ÚÛÄ™yzz¤ýO·£²š›ŸAؤ�%<Ö5`°X°„–Xé_²�\^X°m ¡‡ÛÚö3˜ùdØ å(E­{œ0^`U]ó½C�•´{I°Ä8eðg1ío-Ö1½ÃaÜ1CÛrçî{o$S>QLÛ‘R⯠g©{ ó©8™P%æsL Òg»Úà çÇàà[áÜh�A&a²Ú^uX ¹3CÈá·tλ¯.¾¶­ø1Bw+ýžc;¡ËÃy7Oø­ER4…Ÿˆw;”,Ù3oUï�¯­MŒ�Þ_ð™6çUs�0èM�6&| ²Êõl�HûtzÊÌ"íó Þ,�¡§‘ÊQûFc×܉Y�½`(¨A4‰,2ÏJ6Jõ˜š‡þ; ç[wää>Ž.ßlÌJ5欼m l:$Ú}c'ÎÒ亵"m�ªœW)Ü€°8,†@Câ|«•UTbcc³)©iÿ%d“o=S¯^̳F.ÔÞºº@ÖlßÝaú­t\B=³œÒŽÉÛK-Õsçܘª{¢jO{ç3ÓÕbD0­IñÏ·ø.ŽQn©ùïà÷Yp>qÔÌß7¾íøG. fõzij:“ˆyÝÉ(ê[¾Ì§ƒxµÄ±¹ºš½*´ä!†È�”us#*Q¨?ááÊ?ZðªG<¡!á¿9ë™(¦É͵ºãÂüíAz³l¾/©ÖqÒŽmsÃÆü ¨0Ц•WCÃØ.G!7¹cu;(ˆæ? ™$r§èº¥ñ¿fý±Ùÿj(ìÐùmQÆÆÇ¯Í³�ôgȹ�š ¹îݢ^ÆãÑÇH šK;g¼©CÚãWâÌ=ÚÄaÌ;å.²’ý=<ƒŽ'MFfŸÿç“°ß"ÞeõLߣd�.æÚ�IpÊ›Ìtçn~›™:mw´obùÆ·Øð˜0æÕµKÒŽCâ}ßNÖÁoTŸ�Qºca¸hóÛusAwvw‡ÔÓšº‚2Àú h¹Ìo¾kÅîìmËÌçô�Žš¹òcžÓ*´#Řß®úxzž¸ ž¦YKˆuGo¤ˆy`2—Sôýôâç£)<Š©ê‘ŽC#¼lOé¨Å|Ø € )[T�éG¢¦ ˆeGNµ´)Pòõ`°tz—rÞRÏÅ‹ÅýÅî´wRdR_á<÷‹�w ¼L ´É)Œër¯n·ÏévûÞá|ò�µ®ºˆ×ãªDÁOFáæmüY÷Ï”ƒ 5éÇþ ó‡8ïqBTªV·_v“\¾DýŠùzÿ’;gÎ_ôFÛŒxG)41Þ섺sY‘¬1]U­G=V—þâØÃgŠÅ(ûðÖ�÷¿ÔVGg õÌK{i›€Ò,¨XeFÈ—”¬¿Ÿ¦J ÀºŒb*l®%Š»Z£a§ëå}€(¢OxEe”Ï [«®ˆú~{Ð視î{†µ™fòÿ»ÀÈÄ*]6ÿÖ{®|VÒƒ,iЛàwKzršùnOD½rgû ™¼ßË_•ÿ4d>í‰Ï÷®ç'Ýæû’9_�„!˜ÒF†Q߈Ñ>¤Hü~Õ¬çº;]ÎßÌã�ëãTi¼×àu�€B­¯æ-=eß5I—ÿ Ü“’žò8¨òXc¹KTª«8™>‘¨Æ‘6z6“½7Žî�džs³óž)8§ÿ>k~0wjãyá®%„âQÈ�G!QC½ä\2†¨‚4åŠ6]cßñKöÕÍÆ¨ÏÄÄSVʆ†¾õ'¦}(É`�x_m+¤Êß\*oä+…Éš;� Fýi+VkèX'†xÎȶÎ�A¼)¯JVDÌè_ {ñ„jÚÔ«¡BF¼»)ðKÓ~UŽ0·öº‘Q ïsøDI®,¾RV^“qîÚÜnþÝ€gïŸÝ}f`ÀÈ£Wt!½ºYÁ ¤9keqM^¡«ZÂÐržýµ¹©q Ý {3�2/Gµ[õ"m?ÙC78Ò¦Ùìë-É' ‰÷ß ùÜRŠìŒ>ß¿ž›óPóFó¦Üû¨¨AÅ_G ý²ñ\ž>Ygb²¥x¦yó…fÌÝ9Õ7x$ûuó¾T©L.ÒЙ»¢¶”USÅm~»s¬ï3S;G¾§qîØObÓ}ß Â!7ú_K†T1…ûXXÕ¥"VÞå¿ýõ¹šžª;¬ý|TïÁ’ÈJ°Bu|fªuæ»×5ô]¡wǨ’&9{3þ'q£ªÅú*ÅØ{Nº@½¯9<ýBlüç^_}„ b¼àõæÅ:È6ÖÜ2"tÝcplýÜ×wŸ*ü_ª¾9†@n2`žÍ^CàšZûû¯ÊÉÉqíüIÐ~éÛBËͧ^ .Tl­kèŠ$ËBš„¿MyA[}¾À]³îõ[Ýúª"j))ÆPb %˜ü·®î닃§kx­”å'U0Ø Ÿóg˜nwè_¨MIñ³¼>ú»¨—V¾¹ÒÎèþ�gyEùϯúH˼ýBôQ—C’8-Dó¨(€5¼ù¢“põ½êæ4ynÎÜG'w™5MèL˜vÇd_ÜYû{°Z�;&ûÇá}ÜŸªr«+—® Õ¼{U–�‚áÏ~øõÈÔߦˆ«¢ Ã÷Œ«l$ÏAÊÄ��ŽÊ Ê«±öÌtÓÞº~Ò*ñzen ÇçERš¶Û~&I$¹ˆ0Y+ÖML¼-þâ{¦Ù{°;0ŠxÇœ>ðûbNŸ%ý]Aû9VÙe³øs:`ðqŒÊQpEŸqt£VÙ>« Y®™«í,ø¯™Ak˜ë;¿mF ©P?ßÿ¬`ë~ð”6ˆE•R%\sR<&Íp|üã%óH¡+Å,ñè󃘛ñ½ßߦŸ£CZ“5øvó¼Ð8Cùý x:S*.PÉt:"óþîð°ØÕ'ðó/�ñyš+Øþ¿ßp™8"Ïé¿ï×€{íÙzäÖŠ¸À ó8þ3ÒÒ®Õ¾¬ò­öW…»'±Ð“®«Î-ú;K¢Ê®Jk¿’µÜ³„‹«h„£Ä>ƒj@tu Oå¦å{=¦«©0$— — /Dj�…¡Ð¿Ä“ÚëwR¯1-ëÏÁÆ~€QSÛ!ó@îãóˆMyäaŸÊcã yð�€ë¥m‰Waà|{¸šaàÚ(âêùÏíóÒDÓУE½— $ÊëYª¯/=ª¬u Ò‹WòEÏ_øÿ1 ±´Øxœ]zT[Q—uðâZ¼@¡Ðâ)ZÜÝÝ-¸»»·âîîîîPÜÝÝŠÛ¼~ÿ?kÍÌ]+7yIîMrÎ>ûìýòBe¥E�ð@ ²˜¨ <1<öù Ìû¿ó%�;{y~ˆ¥aoàÌ'ÅUG!¾èÁÇ$¢òR �°(zú·ørd�ŽuA Ž$Ç&­EŽ xÞFTÍ¢#ûwƒpÉçqž„s“‚;B„DùŒÕ–~» l äStY>KwvÁRºì¬Mµ@8jçÿ‘÷á‹�C1B%)‚Ÿ<é5?I¯O–C7¯ìQº¬ ïâKÑ4¼V4U6yôºû c'ä°±³±“qïŸW¢á±t§û çóŸ÷iöx¿�øÐ ùñK ýH>L«ý÷äçðûM$éäÿkÃÿÿé,ú?+·‰CÅX:;···ÿþýÞÖÖ–öoæË×Ðô2 öLLLôôôÌÿgLMÉÑ%ÎЋ£A•AÄ‹ïÕ‚'3¸\íÞß^%é^ŸN*IQ™YX>q8uvzûÃ"›-Tœíí‘ÇÉY§“„, *HÍ—OLtx>;îþúH�“›KíÕÓ`¹âõ¸—h¹Ò`¹X•ó: Í÷"ᨠ|‰=»aLbâÌøøÙÇ“Øg<6]^^��ÛíñÞÁ¿ÁÉΞ_P@§Vc8ú›6–nëììÆt½ùÂÓí ’;Ú;vúøøøOÇãÙë½Q---8$\4ll§ãYÊW}ƒÁȵ# aaáþʇ½D�HBöÇ“y„@?/?’·aŽÔÔT÷Ý¡<&c.—ËÍ9›ûó5~~‡C^3 ´>Yp‚øÞÑL!x,Évo$aáyñõé6•Û³%3“X ãƒßÃÁ¡…¢âlO "«Å¢¬'‘×Óß#__ßŠŠŠY”~.;Ž EEE*•ÚiØ‹Û{{õÎë@6îÎVl›Z[Ÿ÷ÇS[�ÎÄ; WõÎŽ�YL¤±+Ù+(̸ߟã}­a``¬»_´/Õ‚ÁFFræ@VÇп޳‚ÅoGÜŒ4›l²µZ�f�œ»(<ׂv퓾Py�Á`zúJDlÅó39(>4ë¶ ÈË›`³{*» ¡0ìµfûƒÅjƒº0 û³øüŠ´M½ 01ãÐÉ„ 9�Æ™…„nOèk¦/ÆGF~©ÿÛ‡×[¹X±dÎl‹®ü¡69990(Ȭµ©B£±‚J°aWý?;�¿ªÕ«·;Mwûõ67_%«Õî‡b~¥q8š­·¹¦¹ý=ün8�M«ÎÆÉy±X�»ÍäxB1TFËÌOz½¼¾žQûlamý~ºÄÔEKCƒÏb&8B¢’W«Ñh…Œ‚r¾T[è3‘’’2_$GÆÆÆöý{LåX<¥ÂËË D&‡Ã?¥*•Ê/ON²lËXð .'ˆãt¾¾0_ !!çËÔþ³£+›ëõ72ÉçÏ|My¥¥'@hˆ¸=†T6ÿd E¤=îKM…Ôõå%C ÊqWàJŠ^‚MVµ~iW¶��Ðlþ�χS¹QÜOE»ß¿ï 4Ÿ+!?uD#"¢³Ø�+’¿3áþ|Öè*ÑDï¿4ÈÕÃO¡¹ŽøHyÝãûÞmå ”RI-;;;Ÿ««+¹7þXyÜpÇÓqéˆg¸?ª›»;1»]€}¥&Ê—p¾)¯sÿ5{먟°Xê)zçØ4âf1 ¯žœÆSX‚Q>Q½Ú÷!Â+þ…Ûóéï'N§6ü�‚há§ÀvúA'GôíÄø—™<�B{âò)“"dth¹zwíbq¦ÖÃCC¢˜Ü#KK®ç«ÍP°H.¯Ú¥:¾[#$qnpÇùÞŽ7½y2ì»-MMIËh|hJEµÍÍÍ …Òsii4û—CCC¾PpÊSþR)̦›€ ½Ì+%ÎφÁ£jD†Iˆ± fu�Ös´‚'‰&ƒÅÄÄ`£æL˜õôô”îuÛJXk½¯3�% )¶ Cµrh ûóç®§¶’ý€�Kèn÷‚'5:šÁb‘aB«ººZÿ©G^  ¨ã~PÙlžï,·z $mç~Ú;ÃÙ 3fúì]J?}Z·ôcr.ZBƒ¾ªEGG÷XWø€É¥)"Bê`"½_‡Ž‹«Í°}Åõf?ý§ûÝN6�÷Ù2 )bWò ‰ltOóRÒgÿ‘¹$Åw¦ôÜýòh/=jóG{ NNΘĕGsYYYSWNÎVJ…‚�N�›i¯Ú®!‚6�¿êU¥ƒž¼^„Ŭ`NVò  •µ=âO›ÝPßQdXÌfÑìË/:ßßn&y–&8/$“ø®=q^QÆ)?Øe�[Û»<ï$½°ÑËËjMMMLÒˆèèèô9^¬ky_Ñ%Kb£¡‘<Ʋ�ú&ƒ •¹Uw_2»Ø9ÚÇŸØF?È¡r¼KfJÐOdfeÅQ(«ªŠ¶w Uß8–ÂRÚ×Í‹…á[,»�Å619Óá!g½´´ô¯Š Ó•JçÌ*ÊãÍ998~QRD×gy¿¿å¿s@gz»œŸªzA1idp[‡wç¶2{?>þ[‹&\££¢¨ˆ¥ƒaÉŒ!iÌÛ׿þ*“qz:Æ)n=•¨IG¤yº¤À_ žˆêâl„ïô»èdøî‚dÅ1ÃÏ Þ¯Èïí´? § ³V²±¼¼<""Â&èL·âåeâkDlBN@BU*´…v/½}”eFÞnŽGܧ™{G�º´ÔÌ~”Ü1aÆŒƒè_ªHÕkÖϰ åí¹Ü`yrqAï+�ÎÚn³ÕÓP¾bÓHVÁÚW^íR‡Ö5FUÈhC€)þ¶H·‘º`Àûté"‡Ön ŸJgrk©q v-,¨èZËÊFw'U�áôõ¨0p¯Ê:$ù…BZ {ì5°‘ÜN]CeÖ\]~ÂDĸÈÑ“Ia!¡Pšáƒ/YéŽNÉ))Nëk›xmííäý ÝzøÎÊòÒ¿¸"4ƒ|ãßSIBÄ–RY­Q:]m ÅÀ£¶··?:Ñêt¬vwwoâa!ô?\í ã~«{·1Ô¨`#8ªí_žá˜õ’h‘¾Vož˜¤ÏžÀ2Õ¶qtÜ[™+U¡�qžk¶š ½­‘ÛU¥e:y¦&1«µ’  Àµ[�¤$‡w£Â¸¾fN à1 Ö9ô ’ókYÚ¯ùçÅúßÎ/sé­dƒŠã=v¯"h}¡® 1K­äz¦¦ì.—ºžÄ°¯°ô²7'd.ŸÐ¤¥êYè?oç]‘e·ãy‹¶¸"Â<‹¹WÔý*ƒM Êy ¼×»Åén�ðUyýuàïÀúôÓÇÓ ný þ¨óFÓùóêŠòÉ€‹=D н…÷ ¸‰ù]¥-f¸éšò¾¢.ëŽÚL¾ÎYÕXW¯ÞÅ'ˆëëká(¢×Ýá¸iÚ0lZ³™©nêÑ øúI5è»Å …ÉNß9R•YìOÎV–”Ë]bQû…—™“ZËRSWü`ùùù*ò¡çÛÜš¿-~¾ÝOµåàd`ds¡¹W�³XVV«}Ä`µQeˆ¥Ãý£æø]¾7Ûu`�ÏÉB~� —ƒ¸ä†p…å=e7ŽfT Š>ˆmïà€úÞŽÉtNg€Vƒ� ðm—€nm½ÞÖ¼*s%Žá•Áã�q Ûò…‚á”… ¯_$W8¡a,é{ïðph®‰-JÆ$¤�GäÔ“÷Û 1”ŠÝ š�}5—�Û‰ãªkÐ ]›øHž4õbßðÞ]t>~þ_5WAìªLø@ä�·%h¼2FF»Õ&[E÷9!0ŒrîãYãÆr½9xõÓ!g×ÝÑLpëam®ì¬$ ý?ív’—@©Ô€óÞhJÂf(ñ}{ß\õò[�Ö§¤ë€´bkÒ“b]!�ÍêdM¾ ,­'Úu­Šµâò÷v€;aùE0Š’’Ò` <»¤T˜#5¶§\p4‘þh5F£á­P¨¨Q¨ÜßFü©6Üo·áêózßÞìGa1B‹üÊìizU}…ÞõâBfµa-ÓëÈàuËý¾ØüAŒúa í�íßÖ ÝFâ�öùóç4‰;[ï&¥b¯‡­�Eë9Qð#?ªÛý¹çéb5P$¸ž˜™^êÆÏÄœNeüÝ ^œo4³÷#-EÍ„'Î,Öë/�ï¯ôQ—ìÝ#‡³0«͚Yoi�U”/¡.ÓÄ&•à ‰ë(Ù+h¬Ÿ·noëoé£s3såóµ¹¥ïÕ>¿¼ÜL>�³�dUŸh�Åã¹Pïôyö!ï#ú_>éÄé�–LKpÝ{Xä��§¹@fŒ¢A‰LÖ.»fçþJpxgs]høY“ñˆ9jBûàB�µ0è­RW€;Àü�˜øÕ4ðgŽ_’â”kæOwŠ- IIlzm.‡CÙõøux·›‹%‰˜ïw-¦&XÊ‘ëÈàϯœg¨Ð Ei¿b„膂kóî|äXÓ¶´1yk)f›‰Ù‹lÂâTkÉ\AªÉוI~úÇ<áÐ6Áݯ¹lPýjô„…»ÆU6È*ª®|Æ×õÛÛ»ä9 ˜Bò}Œº‡²ª|›ËÚu·Mqä?’ÜÀÛåf�ùK¢ :/Îæ—0}³�y„v�̃"óof*k×Û�VîÖXúM—/”Ä;ዤÎNž Äz«5Ž3ò7áHBµZãqÀVÄ‘tx¥³$5j¤Âîí²u{” �‡‡'c@+$ž˜½a�ÈÏmðn93qAͧ:¸<êÏÞ̹�§±à-Þ%cÝM ˜FQã]Áè¹ü|¥A…–µlH`„Ö%þþ|èWо5QùEÁmìÁç#|ù ’�¯Ux/óÑØ6È•«Øg7­L÷¾ã]¾Þ7Ó »"6kzl)Ðø3.ö1xÃÏ1�JEI{$.(A""Ù/| +zaœÊy”ÙW¬MAb^‹oÌn°Ô­h³Ä0æ[a0Å.-ðŸ}´ÍŒoÞ³î„c`t{~4¤°ÁûåB—®”!â„danβN˜÷Kc•権b¢`cß‘1d"w' •Іù¤|`O�/éêyö¡z5Ö7¬ã£«+åí²7 ‰Qª ]‚²Ç:ºÈGK ›ØV:¨v¸Ûñ×Z£y9ô¥|5óZñ¾mŽ\T:[—«JMì¬(ãµL÷¢¢7X”Þ¶Îð˜.ÊÅ—oêò‘u¿0˜|"Ø©Ž>P¸'KÅÐFd#}�µÞÔ£Ÿªoïð›<ØÚ’[ΤÍÖéôj'¾íòòrZK  �Û£)ïª5/%ƒ Œ’™˧H57ÉóÎåö×ä,ÐóÈÓÆFF0°°€ËVK>àfµÊ#ŸϬÛJoÚ¥šU+ö‰"Ršh•z¿’D oµdv9A‚\è}Šue¸È§5$FT» l¶LG–$dÓn®ñfbº�L»jÿºåÌ×ty߈ î¹àïÒH>þ¥ÛG±Aºþî h,ôà¢Û7´PÍ8<{Y6”ý=•p™Ž¾™<~W3¦‚ÉMØ ÙŸ�OÕ›¥qÁÁS6& !š×ìlæRW±¥‡.gòÍN7…6å;þáËp;wÅ:×ÅH‘WL“Ot�‡Q(ƒ?·`u¿nûq�*0�7¹ :-è.‹’MF¢hfı…ÄLŶy…G–É–ûbvÁ12ë)!1úxs`ŽÊûU2)ëlèv'ŠÇéhº?df ¿_*‰![.Wø‰±kuµ ˆ‹ûÝéT“÷ËÃU»ûý—¿eû”’Õ£|ÚÆ\Š�Dz„ªhl¼{}æt<γÖ0Yo{ŒüÄY‡©1™ñScW¾ð1âââfíQQQ©©©wjFÇeµµµ†æª’03ºµ!&&UÊxʺŽ1²44™íŸýCÇÑØ¢¶§–´tY†$¶Ï¥4PôÊÚ6Gâ²¾z-„}n~¢pžc?¿ë¨‡<»Ï]Û˜¨r'SBIà¸8û¾c¹(ŒÍ�z*Ĺۥ…n(K÷3ÛÊ�Îᢴ"”½œûÌO™4ã®kéia2 5g‡ki1?ü*wNº¯�¡7Š�§øPnðç\åaR(ØÓÄ) Ge*»fŒ€’µûšY:lòjB€>à ÕÆÚŸÃsÊø~Z|KG(èzËíî¬2Ü�†³ErË¡:Bú––´z¥€ƒ_BÉdrt ]]]¬ÜD2“ˆûP°u Y‡6×»b–˜—%J¥€¡•¥]NV�Èøé~2Oëï PEÅpÜWN|žy �çm�Íc'èöñ†"§�¡ìÓü�7³ödËéÊ”“>>¦æa}åÞO‹«<ÂÙé»l¯ñÀ�ZÝdÿn6Àî�úêâôè1ÛÜÒŸ¬á‘²¢7_ Ið_IDIK€Ùn1owbÓ7‚ðTÚ»� …�<1d"å.>|6kêjÙ5/ŠÉ|ÙV»ŽOˆ¼:í[±V ÓF[7æzÁ6Y*Àmª.Ï|„k§«0ºü>Ð]�²Ͱf„ªœ0±!L¹?_áQÍÚØÓ¬3‰É“kCxN¼ÉÄÄ«�OOÿi»-xö Èÿï8 >„ŸÔ·#¦¥¥!ÝÍüÃØëANàh¥z½8'„D MzH¦ Ç÷gãµ­¼Œ2‘§¶š|£@º‚àE¦ݾl¹I!œúJT5²Œ˜}Ͼ÷¦Ë{(�0¬ ‘˜xGVÆ’ý½kæ}7vÚ3ÚÁ'›%¨Äv›áO§¶ ˆç‚Pª!‘Vú»E�´÷}‰Ë%rßÅ4�‘“é55”ÿZω¶Dvboųҵ´»ü–8g³Œ”è½2äQ„a°´¾EÐ@90ód·R}„²‡0»LÚ$\/¬6À5þ{8Å¡&|ºDV5T'ºs˜j•JíOhh›GGà á@u… “ è!=Ù'÷:8>>>óùø@–ù2µ›ë½Ñ6Êw�©©)êïüb.—*ñüô¾¾¾<±¸š´ûå}¬ BfÙ~oDÓ­©Ô Š^€ z�º�On×2S%«Ht¹M_#õ½|b©ç’oqf4AšË> 3;Ê7ŽŒ6V þÔ½å!;¡ºr|Ûx_üâÇBEܹ¬ #´39l‘=àx x ŠšVÙW�Áxo[ú¶ÎÜv*¶<^ëÂ\k!¸ÖUJµ)üô®Ü“ˆ§_Шå†é¬æççÃpè¹].u‡óÆËÔjû*.ûzó%�ÇSwEÙÉ4,,NŸn�ˆL�n¹)miÂÈù¶bÝÙn0hr\Ü�ßQ˜ˆ¨¨ Nš˜½€ý¹¹¾~àx@ê ÑlietlÐnwcL–tŸ—?êM7bZÕmRÝG1á�”i5[µUsUµWá<�†å‚´d>,5‚.‚ªån¡ÀòYp]·¢;uf5&=é½/î ÍT­É˜Á{ÍSĉvC¥w�‰rU¤ T�WX}@� šŸå<ñ›óßœ—bTq%ëèè ¤¤¤Jøe¤¦¡¡!6u³·¨Þn<Ž¿Þ–«®(^!äì쬡Ñ;ÌE� wµ—žª1„eš`edô‘��¦æàÐ%ÞN?³ó:²»@ŒzÊóò<ïðì Úþ®Î¹°Ä©³OYGØùŠzaÜöYþWÁIàWA¡ 52ØBm­æ¹/WгB¢êÕ£h?X":óq¼= T uèùËn~]ô’mnnn|\Jþ° P«;Xj** ÿNnJþĜʉ$4µ9SùíÉ4ïû{ùhÄCÐßoõOÁ!#³ (Ðï3ÐüüÃ…¿vußÊO®PÙÝ‘óQ6Ú¯¥;Ä–ïAl0=�£÷+kð�É—P��a+ü±ú¹Íi}OVÕ¦ºo�ŸéѯȚGSn}«þ‚˜‡šÂšðboŽT[zL!ä �­dàùüˆƒÅ |Ts®^P×i�ï˜¶æ¡ ~È“m}`úýú|O�õ+�ZåúQñˆfQ½P:ýÏŸ?fª¾±,@^IŸkP_M1->䨠‚ŠÞ�’|{„Þæ à²]⸮¿ë¹‰ÛÓÄ@”·á _!Ëw[Dl��&þ] 0â™…Œ,‘aÉÄÄØu C‘³‘xLA f¥wƒ� J²§æ“ŽM¦óÄÞß^û;í"wÍ yv-s‹usv’›¤SÈÊ…Cû쇘÷K >ŽZpX�*……:x˜ûh¸÷öÈ&ÎÓáî»™_R¹ì[îÜo¹zAŠq©±`¾…µR_é2M7¹ Ò½1œQô|‡Q†<¢pÑŽ �kâÂ_(${£Ábqùõ=„ž-{| Ð{ÓÝA^tÖÔ^‡°A ˰÷H¹ä è8Øìlk€ý–5š‹~Ô“nÔæ–‡É¤p˜«!+[åv{|­… ß¶²"ÂÇ/‰ã34¸´…³¥Ú»ÌìììÎ×»Ô!jü1²GeŠ81õÏÛã9�5/í«ëó‘�pýö*¼9öóų̂´ƒi‘Jú‡3E˜ã•+Z?«Ÿái)—玬Ìþ»={ŸLûãP9OD43ýD EÈŽð­ÆøãuGÎú¦B>!(qÒ5S¶ I^¹ÂÓ½|½4Û­܇¬ÂORíIãœø„Ú²u�,ÚQ6èrG‡¬ïð{îÛ OÀ�©«‡R‡Âl ÉÃoþ'š¼ Þó•ByŽevKAAÁKJEyùéxÌ: ‰1”ؼùž}ÀÓî DÌ4.VÔÖÔüÇb2û³~Ý�£C£^'Êöîèæöt¾Öz¶¢âéˆÍ àš4ò–øè~#7C¹†U¬£0szÝ·Î_˜ËÅ�ôFKæÅN’f‹¦ÉÄÏžAòÁsS+@ ŽÆ™T°Ðøe'ÈfõħÔѦ�e¬TDéêI–¤Ö»«•Í !›û»±esí#+¤7K/‹שoÎ+`zÚZ{®ÍÛ˘~x¿½lnn¶:_ü®(­±�”ꉀÕJ3e³$žV£Áb½��‚BØ#Ãð€°………=N¯6Ù>É`aåa–p8+âà$ …aÿûKhÊbbbÃÃá¡¡÷}z¾¿¨üQu9{2¿„Vÿp À!î’oT‚Ú“6(ž²%ùýñÛÈÀx ¡Õ;i[Á3,ŽÍ¼‚<ߌ¯9˜�ÜN$´ÞÞ ) š×¶QÓܓӃBíÞq½#6óùx2ÙnÏA]Ï[qc™pàØñ´m=<=#íööö¦2›êÍ5ZoTv¾-/,, ‚îÌGÔ:Y´LÏñÎ%þøÈˆÐ 8Ø•~X¯—eæ¬LMU¹Ï�À+�L¾>o½˜/V:çÚZ;à§.:ß;¤3RŽKkɵÔÓo¿@³9F|´¹Á o!cq`¼¾ºº§Â"þ± 霛|AJe¡ïl¹xó‚�¼iò­G‹SZR%÷‘ÀÖ½ ïH÷‰”§ÝåkÙÜv¬oŒ›Ñã#'³:¥ÛŽÆí:…‚j®YKNÕò'o·³jAzÀ·š®6I?Ïÿw�M*«µ×KSZï¤/œÃoG†šë²2l:͇qÖ�CQKYÙèÈÙù -÷‹õöØýˆu¤t–ïßщ `7�•¦Ð+\ 8ÂW¨rVÜÃF—fÄ?ö8)p„’W.øŽªB�AÚܰâ_>çS•m½#Z ¿jïgv_ ¨�÷: ·cgØlª±õm¹÷žX�Žÿq€ßµ&舖@dn…Ljû5¡gÒ )åíû’¾ð»´ 2úÊ… g:Îô™,,ªhjj:]®Õ]µÜ¥õZˀʼntΓð§ˆÞåe’,Í3Kq€Væ�?9;kw½Ù�¿Ã`¿/Iæ‡þ"«ôn;QÇkJ�;.ŠN«h÷2³Û¬Î^è¬ù2£"U{2"yr[Ó²XLÛ}ïÎÉ�6ÁF?Èȼ�Œ¡¾ltÍH®œDù¶WnõZfóÒÛmÔ ¤à—0+ûïò ·Â%sýA:õÛZ'°*×þb7W„æ 1Éš(�cz‡Î%’µïÞÖëý»Ï:¦ñ·�ÊÌI‹œY[n²!×#ò0S×og¸:¼0ØÝÜv~º b‘l¬„ÌqS˜ÂÊÍGîÓãŽEù¤È$(QM«çV´ˆÁÄÚÈI”Ï©æ0$…N63 …cþõ\§z-Õù–ôGTÛw¤¥mè}ézÂîH°¹ÿs1†[7˜kuùó;¦ÞÕ>|ëæ ŸZW“p(çM–ë—#BöǤ*HwR1jÞôWs©9^4Š÷–#5v¦ÉÝœ(Jîð¢Þ}jÚa/èTì1¶n”51d´c� …Ý�ïj¹©sœ&Uñ<©,|ÑÊᛎeâ½’/H¦cB’³¤ÌÉPFÜe Žˆ¬‚ÊD•}Ëd_jüf‰Øàò*‚(JV�nd6¹?h¯5)b@Y}·„J�› @Ò/£ÕŽSßÙ@$/`ŠS®Y‘k²+}³·1ù¤;˜Mò±Ä£QW Á¼· š�CC�f±{ _wKt`|+â—ÛeI£Ox½0£ìÁ×é7�"2ôçpèN:ámþ`tï*ÏS+.ÃoÚª®©RfIR ´¾“Y5²ù—æ»^¹Ì©‘ŒB#Ç0j®zœ3LsÑÐWÆ·` ¼§ÜýrB�Oœ D ¯üK¦á�Õ±ï4|h µÂ:Æ3¶ ëhã-’N^Žr+»HÆÒes&ãųHü\ Î`9 ÌÝ$ £´=Ç�V!_Ü0ê; ÁÂEÅ0hÐrs¢—¬ i¬ÊÐ*D5?‰h“6'“Q¤Ôìû©=—˸Қ&<CÛŸÃaŸ¡ùyÒË#7t£6tW˜u)þЇ—z&”®ÏîG�X\âîÊiãú‘””NàÿḊ;ÛCT)pÇyþ�?îi&G´²£°ßùì”ÖW°£D^ÉŒ*Œ&˜dÑVëA³Z¬Ö„‰äõj¸ö²y‚l N¢Ÿ­\…ÜWc<fzwSp¼Gƒ¾‚ý ¾åR­©Ý5Rï{¿Ó{ñÀ³Æa[éÆ6.`\ÁÇWÀT¾»¦›ÅÇ¢4b´àû>]”ôE³cЊ]lYM�…¿çIpî¬V«Ñ$[ÞóÉ’uI$�=!Üî€4Ÿ-⣠% ÿóï:Þž¤7b%>4èqý¿‰�CâMÑÿsBË«ýïé,ú?+¯¢vß!nõOI\NýûAÀ’¬à×õû/"&­¿‹xœÅY]sÚ8}çWhHgf‚ÙmÓL§ûRh×íæÉöÅh⯕äÓéß+�³@lRÞ<[Ò¹çž+[Gâ‚t’tÅY0—¤á5Éëß__“ᜅ,MÉ œ4îõõC�M§`xófíbkޏ"Ÿ“$�Øv§v�­6ó à“,ö@Î�˜)õð²i¹$_€ –Ääµñ;i¨õMS½ù'"¬’ŒDtEâD’LB0Af cÀÒƒT/‰Ò�ÑØ²`r®Ãl@ „¸Û@$SI±7Åþ)ÞͶû*5á¹”©xßn/ ƒj¦Fƒv¸î%Ú¶ÕéõG½²Õý'qBÿdŒcžÓ¡)’ñè)†tANhÀÛd¢È.8“,.‰HfrA9 ŠÏ„älšÉ¥rj˜ðvԊƤnŽˆ5ª“�æÈ]"ÆWkü×`2&_MÇ1ûc«7"‡tý®5¶}¼ûDÌþ¹±úÝK¨†�eÊ$É”†à+ÁF;fÉš�HÁc3æa^q�ÑH�|c:$1¡*)�ž�(!‹˜¤R?y””Qó"znÄbe‘› ØøÒsFH—¼1þxÓ¬±Ø 3½h þGS¾jÖ@ÝLõ�K}ß Ù”ãïF�Ýäƒïb¨ðA]ÿ¨«_¯~tnÍ›žÛ™8N¯?vGƒ‰Óé¹]ËùÙ«^¢Ý…ÍBi BÖ›5/‰g,È8¸jÆ5žBY„*'nø îe’,. üÑê›Î]áÕÿàoß©WËŠ…¤a¨æÍ4Ái½ +Þoá<Ô:·•Ðùt“ë'­a’x8AxëéÆb?Y€*H”ø¨;[£ªd>Y6Î’cùu{£1öP“꿱V46mÛíšcÓ Æzð6µzóÿŒ¼/ÕéµÄë”J |uJY�ÞªðizCž\”ÓeñÂÄ»?Q”�±ç�äàK’f§éüTMö†ŸE–ÌP&ùeö‡ŸC™"ÌP&‚è9Êì?‡2E˜/  ¬@œ¨ÊöÐs(²�w²3\ŒÝ”'¨oÔ@”›ñ`è*.îÓ¶p$:¶¾y‹Ô6ÑZJµÖw2ŸJ ƒ¡Šiژˬd|ù)S$AH•)Ñ€d“�»ÁÕͺµ3¸½5û]•G1àÏúqë´"€nu(ô,±T†CšpI€ó„ 4‚T´¾ÚÕ×gËR'+�Ž •Dè'åj�Àå.O`è †=g|G>™–í:½ÏÛtÜÞßC§7ÒN²¾ŽúžÔ˵٬¨ÊS°lW§Ti:gK¯µÕJµ»œW'TI*ç‘i³öV¬Ôþ _¡X¥ �E¯|E®V¯Gë~uz•'t½òuºZ½¹�êô*Oè,z©U¼Z­v|Bu:'r”F h(†n�i ô.ã :¢Ñ=KSuž“ç±>kÛ8uN×j‘o…îä›Îb–d±¯"Ä>z”fm×ó˜ÃáhìôÌÛŽmmœZ$!9Pü³]ƒ³Ýù [�:Ú›%…uÝÆÁj>¸©ƒN4á�±UZ}Í£Ë(ü%Í4é܈ŠAé¾’ÛÙ—xA>©Gê0§ SFãß âLHH[S* Å³X² 2*(β{€”èC,uvá$C.ðb‰xú�t¡æÞçþDá¨s@ð¤0È UÔhhìÖmhãË�ˆXx]}_R}â†óS¥¹šEaY4FÑÛõ s¥¯ÇÁàO¿"¯~èÝe¡Ú´p`ù³ì£’SE9[¢ ì×ó, ]iš!å¾pçÌÖÖ�Gæ\Ì£ÒÔý$›†pÖÔ»òÈÔ‹y<#õüS“o�õîXí·óŸø·ïeê{Óžã%Lx[x4Tÿ‡¨íðöAð~œÖÕ»åÕ;#�ŸMƒ¬C! ê¯ ù�Š?Ààújy}õ\ä›m[Å$òmh9½+ª˜Åz‡w€Ãf³Q5�|ûTÎ$·¥3y0ÚåLò EÅL¶HåL”­˜…6Ó‡ßÖ ”7çI­·×Ë·×9�¢.,‹w Yz°ÃŒq˜%˃}¤ú Ôn§#þ¹ÚF+øÓLE,4E; [&âÑó�åöQëΊtó5R/8p}ÒU1‰±þŠˆ„ã8‹:xœ}’ÑNA†c¥uA¨^XJC¢'÷Bcâ¬t‹•M Ñ ’íÐ=-“lg×™Ymcñ LæÂ[nM4Š/à£ø*Îl»ZŒñb“™ÿ|çÿgvÎçKùƒ{éáyý^NÅ9žG‘ôûÈ’€êï�Š­’jOÏ^kjeW vR!«êSîÎíNĺ´—p"iÄL[€]’„ÒŸ,(§0oæ¶Rq¢^H"q²/²ÈÙ–ÙeüÇÒÙi… =\­Ö£„°üî…Ws['68A@Yt¬Ð0²Î$ ) qØÞ;ÐZ ©ˆ!“d`WïÎèà%h‘ ®ƒÒ ÞðÜ– ¿¦ÓH“Ócöã¸"T_z»ÎŽÛô{­}Çóü­ç{õƶ_k4OXndn5<¢„�<ÔFaùß…ÏÖ¢zmAñbÑÝüO�À_€}.γ�xœ­W[o9~ϯðòB²¡0�抪m’Mµ�¤Àf+­ªÊÌ‹ÁÙ­úß÷ØÃ\<�ÒÕ.R”Ÿ‹¿s?óí€à¯Õú…üý¬'‚Êà³=ÊÞÈò͘ߧùd8è?=8‡æ7L"PxZƒeËZ×!D‘!Æ~Û;­ût-m¨‰ª�ž¨d”[ã:ªPbÍ·—–(ß‹Ç!Ä@õ ¨­éAD×dS!�0Î4£šñ‘VB5ðŒ,X1¾à�j: Ré7äÄóºUÍé�ŠÄ I*ìÊ©휾@¼ùW‰DqÖCm’“?oɵàS6KÒS‚Æ�O½ßI�3’X -|]‘È%BB?bÀõ+Åøí 5'ç/»Ê FÉlJ§ò/‘–Q9øß3 KUžLÙ�sIíZËèø]¤�ûb&Ž?Ö�ú1aº»‹£o8úŸ�| Àwò�-M¸ŽA.§Q÷`§R˜jÃÿ—�sS%,™HÔNõC6 ]‘{xÖ»o¸Q>�ÁŒ©š§¼9ë(dS}ü#�O!•³þ[ è^±RLÙXñ‚­/VY0·€½-Çb6‹à6‰"õcª2úÌì+%̘²ã‡ÞÃᜂùv–L&ÐôÃ#LÏ�ÉŠé�ܶ; ¢YPí‡�d@ç ë +6¤Æ²¹30ÇP=žüÔÕÏó‰/ Ì~X0¬ÜO·g#„â‡_žÆ_Ò²ºåDðhm–Ufwc¡�R ›d”«+r°‹h¢B‘DáBYˆ€MHeìœ@Y!v A“ŒR±´M^ !z¹CʃP öµUKª¶9ĵ¤m<“¾ŽÅÓ¸½-”®DÇ•èì—8q%NöK¼v%^ï—8u%N÷Kœ¹gû%Î]‰óý®ÄÅ~‰KWâr¿DÛ«„Ðû ™jØ"îíJàÛ�­ªèðÅñ�Ð×Br�iÛÏ_�¦Ÿ`SZ¦ b�ãÝL'ßOâµÄ”ø©ÜfFkÉp ICªßÞz‘ðã”–MåÓòTÆ«nñ ØK6ëc›†_oØGÛÉÍó{¡µXX Á*ËRzY!"FDÿ6PRö>Ð%Ô ,qºª´B}ÊÑ2 2`õâ+õͬ,�OÁXE;&Û†ÅêG–{ÁÁ!Z„™‚�U´0ÃŒ0EdÂ9ftš ¹BÄuX”hŽpŠ3­fÏ�º[a‡‰Ž½Ù¼6/ŽV3 ±ìj7ì« 7O�ó lª8™ÆZÁAhj²ÇTŒõ(º‘ƒMÜ«|çtÆ–)dŠÛ³QbI1%íËŽ÷Üö.» P2Åj1ˆ¦¤Óô¶Ì:‘éºñkü*Â4Ì o9D.﨨.wñPž•Þd¥5ÿ×]Ÿ*·¸Ô{º0éZë=¾ª > Å6;tÍkx*Út/4\¥[5�c\ÂUÖÜŒù˜W6(0�WcYZ2Å&f ›mÞ¨Â%Uæ^o¾0Ë ;¹xí=wÚgÞÛÓËæååyæÈÂ|ž4+_6c‰k ‚]dß ÷æ9*}Ä4Êñ{‡Ñ6ñLTîzƒÒK#RfãI£ºª‹ŸZR ÛÜu––µ]AÌB`”þË dÖµÿ릉4s ¯kZF± �¼ ÷ÅÔή ”±Lšž‡é\VF'c�»cï3¹jí¶§ÜIò¦ñýàÿß|n둊xœ�UAoEVãJŠ BABª‰EBCœuƒH‰"%qc%"n‚í’w¼ûìe<³ÌÌÆ1(ò�{µpä/ôRNœè?@œ8rãoðfÖö®›°eõæ{ßûÞ›÷Þ$äÇ¥a¹¼@¶#ÚÆoùRtX·q¦�5”+Îà9ÜÅVd/b‚U&HsCR¯XQäðH€l+èŒì­âIG*ÄʸöÑ6>�‡\‡ìR ӼޡÏ{ô ÔùmIU@¨À¿ØHm¨2€ßrøÎ}?Gð7o‘Ô`䱆!Ñm ·Úg\—ÈÙÈ÷Þú²Gš\“H�òdòyrz֮׿¶Øº”¦"~9«=íÓ’ßg׆�>�l_ó÷K� ûOâSƒÍ#Û´”ô).*™¸À§4H'ÒÉ[ÍZ8÷®­â1>ú­Ç‘v›(65Ú |X®/ºƒÛ—9Ô!ˆýÜ›P<­~ú &Å®Û%ø è1þ^÷ß»?6¸í‹í *;Üd}qµ¸¼¹ÿ/\©ÔÿfZY_,.'tvñù�›k…ç_ßúl&ùmî›dÿö·É_·ç“ŸÞXJ–î,$¯Ýyý�5ï~&ÿ²ä>—7ç.ÿ/ƒØ¦ï“*xœMQAKAÆ]ˆØ !¦�@/®$yhé …­ ]bÜmØuf™�E-ìÜ¥ƒÌ¡¿Ñ±Ò©A‡îÍŽif¾7ßûÞ÷ó$x\4`Œ�Ïè€ a ãpÌ�À9žöä@ªN"X, XÅQ_WÄeëÞÒc�>¸ ú�–×kkb^/ÌQþÚƒS–ÅØþ­íj=È ¤šEø†2„! ð4^ Ú‘î“ ~¹Ù:8ÂP,|Ôe5““^¦Pê@�ypøO¬/5cƒPDè0ÖEòËÈÉ'#+¿�-ùbl+ÌÊ7cGaYÇ5³ ñÙÜ\»ÀáZòÁ¬ÉWsW¾›{i*ß´•Õ¥reãf†±Ï1¦®¶>iýåZpBFä»Ör˜®VzÌV_v†BÜÅj(�¯Z©(ŸOkßTzyºÑ(Ö—PÙ¢®°�$æNŸPgÀ˜°Wr­ÙK ’^»:xœ•‘OOã0Åïý³‘�¨¶IN+‹�(°ıríIjÕñDö˜¶ûé×N“Òj¹ì)šß{~ó'eùf­¨�åÒñ²A–­Ñž+_#›GTthTÆ7Դڠʵ…•†A’­tœ`M*rÀk„)°€"ÌVplÒe%§BÚø¢3m¥ ¿o·Xi«»&T�æÿ"chëáC[E[˜‹ :8¬AN¡+F—YŠƒ”—MFp™]·Q—ÝÄ>‘ˆÞÐ5Ú “Màn‡20N +ƒwåJÛ²Šï³ñÁy³vÔ`ô-ÖhÌ7暨6˜ËÎyŽV¬b]¡ààÐOß=>ÿ!‹/Fp¼B-”ê¼íÁt+öFX…àÑåJ°È•vÓ’›¶<äæ½'�»•ž·�}QýBsQ[]í㜩¯û%^Q‰GñP öÏn±UÚÇ÷~¸ÃB|",XðWLB9ë+§ë5[ôþß«}i’ xd¸ºÈ‡V:þ~^] gù ·ñ­�xr™'ÜÆ”£r­Tßî=;Úãð|êèy½óžà�àÅ᧦àO½;3>áî,0Õý€�$7QJŸE\m‡ïvš'ð;èhýÖ“4º4xœ­QËnÂ0¼ó[KH ¦äĽ"DÅ« UUU2aC,‚Ù�¿¯MLm/•zŠvfvf¼ñ}`Â8ÓLp H!4ì�$BrÅ(‡Ó)²�"ë4Z$´ª‰Q¯¾Žül@‹ôò·ß¿óf6£vºè'^èñ ¾rž†C×8Á×!Ki%iM•!€ZPpj’âg÷rß©�ŒWpiŸƒ© :»§N÷ODXóJ²†Š+h$E&aÎ0]¥´VÀ*HyYŒT)…%S‹6ÌÄFˆ§Ÿ)‚Öík<Í퀨–ðB©Zö{½åri“–©ÍEÞ+¶V²7ô]/ˆ< Ù¶öÓª R‚ ?&0ÏÙH�dR2CŠY@rAñMqMv)˜bU~’ÏÕ’Š(“J°Y£Ž”ÚSÄ P+RAljÀ�:pãD~t†~|7žÆðà„¡ľÁ8w üØxº'x‚{?œE�0 ]ÕBóG’LkH3-XDé�9ß’5MÙœ¥˜W•7$§�ó**Lj*J&u%%ÒË¥`%SDµ7¿%e·ê èœ4…"ò¦¤•’}¼LKòLÁ¸#çÞKü Š�á0™„Þ­ÿØŸ8ñÝõ‡_wã‘·éÙOI6X70kX‘F뜔¬beS&û²˜ß½0Bàʾ¸êµà?hªÌe¡­ì_Ïí `àEnèO´hÐy kL6ƒQkh�·"¨•p 1ç…ìt ¤nã Ž�²øÜ’¼ØŒ-'iº]Íxœl3Ò)@çïI8þæ¹qrãNø” üpÓsuœ![Új¥: Ô &5Q8nlnz�~GÚÿèmƒDJ¬%–ÅÄ_€[…K¼0‡í¹£ç %•ž¤–v#¶$�Û 3Hkœ^éQÑu%pÌ©�½®ýoÕÙ‚N J°EÚš‘®lf¯`gm÷ЭlýW·]‘wu{½6Ól‡qzÚbv1/Ze˜x+´?oÁœcCõ!F®kV_a&R‘¢Ëjô¢Bxõu¯²‹� Rš#/‰÷è¹Óعz8xµ5…Ðûgê‡Þ {ìøÁ7çwæ9ð1X•MFMo¥pH1Ù6XKï\%ÏX™ƒM�Qt†[«ßŽBÁf¹z¶®¬Œ¾ìŽhVsÉ´Šöùîá…$m=îJ·oƒZ‘BrX�º×á5XAÖTXrA‹Â¢ûÆý ]t8Óã(Úí²_z9.…ffã:í-Ë岇ŸAlã]‚‰“e&Z%­QÒa!£ñ4t=ÝѺO·]ïNÃÐ âäímÓ{ÇQ÷À~Ü�|�wÀFÛá^»õ¿NC�FN0ÐÖïK¼é`Cà‚›³Õõçä/ðùmèððÆé î�½R×J4CÝLýáà0Ìû®ÐaÜ#ȃDþ—ß¾~F×øïüm—¾xœu�1OÃ0…÷üŠ“`lÂŽÄJP+¨h¶Æq.É)ŽÙç6â¿ãTí€D—“îù¾÷üî ›Ä0*t`,°1Ê�óÄ¢RM�Žj=Ú(ÚuÖø¶ƒ–8\Tƒ©} À9¨É¢dcO0Òì‚„%ãÝÅtöòŽt Gân…u–pÑÒ€š¯O×ÄdŽD‡^Xm*O*ÌÔØ¯8�-"”Ë"099v O\ž½Gó¯PQã-w!{Ñb`GÔ5jIèBNY–ÑŸZàÇZ0B“z‡vÞ::ÌÒ€¶Å[÷²CÙÿ?#çb¿,‹ô-ۯ߷»4Ï÷›Ïìuýõ¸Iw«§ûïÕG‘ý<$ÊH¡ �øùܸ>ÿ1ú)Ë–ô°ÚxœµWß�7~ç¯]Ê!ÊF‘Ú‡\[…p¤AG¸ê¸k”§�×;»XYì­í…Ъÿ{Ç?àØ…4Qš"�8ÏŒÇ3Ÿg¾1É~þV$½d0èÁ^¢BÈÿ€2­Hä¥\Õ;-Ê•u«É~}~ Ïž>û~[‰JÔ5Ü04¨¡ÿÁ¿(›,Ã_]¶·Ñ–àW¥J:j>ŸÄCæ‚£4˜C#sòaWãšqúŠš!üŽÚ%áÙè)ô�ÁET]\^9;ÕÀší@* �Aò! øŒð#ÇÚ‚�ÀÕº®“a+ìÊŸ½Œœ�wчÊ,#s.wPű!0ƒ^Y[›çI²ÝnGÌG;RºLª`g’ùl2],§ßSÄqǃ¬ÐÐøG#4e›í€ÕgÅY±-( ¬ÔH:«\Ä[-¬�åŒ*ì–itnra¬Yc[€í㣴� 2&áb¼„Ùò^Ž—³åÐ9y;»}ûpoÇwwãÅýlº„Û;˜Ü.®g÷³Û­^Áxñnf‹ë! ÁEçàÇZ» (Lá ÄÜã¶Dl…P¨’©‘‹BpJM– +JµA-)#¨Q¯…qWj(Àܹ©ÄZXf½è$/wPÒ{" R�¦oço^NÓ—ãÉÍtq�¾NÓÞR‰gu´QòªÉ~26Ï”ªF«_ÚBÚÝ–U"˘Á$~ŸWÆ:U� o¸…m•üuÅvW�"�ªÆÖ�MILÿµ4†ÒëžëBx¥4]r9òŠiÅÈ¥mw5º´ã¶t[­3Lc«¦ÎOk}åò-N¼®ãþzøÑöp1¹€¿zOÈPIÒ²ÙsÁD#³hüD·�kA—gF{b¨)ÂõQ¾imuKs;#o¡ÑÕ‹Ü®ŽÖ+Ü“Op% QÒ©¼»ÕPõ`ZH¶Þê•mC‰ûŠDM9Rò†®œó¹ëÔZéPk]�m÷õ{@Ÿ“»wÆÇ¸ ÛfguÒ‰Q ¦`|‚\Ø$¤ À=.na�Q 0rÓÁ/X�ÐXà+¦aÐ�ñòêP×HÑ©�9ƒe§,ö(…kHz%òN™æÁYÿ磽Gg/‰²‰c�cFw|IÕ(a#´mXÖщdÕ¨ª™CO)k(FO1ŽÙ†‰Êí>Ò¹*�³öwìR@–˜nlÿ ²7C QJ¥c;ø évA;ݽtcSÙ¬?•¿ñ ¥V¹ãþ „!±¡¡ˆÍƒÇˆ Œ9§¸©^áþöúödò>‡©£TI·éY䨲ö ЩiR}î:þÛ¹n+#,u\·�š/-ˆ;ßÖáRj-ÖLÓ¬öí1‚I£5J[íHˬ»~gUMElhH5aìˆ@ Q:ë„äÎsÖ1½Œ¡VÔwnj…}ÒÉo¯SŒ%B�£´ÿ5,ñ ب–2 ˜^8au ·ÔWß “E×ÌTü‰‡©E1D?¦ÿuYÁVJzgPƒ²ª:�û>%zeQ.V7S@¢‹âàK%M“*1±¼­{Rá © cžò˜R+³še™xFYRFP¡ÜreJª`bÜ|Ë5ÓVt’— xg<¥�ÂðiùëûÛ�2øô¸ ïÂÐ;#9/qh‹Ìʸ¨„ ELaÐüúùÏ�M¥“HˆÂ[éȈ0eÊ3?yæ¼À�.\ªÄ¯Ó�Ÿ ­o�ê}…)U¢Ž5„»baØøÒÐ_^y^£·+d#½êŠXQˆ˜i!{R‰†%ì Ñ…W&�Ô1WE­Ì×ÃMìÃh>‚?½32æ)@ôtÃ`.‘iT'ÉA*ž´8ÃJË/þaZTL²-ôw�6^sØ;d4°eó:’»š+óDç�uŽÍô²2‰º&69å•»‚R=W�Ë¥97(¥�®ïŽª7é­cKÕØúœ”Ñèvɘ«½Òá4{ìœ(øqº]¶NT-_Nï•:§Ôï($œ-™N�—ÚqÙ.•4p]rƒQì•=©C|N�)pÙyGü§0ÀÞQõš'ðžOúV‰ <>.LkØ |­ø As‡ cÉ+3e [v¸^+`}O——EãÎj¢Hiˆs&�Z £ü/ =XDj€ ŠÛov°©ŒUG×Ëæÿצ? vecº¹,”»êèiÞoÔ¤{–*A½a¦µ*oÓ_——½8ÆÞ|)蛼–ý!ï­ë“ÃÙn�O›°:�Uâ&_‹×μ–쩹±œÐ˜ñg4,(n»Š¬™?ܵµuý ˜Ú£÷ÒþÝ»¿æª¢S¦ÂCäãöboWÆôxQŠÄÝW‘ “¢ ‡ChD-�NåÛÚë�ag °½5¿èÍ‘ká8åñ)òshà"üË^{íuø�—a#&Ï ÷r¦—8ÜR7ÐÑ9¼œÿããÜøûsÒ˜î2‡9xœ•RMJÃ@E­G»Ñ.?AÍ"YtgUTŒ �¶EAdÍhBÓ$ÌL¬]åbɼ‚ïáiœ‰1ô'C “÷¾ï½÷ÍÌ÷úçÊÛÃÒ‹ý„à ŒÂQ÷~®þñeÌÇw'gؼ2;&>Ç)6yt}Oâ9qÛ¼Á�–�;—­öÑqÓ@Šëw½È&°7ð¨Î‡!aºXaiÅâyšs€”Èý³ÍH‰ÿÚk<¼žËõì«95„§Q—ƒÔcÄâ�q Ÿ¥��®ªpÐ�Em°I׳¨ÅÝÀß…·8�à¸C SÞb †ƒ´WKÞÖ7­ÓÃR¿?�%µÒÅuÖ„@/†Â§?‡œNq2y§ %<¢>˜²ñ¹&�…ïá )VÌÈ¥H¶–¯Ô‰›,Ítö÷Áðmy1òbÍ�Q¥^’?b¥²òxœÕY_oã6÷§dâ^{±‡Þæ{X7É6FSï"qôI¦%Ê&"‹*IÅñýî7CJeËŽãmq8A,‘œùÍ_Î�ý|ü»èô[ýN§ø‹„ƒÌM–› ”i,f½9رPf+%fsCoå ´Ã3xÿîý¿áë\$"ËàÆ5WÐ~´ÿ?Íòé”÷ÂùY}.ù~–r† on. &7"䩿äi„4̜à c!þ+FºðWZÈÞ÷ÞA›&œC'gçDb%sX°¤Ò@®9Ò¬\ü9䙑B(Y"XrX 3·| *=¢ñ{ACN Ãé Hv�±?˜)@Ï�Éô‡~¹\ö˜EÛ“jÖOÜ<Ý¿^\�î®Þ"âbÅ}šp­Añ?r¡PÚé X†ˆB6Eœ [‚TÀfŠã˜‘„x©„é¬ ZÆfÉ'2‘ÐF‰inj +ñ¡ØþTKádpûøip7¼ë‘‡áøúËý··ƒÑxxu_náâËèr8~áÛgŒ~‡_†£Ë.pTòáÏ™" ¦ UòÈêíŽó„X:H:㡈Eˆ¢¥³œÍ8ÌäW)JW ¡É¤FD& a˜±Ÿ¶ä"FýÖã@ AðpóëOWJðõ~ ìÏ߃ë h½Áa‘ò=3�H&yÄáÇDL§Ló~ñ¿7ÿOà ʩM}H›h*e²õQ¤—l…²GoCÔUjÞfJº…|ðY*´j¦¬ì=’Ó¬2Nr¢óÐ@°LSÔ"40Ðøõ¼Õ*V-U žo †!C³Q§N¥a–äšþZüÙ ™àäâþl½ái$b€~¿6Çâ¤EÚ8Õ�I-v ðg ð¡×R‰o25,é’ƒgâ™'Ú.¡¨À½GɶŸçó5 Lcæ•Vç­¿5Vⶪ;N¨™ùëà,iI%Ó5§ôø:s»¦¢qËcŒÎ9 qKiqý­‡Áià]—òI‚¿¦,|D{"“ð†sÌå¢'–äü¦Ê1٥̅ŒøZ‘—\‡Jd¤?Ê¢¬Ôk—2tœ'6ÉE"^U£ûU=b îR2&:o�‡s¦ ãÓIqY�UiëaÎmNcpJ4Oq¨,™&­h|[Ó¥Xo¦;g: Ò(pI÷W–æ1 M®�ø&ÐñœÊЧ öˆ,ÑF7·”'y8?DŒ…G¼YJ™§>†ãó™UŒÆÈ€¬œa»nnÿÀ´¼ŽKÁŽ�êîÁzg'Ö1ØŠ£¨Š™*!™b;×e1rH¤\ò˜å‰£XªqK^Øíe½9;m}¶ÆËÓ|�`J$ëu°Aâ|‹…ÆlL›:y ‘—¶ÛÍ)’9U< Qò£©RùÚz?<¥’):@ÕÅL�ä×"¥ïrŠý{ÄzGØá%õS,>yáƒ*§ c¿®ËÂ6L÷DÚñáæ€­�uí¿·suÄ™J*Ô!n0%Vra¿Â'ÜWšë™¢$E~ZsÔiúL´·´íDÒøJµ\�:õtÜ(ÅøÙߎt››[Sð´èjëÛ;­‰kK7ÜFZĘۇ¬²ŠE‡º¹~ôÍ׬ØjÆ+!ßg£Fj¬ˆ8Õz…)® ŽÀN›‰'ž®C÷ËXZüORD�ŒsËãõ»vÚV•Lð�uËêž«Ÿ«n2~rSk3 6e�xêEr2Éé€äepË©u2eXb¿'l¦{eOža¯°€ ½÷±Î µWYÔ|®ŽÕÕ¡.TäéäBqë R¾,s»#á|bRé~²!zÝ*嘲1£Mr…^©mÝßÜPOŒE¯T®§|EN -d¼¡}˜Óx2—Q»ôrqY’×±ò?r–¸Xñ({a\WH•ʪL=ÞŸŠdåÉ£:�‚ƒÉ–Œ•A ¥ãØ& S@wʵAÑÀMz93;g³.ÜqøÔ)â=šŒS,[Â9ÿÿTf�ým®eüN…¾“¤B{ ‘I½)ÃêwB/6¡mj²øþ?‰P‹³TH– é¢Õ(w%\%p ê¾$ìË8ðI5ËUCµGÜd¤/ÍîÔÛ¸ïbn”\émYrïÈiç.¹åíCù‘†)î€Ø|ã5®^s³Ã�Л6]×lìq¥ýк\¯/¸L©!ñ{¥¡±‡»!>‰”ÈíyùÊžKºs S¾’ø†5ø[øS]2á¼HËæ�`g—G¿w…·?O N·{wïˆ~^h®ûbÀ}úV¼h²nâs�‘JûL/ì‰ þâmˆ~a€¦Ø�Ç·]goá0.Ò.K×ihÓÙP·v¨ÓµíXÒwp†5ß“=ÿ&RšŽ§ÖU›H±*4óšäT“:ŒI×~m#뢳·&HR¨Ò™âN‡¸Š¿¹î!ß[È4Íßyÿ)«}¦K“Êb…I÷ÄÕCÓåµwl6S|†é£K¢gc»<,6ÔöYyââZe_žP*d•aZ LãKGÔìüClµ±uÿ³ær’(Ì·“­«€Ii@C‡”¥¤“’éÖÖ¾×LŽdó¶Xu¬�p&Ë<¥¼ÚÒ"qÁÑ Ò^FmWj�§*¢e_ɈÇ­¡,]õ»3ï¡%áï6d!kÍž•øUÎÆòÃÇè�“6:÷Éw QÜ上ËBšhnvÝÃÍ×BÙK êrhçÕPñ½ßÙ} 箟áãG¸rûêÆõóÝDò¿X 9eî)�xœ�’ÍJ$1…ñg•ADåb/Ô¦íGdD[Û?üPÄ… !VnY±ÒI‘ÜR| ¥Önæ æ%\¹ñ |WVU«Ý³²6�ºçÜó��?«/C·¿Ù¹â gS#ï.Ïúþ>l÷W6Ö7÷vŽ~�nñ]ÎYEb¨ çÝAÖ˜ËîØ$«(èT"L‘µ:VÔx;ëÑË–O�TaÓ¡ ô  X‰N]¡„ÐÙv>j9 áZ·)æ‰0¨9Õsoao%‰6\[ûD˜Ý~ŸÞÿXmÝ»Ð!¥ÎÀ:hå ŒÍÁD@Ê\€ð #5BhP„½(ÙóèØÁ¹çR>^ø8A5‡1:ÞUò l2à ÿ:¬T¥ãñqbi±VÊReèÇ|>-Ig—kTKÚ-ôäì�ÿ‡&û62¾ÒS;•<¡¢aƒ]Y%á?TR—Ȳ³0{Yk#u—©o¥•ï^â´ïÉkE�M)I©@Xý‚Gâebñ3Ó“»µÏÏ©XѨB³ ÛEjØÃP�ànx~°P¼ë¡ã»´–xœ­VQoâ8~ϯÑ—‚ Ù«t÷°ÕIK)»EKéªÐ«ö)8É$Xìœí�¢Óý÷Ça7Ú»•©¢xÆŸ¿ï›'Á~ÿ¿>0¼`0ð`Rž#ÈÒ¥ ·L° •¿¡HŒe±W<Ûûkrø—q®Þ]ý_6<çEŸjTpù­þþ�•Q„~¼éw·Ñ–_á“”�8ŸOšCæ›LËéˆ7;žDŽZƒÂ?K®Hm´V£˜EÄ3gH,SH1#-ãJqÃE6-SS1…&áÚ(•¦cØ�Én'�eL@o¼„Ù²7ãål9´ ϳÕÝÃÓ žÇ��ãÅj6]ÂÃ#L·³ÕìaA¿>Âxñ>Ï·C@²‹ÎÁ—BYD“[+1©}["v(¤ÒQÒÆ<å1IYIm™Ü¡¤ T[®mI5L,LηÜ0S/�è²ÞO)�B>Ïïo¦!)øò´ ïÇ‹ñ§écx†ÞŹÀ·R<²§Œ TyHV9Û_ÿXRaÄâo(’ÎZ3”Jÿu"šèáµg‡ >JEEJ Á8gª–òîH^îLq(Ð̘ö­$³/ÐJj Ã*ßFv§1´G�[¦sÉçH\䥶¾rz“üå]�žA'ç0ü3¡ †3ƒú˜_Í9’4)kR:rÑ‘‹nQ˜Qi7SïŽv¿¬-)…õK’RÛqn,RÅóœ*­ ж%ñ†Z„paqÖ'–‡…Që¿,âZ·õ"u˜á;ü#EʳÒY¯-wó†Úƒ–þáZ+¨>ÛVØ:�¦²gÖOˆÅ›þi"uP¡)©"­V°ü�ìÚ¹Z<ÍçvhQ)©|¸/µ�©£¨?äëA¡{ƒÓó=Ñdºyy¥™g×c…äì¥ô9™»§íÕ°›Ö¸e³ZÆ�$u¬sÉ'nžljüté-sû×ß»øÖ‰>ãêœ÷TýTÉíÆ9ùG r”ãJx;É“7íw.¾fÿ)jKË2fô˜Fsâò´-¸UV(¹ã‰íc›)‹ õÈ |ÖûþE@�ó³ô]ÍYÚY�Ð?5ý]_D?.¨W¯§f=¼q‘»wz'‚)µ=§�ßa~òuÉâý“Û*îå&†Fxœ{"4�oB,Kfr~ÞÄädFã�Ç]™ä=�ýýâ}ýÝ]ƒâ=âã¹”SRÓ2óRâãAR“­™'Ïa–öêRðMÌKLO-RÈHÌKÉI�|�YB+1;µ(dd|.D2¾D¡<'‹ðäYÌ2"!ù9©e©9 9$s\ÁJ ²�ƒÐÄ­¹¸”3ó’sJSR”J‹³ãs2‹Kô2”& ³8Š;¥&–¤+$æA,�:`²3«†yqjQм‚’".- Rp(J-)-ÊSÉH…ºF!?M¡ÈCÑû€UC’*’sr2óÒA:ÓP|:Y†MR[ph¡¨ c³©‡ @�T‚p˜&ÐwúZ`ǹ¤—åWƒÝƒz0×$%æ* Øñ›>WY~f ö˜H™¼”�_«KÑMš¼†ÃG=‰€L××R°µUpÍKØ_꜖“ã8Ù�Õ8ë0�gxœ}’OKAÆ©]ÚƒÝ,âÒ å!ËþyÑ(‹@hwW�ÒÝev-„Â/Ð!™Î}‡®uîÔg šq!SWç4ÌüÞ÷}ž‡÷-üy¹‰XN×CvÝ5é=öˆm Hm!óþu²˜)Ÿ]VQåð¢x~uP-UÊè!5n˜ b™€P)èŸ}Hk«%Ñþõ„»•’û“£P[¸iRäÁC»ƒïø�ÌfrìSŠ*•᳤Dˆ:¶K†œ_Ð,€È©ªëÑ®.Pd×iã^nôD‘Þ¥®MÇž\‹º8ið¸}�=*K¬&ï†ÇݲWyû¯9UAƒ‚ƒ)îŒZ³o9™L‰ýÈѬ Å1L>Ûî™Ô{ ãv›XM(P>zFTì(Û™“°6«î)´×Ÿð)à‘�-UšJLJü»À6y:iMŽ}ñ.x-Èô6ø¢Rõà ÓaËÊFxB\J‰­Ï3Åtå:´ˆ ¥¹Èç¡h`7`j³R-¬$eþ[Êz¹·xœ­VQoÛ6~÷¯88Àf©Tèœmˆ›¦‹×)â¸AžZ:KD(‘#©8^Ñÿ¾#%Ùr¢f@W†¡#yßwßïŽà�ŸõÀ(ì…£QFp¶æaÅ ½{S,ÕVó4³îí¼y�A<„“·'¿ÁçŒ ®\14¨aðàÿÏÒrµÂ Ά‡ÇèÈ;øKÊ”pf³ódÆc, &P ù°ÂD±˜þê•cø‚ÚpYÀIðnC¿^êO�‹­,!g[(¤…Ò ùà|8ø£²À ˆe®gEŒ°á6ó8µ—Àù¸¯}È•e´��‹亽˜­IgÖ*3ÃÍf0Ï6�: EµÏ„³éùÅ|qñ†×'–…@c@ãß%×íj L£˜­ˆ§`�Xª‘Ö¬tŒ7š[^¤Ç`äÚn˜Fç&áÆj¾*í�` ? »½�$cô' ˜.úð~²˜.Ž�“»éíåõòî&77“ùíôb×7p~=ÿ0½�^Ïéí#Læ÷p5�8$¹Ÿ”vMî¤ÄÄë¶@< °–%£0ækShEZ²!•�¨ Šêœ—RCçFðœ[f½éE\(ìñ5-¬!ŠîfŸn¯"Šç"ºŒ¢ÞYy�/\iÃG©I¼ŒÓb ÂB—]æ+P`Ž… ŽÝ*t8¤c[ˆ6"·‘ÛYh¿œöˆS‹2Aø]𕳆õ�ýÙZ46YI)œqoíDzpµ†:ÈúaÓ»û%(ÍÒœ�áQµ67 ³¾s±®„ˆ•(�ûõðÉ’¸Ð?ïÃ×Þ _{—í=^Ž÷Dn —R$æYðÕ½0–ÅT;¿ÀZHæ °Y7®ž¬T^¤Nq¾ö€‡²()Ç@‚»‚©î�W¥n)ù ¤*•ñNÝéÊáNòÚ<ÆùܯœîÀ¾pmK& G›Iº=þFÕ¨þ�çñëA’÷Pµ5zÌíLRÊ*¼Ö2å»�ü,¹„.ªiÂi¸%Ô(Z1wÂíã¹ƒÎØ,*Ká};í5½yZP+`‚ÿƒfçw߯RþˆÅ.º¦w+*öÜïtîZ¦ÈŽ5–$Uë9ÓT[í¼ŽÇϤwÎ^yÊÂrñ ]Qîpb&õ¸ãBP®ð?¼É !šŒSÝ}¢þ¼B˜/g³]˜mI…O刮>MÇÔ¥ªŽáî\›1°ƒV.ê²5"wæiÔl¸ÏɲxÃ;Òò}ýÃÞ£äÉsIÝTj ]|¾à\W”Ïʺ­óžóÁ�#ä1µPkëëDªVoMWutTo“b È4Í õ†¦òœ¯�Ë>å2Aê$r[ “×붦SUƒ6z¶÷’Þ�Öäd’øÞÙtÊæö0 ÈìûÙ°‹˜*Mö?u0¹Áœ†j™ _ªŸ ¿¤úK¤KM_=´D$™W&rË>%‡³³¶žv³o~tíGÚwZmG/fõaKÊpA3†®C5”øËÙùû…Vµ©±Ìxœ­WmoÚHþί‘/�#8ÊIýд§B¯(9RiÔOîÚðªf×·»áªþ÷›]¯Á¼]sQ-�å™Ý™ç™—;hÃû_uA;hívÚp5ãB$U‚ “nJ2'Že¾R|žûÔ¯à4nÁÅùÅø”òŒç9Ü2Ô¨àô›»_Í‹(Ânœ¶¶·Ñ–ßáO)çäëî®ï�Üñ…Æ AÞÁ¤½œÅtóš|F¥¹pÑ=‡S» éUÍÖ¥5±’,Ø „4Ph$\ƒ£„Ï1渀X.òŒ3#,¹I�o¥km|ñ6dd-g`¹ƒœÕ3tjL®ßÁr¹ì2‡¶+Õ<ÈÊu:¸ö£ÉàŒû"C­Aáß§C´–¢˜E„3cK� Ø\!錴ˆ—Š.æÐrf–L¡5“pm� ³° Ñ®/ �1ÍÞ†“&\÷&ÃIÇyN?Þ?Lá±7÷FÓá`÷cèß�n†Óáýˆž>@oôn‡£› …‹üàs®,‚Ém(©N¬­ â„™,!éc>ã1Qó‚Íæò • F�£ZpmSª `bÍd|Á 3N´ÇË: '|FŠ„áãÝ_ÓÛðú~|3nÂ�aØ8! xXI[Eœ Â;m’Hʬ›þQ“63ELcàïÝ´Y×b† ÆJ7ÂX [(¨¶Å cC„3Ük³*E¶ãàƒT”ÏŒ3¦ë·pí»Ö–ASD°k¹›UŽ–;嶈 „Ëla¾…UÆv—–À¬ Wœg…¶¿>J4ûMøÞ8A‘ð@l­q'ÖsUýÑ2‡é(–ï  Ë™)(Õ@$mÝls&ìºrûÚ4í¯.mwo4—k³Ÿ¹2Ë`�&•Ô.®…¼}· |Ê BªG{ K–J�›Ç~eø´0kגΫ°ô^SSH+Ÿ$FÛ>µ.+g&e”žeä�R/uÙèÞGœo8´+anj”'¶€¶c¸¶åQš\05ç"t»¬±5`fœ9!•ípš'Tnòh5 Ó~ƒ%Ò-–ŠR w𯫽Ơ]™ó%Q[´Åè0ësåçÎüúWùÒ²ø•¼+s¯Âb�ýÞ~ý_?.Õh š",ãÿ±­f®N5?Ÿ¦t�KÍË3¹^çv~Ò|ÒEœ–5ï2~æ!YceÔì8"íéyÎ[•Ÿ«œN¼ÅÚ•Å[×j¿&ueìeN¬Ð,ª{´CŽ�Ä4šÊ1a�÷Ýã�SN÷ΩvD§Ç®Ý^­wèl£ƒ‹ èxs¶×´Z›t=ˆ3þÂŒŽdÐx’<Ù ÍFþ“ÔAä ÿ¥÷rr½(yõ4†¡Ñ”ä_ŸŒ×…Æ®bôfD”C‰*i¶—_û óµç«ëȽ2Øáà5/ 3%šSç»#†Þ8ÊÆ�ç’]¤C«v1ÛŸï^sxÌÿpC}3ì�Žz/Úß›Êúà€�HììÙ| ¼ê+ÄÚûþ¬%5ä‡oxœU‘KJA†ñ�JÌBÜȺÐ'>p%A£D“,|àBdètª3Mzz†îÔ•WPç$ÁÃxO ="‰Öª¨ªÿ+þª÷å�ùç»ò½Š{Œ�aœ^èvbïíýtrí¨y|~uчé FÓÉ;wáN'4zÃÉ \º“w}@’‹âàC.MD“)1´ºÍ7(D¢¤¤r xÄJ-‹ #ÄâeFAŽ2åÊl©"‚¡�IxÊ5Óvj'/Èéðˆ"ð¼ëñ‡Å¥w6ýË»ð¼ÎMò wæÉ! ’"DøMéÐ",ÿhÍvîûL¡Sý–Ýöj 2sPÒ<€ã€{}s¹dqÊNà1oc‚)fÚ@4“J?&%*U¼’61„ƒ„I›ê œQµ™ÜôcŽ&7Ú»"Ðà­’TßyT‹ž†Öÿ§†`TŠäI¡Ì_4 ÝQþé`òÈnÛX Sɉ¤ ]Ÿl‹fEJ(@�ñ¸’1B$Æ’ö’JM`Ažk}^)èF’F]sþÚà&i l�Zû2�¹O'‹á¸¿ŽAÕ­© ^ÁÀk‘ïAÿt>[¸#ƒýÔRM4Y[MÌ9�q[ƒ}â7RÌ :¶@{gj`Óј”NkÊäZ?Ê86+§Mâ\ê‚òNQ/U½í´u²Á^A}¸6CU³Þ}ª×Á(ÕØ+ãµ–›ˆÏ€xG³¸“‚5ܨÎi]õäK›UÅV/§¦¹GžMô”ɘšâw@—mä5ô‡¬,ЩÚþ°r¶¥Ýiç©<<¦]¹õf–ð/¨ê¼›×J.Å=©ÿÞïÙàAýnÍ© ¤ÆÑ˵lÍ´¤nÍZ •¥�–¨ *z⌦æTÔ�ËNiz]k39‘=ÜÚ` ½*tÿÙ­o�JC’Ség´ê­™5r]e¯ù®b_•ÂéÜ ¶8Qç‡ûÈ·b ÃPÁm}žhíÖ¼GM(Û:Úçý[[ÐÙdž…Ẽ,ö^jý­:¥•û,¸ûŸxèÿF{†)½Ð·™“é6ßYyç1ûÎt›¤éæl]Ó?1Si¹Õ9üxžë2û(x¦Í¥Dìtg2‰v°(•Û*Эm0;ÁNϱ¬y\å!½°š^SÖÝ� h­”/ÏZǺñ…›MsK*…º¬Üouˆï.|ªz®�¸U�«kJ‰ã+ϬØðWœjrÿMçÉÞkšûÎWo;Õ¼ÓÛ¾–ß;ôýç$)e¿w~ü{Êàý Jro¼ŒxœµZÝoâ¸ç¯8·ó°QXÍ•îC»3šNÛ¹‹†ÒQ¡Ûݧ`C¬š8;Pîjÿ÷{Ž�„$„h»H±Ïçï|ø¤ý|x«tú­~§Ó‚|š ÉÁW‘a"âI/ÄE»î«x“ˆEhè×UþÚþ)¼ÿùýà[(¤ˆcøÊ¸æ ´Ÿì÷§E:›ñžžV�á‘ÕZ ³áð*c2>�4 �¤aB—1óñ+{Ò…ßx¢…Šà}ïghÓ†“ìÑÉé‘ب–l‘2�jŽ4„«öyl@D¨Ý2–‚E>‡µ0¡å“Qé�?2jFF¤;¨yy#0“ ëó~½^÷˜•¶§’E_º}º?\ÝŒÆ7g(qvâ!’\kHøŸ©HPÛÙXŒùl†rJ¶•[$ŸE¯aD´è‚Vs³f '2�Ð&³ÔT –ˇj—7 ÉX'—cŒOàóåx0î‘ÇÁä×»‡ <^Þß_Ž&ƒ›1ÜÝÃÕÝèz0Ü�ð׸ý_£ë.p4òáÏqB ˜‚LÉk·1çæÊ‰¤cðQµh‘²‡…Zñ$B� æÉRhr©F"#ÅRfìÒŽ^Ĩßz'æø`ž÷8¼�|õPÞÉå`tsïýêy­wøÁ»ç)Ž|™~Ñ&˜)%{áÇÒâšmPÎà Ñ‹Bžù*á´a»ãDŠÙŒiÞϾ{áI«…–N}k™xåžA÷]PTÁ• Ë¸/Yb;ÇPÈ#Œô1›˜“>o-—æÉ+¢Ð#µ•ChÃJ$&e–Ü„ ÁD;ˆáji˜ÚÕ‹²)¸äK2ÁvQDqj—ÈOÎM~,SM-þlÐõpruµÞñ(s€~¿²Çª6F ð<êüйö›é¯àÇOZ€V!œ6¡�Ž@¦ß~4ÏŸ\Tk0hNÞ�ìÌ8Ÿ²Ž!éÂìaëlí> Ó£WáM�Ñä™öcäÆ=Ô…N``RtØçù¶iÈYàÅ&™R> !�Š—J›\Ì.L‘„tÛhÏL£–gvŸŠ ¢Nö™ö)1³TÌEŸŒ¤ÞJ c  P(to Ç ºFò”H ‘%'LçYep¤¥6SÐb¡OÐ Ó]g#ðé.Oæ³Oí,ó2ÅÞ‹fy>%ˆòªWùŠlá™óóX‰1ïIÎV vØ‘#§P9W’`k]ƒsnÍQg뙟& ýÈ „~ª{p‹%mÆaô0�œ½=]l±N9V€EÂf/æO‡g ?ÄÊês±Â˜pùÿóÃdr7ò®ïGÖ[Ÿ'#oxóerˆ’Ï�‡%Ô¨èe6xâ›™¢ý2/äÇ›ÜPÈð)NÄŠ²e&Ì:ä¶N“P’aKd dØgpa7±bR¸;(øS„ÆOi·ç(]´þ¾håÝâ ¾7ü/³@‘a·�Uœ¨•Ð�µ(ï.c,^ËíqÒ/”p“bÁÀpáÔÅèÔ÷±÷p}€¼^ ÕÞ- � õÓ”pÉ‘ƒM5b�Ã{ДÐ’qøÙóõD)ãí¦¥Wiç9™xÐ.Ág¿®Ýï$ÞÎùJöyˆÎÄ>åF¸Œ6üµRR×:Í+Âá€Kû,Ÿº†ñ;Öë·VJ;ša['Ó¹$ÿeèB–¬Ê©f5î]KNJ2S;ƒ”.,\[ÎJhßp,î“m Z¹Êî’ËÊïn7r�JÂì72ËsÄ1ب' N‰Ùq–DEQEZÂ’ˆ ^²~ÒX´ Þ4Wu0ß>.ç422eÆn-Ÿ6XrÆü§CC°‘Õmª�¥Œ¬g„®ºæ¥ö÷ȯrB£ÜG9ìž/ñ¦U÷Ù‰3P­ÅÿÁÙ/ƒßooàì£SÎY´ æDþUëIæLÒižesÇpŽ7Ŭ=ÁKûäîúngÒwŽƒ˜o°åŒI7_Û VîÍ­MÆÃ«Ü<Þ ]å9ƒŠ·p{Ý{Mˆ¬f°FØÙKF´9)‰¹œ†u[¦g¼Õ‘‰,­�K; Ì�*ëâYT%×ÍGeØ\Çl�ݹkË,•¬"7ex yT×31î«îæZ$ÜDÙ"öeª1ðäY±r‰¦ìNzXÎO¿:±Ï ¦±8ÿˆ·ý—èš÷Ò¥ºø­š*]%®ß^ŠîéE=S±ê|´{š—¼T1ÞÛV;[e3i*Ų>±i“A�öÚµ"åùQÄ9¶ïh/ªœÞ QUÈÍ”¢kΩg(åt‰»¹!]¯�Ù‚^Ím ÌÛ^Š`¢UT?× hX -fB ³qq„Oíû¢^ŧåªgAž+ÖÖ§/6lg_Ùèë˜¡Ò½í¡œ�×’†,ì„Å �ìL<‹M;XŠYØ77Ë;‡q¨RaVa@`,ÔÄ�ZQ]Ìä­èºM®°³�l¬äÆ%q]Ý3Aª[¥úü¨q uŽ;Ûhí¦A’!{}„‹„8íÑ^O ¯È²Gé+:B (—ö²O;#KV²�eØ y}ÉO8�„Èy€mÔ©{EwÍ+lm`U9÷öB0ýP&}ˆ)Þ»ýmß²mß¾í}÷–­÷;ÍïOÝÿ À‡pÙŠ[ú_�—üGÑû?jîõîQ‘Fxœ…TÝNAN°´f¹P$('Ön° ‰(V ÔˆQù Wf:Ý=¥†Ùfg–Ê…é�·’Ì#xå…1¾‡Oào`|ÏÎîRJR�l²;gÎù¾óóÍþ\ývëô}¡·Z?ü¯#ëk{››ÛìcNÙÇV Ûßz½ûŠ]8rÊ�ôDì#<A³ÉV³w¥ýÔ<º²f>æÌHazl�€1¥¹6Ë…ÛSM»gºâH²|»b¾žW8hü ã4¹wˆ>¤Çp«ŽÒQìi`ý83>zçFêñV% À#”ÚÔ®– ýühU]ÖãH…èo%tÚü¡RSz­Ð‹UBc–&'zž  ie,µ­8NŽº¿µ�¥íPø�<ÝFðBr—Ú‚Yß,ﮈ²b![nßÄ:š säúއÔöƒˆwÚ°úö%ÈÐÇ ¼‘âÄVÀ[IÊ"!mxr�L%A,qm˜ïÅ™éœÌÒ¤‡©ÉœŸ³Ï»¥åü³QÚŤTS-•ͯÒõq»c"PTR~M},Ç­8àÒõ�ø‘m„�ÙIóÛ�^VS ½08epûõÞ§^Rµ Ⱥ@.ÄÛÁ¡iœ“4ö8 üA‘Rƒ”­÷?ºÔ HqǧΩûùÏg÷îÜ88¼t¢«V‹é-VHÒŠlY)YvÛ\g�gÎ&nš†S\êBÀMW„©òO aëÒ5ÉÑ.Döü8â:%„-ô¼JX+CºDòY°:>—Å!ª½$׬ŸPïEH7VÚUÜÁÈ\©ù‹Ùg“x(ÅF£a/Éew ·Ì>\HSç©ì‘àè¢4Ý …Êü™½w- kR¼™�wsýÿXB_u¡Vƒ é'ÉYŸÂr©F6:ü p|¸zæ_„-xœ�TMkA3›�‰¢—eüH¹9˜Lböä%aE‰•`F¼Ã|ôºMz§‡îž�AÂþ¡=ªAÿ€w†— x<©Õ³3ÙÙì$8Ìaºº«ê½Woú}íÕìK×RŠGƒwg¬��­‡úÂX­ÿ±4^ñ“V‹ˆ•vÝž§QÀ’�@�0Ò!‘ þkWõEkQ«Þ}¤ÆØí¨§�PθŒ©>°êÛæcnC¦i'?g‚ؾðÈ$ÆPgm@\Ðg‰CWZP ˜'¥~cÍéfeùæòïR¡ÜïÕæ!(Ïgd¤Û€LÞ úK¥>“ÉG#8iO¢´ÒÕß+õipò «Ï�Ï试Zm� "‡Î?'‹¡9#,*˜óÙ¤R‘H‚âý¾òL9N#E´x�H}8>¯_OÌ\?%�qIF³g�w‰ËÒgû9¯#}ΙޞªNæ;úóÄÜlê|áVì ¯“Ó?&nôRÁiDըߊv3Æ26ž#ÈP ]“Ç&+K›™:£ÁnGÝßI7Q‰ˆÀØ…ù©Ì7¶á\bš¡IÁ.ŒlÀ®‡Í—Ó�¤*ó¡SX ‹±Ž“ÃI'qAŽÓÈ7ìô/ö¸ä¶Pü´×&¯Þ) _!¡'û?ýÆàúçdu³Üñå°\IÔiºé?S—Ï ·ø=5¶ØB¿µï§W¸þe?�37·{ÏuM·†Í&lD¡¹£²zñtóF´ Ò´Ùxœ­WmoÛ6þî_qp>,1<«(†}H¶!®ã¶F\;ˆíÅ0¨”DËD$Q#©:FÑÿ¾;’¶¥ÚnÖ!GÇãÝsϽ� :ðûK=Ð ZA§Ó‚\/EÆ!‘ñco…ïVËr£Dº2ô6ؾÀy|¯_½þîV"e ·Œk®àüÑþ^§Uñ^¼ºhnÃ-¿À;)Sô3¼“±ˆy¡yU‘  ³âÐ/YŒ?~¥ r¥…,àuBÛ/µ/®ÈÄFV�³ Ò@¥9Úl8ü)æ¥Q@,ó2¬ˆ9¬…YY?ÞJ�l|ô6ddª3 ØA.ëŠÀŒ½2¦Ô—A°^¯{Ì¢íI•™ÓÓÁx4Nf߱߱(2®5(þO%Fm€•ˆ(fâÌØ¤–*ŽkFâµFi´\š5SœÌ$B%¢Ê4Ûâðë H+ ÝŸÁhÖ†7ýÙhÖ%#£ùûébýûûþd>Î`zƒéäf4M'øöú“�p;šÜt�#]è‡?•Š"@˜‚¨ä‰åmÆyÂR:Hºä±XŠC+ÒŠ¥Rù™«#‚’«\hJ©F€ ™ÉD. 3Vt9 Zgb‰ KÇñ‡ùmx3܆ïðu†RQðÃÜRÄY•pø-QÄ4üooõÇ‘EŒJ›æ’6I$ef…ÞÍÃø>\̆áb2›÷ߌ‡5íu¦‚ʈ,àIʵÝTYÌ{ö›Ú<ã9/Ðo»&,YÁ³¦H›MÆ›"te%ÔÃðV*¬’gLY./áûVH2*‰fSr„ERÅÂu–›Ç�º>4P¹"ˆKÇw\f•¦¿2˜ChÚð¥uÆ‹D,‚ ¡cÁÜI-¥[–.¯ÛÒ°3æ˜/-ÀÇîV¢(YDän°L¤hÈÈ|¨´± ¹Œ oÞ ÃÉt2´–É/ªCR¡MÔ\íœYŠp0¬¤ê¢‰ˆ¯¢ª±Qî£ Íå%íÿnœÍ«Öד¦®Y3Ì,% ¤(·™;$Ë“T3e ¢ÆÚÕs¦RQü¤Á-úùEê(‹äÓvÜÛå º„íÐÙ®83ópú.±,hXjøŸîb«W2kêjë8�Ø~ηi¤`§ˆ­|0Beuâm„f»ùºÄšÎá€ÃÒ¨Ú²�ÀˬXqSaÁî\»VèÒ›,Æc‘\)©|Aa1%©‘Ÿ1Ë(-d‡žo‹Â+ºáÔè£Ní-¶Ô�[Ή%s² s,¸nmç©èì¾ØçìÆÁÓû*:Ƥe*h}–"9Ûy3¬í–š—7΃CR¯Ú.¸9E•�Ln«…Î7[ú4œcÉÜÊhkx*ËH§Œê*Žñ¨r™¡ñÝ ŒgÀåäx„?ÆúIí=Zè4‘×(ì'‰£�4èì>a 6Nå�%‰ð|œ5°ÆmB¼ç9Þ5”K%󗩬ñ—€º+„;) CwG¦®ðÆ�‡"Þ^ žO['ŸzûfÞ®×»Ùʾß?Âþß�±×¨ñÒg ÜÑ…†.Òv!vu¬Di°I›ƒ»Z�OÀ»ZŽð@§Q‡‡åoåýí- aOãNùoG¥9f;$}ÛŽÍ›‡—¿€|µ×�ý5ää%Ä˃ÎÁµÐ}óà7 ñ”B"Ü7Ïÿþ¨"{ÿ›bx²»xœ­Ûnã6öÝ_A¤ÀÖ6»;ö!Ù)&ͤmÐL2H2�í“G–h›ˆ,jE*¶·˜ßsx‘H‰¶eO´q$òÜxî<žÉ�¼ý»~ÈpÒ› ‡=2$ïæ,¥„¦tE39^Â#õ4æù¶`‹¥Ä¿®í¤È›Þü‹|\²”å9ù=¢‚¤ÿ¢~¿[”³ÇË�¿ ¶üH~å|¨îî® ’;ÓLЄ”Y0ä’’«<Šá—y3"ÐB0ž‘7ãHœ™WgƒK±å%YE[’qIJADs´‰i. ËHÌWyÊ¢,¦dÍäRá1PÆãOƒÏdË#‚¼>w’H¢—Ræâb2Y¯×ãHQ;æÅb’êubrw{}sÿts›Ÿ²” A úß’ÀílK¢(Š£ЙFk - ï$GŠ×“,[Œˆàs¹Ž Š`&dÁf¥ôfé¶Ý ²(#gWOäöéŒü|õtû4B ŸoŸ{øôL>_=>^Ý?ßÞ<‘‡Grýpÿþöùöáþú…\ÝÿI~¿½?"Äxè&/� “¡(i¢äöD©Gœk’DNc6g1°–-ÊhAÉ‚¿Ò"ŽHN‹x¤LLÊVLFR=jñ…ˆ&½ïØ^ÌÉtúùîÃóïÓ›»›7÷ÏÓߦÓÞwð‚e4ø6fqZ&”ü;e³Y$èÄü/r^ ™Ì8O[Z„ûlm�©äT8:�yÑ€´y™�º­x6©>ႊÊÏw�ÓOO7ÓO÷OÏW?ßݸ°ÓbRJ–Nf|£€–ŠçÖŽœrKë§„D§T粇M~áèKB§Q¡¤zAnŒq£(å6§Ö™®Ó•|™óŸ"Tïï˃PÉ++d¥dEå’ƒ£Vw@õºj£SÏöqrÍ3´QZìAÛ5xçÉ~>§çR�º�q|K_­Ð‰Û·øaªV\:jvƲ¼�9ë|âȃ˜ 䜃ºgûŽ AÛAœ§¥Àÿzt›ÀÔ¯ÏÈ_½ïh–°9!“‰·F1}ƒÄ ë×èn�ø«Gà·|Ô$ŠÿGïän¼*ä]û´” ýS ATb› ¶È@Eª»V¾láKé\ºÈ.ª7s—‚¤\ƒ%¥Ñ+mcYqSª�íjë¢ÄöžÂ'¾ únµA)|HÀ+g é½sÐo:ˆ¡ý˜ËbpéuA#I……ü½ ÊM‘EåKrõñ¢u±=†BE<ß“!ඈí ÜJ†ýa¬�8ýž•þ>’GÕª %Ã61a.¥C.ÀÕÑÌçÂ} r~¥È ó¤‘sÁðš"_P9­Íhnþx¸ =ܼ ­|Ó`X¿’‡ù21ÅHî›�b�U§=6;ìÆwyYPòŒ+Œvç8JSp'ó‚¯\s«Øò¬§ \öŠ82h¼ñ¬µ~]­*¨,Ác~^R•Ý O† -Š1mÔYšËéc’–©¼�Á)ZhJ‰ š Æä„±Œ$p‡™+‚U|D,y š®AûP£ Ð2zU ’ëè,ëZM0E5±ÒÑà„‘ÒÑê²ÏÑaS†•¸ÆŒ:‰‘%î,á`峿‘=ÚÔ”ø¤˜›6¼&Å ]+%H̘Á  ÈEk ^.–®Õ‘Þ2"çcÁ^Áy\�¥��*§[$lF­^ª£E‡ÄH©`vVDϪ]B¦±’Yg§j1ÝBT�•Oµ€,_‚ÔtŠr¬�èÍ ü#%·Ú­¹øÈ:(Q®T%°[Eõ®£UaÛÜÍ£64iß+­¨’W´A£;IZNn6E0GÉ­ÆÛ]j¸ç[¢Y›XÕÂLøsZ‚¯ï'ô"�21¨Ób�‚µ²“�Ï£…;“r‹žY#Fd „ò±T• 'YT•‚πΣmé7¨ßRå`‹Fª/šä¨¨µ4ëá£(Y�šå”±à°0ˆ‰Ÿu¡XE (š�X¼TÝåA‚¬ |�!˜U”cM G R$˜f¼‚“ôµ]¥ÒW¦õÖ+EÕ\‡õ´®‹¦¶ÉÔš&ÿÀaF)¤„Éö— ȽÀ‘Ãöƒ‡Š6ÕV¢58|a²ÒXAhpÌ\Ï_¨Yhx^t“ÓX 7fØ8çÐb¥«Ù¬œô#"hÁh•ð¹²3±]íÐȃ‡¯w7&{cŒ²­D ŠEyÝŠ‘<¦BR âhÅØ¼Ì¦š~X¢?Ô/iV®ìŠiÅPÍZ½²Ùþø@Tìç‘&E´~êŠ}�:Ü£µ²¸*è *à ´ÊöNs\i´å¥<è±¾šªðIùÈ7£¬Cè€Ïp`ªP‚ÍV•«jc&¦Iš2!UÙûªŸ$Å'NµÓ¹.m²®Iõ|®«oÓ†¨b€®´%³RõÙ!®Îϰg4åÙƒÒu’Ì·_d†ÚŽÝ|R>ÕB¶´[›ÎQ¸ÌE¤Rb£89PÖ*Ìþ³Þ;86š‡‡¨¿˜:ý 1-8ñ/m_=‹ÔªŒÔÛeWË*ÞSJªJÞ4‚Lí` §ë†ØwT0ø®mÃÿ©*éFñ�-ôœm 8ñËðC ½ ¯Q ^#4ŠS}„åÅE[ÒR,8A¥ŠËJ J3Ræ‰ Úi¥ ˆÏ˜ÁYb7RCf»ßšˆº8<ËuÉr6R±ôÃ~¼ª\ÚU�% U³ÁsHXìo.›ýs—@ÛÝŒ�2|ŠVàvfØ 7=}¼ë€âN§Œa1nÚÔm}s+õêˆá¤˜`è@úèÊ3•\fxÆRùL×c/N ´hm�Í‹²@5žLçÐïîÌx™%¢B«äL�{/Þ-Ù òÌt LCÝkÃe0¨Þ¶*ìôTZ¥ìTxžÙ^¢�~á& E¢TŠª/q=>Ý ðÌß M7ÌÓDCÚþåƒØKaó)†¨6íJò‚¾2^6Á›» ÅÍáË ªîBY(rk^ê°A¯#“ë”Fxk–w>¥IOå">ž9œÃa: ^ÝøD|:]ÇïV’1Ô õž)î±h:aö[PaÖPÇís©ðV¬"b½¼Ά††D_ÒOÔOðëd£™éÆÔÃ^È^u>0•Ü�2€½ƒÍ-´Cq¼‚«.®3ºV­È`A,‚,Úªµõ¤{Ž*Dx�­fÒ%‰¥ Ý<^Þ¢£O· mcqAWü•Úõ¤6‡ò¬òlÐÉòBYUuc"KSÀ#¼v¾ÿtwQAá�³ÎôV‘E0†B_Mµ\`4lÐÕ:Û™b0CtæJ§|êÒ<¡æ³«¼»àÉü$ï¬Hoýäf©Æ–Ú§ÓÕ,5òÀŠV šÖI§Á¤�³‰c¤“eUè˜Ûþ‚žXPw˜¢j\0 @*§EX8 �½B1Rpå¹..Z—4ª5¤ê�…Mýqë^'1»npw‘.·�ØÃa†æ ÞÁ³‡„¯¾ þ>|ÉóÖñ?ó¼Ûf5|רþˆÏZÛC»g\JÈ–¼í?«gû±c‡ ăSœ€Ù¡�µä�RI+Nç‘‘‘ó¤bÜyV³3èéV™ƒàü'YãY�…£êÁ ÓH«:€êJ&ÜÌð›–ÇU»Æ0ÏýàûÑ\ÅˆÖ ý¸³Fè]�âX{j`çùì›Ø/<½ó”¦I—A� àœÑ€î†{: mßðÀNñíØ/<Ü×Utß4/àf¾5íè†@ ‹új ü›²òBMQ•LxPlcÇ^§êwõ¸m ¥¢XOéÂC¶!ib.ôJAy„_ëtÖí¹³ƒLå€&Õ¸×þV«þÏ𪺿pŠ«F·å>1á`5x”؃Ê8Á»5Z ômwsvM—N(êŽ1D…–˜T×&DÕv@?ï²R·¡Ý(ûx gï÷w«‚õ‡ ºkÕh'ä¼Ñ¸Î>Æ;�\HªçÍÞŒ;åúÞ–î9\+Wóáø´=’zw´ß%?`{þžÜë‘C.†}ã4vKÁ!§•±î�¡9VVb»:RR°£«œvÎ|t›÷Ìzt•_EdKz† —7JÒQ~ºÁ¤œÙJ§¬›±tiKí2³7dè<üaŽ Ã€ê�HÞ¬ü OötÝ–jct—9ßmÃþîŽKuËtÈó�KèàGÈ1YAò)ƒÈ#ÕÐqhÎÅ|ÙFç�3ˆ]˜u`kªõÅ)óÂ~™)zq.ÓKAçeª”ßþ/âT£@‡çdœy»²¾y€’Žá˜Èž; îpX>õ¦wÞ·Úes�µªiuÁZÜù© oçòûØ"¶?Ð9²jÙqëîC™êýõq×"êTJL¡b)°Üád�WH�7¢àI’ÆÁ5 Ÿl3@s oª¦8UÆX84ù:¹›½œHƒ¢EŠ;à᪱ºb3�‚šlö0FØT‡@µ)Ä´J¤»f(Ç2íïÞ#ú®T`�<•Œé'S`bÙ‘¸õ®¶1+[rªê–%í¯†Ã�ëoõ¡î¸_™­òŒ«6ŒÌw…mšÝ®+·íçˆÉG5¶×–3â:¬A ~e‘mÜlÖ�ðzÓΰûmÆÀfíÅ…5¼Š.¥-õíø…¿ÜÎ`xOõ-p#¼S1}Rrbj§m޵`î|í`I�4nce „åfâ!itp°—ëü­{¸ÍºË vÞn4ÓÄ[_W˜þ2~¬ÚË^ße�ˆÚž¨Q6‡F;�hS5æaUøhî‘rË]¿›p—Á/@U_w®¿½óKÐæùdú·ô¿ˆAÞ¾%7 ªznz¼<í_Ý@xÿZRl æBŸ?xœ…SÝnA ¼ØƒAˆŠå¼ !$MŒ6˜b¢±©¦X‹¤1ÍtØÊ„ýËì¬�zÑxå­dÀgÐ'ð|¯| gv–‰{7ó�ï|ß9söûçÌïë_Nr�DÁŒ¼Ílû±¿q¯ÐôÑs„¬¡cæQ@høâåÑ>ZTdÿJQBöV®¯{e/{û8fAÎWLQrÚ±¬ól'"ª¶ï ¬øxkRh·aox<€€ã3?‚)¥Á˜:Ô¥žPP¹qµÛ² ¯ü1`ˆéõ¥¾ï™#$ü!`ùªS¸MÀ¿ŸW`q1¡ÜàØôhBCz©çÌq`D!ØžRxä¿£ j[µ! °É‚MP¼>'”k6µœB¹ª—=óÎB‰Ó¶BÁ#[Z±>Z ¾vC‰‹5ßvp&,`�:r¯T; u¥þ)Wçm=¥0Æ.; †¾Qbc/UoO˜C‰šöšÙ"m’ùR)Cä¯b¥$`ñ5¥(ò¤¼ygÅûÝsìRàògí¦üZnn™æ1Q�—]ÕÝHšmÖ;šjàÝ×p†ö�† U-êçàÔÕ�ÀŒ¹ïÊO•…-!óÛ…‡†]e—Õ×ÍÝR&j©ˆ¸gÆ�°<è3BÞ­”/ 2Ñþ?š8†„¨è­F „§)øt�kKwI;Ê¥j’a*KfþÚ]š–œ=.ÊÝ�\ùdNj8ÿü_ ¼Ž7t`W2EX™Í(áåô3{kIoXÉLZcý߈(‘ŽHÊéE*¿×r�X^Mêô¢>œ¼;y$2•y jè}sï�ë" q%ý}5Rù~WjMÎÌç“ÊÈ\F˜Œ¡Èb° Â8'?°þ2„?Q©28½ƒ t«OÝþCÜ«‚\¿‡LY( †4à8ã]„¹™A¤6y*E!l¥Mœ� …éÂ× C…V�¸æjÕa+§kssêyÛív$œ·#¥×^ZÊo>›Lþô˜<®4®³��RÛðDNE"$?S±¥A¬5Ò7«Øã­–Vfë!µ²[¡‘abi¬–aa÷¬ö�h·èÈDݱ3¿ ¿�ý™?d�›ÙòóåõnÆWWãÅr6õáò &—‹O³åìrA«s/¾ÂÅlñiHÇEvð.×̀ܔ|”»só÷\X©Ò%“c$W2"jÙºk„µºE�#ÈQo¤á�r0f˜Tn$¥ ÛzÆ‹ y�#¹¢+‚›ù—åEpî >Açˆ6)iŸí“B¥EŒðK*ÃPôª÷(ùµõÑØ8T*}¶I°¼ÇvK³Qž†ÿ:xg‰ t']xèaË€çíÉpñÁ¹Ò¹bŒR¡¿s±÷92(Ūˆ,Ûtc¿T¡�…Öï³Åo'8ºò%¨‡Ð³Ó ‘"‚”wÊ[>|>Wäà´YXf?®+œ5ˆÓ7ª9ø�ÚuN‰˜g½ç ‡¥Hÿ´r’Þj‘ÇŠVo°aUðĻϔJ)%N��"²ÄØR�Ù'fxŽûaøÞ )áú½vlüÊ­­’1 Š˜WÔ…Ûak¨4 ãÚcYäLkPìÏ’+Œ6Ú‘Ȉ’yædBIÃ9#,ã�â†i´H̆(fab®�âQikøaØm”ŒÐÌa<ïÂõ`>ž÷-Èãxñå~¹€ÇÁl6˜.Æ£9ÜÏ`x?½/Æ÷Süº…ÁôÜ�§7}`(®Ãž¥² Mn¥dq¥Ûœ± ‰p”´d”'œbhEZ’‹K<1U`D ™ZsmSª‘`lar¾æ†˜jè(.»PÐ9ã N$†�“¯‹»ðóí×ËÛðKvÎpœìÔº4/c¿P•ð³ßZC9�"¢YP?«Éëq2 —óQ¸œÎƒëɨåµÉU`¶óŒo!©Êµ¬yv0%%5°·¾´¼ •y©íÝaÏ‚î° uÎXó lš ;TŒ†úYT!Œ‡ †» "P©"2ã´±ðëŠ\Ø]Bw0¯X÷m†°‰ÔÀ  «£hV ä•$ЬqƒÅ&k}g¬î!Õ˜b¦Ä(p„ãÃ×Rˆn“œ‘ª)h[0{“0VBöÎ]E€éÐ5±ÐEî'{À«,4O D®HöÇQl*�Ê7¸šSz ‡f(¸­P)xa¬ê¦ËÉä¥{ÞÒT7ÀƒõÐ.C-ïö>æfmáÞ¸ÐÕòØ;W] %´t.m11@‰m¤hù%ynÛÁfúAH§ó“àñË„E™ç<ÙöN$Á;Ž»¥é¬Ê¿“ï•¢Ûu‘QkK¹ Ú²ž‚ØÕyGŽUËË ¹Á€�ð‰‹RçÛzÿØvþ–}oT§ÿb{´bxC(W)«CV°Æð>¢49rMzµ'`¼F{j¢1C¡ÄÖµõ Ó²lïªD‰u{O�Èþ!™‰ÇœT­84«]¢[õú½> ßkaÿ¯°·;Ã^Ïz;Ý ›Fm›ñ±qfí„×M¶6«‡W® jWðÜKMù‡ªàÄ�÷wu¼í�½W½z<ðNñî/ÿŠa„�ÿØš¿ØüQ¶xÿÌÔ Sc }åûÛíÖc­'UâgÕ9íϦãÉ|9ù•×7Öy†Z“Èÿ”BÛp¬ Dœ…„3c[� X¢�öŒ´ˆ·J‘'CÐ26[¦Ðº‰„6J„¥9¬ÁG´ÛH2–Cw´„é² Ž–ÓåÐ:y˜®ÞÞ¯Wð0Z,FóÕt²„ûŒïç7ÓÕô~N«70š¿‡»éüfHrQ|,”e@0…•#§ÛñB,+Hº@.bÁ‰Zž”,AHä'T91‚ÕFh›RM#ë&a˜q¦^6�ß¹1mij¿VwÁb2^�æ·³Ið6:´%r|b—.ç<+#„ß2†L£_?½ô�Ö¦6‘È�µí�]ÌpƒÖÚµÖ¸ÂÀ‹¬ÔößÁGC¼ ;îÂçÎ摈|ÿàŒí4x#¥1‚yÆ”#{‹¦ë@ô,S³+ÐF¡L–Ü@°Í6æcðµ?'–ëNÓÌc…䇄m54¬5Æeæ’³a*•ðJERk¯é÷‚`m¨?"“¶Ö)6# 6p™IÕÜQhJ¢ÿN’p6mÒeNÒ²U3_Ïf¶$Q)©ª”ž›¸£Óëý(BnøuYa«Ö%^^’‡¯¿—äII¹Ó‡ÈŽXïCF9tŸ¤ˆN”Ž*_½3Ð<´Â/Ñhè‘eÙÔô«ÿb3_¾Ó³z´FØx•ŒÏA~[ìg™9ù–ZS[ß`âŽü•³…A�g ·* ]ÒL¢®¤ùF×~ëÆ§‹'döÕ»?tR»õÖ�ÔÊ­UÔ†jÁh_¤Î5î%+¹a~ÑPGiqh¤^T/ �ì0 ž[Ϻºrî GÉk¹;+‚^Ž‚g˜|÷$°Œ�$miß‚Ô$x� T•F»tÕsšÓ+Æ@¨»ãêéÌ8­·®Ïü/n¼ïÇþ“C¿¶ûƒóo¥ê Œ¾è`B³˜J¬õö3ßyÖß!�U/å0†Lxœ…RMKÃ@Eƒø‚¢mT°†bAA-("*êIÅ‹².É´ M“°Y[<õìIËÞ<ú¼éÕâÙ_ 'w“6Q›ÓîNÞ¼÷æÍãÄûèý¥æÔiµ�}«O¯‡ýùƒãí½]²Oˆ>gaÅñ9?:>=$ª"V2ó¢–±'O8å~(pßwkÕG¼e²-u@¡éÖy�tnº^2  Øa(±á$®Ã¶èb=¡í7¯ ÜÆ¸º$‘ ¼PFëñ (·IÀY·Æ�_3Oì à+\VV;û¨ø?î(fæ0$02t¡=ŠÕoyí¿Z¾w虲e™|­un†?÷X)¬ð<Š-�£èîٜǽWexè¾þ´ÎÒ²€ÜÚ©#›¼$mu”äz(”œi”'k±¼ÂxÝÝõ9´êïÌz 6�ëõ”ÇZŠˆwwf¾ofvf<èÂOoõ@wÐt»-èÂû¥L±EÝ�iÁ­…*Ûj¹Š-¿�ªè„pùîòøËDfÜ 4¨¡³v¿ïWùb�ý0¾hŠ‘È÷ð«R+24™Œ¼‘‰ 15AžF¤ÃÆÃL„ôãwzð;j#U —ýwÐám¿Õ¾¸f[•ÃFl!Urƒ¤Cp|ð%ÄÌ‚L!T›,‘"  icgÇk鳎g¯C-¬ ã˜;¨eý ëAÇÖfæj0(Š¢/Ú¾Ò«ARž3ƒÉxt;�Ý~Gˆ½Äcš 1 ñ�\jb»Ø‚ÈQ(Îï( b¥‘ö¬bÄ…–V¦«µ´…ÐÈj"i¬–‹Ü6Vá#Úõä2‘B{8ƒñ¬ ¿ gãY�•<�çïçð4|xNçãÛÜ?Àè~z3ž�ï§ôö†Óg¸Ooz€ä.²ƒ/™fS²+1r~›!6 ,U ÉdÊ¥ ‰ZºÊÅ a¥>£N‰d¨7ÒpH ŒXM"7Ò ë–^ñbCƒÖ™\ÒÆ‚àiòÛü.˜ Ÿo‚�AÐ:£e™â‘JÃ$�~46Z(•ôãŸk‹‰\,„Á�ÿåM¾ðAiòx†‰Ð×Lør€!˜Øg@v›!"‡ç¡… H6v¸+Xh¼]¿�V•Û,·Ê«ÅëÛ6&¸ÁÔöãvm1)’ÚƒŒŸž!ÓbµW°FÌj ¥×†ÒO>M¨H4”˜¸œÐŽ–§C›àró žÙ+:®�sR”9fInø¯…/–ò Ú£6üÕ:Ã4’KÇ¡~¦ªz#�äwÊB_ùÊhºü©+Îc†®`Æ Ly LŒIbúU•Ìñæ5³ ³æ”ÅMgTRmN0?)™ZNwå2¾4Ó­ ª@Wiú8™ð=F­•.ïA3ͺõ×ÐÑé´€žSg‰#H©’VN¹A’U[³t@µ4FBÏg%£FBF¥|çèNÊ›:æ“Smà�¥×èó˜ä})öqÚùa á ¿üo˜Ô0ŠJª.ØU |иWPÔ*ÕU�aÎm¦� ÉPƒ[!»ƒ nžE…FDÏ�� w‘8SÜÈ]�‘"SDê%%Væ8 [Ý‚ÓQ¨ÖœäÁZ-ÜUȸ6B&¢(p²eîœòZ¯¶[š¢Ý�ÍÞñ¼k$\Óõ¸¡¦P÷þR«ÍWåÝ1ÆGrQ; žÛ—i�~N®1Q•uÙsåŠñ$¶ çI¦•U!ÅŠr�õKË-þœ$7¼èbUø"�zqr£Í ÜXÂ5Ù`udq)W9�>¯wu� 0kÔ=(ðœ²swšUí"dx41œµ©´DKœÈ¢–¤âzóß÷’ºÙR.E$¶Hž+?~¤§#x÷³MÓÑh#x¿)‡ ÏŠI‚ï¶)”ùN‰81ôvQ¾À0<†·¿¾ý >'"yŸ×\ÁðÁ~¾�‹ÕŠOÂä¸-†"ÿ‚?¤ŒÑÎ|~á�ÌEÈ3Í#(²u˜„ÃyÎBüð=cø/WZÈ ÞN~…! 8ò]GÇg¤b' ذdÒ@¡9êl8ü{Ès"ƒPnòT°,ä°&±v¼– éøêuÈ•a8œÅrÝÌx§cr}:�n·Û ³ÞN¤Š§©§§óÙÅÕâöê=ö÷YʵÅÿW…Ñ®vÀrô(d+ô3e[� X¬8öIo•0"‹Ç åÚl™â¤&Ú(±*L+a¥vs¦Œept~ ³Û#øýüvv;&%_fwoîïàËùry¾¸›]ÝÂÍ.n—³»ÙÍß>Àùâ+|š-.ÇÀ1]h‡ÏE€n J%�lÞn9o¹°–Î%�óP¬Eˆ¡eqÁb±|ä*È çj#4M©F#R“Š�0ÌØ¦ƒ¸ÈÐtðF¬±c Aðe~}÷)¸¾Z܃`ð[EÆ;¨´áƒT˜¼"¦LY§p�e ÚNù„t›]ÎI7æ® ÛtcZ ��æËÙJ—|ͧ à $÷Iä�JwÏ›±ƒÅ×mƒ�f4�-›fïFFü `:…Ù—¯÷�+oØ)/ã^Î*ÁߥÂ5ñÁ•X Î0I­</sé5Ð׳Á?¾Ð¯±Ât ƒU¨]uØs!Õ†¥0ÜŠ,’Ûc°Ý¨ƒY §0çkQÓ">BV#Ê[ëºr´±ž¯o.¯‚ÅÍòú|>. ÙO�%m/'NSiËY²ëQQd�òq›Ð®à¶#ïöZ`³xæõaÑ1MHEQ·Fï9ê%º½]Îþøxw�[Á§*�W�XËU>s™y�Õ=tø{?Ì;?°e¨"çA¼Â Jr.N™L”,âÞ#f´fX$2i*Ó"ÎXŠå�TÐ*£d0tKd†¸F1üÂŒ·9õ[±W%8¥P=$„ˆµ1�êºü\…n·D®1+hoÅ«pžUìå+Pk¦À"ƒyI0K+αÄXš¾H#–ºQr‡ÕßÌ·sVB¿¥4Š3õ¯çIÉ–rÜ 6P#vnTÙ¥¸)f>wdVã¶Çi÷ó9mÙ\)©Ü–×Ú†F�·Ðz0´ñôl"£–#ǵ÷—.J]/évV“ó}:x”"jZð)¶=+E¼•*ÞŽJÂjk”¦GwÔ¡ d¸™j·ú÷»›ÁûÖŸåmM}ø¾¦ujz¡éÖRÚ_�æôÔUÖdo¦ËzkÇlŸ¶ëè˜Ñ‡Ü£w‚;‹Ö¯†:“ñíž>'.(N•«‹0D$uµKÌ¡U›Ü¸ú<¨ÚF$ãªè„˜:jD}öÀ ¡áQhAôš8kæ_ÆD9<4eÊ.>R�Û~†ÔÛ‘Ú§såP°suPŒÔÝ3Oc°Iq ö‹e›p˳+`äÇ pçöÎOº2ë¡ý5Åá7Éç jB‰Þ�©³/äô=�$‚þígæÀ[x¯ñÔÑáD?9i¼ÇO{ÜÈ×y• o÷û&†¼ e±êSEvÀës·OÝJ©Ò\#ˆ%ßàÁ«ÇZÉÍO�DY#?Ìkã‰ñ Bì[kÈ7`Ú�üu±òè‡î[VC'…*J% éx|]hc‡å`éÎeæ€;YvLøå4ÿŒŒ‘:çÞ�g홫±šx§KKUc‚?›:ËÿÊ(=#!TÚ Í÷÷©ÒÜÞÒz>¨Öd–Ü�øeËqš™©wJôû%¸ü’*uŒÑªt¿j?z®:Ýkm”–tÿeѬƒ”g :Lâ¨ààˆ¦j>­ýI‰“€'UOy6®X*–‹°ûSaoéA¹•,²H0׎:±M¬�Á�ÁG/Êâ­ w"Sºj¾'»k={:g)ÊÓï Ì>_ùÜÏÊüAùN–‡�±…‡2ÊÎá–‘({f´4¨{�‘4ÎÏnÏUÁè@Ê}ªä®•Üï�©o⪳¯êñuØsYà}mŒoàÌ’Óý)MU�þ!Æp¢‹´µ[¡Ìˆ_ž—n2óô¨&ŠÎ1;A{žu!óþœŸ7`-\òÐÞ–Dã@8û&WâNã6'¬¸žj³CYZp7²‹;[«‘Õïb^é< ¼~ô¤¶dËe´ÈèžÊŽD·ÂÀÚ€Qý½F8›ƒQÕèS6«“ê$žM·ZyC§'Ò¦¡åö<‘h/N÷jâVþ®~ƒ+çÏq× ¡v”Áa7#wì�=üIòª¬î/`ý/«…’2)*º¨Fܜϧžl‡'2÷‡ƒñäeÜøܳ«ò^rkGµ�sØ—Ô;¿U~i&-=„i´¼[¾Þù -ÃÕÙêÚõ`¡Zk܆^ïOŸâcëõ7wí¼‘xœ­Y_oÚHϧч¦…ªWõ!¹žš&´A%¤ ä¢êtr{1«,^ŸwÂ�úÝof½Æk0)¤EJ©wg~;ÿgÖt�àݯúÀQ÷ {ttGð~*$‡9Oò@>ïÌpÑ®‡*]f"žz:+à0|¯_½~ _fBŠ4…ÏŒkžÁá�ý~ç“ ï„³u6dyŸ”Šñ°ÁàÌ2!O4� O"Ä03§) ñËí´áOži¡xÝy‡DÐr[­'±T9ÌÙe ×1„«yj@$ªy*KB afö‡Ò!Œ¯CM Cr¤;¨©OÌ8¡gƤú¸Û],f¥í¨,îÊ‚Nwý³ÞpÔ{‰;Ž›Dr­!ãÿä"Cm'K`)J² Ê)ÙT,Î8îE/2aD·A«©Y°ŒL$´ÉÄ$75ƒ•ò¡Ú>šŒ%Ð:AÔ‚§£þ¨M ·ýñÅÕÍnO¯¯O‡ã~oW×pv5<ï�ûWC|ú§Ã¯ð¹?†TÛV~ZUž@‹8a²òÓ ådËQØü&œÚKÄE-½pÚŽêhOlÁ­ö¥1ì(“ñußu\{*Ã�«ZŠ4‡ÐÒG€6x~/´À¸}®øë’ó}ŠõÝß ãÓ 5™îšOQaY<<×®ä–ü79FÞ…!N}SÕ½Ö§m²÷ðf0 ¡`Ê„Ì3×V7;ÅÑúR¡Ç¡W3í^%2òÔäQÙì¼0°^ ¡ºòÕQÈl¥ºW"ÚðˆsÖaƒÈ5w<Ú�,£!Ýb4%ZoºÙ�� çwš TÆÅ¦�Š� ;=&£ £Ô†•›ª†ñ¼ƒ©~Yü`Šp �‡!ÎyEpÐijY$¸)|¿›¦í'Å͈/fVÖÀ—*‰qK• |ÉÄ=£ìÍT.iê•K*§!“²´×�Ì¢(ØÃ´v½²ªý\2{eT›â*âo0ÍTAßÙÅd>ÆP±‹O4bI°Åj:ŸXÃã8o×Ô[ÅSPÓ{ì£Î¨ÈjÜ/úÂØý›ª”öÈǰ;Œ·E‚w»™H)B¿yg~kÓÍ ,Ïú4;F”‚`+0~pw/Ï.?@£{”u(ïsQ@Q±÷Gf}ÿ–Ź�ô�'ÔÓ#Á3œ eùû©x¨•ȲV½e£¡4ÔÈx_}·Õ¬csñJ£}2w Qìã;¬y¤V4ú§ìE¾ íf­Æ“,�MæÛ]¶õÃmû—ÈÂ}Ûš %mì�nNlÏî=Í·èÊ<�VÍ�FüU…Ù·€ÙûA9NlícDµ�ŸC•`m g,ƒ£ò„u-°Ù7&f}·Ü¼ E,[� x—PÔš‰N%Ý|†r[ Ï´ù×ßîÌ›D¬}T¤Ð!º / í¢úmúÊm�4¾äøn_iT¯:¶¾èpëÝ£æ÷±ÅoðîôpÈÃ^äýöð”_8ïû"*®»’xœµXßoâF~篑@>å¡—¶ %i…Â)�F÷ä,ö`V1kww‚ªþï�ݵƒ�1NWKñþøvæ›ofwíõà—õ@Ïky½^ zp½â1B’é4Ó¾–,xF9XS�í ’t'y´ÖæmT¼@'èÂåÇËŸàËšÇãAĨHü;ã’¼]dQÀ–dg̶�H`‘DêÓ‰±x+¹æ"êƒJVzË$˜�+-ù2Ó ûÈíò¢Œ hç0ž·á·á|<ï�Çñâóìa�Ãûûát1¾�ÃìF³éÍx1žMéíwN¿ÂÝxzÓ$ºh|M¥ñ€Ìä†J -osÄŠ «Ä™¤R øŠ䚈2!DÉ JAAŠrÕ ©2º001ßpÍ´mªùeòZ|E+ðýÇɟûÛ{Ÿ|øò°ð÷Ñyýìû­ ÁžDP"ˆ³áç˜/—L¡—ÿÖ¿¶ZD`hØÆÒwùrUoòcFª£c–³*HãL™¿¾jòÚ£6üÓº@ò€çUƘ¼„9¹Œ…È0¹i\Ö» x¾¼¿�7úÙ¯&±oÌ:Ö|Õ*rÄâ˜ä°]£€§’©–O , �ìŒγAQR&Ùª³JYh`AŸ(÷dÈ4sÀäÖµ$ã�ZH$×ß‘¨3blú0™X¡”‰ìÛ”€4áB[A0r•Ç1,R¦”5ÙL7OóRùk–’ièÄÅ’:|ݧd4*ÕüãÝ9P!R@’] Ë)´×KÂCèuz§0%ˆn§e¯) zUîûv˜[‘߭EšßEœ×ªß|‰ªvÊ”›j؆쀪ǚªÍÄhà ˆ³$ÎL¢šè¤‰âöÿ`MiŽêÊ4ÚqL˜LHŒ™­EΪPNl4ާ’ž§´RCeÔû§É¯iáûÉ/·ìÇÕCÒœ|9 °’Éæ»ð‡ÐR×õÿÉ‹M³V¾FÇ1‡þ—ŠäÏãa•°¼n)ꊓí ’gãJ”��H*mC¶±\¹†…õ”0"ŒÑÖ.[Î(9l5s%¢¡‚‹³ª!9•Hì ª¤Ä�k?«0íÉùöäZbíYüöäºüJ|—”sãÕ±]s–sKøÛÖ¸„5Û;{¾DýÁUJszq*-à‹¬³Ì5 _¹Ó¤„RÉç{+3çrÛ¤¨©mkêai9i_cQ)KÚ- /,ÎðôfÞ¤œbCu;7Sej�#oÐ5ãaæ²§|U ÔŠ'í%æÄï¬{Þ�gS„:oê4K±€þûª_§· 53¥?}:š®ö´w*ÅüqÞíOYPúTÄêÉÉ� g3×–*§’DC…>®­JÏc=Å�.O¥”*f3½#—À7g4ß »�wîoµ§¾á5 y'Rîu·\½‹¶\c³J½£§*)VAùÅ# [”†¥òM‡¯H'Mw„¼ÿø5æ_{iÙ_f¯2•ö÷.Ï|Ú€[U~Š8ó«‡q÷?˘ԿµçxœµÛrÛ6ö]_qÆy¨¬Q¤6»³rÓ‰ê8­&ªì±ìõ䉆HHÄ„"¸hYíäß{p! ^d©Þ–3‰LàÜpîàýßõÀ`Ü=À‡5K(d$¥É(Ƴòl/Ø&Vúí²x�~xï¾÷¸‰Y² >*©€þWóûa“¯VtÆçu4Dù7üÂùÍç—ŽÉœ…4•4‚<��†Š)L3â�Û©�Œ§ðnô=ô5À™Û:;¿Ð$ö<‡-ÙCÊä’" &Áœ‡>‡4SÀRù6KIC ;¦bÃÇQi_ ¾RÁ è³_û€@”:V*““ñx·Û�ˆ‘vÄÅfœX89žÏ.¯Ë«·(±Ã¸O*%ú¿œ <íj$C‰B²B9².€lÅ=ŵÄ;ÁK7C�|­vDPM&bR ¶ÊUMa…|xlUFR8›.a¶<ƒŸ§ËÙr¨‰<Ìî~½¾¿ƒ‡éíítq7»ZÂõ-\^/>Îîf× |ûÓÅø<[|Eu!úœ }“iUÒÈèmIiM„5·"ÉŒ†lÍB�aöðå2A6[2�¯”f0Mè–jg=gð‰ 4d "Ìq'0MÁÁiw#¨°=CØQI’S¸1a©• öÕJ@#ç¡‚`—lÕ×À„m  övq˜å]ìBý; OL¨œ$°¥*æè{Ú�²zÚª;³r"ËŒK¦W�/ ²Æ?ÊÔCk0¯í\øæ2Ê cÁSžËjÛØg×:3"‡‚”å#&fNŒMMF @"’)Ç~*Ï?šó<‰ Z+ëʈU¦Þ� Ì&Gí¢`Ñ`$ižæƒñ Їr™��‹TܱS[³êÔU9Úõa~ sí°Œ)Æ’_³Mný´!ÉØüæ˜þõ-Ök¢@Ÿþ¼o¡ð©GáÀ9¥ÃIXqëKVR¬bß.Ú>õB˜�Ò»à#•¦âöEEËÊŒRˆÒýD90È-šVþ×µØÕ«hC}ïgÚmØ SG¦Ö<Ò4GË'" úqÔ²Rë±4PÁŸßˆØ`�Esk^ ]ÿål …@£^ +^£GWÔõR'¬xöJ²ˆyˆèŠ+Å·¯¤k‘=/1< ZØ‚aÆdO~ç)¦ ä™±gš #¿ýA§5S€â–%¹@“«g‰‰Œ¶åﮓUp,slFë”îlºo˪ïù—Ô¨ÕN¥Ù“êBEÛ¯ Ńož:ox–gPu¶°™Þ­b²@y‚ØÐ»&·©—¨¢lmqEžn¤ ÂÅý|XSTƒ�AnDàÀ.b2¼è`Äs•å5V:æ¥ä!#ÊÕ‹N&±à5¨-Öx-xdÜ«Tu “‰a\©n%ƒ(I°�RÄõ�%z¥f˜ƒºißzvöÞJÖmð,Åe’`øxypTŒc¶º•ÕÅ_ó¨Û�žWò0¨t™‡!¶ð¶�ÖýnMB†¼û-¹Ûå ]G¾Ð~!|]˜óêx÷é[öO8î=qÕØa¯Ïú‡dô¸]a#—F~AÁ¡«ÞDÕÃSž¢é2ZÛzÖ~Í1w³IvF¾9ÐË�45R¿Î–Ú &£SH)dÆ1¹êw ¥I€^t¸Y$è¨]M€AMn»|Ì0¥7•­.ƒÒÆšaÛ HÇׂkÁ·ï%Þ'³.'Ø ™<9Ôk.©e° ý¶¸Nкw.©ëôm&,¸>–B?VÞ(¸¢¡Î‡¶Á·µÍÑúª�sŠ~s 7º¦´ÈÒÚÛŸPºQ�MEÒ‘òÆÈÒoiÆ�¸ù½‘Öìœ;Ô×o�g6[µyó´^ìA:|Ò sЦJu;QÿßÞã’oÑg\×áô‰±1¬Zæv¹PЩ=×:O’€Jë¹Ô¥¾Ü1ùo*¨mªÝbÛ-�Íš©Ë/ÚÆÅ5½²ÖõkMB…ý6Ÿz�3·´e/Û�â½Ktˆ:W(ïiŠÔ‹ƒL5Æi’GÈõ1¸Iº?Çîb¥ÑIqÔ蕦f†_ñçêN¨2JaE£íóÎ8…<#Ýjij&¡ 34DOLº»s)±ß%öú´«ÆûÑd¡Ü-´=¾—µ\Sã]ÆTâ¢ìß¡âQ²­½Cëygy¸ºïk J—“ýN}¨×kÔ\Ð6A~P¤Æ{lÃAé±;$’šÙ·êóÜ ¨å‰íŒ2Ê¢ÍNµX¾xárûùêt9´6Ôùø…›Þ5Â7®³ý¥‹ú¬¿.©w û5R�‰ß ü“ÎQ¿Àð³VÍ”K“‘ìÝ…qft‹•-Ç:ávʦ·Šø£Éé�f›Ü| xPW�T·ê\_£”±Ö¼Ù{´µÄãÿϲ/î¿YÓ^¢Ÿê$M<Û6=§Ã¼ƒÖZh °£UÃ]r*Á÷¶b4\Í+lÓÈbô»$ôÞ\v|{øf¾4T_ ~pëãAû£—ý¨ ïßÃfÏ2°ã×~6ÖôþÚ’âù·ÅxœÍWmoÛ6þî_qp>,1<«È†~H¶!®ã.F\§°�Ù0”t–‰È¤JRQÜ¡ÿ}GR¶å—¤i±`ºã=¼{î…tЂ_ÿ«ZA#hµЂ‹Ïjþ#¦:s:y,ó¥âéÜØ¯ÞêŽã8}sú>ÎyÆó®jTpüàÞiE؉ç'Ûfdòü.eJ› ‡½j“!�QhL  a˜9B7g1½*Mþ@¥¹pÚyÇvA³R5OÎ-ÄR°`KÒ@¡‘0¸>Řàb¹È3ÎDŒPr3wûT(‹q_aÈÈ0ZÎÀÆrV_ÌTNÏ�ÉõY”eÙaÎÛŽTi�ùu:zýѤÿ#y\YÜŠ µ&’?\Q´ÑXNÅ,"?3V‚TÀR…¤3Òz\*n¸HÛ åÌ”L¡…I¸6ŠG…Ù"lå…]_@”1Íî“&¼ëN“¶¹L¯nn§p×�»£é ?�›1ônF—ƒéàfD_ï¡;º‡ëÁè² HtÑ>ø”+¹É-•˜8Þ&ˆ[.̤wIçó�)4‘,EHå#*AAŽjÁµM©& “ñ7Ì8Ñ^\v£ qÄg¤˜AÞ ?L¯Ãq2ø³ÿ®;¯Â°qD*.ð­­tx/ј@‚qÆ”Ûì ¦Üÿ¶êíf™£Ýƒ8,ba™-ÌC¸îŒÐÀžä¼Až‰8+„_2ELcP½;óß(‰¢yiû(>ppûЭò¯¿}ðtGãh J(ÝâÜðßÁ ­ÚåãÀU R�ì_Üߌ÷g‡{%Z‡ï2þÞNÿ /›’Ú½ý;þX¸0÷÷®´ÎxœµYmoÛ8þî_1p?œøe·w¸éõP7ɶFsq‘8‡ƒBK”MDµ$ÇØÝÿ~3$%Q±�8Ý^°[[9óÌû =:†÷?êŽG�ÑñqŽáC*2JJ3\â³]Še±Qb±4ôtZ=@/>‚·?½ý;|]ŠL|a\s½{ûùaQÎç|/�ÚÇðÈßà“” äsqqê™\ˆ˜çš'Pæ Ò0Kã‚Åøáßôáß\i!sx;ü z´¡ë_u�Þ‰�,aÅ6�K¥æHCh°âðǘD±\™`yÌa-ÌÒòñT†Dã›§!ç†áv$;È4ÜÌxÐKc }2­×ë!³h‡R-F™Û§G“ÓóËëó"ö'nòŒk ŠÿZ …ÒÎ7À D³9âÌØ¤¶PßIˆ×J‘/ú ejÖLq"“m”˜—¦¥° Šn@•±ºãk˜\wáãøzrÝ'"·“ÙçéÍ nÇWWãËÙäü¦Wp:½<›Ì&ÓK|úÆ—ßàËäò¬Õ…|øc¡H„)H•<±z»æ¼!•’.x,R£hù¢d  ùÀUŽAÁÕJh2©F€ ‘ÉÄJfìÒ–\ÄhÔy#R|‘BÝ^ükö%ºšNgÑç(ê¼ÁU‘óíäÚð‹T¨¼gLY'p…n<ã+ž£;/s´D–mNÐî9yWGCâj6'®¨Õ26­³•¹�(L"áûýÜÖ™Yš¢4¨ tVc)£@yœ• ‡db>gš�üçpùÏà¥6É\ÊlkQ䦽¶fTv2ÀDMb©,¥fG×K<\vƒE‘#°ön*Û+ºT)‹‘^`4‚Éí·([¬Ø Üs^[×"Oäú �RÝëâY²¯wTaäT9>£îÛŒ,¢A£óp«ê€ˆ]|GJI�3ÅEVjú¿Ã :(tO»ð[ç Ï‘ZTáËõZ,r–¡ï>0‘Ùø­¼þƒBš-A\Zïò¤ß:€6ùÒ_E}“ J‡ë%Ï-͸TŠÜ´Ö…y¼Ä°²!èŸ1ƒ*û*Ñ)(v¤=™ó5Ü!vÏUT�Ì]Ÿ¢øòæâ¢:?²Ÿµš"m±@ý×öŒQƒ>Ì0»/0C[Ä=oK ežÉøtǘޜ6žççNDH-GKïä„)Úy©c…¹Ô%NÖ`=€�£¹ã‡0*sÏ*Urõ}Ì*� Ç9FO+¤\x#Zá‡0V‹“P£˜Ú�Ì�j"s�.=åÖÑw�?BÙ5MY¬êNg†këH*H—ê9(0àVMLE…QOÖ[ëßÛ-Š›#í3f+ _dC&•R ãJIåJ@+²Žƒ§Ø‚íµ5á�ÑÆcÿé¦J·y øQ£—3ŽgåæEÅX\NæQçAŠ$TwâˆôÚUGnŸ¸qœ47¤(g§ ó`£`{6˜I˜£ƒa%ÕEEç5r?¤Ð AÖØÊ_''Žã–þ+‡i™Á-¾,•3<ŠQ;ûJRîö>¿uµô|ßß ºy´Ò<Ö<‡rß.¹ílèì 5#µ6Mˆ'à4@•8TPEÝáw¸[|çw‰,Éj�­§�{*‘Ô_ßâ™ZŒ~@¯â„$‘v*ÕŹSæ &3–ëÌŲ¬Þa"ÊGe1б½%óÌ~�ÆØDñÛê‹�xf=¿`»Aj26µǶ%9—eØ‚*›lÆ¥Q`D ÛX—+Çl¿ß»/û=Q3aÖmp™ŽkÜO"¢Æþla�X¡L!!:…*Û§ž†Á+TD‡^VÐÕ4°\UÚ 7ÐÈ8It«'yY u_·¯R±$iZÇ—Åpï‚^¡‡<´W|…¡«n?²²„ÿo¨É+B¯ó™¡Î ›*’©ª¨mÎ ÐÇÖÆÑ Äy±¼¶Zœ?ÂÙa0k„ù‹¶×%´Cæ˜ÈPi8õR.«÷öiÑÛ9…¸%RW>-9ßáêz†N¨=ßÓEû52_¯…‰—®Æ¹ÙçÑf‚§´¥�?ƒKäƒ�¥þ³8*:¯ÁrU¢“þžbóß;úÝΧœªù+�Î�e²)R�ˆúkBÎÒè“£Þ\GIF�k”ËÄA’Ùo´Ým=vü�‚ÓÕR#ô�McÞ‡R“C;SÈAuüI³>«šøU‰aB�UŸfOÜÊ2œ*’Í&iµdGX­Ó2Ë6ýf v�¨Ñ•fl¢1$èªáÎs$¨wCËÐNÖ!Ý9í—màI? {y(âÒºáªd«ÙÐ4Xž)s–#8ŒE2„^î«q´óÐôZñ¦2ßÀ¬wcE íGòúO·Euó÷SÍÑùJ Žé(Æ¶Þæ84m"sܲ 6Zk•B[¯ˆS‹Òvl/rýaªÏDjöiÝùÄÑûëÏØå8‡î3:OHA¡åœWæëS§iÃ%˜£=Ë:2ýG³IûîéÚß,]Œn#cDYÝ:Û›@øÏÏÿµ÷Ýšn–½m«sz)Ë,ñe¯ oWÍg!2v"¸ÖPl¨ *ü¹È‚ö¶]H¸ŠÚ̵Ø2%*w•Šî*_óK‚»¼²¢Uâè �G< Rƒ‡Ÿ_éhÛÙÚÞ¬•ym‹êGúi[D”°9b¦¦3„ÞŒ¯ ©˜Ú5ÍPÀ'¼Zp«Ï{ÜI}i¥ÍÆßÌ’åv¶&îæd‡‹kl¼ìùƒ{Gw·gÏ`šZôOמœ¡[öö‰'+¡`¹0vúC?D‡° ý}µ­°„Òk‹¼ýÄ_Ý}çý‡½ánn¾÷Þ{ûõÑñÖÏ,î7DxÿÎ1]ËÔÿ†øÝ?R½ÿŽy¸ºêxœÅXûo"·þ=Å+Ý› ½iUUÍÝ«²„$hYX¶ÑªªFfƾkÆSÛ’­öï9öi±\Yú5(~Àixß\|ïWBŠ4…·Œ®áô“ûþi™-¼®ÎšÛpË·p£Ô�ǃ\ÈX„<1<‚,‰î8ôSâW>Ó�Ÿ¹6B%pÑýNiA+Ÿj�]Ä“Ê`Íž Q2ÃCpöðÇ�§D¡Z§R°$ä°våää(]Âø˜c¨…e¸œÙ*®/fs¥WÖ¦æÇ^o³Ùt™Ó¶«ô²'ý:Ó�ÃÉlxŽç;>$’šÿ– �Ö.ž€¥¨QȨ§dPØRsœ³Š4ÞhaE²ì€Q±Ý0Í &Æj±ÈlÃa…~hv}ºŒ%ÐêÏ`4kÁ›þl4ëÈýh~;ý0‡ûþÝ]2 g0½ƒÁtr5š�¦üu ýÉGx;š\u€£»PL5Y€j r%�œßfœ7Tˆ•Wɤ<±Ñ´d™±%‡¥zà:A‹ åz- …Ô ‚ÁH±–Y7´c ê�¼1NÄ÷ãwó·Álþq< nƒàä‹„ï™ÁMI(³ˆÃC&´ê®þW‰µO)7ÍQ) fx/ÿÞ?‰0¶9el´PJÒ éêU S™ú;á�͇֠¿Ÿ¼âI$b€^¯±†ŠfÞsÜ8�‚”†¡’J“ó09²šŸ1Íe—ÜC I&O²5JüÚ-îú¬ˤ‡€¥f‘à‰u›hUÝgƒéxz̦ãÑUç/AÜÞÜõ¯FÃɼ‚Á*¶˜î/ùyÄ}cº\ ¶TÉVåœÆòÅÅž-Å„ÓX' Æ"U’Q•ÄZ­Ñ«é¹ä±%÷.”µj}îI+T:Ạ}Í&\ a—â˜Ì¸fZcº#(¦xJ13É4�Ú³Xœ^íB5˜S¥ZEYˆñf`ÖJ!9yØXª °P+¬:&%rGh�h-ZbÖæ ‡¼wuÐ{}I©ˆÕöÀK•v·œ§¥á(QQ•­\A}¤%¸Ñ¹±«Œ‡é.±¯A˾¡€7œˆeûÕ õkQû¹¶Ÿçü€ = "ìå>¢Ès†ºv )¢]jÀÎ�…¶F.‚¤¬±L[çÉ`Túw7oàütaŒÞë”lS–µã¥²t3Œÿ·�…��ÓêrË78%ƒˆYvÀ"»Q~¿q*¦ŒÚr§·ΓÚ@‚¼Ü•‡µïrv[Úåè_gÕ~gnÙëÏÐ5ÖFË(N6VXÉL?ïÆy­¹Ýh!�^žJ�dªÛU¹ç=ÓlÍ‘*Ìnÿrv'¤§—[왹\vV–ë¶%î&”/�ËÒ×ûØ~Ü-ߪh­¿þ°ü@Ã{!x´üˆ~ðBAlKÒH)ôÛ¹ë3%OÖ[¦£PEHökö’'K¤ü=¦‹^ ßrgz(?îkþ½/v’ µ½Ïkéämýa�ç»a3)Rí)±AÉ ƒŠ™“®‰“ý ¡lV·%U2ÐeµR:"W[…ÙÈêÿÌå n]°•Ú%UU©íŽ2ÂGXs*Ø"?iÓá¢ó?.›ÔVeM=aþmü¿‡9©ÐŸ~T54Ç —¡{‡©…'­âb ä'AO@xfqoŽî�÷s›ðPa¾!·äô(â<óZºw°Ìàm"N=ÎÔ㪈r:•¸×lÅ!]\üSœëŸ ÿÇKG�Z]Œ¸ weÚš¨�¹aÍm¦1ŒoWxÐ2Yˆ×NãŸWèí¢a�×ÔÕ 7vaRäXhWºøæ"‰Ùüç–VzA»©£ßð ð`×.s_Á{oÞkï>—ùç`xý†ØaU\<ÿ‰g‚ú ,"ô±çxœÕWßoã6 ~Ï_A¤kƒ6>Ü€=´ÛpYÚÞM“C’^wO®bÓ¶PÇò$¹i6ì%+Ž�]{0,@„¤Èï#iŠö:ðÓ¿õ�Ž×ò:�tàCÄSUȈØMHd¥�ÈW’lj6¿úëpœÀûwï€Ï OyžÃ C…Ží÷‡¸˜Ï±$'Íctä{ø(DL¡†Ã¾ 2äf C(²�|è¡—³€¾œæ¾ T\dð¾ûŽ�AÛ©Ú'ÆÅJ°`+È„†B!ùà ,#|0×À3Ä"O9Ë„%׉�ã¼t��¯Î‡˜kFæ wQݘv ­suîyËå²Ë,Ú®�±—–vÊúW£éÕ!v'‰¿\Ûù XNˆ6'œ)[‚�Àb‰¤Ó ^J®yŸ‚‘^2‰ÆMÈ•–|^èFÂÖøˆvÝ€RÆ2h÷¦0˜¶á—Þt0=5Nî³Oã»Ü÷&“Þh6¸šÂxýñèr0ŒGôëz£¯p3]žRº(>çÒ0 ˜Ü¤C›·)bB$JH*Ç€G< jY\°!O(3b9ÊW¦¤Š†ÆMÊ\3mE;¼L ¯uÄ#RDàû÷ÃÛÙ�?½›\÷úWþ'ßo‘‚g¸WG³ -B„S>Ÿ3…žûî&?×”J‡s!Ò¦pÉVD <£¶&ôg��ö”ynàZH*K!)“ü9L‰š¾aTF‘>rý�‚ûá¤z¸ ½ÊÑP¡R�™.ô£ï,| [¿/‡ûÂ¥.X Ô‰ Î±Ýäºö[ã?-ô+»¨å²�).0Óݤ}06–‹±L¥¯�‘�º ôÞ�=‡ñFnŠ^Ö<ÈÓB™¿>kê#h÷ÛðGë³�Gž×°Yϵ¾¤�¨6ÅX§")6K5 ~®åCw=ýr¢±€-õ¶†ü;±ÕHÔ�ëA.x¦M …ÑvY»p[( s¤œQĪ KSzJŒ;óÙsÒ™—�Åvïtš‚Àfà¸e\ífÞX7è�îÒqgå¨ÒÌ}Cz½üX ¶êÛH÷l|9Þ¹@ÎáeìFöžL”ìöTl+Êÿ¥juØ*W3)ë²Eõm¼,A©úÀ¨FE3­Ítz­'ÁÃý�wØÕΚàëè›wÓ?[‰wö4ºÜ˜! Ý4Yèâ™Òæ"_½®Ÿ›ÎiˆÓM– štÕ*åbQ�k5ª„ÛO–Sü-Ù-®´ˆÐUg®gůfõkR^u0u²%KЬX/Õ&F훈eC½ºì J1^‡ªÉ6¡jìz�æOv¼• s'ÃCÀ¯Œ^‹ÞÜä×hÕ÷ Ú�¦†ÕìN•½½¸¬‹f$®jH^QwZ~³ m:<¥Å 3 Jë$ô/ÝtäÂåÔqFW9uDýNzèÈKPúö鬿vî—>^›¥jJT S¹ª¬HM›5�V~Td9rèO.ÞıÈþ K{ú?ãi‰Þe4©Å5 {Zò-b·ŒJs'(ߨmÇî,qN±§ùÓn0›Íæà^ãä^gߪ[¾ÐÑ "\Qþ)ÃÕ Ý·½4oà"²›xœ­[ÝsÛ6÷_�q*kd¹MçîÁinâ:I£‰kglç<¹ "! Šà¤µÓÿýv ~IT&šÉÄ"�Åîb÷·€.ÆìÕ÷ú°ñÅÉÅx|ÂÆìõRÆ‚me©ít Oèa¨Ò]&Wë¿]»/lž±?½ø'û¸–±LSö� -26úBÿ¿^‹…˜†ë³ú4˜òö»R+XéææÚ.r#C‘h±"‰€F¾ì*å!ügßLØ¿E¦¥JØ‹éOl„Ní«Ó³—Hb§ ¶á;–¨œZ © $¾†"Í™LX¨6i,y¢”ùšÖ±T¦Hã³¥¡9‡áœ¡ìL-ý�Œç–éuž§úòâb»ÝN9q;UÙê"6ãôÅÍìúííÃÛsàØÎø”ÄBk–‰ÿ2i;ÆSà(ä à3æ[¦2ÆW™€w¹BŽ·™Ìe²š0­–ù–gÉDRç™\yMaŽ?Û*ã ;½z`³‡SöÛÕÃìa‚Džf�ïï>=²§«ûû«ÛÇÙÛvwÏ®ïnßÌgw·ðí»ºýÌ>ÌnßL˜uÁ:âkš¡À¦DUŠˆôö D�…¥2,éT„r)C-Y|%ØJ=‹,‰X*²�Ô¸¥Œ�L,72ç9=jÉ… ]œü —ðbÉ‚àéæ�ÇÁpw÷ô"x'?À ™ˆÎw01 ã"ì×X.\‹ ûÿtý¯Ž— ›Îë¯t-”Š[eÒ¸å;�7:/aÏC•Ñ"'°'E˜³mœªÈÓ"y‚žÇÞ© v6b‘cž‘ü—ìÉx!ÊœïR�2ÛùÁ6Þä_ã¦Òó¿Zš÷b)2�†v„Z4ïÔ@e‹ÃtiX�‰esïÅKO­§"�ª8õ“¢þÌGþ)<ƒÇŒ]\°ÙÓçO,ÍøjÃ/Ù!Ro°áµ6¿Èe\²UÙ î'ú)¢Ù« Ó¸ÐøïD|ÍÁÙéõ)ûëä‘DrI³ý1¤Ê¹Jx FúÌeLŽêÌû5h¡¡~[¨u—zÿ:að!¤ÅOE7_óÜÛ"ÏsÁFþÜáO@>ªD.w6�NÏX¸_"¿#2ŽÚ“?9�_À@ñ °ÌÔæÒ =ï`\êÀ-·,âX‡€QÉ��zÍ£’šûˆ°@’#P�üŠe\"ôo×"©›j¹³8´!7X¸¦dªX­;xX‰<¨¬£ÁHÄs¤yÆR¬ë~â6¡c‡Ï¡ ú¿têÀJU~ˆD`ÙW±Û_ß÷Ž./y˜ËgŽàí„°ÈZΫV;dl[n‘й‰9`6a¬tÛÀ®0P €þ$Þá@C@›@Jbø¨|¼¿ûøöþñsp}s÷põÛÍÛ¡j³ÜÄÅ`AØÀvÑPjâ6eyä_@Îæ$/Ô×ñœ�:x¶*ÉŽd×®xŒÒ…†BàSƒÃ5þU¹W/׆j¬Nl¤Ý!5ð#ŠÕ@K$¯Z$‡ÊTÍü’•óÝkQ*W9ñåÉßu÷D4(ãèF ¦R“”8àßVÁ¹\«"+À'ÌËecØ«Eà³HÀ�²$Ü×—5ReÝO«f‰•ß+jVº¥¿} •AæÕMrû"KÏ ì!fFj€“|+×­$· $HGV3_`»ìÆØx—f É€©s!íØxpíâ¡&c�|•23-!€¬Ke6éÜÊ8fz tÍÐsŸ·nx»û0ûâ{Å>Íny\�~>c¿þÊ~š40‘\îcÁ¥52L(ê3Èâs•àbyûìݬ›�Ÿ[|X„?È�Æ�ƒùöò/&-èøÔWâ\ÞZa­††Q©xBaôå<ƒÄ’ëÂÉJÌ·I'Û:7�\dÁ*ã‹jøÝ›»VIz T¦`§‹ª0‰Z*L®IÏ–Ï~ÿx Õ݇¶°¿4ñÄèÎ! UÙ€(­Kñ§®ÔN!ýß@Ñ b$y`…Ã$Å{[åßõç˜jÍ—ôÒÀ2ÝJq ‚bCf pvûéæU.²Le¦Æjd´ãÚ÷�x‘zÌ Ç&Œìà|â�ì(-Æ5ŽýÁ y`h[³J§oÖ3µÓ>>×Õê6„sqò¬dTç+2$F-ùˉÞz¿‹Ü¬‰‚ž‰^‘QÊíï„ÇþÆ0†•¥HD¦÷ñçoÜGc×&C•º²þÎäÑ0Ò±“.²56Ô<&ô=1däv®‡¢ñˆ$󓀄äGƒàP“gß$+(·H±ë`—5wŠl"‘éE8¡=«¬‹kž/¯ÛJÈB2Á枉Ï1åi3æ âºäu§öEï«(±v«iÜâÜgÅ© ‰=½x¬ôȼP…±YHŽ›ÉÏÀ 5d¾fëév»XsëáÛÃ;ÄÞ‹Ìã’Í`RHˆZ梩Òc@�®UQm·wC]©6Ì4Ðÿ«ìãí¸‡ÞäòóZ Ñ?¦=°…•_EkÔÆÛšb ° ˆZ¹ÇDS6ˆ¾hl2É6”|ƒŒ†•Ùåã�yþ£®‘§ì ³•BŠD‚¤ö|:ÑÀОP†)Ð.v„!¢ÄrT‘ÇòÙöŽCÇûtnÖ¦oÀAíÒZì`ÙÉ3Ku8!wÎŽÜí‘v¨7^·6u km8¦ê[Kì'{l^Q˜Û‚C£ƒØ=â´|º’ï�ªšˆ¦ötsÏL³´U|}¦>¬ŠTWß•¡ÓÔcêi•­Ø!Æ\�® úÎð†�ЉĞ5õ±,#ˆ�e’Ëõ ôêt—©SzFGâÀ˜Ág]sw?$à(ˆŠž”ÃŒµÕ§Æ]õEïLƒZ“:ú*¡œSæV·OCLa£”ÉÚĄɮ” O|§~lz¡wcÓ»Wóºxs¦LŸâmÀ”ÝჭÄs¨Ì¹¦£–ÖS–yKó†ÂøJòxÇÒ˜‡tDƒjqôhËå²1 AlÃÓ‡“@Â3²³Šn…ØjózÍþ[\’JùžX0[²9½/‘¥,f+LyeȾ¡.ìÑŸ+‰ynòß9ט6Œ„XÍè" …¶*Á®RÛ@ˆò1è éAÆÆ%C=)“ÑN­¥2œu3ùuµ1Rœ²'ŒŽa5{ö G&âkNíl¨3Z”R“V³k}; 󴜣–@��ù*wÀ¨¤e_FµÇYU‰‘¬ìbO€ŸT˜T™LÒõ!s©ö*©¦•­mTØšC8ÿ"v …'lKtÚȶTùÓ[<,•IHèà7A|Ô¥^þ{-¹ØƒÌå˜aÖGæ[Îq¹¯³ø²)Ú1åÄi·+Híñ3°(21¶£ûnΊ4¢¨ òü†sCH¥DÒ­Œòõœök¾Øšæ¹PëÒ‘¶šýžü1>Þ(PÆ–¿~ƒÉCµÙÈ W›²×U*cŒ;ز퇴ªS­ «©®l‰^†è ¬•ä}7ŠêÑ‹!{„Z�Ì úÓß[!ƒFF¥JάN1ØÜŽä’Ò—ÜX˜iî4€_›Û5ðìkst´š†&ÁæòOS—së45W�ÚÚ¡ú•ôzîQ£ND+lb"5oTJ•©0oØRf—[š)C¬ƒæîÁ"ÓçhØ‘¥#ÍiÂQSX¸œÐRWmñ#±GÙæô··ûj�Ç/¿‘ !±‘=Ÿ?û¼8¢ßŒÄ‚½’h±‚¨÷èlˆáø§ü{Y®îazb¢˜ÇÃá0VÅ0‰}_¦�ŠD<Ä‘§ÆkB«fRQ¹te3õ’”{Óž4h-QI>ƒƒó…ƒj¶ˆíÉ4I¿-Ò`Âs3¶ÛciåˆÖ0Ò/tv à¦Z�ÜÐè·Hðþ�ÖÂÀD�qõw嶤‹qùЪnVº[Ö“ÿúŒè�#C)Ò¹Gç¼Ù«rK` ãZü{­ T¹ÏXpK‰¦ýÅŠÀ:§’Ï[ÈfŸ÷_8´ ‰%/âܘ«6·Ôrsj=èJ¢�ExÒÞÞxãìûn*³µÑí‹J«Ÿ½ìüQÏßôžê§=½?ì±Ï/Æ]¿3?‰d¯^Aaòa3äõ·ýêÉýR•�ÉàBœ-xœmS_�Ò@�ÃÞãÃÈår@jIŒ/ŠÜƒI‰äðŒ''5›Ònéjé’î" Gø žý$ú|ôsø|òÕÝYþ”à¤ÛNgæ÷ÛÙÙ™âwñ뇜¤B^yï®=üþóôz®ã¼ê¤Å½[F¹¿WJå þDH¿Çyd‡'…B­Z…&OÆnâƒO½ÈM\ÉxlCµV2yÆQBøHG²žÖò'‹‹˜IÐ�ÇC àÉcè†T†4Òã¸Ò ©:ROREÍ4ŠX ó¡OSè¶ŸwN‰Î޼vÎ[Í·¤Û>úâ q^–‰ÞÂ2QkªÖ˜ù2TßÛ!eýP{ÔNx_%>‡YúgÿàÛÁÍì³rkÉœF§HðÝ€2ÒÔ—à… Ü@X,Uœ‰šTÔŽdŠúuLÿQC›É �F­Ô³|�²+¾{ ³½=�ËKd]YòöT™t2³‰hY“¯Ñˤ0ɬ]&#˜-±ÛL=�µ%�Ë"âÊr¶py{;V‹¹ <_³ÕvÑZ»u¦5ƒÍ/vQ%/ŸÅ}˜ùlÖÜ‚>—p|$Žw\% v©6vÁº Ȫ PÒ¿ûE•ÆÅ.öP×ÃZÕ{¥L­l¡¬­‚ª+Û*Ùü?�µ<ïÆ!‹(”7´bÆ©¥æƒ¹û¢À…�!1 æ1ªš+à N’*ÎÅgÎ|:�ŸÌ mP„)Öòš,íß¹›Þxt8;ÏÑèZ pbx€´vWÅ9íû5ë8Kà/ hxœm’ÏJ1ÆQ·{D=XD† zè"Rð‚ÁR¡‚ñ !Ý�݆î&K6«õÔ³7K޾„zñè“ø¾€¦­–nr~ß$|3ù^³·¥Ç›=%DÜcªÖu¡j\ ±Ä~’Ò!áç rE¼6Äë¹Ý—÷æüb»Õ:k6ÚzyamðS“SB\×ó qu} ©¤QB ƒãû©�*s·÷ã<@¨th†µne– ÄÀ¦} äaˆr†ÕÇP)ã¯Ðè c›ñB¯Áf¢ž!úÁ�ó0KŠböÍûEƳ ñ4WEÓ{”úÉ��YWN˜Bëi‘+s‰(Iýží)¥c ‰4O-$Y»ŬÈ%úŠò(ž˜Ô¯Îš~vVË„J¤V§#ÛúÃÙd¹ ©o ¦0³æR,¶{˜Š±Cåÿ”ü÷Sq\Ll~:%Æq§¿œòÆŸÜ ~S£½Ò�Þ)­èõýÃí™°AÕs½*Á @LÓ[ëÂp¹îŒäo›Ë÷Ôç:£oxœ•SÝjAÅÚú3&B0^>º½¨K» M‹?!Òb#Á&-Tƒ"ð3éÙÌÔÙIƒW}Ë;D‰‹׉äZCÆ?ç"Cm§+`)J²)Ê)ÙTlžqüfI¼Ì„Éü´š™%Ë8ÁDB›LLsÓ0X)ªí/@“±ºÇc�»ðÇñx4> �›Ñäíåõnޝ®Ž/&£³1\^ÁÉåÅéh2º¼À·7p|ñÞ�.N€£¹�¿O3ÒÅdJY»�9oˆ0SN$�òPÌDˆª%óœÍ9ÌÕÏÔRž-„&—j0")Â0cI-½ˆÑ óDÌðà ‚àæü¯É»`2:? ÞAç REÂÛ(´á�ÊÐxD<”,³,Ž`Œ¼8y—¹ˆ'|³J9á£ýòÐ@°” ó) Ï�ÿe¸wBAw'2“3 nb…Þ$ïâp·0M.–0ì ÖI(óˆÃoRL§LóAñÛ�ßð£Mó“6ÑT)Ù"ŠÄ.¬‰Ýi>›ñ¬w=Z¨Jˆu2—|Á À'j³¢êÑ%Ì™sU˜Ê\Ó_‡ßt?tOºðoç O"1 k¬aÇ„*uÁPÆSå¦Âxž©,[|¬g„”àˆþ,ü4Ϙ…¢ Ç®°dHôBñÐÛ°‹/¼,„v@)šŠ{.uߺC9³+>ç,¢xAÇ,å§ þây�_?±kN'hq4­¥Þ2feGvyïD^Ðär#"¬‚…BSþ…ËG@ÙuÁ’¶;_aèöaC¼oõ[;èkÇ]§¦¥ö=zƨ¤p†½žæ&ð•Ûßk¤lÏþ¦&;¨5j,G†eȵËÁÆ2PËzlWRÅePç¼U2"RiTáJ¾z-ìª�ž±uŽå±þR{lR[ek…©¡=W¾Öpg.ƒIë׿ªÛ©÷ÑöZÚ©"I•%HTÄ}ÈH͸.¥o4ÒWI‰=‰iŸ�«8�Y¾$7bÏÒ~ é¥T›­Õ«$دu9ö—4꙽z·fU.·•£tùìN¨|KY°ê>4ip+ác•o!õ|é<ů“Cñ¿ü8èÜ)ù pª[jºÏÈëí®ÿ“ã†"ßü”às£a�Ò…\ï[µp²b¹4í*UÔÈRå ÕB;k#J5�ÛÊŽSý¼�mêË=œàÎ�9ÜÇB#îx™ñ4yš�¼‚ˆ6cœíy=AÍ`8åÙ#ÌÞ,€®2¼W‚¢, n[uô¶_w—%göÀ∱+Á¥“”«i [Qf�{šÍ¸\í@Š2•’nyB0NßÊÒmœT+a‘£óÌ„±_ ìX³w3&é(7óøà©.²§Û&@µ}s\zÝÆÁìŽÑvïê5ý´œ·^ ¿¥æî÷ -nKüÛ**/O/[Gð£çÑÜá3ªi!Œ©(Wðk þаN•WYÏabj‰SÓ2é+lèKŠ%<¶rã öjgpzZm¬L^ÃÝmÔõNØóÐeÏ¢_þ{àßdOoï÷Û³û!ö¬‹85š¨Q­Ÿú“K дkÝ/=~¿tÔ‡S£A·fFÛ¡nÝì¼Î´ôû Î\�[½×Å{5Ìþ°ÁzèêÖº!nMå6pq"pðVÚ±Cï÷A—"*¢«žˆë0©A}«ã•븖iq)àZãT§2  A»?ZúßÿL®q°ÀNƒ{B¦¹»$hi»‰A@›l´5¯t êpãÍÅW{OQß_l½½(èƒ^ë*ÊݳÂË—p†mšNÝöžõ›/r ï?ðx,‹°øxœ­WmoÛ8 þž_AdÖ½xë€ûÐm‡eYv3–¥E^.(O‘•X˜cy’\78ì¿)9‰›—.Î@X”H>äCŠZðöÿz 4‚V«-x7—©+m*fL·\sË\å+-‰¥·îúÎø9\¾¸ün™Ê<‡ÏL¡áì›û·(f3ÑæÉùÃcxäü©ÔmõûÝÊH_r‘C‘ŨÃ&:9ãøWI.à/¡�T\¶_ÀmhV¢æùkR±R,Ù 2e¡0$‘$qÏEnAfÀÕ2O%˸€RÚÄÙ©´´IÇm¥CÍ,Ãí ;¨y}#0[9�X››« (˲͜·m¥Aê÷™ v{ƒQï7ô¸:1ÉRa hñ½�ÑÎVÀrôˆ³ú™²”¶ÐeV‘Ç¥–Vf‹ 0jnK¦©‰¥±ZÎ û `kÿv}†ŒeÐìŒ 5á}gŽ.HÉ4ºžŒaÚ;ƒqØÁõº×ƒá8¼àÛGè nás8øpÃ…vÄ}® º))”"vq ñÀ…¹ò.™\p9—¡e‹‚-,Ô�Ð"‚\è¥4”RƒƤ&•Ki™uK{¸ÈPÐx&ç(˜CMû_ÆŸ£q8î÷Þw†Ñ§(js èÄN²n†×7½áø6 ‘ŒáÇ[x “p0~uuÏ^žÃ›7ðââ°%ž*#žn§Û¿õö­¼lüØ x¢”=yž†~ qo‘ÝÐì61ÏD˹‹y}ϺÝvµ`£Í| ‰XåõFŸ8/o¯ÛqެZV¢(·º¶èøP­¹e-l�þÜ(d(ImKöðºpAÕ<˜ôûÔ*„Ö Yú¥0fXÈØŠž»Ú¤ÛgiJÕ‹ªç»Ñ‰ÒT­|¡î‘ºµ³Âþ3—/ª0ÑÖ :ŸidúóH@kƒoƒuˆ?x‡ÌCÔ;‘Ü&×Å-hÜ)�v¶�ª~¼fz$¬7[Ê/šzIý܇m’ñìN.ë„'2º%òÅœcož©Ïµª½` 9­õ¸ƒãc_dF.2¼HܹC}~ â1Êbªñ±õ8"§ätD¿È¦ Þüp«<tMÁæ¡+ôPQm[Nû(;)[�OH-¶lx¶æÐ.Þ²F·mßÀÖʸ•wXÀñi�7Û±Ùu:lj͹Ê}:�äþ1bISsç亟ä±ëê[ƒ/…Ý+õ´˜¹eZÛ²z÷8RY?j±¤)õŽ¥’æ@+Sg$#ƒÔ§ÑêºsyŽòà=Çka£õ'оÖ!|u#¨ï˜žÇ3î^žZº<Á$¶6á©…�w×– ä4ôüDô´ ¢Iß§…æ³Àxœ­VQoã6 ~ϯ Ò‡%AzÀ=¬ÛpiÚÛÍÒC“®¸'C–iG¨cy’\7ößGÉrë¤N· $&)’ßG‘t0‚_þ¯Œ‚^0õ`Ÿ‘!a2Œ˜ £Ò™O6¤rZ.‹�鯨§Yó>„Ó§ŸàÛFd¢(àŠ¡Fƒ{÷û9-£'|3Ü?FG>ÂoR¦r±˜ù Á1×C™ÇäÃl¦ãôã5cø•2‡ÓÉXƒ¾Wõ‡gÖÅN–°e;È¥�Rž�Ðà�á#Ç€È�Ëm‘ –s„J˜�‹ã½L¬�ïÞ‡Œ #s;ȤmÌø¤7Æú§ ¨ªjÂ\¶©Ò «ít°˜Ï.—«Ë)câ6ÏPkPøg)¡�vÀ ʈ³ˆòÌXRK’ÎH›q¥„y:-S1…ÖM,´Q‚ê´GX“Áne,‡þtóUΧ«ùjl�ÜÍ×_¯o×p7½¹™.×óË\ßÀìzy1_ϯ—ôô¦Ëïp5_^Œ‰.Šƒ�…²(Ma©ÄØñ¶BÜK!‘uJº@.Á Zž–,EH媜A�j+´-©¦cë&[a˜q¢¸l  w"R$†w‹ß×Wáz¾^\žOoÂóÛõúz~ ÃÞ ˆ_µ!G9ÏÊág΄’“ͯ-Q&¢ˆi üï¾R›8’2{!¹±²ga3Ü¢•ö[ÂJ䱬¬Ìö|‘ŠÊCŒV¬²¶µ«Ó¡6"»Üè=dRÄ0u°NB3ô€>®B„uTÿ)ŒSoÐbGƒ¡¤¿OIÍe& ƒf®Í2ƒt»š4škÙ›4#® æ·À©ïà ]Æ UK^W̆m m‚Íq…¦$æ¾=ÓÑŽâ£�mÓ,o Û‘¨”Tõ�>VØ#ÄpªæÅ3¸—÷pPŸóI[?OéÇîØqƒ¶¾»"w‹ã ¤{&wº ñ½‡k>ƒžCq$fí}ðJZ”‡ãV†+4uvŒ.íƒk0j*fJ ƒDòRÛµ@”•ŠFò°™öïÁÒh}$Œÿ ŸF>¿e].;ŒžC¶�ß -Vgi\+¤âs0Ô÷% õ1µ³ŽºIàÅ›P7d†iò•É�ÖóÛ­l"·D.¾7k7M5´¤KÎiÕ=ä(èfN9ÞÍ­n&«€V/�ÖFv`58k MEmà•9 £'öÊj‰è,ëšx)ìžö›ç¿Ôï•æœÑ^¢�O‰OÒ;M{€= � .oðî^¦—'aè¶eœvr½KüΪ™¤2Xµëª£‹Ñt¯À¿ÝÂ{^„G× —£×^2êWlze‡Kj(;W_±ßö:oýýîu _¿¹xœ­VÝoÛ6÷_qp–žTd@–u¨ë¤«Ï l§AŸŠ:IDdQ#©(ưÿ}GJr¤DÉšb KwÇûøÝý |ø¿˜ø#2Á>Æ"C0Âd2¸/%ŽcrYì•HRc¿æíó8}wú®S‘‰¢€K†ß¹ÿ�I†èñô¤ŒŽüH™�ÅårÞY ޹ÆÊ<"&E˜ŒÓ_ÙÂWTZÈN½wplÆ k|rfUìe ;¶‡\(5…“ .0|àX9p¹+2ÁrŽP “:;�ÏêøÖè�¡a$ÎÀÆ2î 3�Ó©1…þÕ÷«ªò˜óÖ“*ñ³ZNûËÅübµ¹ø™Í6‹ÍÔ*¹]l¿\Ýláv¶^ÏVÛÅÅ®Ö0¿Z�/¶‹«}}†Ùê\.VçS@‚‹ìàC¡l䦰PbäpÛ ö\ˆeí’.�‹Xp -OJ– $òUNA�j'´M©&#«&;a˜q¤gqYCþèHÄĈ!n—n/ƒíb»¼ø4[×/Á— _äøšˆ-|ø,A„ÔmªHh ûRÏS\ ‰ÜÒ÷Hq©h´Ø`×4NÃØO ÜÉ×jÖ+ó&;�±A£[+¶h(Y¦Ô½® ?éïoäguòÍ­ÑÙLKª^ÛÛ ©Û^—´äh¹Ð¤ʉ#ÍÌaM¾}tu‹ñZRm'ÈG£Ð®ŠÎ~´;<~þŽ<ºÛC}o‹…6—v«´Ù>u¡P�Z¶Cÿ¥õÙðÏwÙ?ns=n´÷YC÷'¯Üêë/]§á‚¦];u:×ß7Þ´­¾ÎÚ¾¹±xœåXYoÛ8~÷¯¤/¶áHE؇t»h6I[£Ù¤ˆ�}’i‰’‰Ê¤–¤âx›þ÷�¡¨ÃGZ#èÛ-ñ˜ã›o9›_õ�aØ ‡Ã ám*r¥y°Àg·«b­E¶°ôtV?@?À«—¯~‡O ‘‹¢€�Œ®¡ÿÕ}¿ÍÊùœñb°y ¯üï•ÊPÏåå™Wr)b. O ” ʰ §‹ñËïŒà3×F( ¯‚—ЧG~ëhðšD¬U K¶©,”†£ aÀ¹Ãb^Xbµ,rÁdÌa%ìÂéñR’ñÅËPsËð8òTÚ=Ìz£Öæ$ W«UÀœµ�ÒY˜WçLx9>»¸š\£Åþƭ̹1 ù?¥Ðèí| ¬@‹b6G;s¶¥�ešãžUdñJ +d6£R»bš“˜D«Å¼´€Õö¡ÛÝ“pt:�ñäþ:�Œ'#r7ž~¸¾�ÂÝéÍÍéÕt|1�ë8»¾:OÇ×WøôN¯¾ÀÇñÕù8Â…zøC¡É4S”‘z¿û­ø!ÑŸ´Š)èãswŽ– ‘Dö}pçusó– BçZùäµ²{í½Veѽ—=y/£{ßÑ•¥ýºÞ¥ˆð$Y7#‘8Fj÷g‹™d9¦U‰Œ£ä£¥,7”q?†äÆ¥#(C EžCÆ-– )yl«ìu àÔ4~lFÜ ðŸú¹áL•Ò�årŽÇ°Å,Ï J¤êåQæÌ âœ3ݪÿòýx9­Ž)šø˜0Ë¢ÂêG`:+—^]ñ– E#j´D7÷J${ÃC2'ªEn…† måV­`ãô¡Äxº›ÃpG}jy†*oú''Ukñï?=½1ll!t d×>(žëè&«7¢þ,;$ü…V¶´\ðfbxŠ¢‡2ogž8ˆ�Û·žËÄÝiÆC°Ï¬™¹+³ÃP«ö…ôpZîíáW¸·õ˜ŒýÓàK+Yâß*1¸<7•t¬�‘Ýщ«ûßL¿»÷ÐöýôÉ·S¿wÞÍ«žà͸@8±AW?<=û—-’÷©þR^º»xœíÛr9ö�¯8å<Ä¡0d’­}p6[Ã8žÊŽIù2®<5¢[Ð*w·z%5˜�šßs$õhÈ0[û°T%ØÒѹ_%ºðñXè:ƒn·]øq&K©žtÎBÞ�qÑ®‡2_)1� ývQþ§áx÷öÝßáK,‘çpŸæ NŸì÷�ób:åý0~³~ �¼‡_¤œ#±ëë OäZ„<Ó<‚"‹‡‰9 ‘ üò;=ø�+-dïúoá”NüÖÉ›„b% HÙ 2i Ðq V&þòÜ€È ”iž–…(¨0±¥ã±ô ÇW�CN Cp$;ÈY˜ñLÇÆäú|0X.—}f¹íK5$N®G—7w—gȱ?ñ�%\kPü_…P(ít,GŽB6E>¶©€ÍÇ=#‰ã¥Fdóh93K¦8¡‰„6JL ³¦°’?» €*cœ ï`tw? ïFw=Bò8ºÿuüp�ÃÛÛáÍýèòÆ·p1¾ù4º�oð·Ÿaxó®F7ŸzÀQ]H‡?çŠ$@6©’GVowœ¯±0“Ž%�óPÌDˆ¢eó‚Í9Ìå‚« %‚œ«Th2©F#B“ˆTfìÒ–\DhÐy%f¸1ƒ x¼þ|<Žo¯î¾ /.ƒ_ƒ ó ·DÆwìvðt&EÄáÚDS)“~üÏõE‘™õµDL§Ló�ÿ¦MЏCFyéuÔ�f•sâmP„‚e’š§ ‚ l­|èt<´Û2è·�6«„hp}žrâ𤱘°W¸0ÀèñëäŠÍSvOœç @%¥Ùn)²H.wCZù– Ý1‚ˆ‡ SÖhçȼY˜¼0hq HcõQI¦·ùa{)pðVÚ™3p˜'…¦þlÐiàäâ~ï¼âY$f–µ&L™ÆF¸¢i4,cÆ`µc�´| :fISŽ1H†ñ!²¾�Nòâ ÏŠÔš`¤Ÿ¯Å9[¡Gëù WÒÈP&z€2œYø3ó$9+2m(œÏ?8—-ÝÁbþ½øÙôÎëá×ËÛà§áÅÕ/·ã‡›OðÞö^‚ßß�?#Ô/AÝ�¿ Èû—@Æ¿]ÞâOö·^ç�-¿´rYïôÚ½PÜê–5\¾¬9ºAºmÒ 7 ÆëNñ¹Ð†% ¾@ TK9�n,Õqá7ìžâ¦@ÿø"1zÉØr'"2Ý£¤uóp}M‘+%•ç�a†÷µMAôYF8g[êˆ8ú­\9³nGuws)´Ú:µúßåñtªEcÎh!fBaÌtKm4wvä è®ëéMmÀON½™¶†…—Óñ Q‹ä ©P!Ä()3φ±+Q�5�…Ñ.½�¶©¬‰¡Áç7¶Êy䂹2fÄ‘¥Dƒ“rSÞÍ™æ&ð�™¾Å¢Ó>ú™ÃÜàø–cÉå îT‹Ö¢"çq»:!ôî¨ÙÁ~iô6©^Öùüû$kz\·I½¼[Q_—Cpí^k_ (�ghP¶@~l¯CBÊž±àÿ½ËUœ}mÚÊ-öã †~"åS‘[FJ²3ëØ×a¨Sè•‘¾ãC}—-!Ó.?åJ¤L­Ê↖Ä3êo$ !La>•Ï<Ì0íai:kqÕJp�%r©•P—ZÖ‘´š¶Ô]àe<ÄÆ[©h-í¶k©Îͦgì³"I4¦>žý߮۵VÞ_mÙ ™Í,ÿÖ–ÖP¤fÅS}�2N(ÉÊÎI¤·ÊØž­RGC¸ÎdØ’9$Öv¨HšÃRl•Ü(³Æì¾þPáÛ�æB'EàØŒvbO|5•ÔÔÏdXØ© XhÄ‚™FŽ†Ç˜g„Kñ3‹‹ê\£ìYÊš)Wè?›ð ; ›ÎW›„�ÈáÚÝOo°²§ÃxÑv{‹øfÒš Kt¥;”Ù`s; ¯tryÀº€ÅÐN?:¤úÌòfˆžÃ0Š4³Ýå–Íš²šLÙ·p ¡=bÌaTû¸ù¶&;/åcØ+ÝÅIº©Û!¢| ÿϼ�Þ‡R¹p²Ï”Lí¡J7GTC‘W{ªÂÊ]+âµ^+2‡ÊV.>7~^½Ü/—ç=Õãäܺ½|®\µ4"ùú�×ÈÍ~XÅT–ET„¹ZPjš¸a~OåXà—çJw£`gÊæ$X’oMñvó;4µ9‰;⤖¶m2ÕA”$B›ÀL¨M ¥°Xcö•x¿†l½÷ÙÐ8¡ïÃ�4Ü5AåèXIdë>öc)ËD^$er§O„EˆÌôadÐm)&mGõ'ŽlÎi4o­¶u¤•ȰԵB6£1'nRÄ|\«fGvªñP—à/˜j¤Ùj§l-¬bç;§Èü”…OoJ;ÜKRqë.…›’¹ ešŠf{øg‚½Æ²;ÒË • šÇ u[kÔ MŽ2Ì+þ–é5¥ò×Õ}²¿™<^Ÿò¹ÈJ.‰ÖQ;È-i×8³Cžªå‰æ\ï)Ÿ£~L#˜Þ¿ÃËFÙ( ±«sW°“úÀ¤ºcªº>z—àL%CÂkb§ 噲ÞµÑß‘èëÖu;M�&–­ú�»ÀšDK~©°$BÓ†�Þ,|5Ý)GÏâ]×òT¹¿–CfU8¢ä¹áÌ>ÅW¡mæM¬d1�Á0ý¤½Ù*›Õn‹]2N4˜îí0À==ae [ôÖÌ\™0Ã2ØËõ¿å ¹â ! ýª”ÍÐ#þ×5º¡‡?©Õ†}Ò(ÜËëQî„ÅÞ`i›u#s iG¨j¦ÜÅ“UÂ+æ[F|] %ƒjz(ÊÛð:öýJ[ðû­}‡¾Š³�¾‹ea¼ÞôÐ{bÁ>o²†hÒàËôf$Ú;øNþPV*WTØ…2îñ'KV�žÇj[øö,ŒøE×Ö¥´»P©¿’Æ î³ëš(J2¹—Ạ¥¥Ú.$ -Ôg‹½C&k��š8üîÄÞpšµ,“îœãp[¨kÉÝCF7\»KÛ–7sÿÄëž#ÁZ³µ<–û­ö·â?ìËpýb¼ó½Ø¯ºí-àþ2>~„Ë,"}ÕüÆßó÷7„ï?£î2Oã7‘Hxœ…SËJÃ@-RâÆ¢‚‹K’IÜuáƒÖVõQ]È0M&48MËdjÚ…tå^™¥[À­à'ø þ‰ 'M*i¬™œsçÞ;÷uÿmîá:ß ) ºÌ}±Ÿ§—õ*:=¯íY;t€�¢:Äõ|eEõ\ˆYG«W�­£J Xii]U­“ÝRdãÛ´ëØ ^£�b&_£¹•b‹7ˆzA�Íb —Ú$ >r›â>7_1@7•€³®Í!¤ õBܧØw†ú±MÖÅÔu¸¬Ç0„žï´CкÇ\‘YÅ 9uŒá2w˜köÒ¨‰tåJ[·êˆó`À À6Y MÛÅ&öÝžˆ8Yí†Jg ²P•E˜˜#‚r!Q0ظû¾Ñ¹'t¨]’ûv«ÛyßÂØ’Ã&Wb¹£„—1^ÖrMЮi^½‹2’}ÕZp—‚{'\²�ÑvÉGW€ïú€eøz½eS¦ߨÛe˜œÚf ¶ÿ!5ˆ™˜/Úž+ÎXÏZ0©ëŽd=lêdj›: öíd÷:b-ÔË Ù'ÿ3ËãÇ�AôJ 2R?cÖ’hPv½Þ…?F.3üuôíûÑ7ü¢y´·ÚÄ):§ß¯jøÈ§ÓÁÂÆ©Q]lÇJ>j~ÙA‹+úã‹CGŸlþgÊNµÊé»G›öö¸}ï+õ_…§Ï\]w B Õ½‚;WÑ7%·{$�í8zù6¿œOþ÷Ü·ÏxœÅWMsÛ6½ûWlyJfôḗN*;Ue'ÕT•f,¹iN2®(Ô$À iå×w�é(±ÚéL/ö€X<¼}û�Þ=e)<¢6BÉËèÍà<”\ÅB&—ÑÝê}ÿ‡èÝÕÙ(×Ê*®R�,Ã˨L3ö€z-¸’ëBË6)®ßDWg#®ò½ÉκÀ¤^ÂÅùÅ÷ðA©$E˜Í&g~{&8Jƒ12F v‡0ΧÕN~üàbp¯œATmE¯ô{U@Æö •… �[A×àÇÜ‚�ÀU–§‚IŽP »óU0ò©QËÈž�óÔ¶m ÌÖ;ksóv8,ËrÀ<Ý�ÒÉ0 ff8›NnæË›>QîdŠÆ€Æ¿ ¡ÉÛÍXN„¸“RV‚ÒÀ�´g•#\ja) =0jkK¦ÑãÄÂX-6…í(VÓ#·Û¤“�—0]Fðóx9]ö<ÊÇéê—ÅÝ >ŽooÇóÕôf ‹[˜,æ×ÓÕt1§Õ{Ï?Á¯Óùu�ô¢‹ð)×Î"*œ–á–ˆ[H™¹Ø NÞɤ` B¢(Õ$99êLUCc�“ŠLXfý·/\sW�†­Ôrk!-ê-£�†¼üÜIÌŒIºT»¼l¥x²r£áZäî:0E–1½¿Œ¦t~ ç*C€•˦ÃUžº5ÀIIÿ)ZR�“Úîj ¶N VÁX•§øˆé€*"Ë•ViB¡#MÊ”“˜`½ëõ º¼Âpà$)…–jÎV‰;¶|¹ é6r‰†ÆVÂ��Õj�Ýçõ²àD qò¸×áœg³ªÙ´E4•±ù†u}Y—qøT1>î@‚v]Ëá£ûñ‰FÒ‡r $–ÏȨ͟Èm›y×ZŒ€£¸`®˜š®‘ˆG”ðÇõ‡CH¿î}d:©¼qºgíVMZµ’·ã§ËÞá¨Ú¨ |;€Oq²>˜±4Ue_iJ‡u�GQ«¼ýh™®k£á±È�†�ñ 5ú…›§i'šÿ JÉz+’BSo TcUpÔ”ê:íVÞÿUsGÊè´bû÷EÆøÃºé–cþ U™bœPÝÔ‡X°q 4gÛtO1?±ˆ jÁš¼/(äÑ�–Ÿ;Þ žÜÔ\itQÅÚã Kk¿[¡ŸêÿmоÓï�øÜq{Y$ä0Yº�f0¶� ÎmÝ᮹ÙÍC:ÒóŸBB»Iß¼%|ng”îͬ�†‘ïZ æw³lŠí–šp(•R¤)ÐK�V÷�ë÷A¯^&$QÏjRƒJ_�în¤yWŽ9 ^­'ÜëŠï”{FÑ3Åqñž4‡‚m3ö yj8Kƒ„ÆQ�JöۼЌžAhœ6-" éë�#µï¸£eVP PˆÝü¾ïTÁý¡í‡´z+‰]0"MÏeîr!*$2)WÄT±Ö=ã,ÄÆœZ¥ˆí®®�n Ìi„ùí:BÏúug‡î5õu ° ÒP–~Yåwý>¬׋WŒ´þ))6ðÝë·Ôû¶BÒÛ^kJú±‚þ ;ú“:©ûý#£h4¬Ä\�ý ÑäAǰ¤xœµUßoÚ0~ï_qâ¥/])YI·¡>,ЮHk+µH}Å$°jâÊvÊèÄÿ>ÿˆC-d3R�Ïçï¾ûîl·Û0šá� Š™ÂxO4KønÉ3Šc ¦$gêôè÷ÀÑΗÐ8£ÛÝê镈ÄÏSÁó,ésÆ…q"b:ù|KÓ ;¬[?Ò®4€v¢¥q 67QaAƒ\"¨Bì¶(KT¾`LSª™Ò̮ʥT8?–.æYJ§¹ Šòì†)ddÏ6W€JH Û ÊÅK‰þLt ¸G3Æ&éq%ò_œZ—ûW‚&øhYô7"\ÂHäØóPýJ'."ÏØ”Î0M1V@Sï€kÖN¢ÖKãQ— uþ´Ðû>ÈÓñ-êWî¸=Ò7c ÎÍte>®Eð%ºÕe¤Y9݉šéy§W¶ ~fGË9¬JÜeXaÃsç¥Uº k7tÖß�ù€�³]éÖt®Ÿo'! 'a˳ñ�Õn§Û ;ëÕ勉7øñð}0¼ºÕ)÷})ƒ]kŽÕÈ$6{o�½¢¢1ñ˜OH§3ãq–xc‘X'¨DÐáñ—Ú)Ÿõ+N¤�<¢ŠaDD• �s)1©i³·6 ïÞºêÕ€ß'êF‰±\ˆF$B;š’(p÷ŠeÆ�?ÊíÕ&*»v«¯ßéìúU”ÚhŽ�ÛãƒY·H·†]ü= Ôµºï—šØÑTêR¾‹õéŠò ~Ý¥jq‘`•æ?]½zn1Ë|¥.Ï¡¾»ÿkQuM™©k£³Y;Ô{/ˆ¿oû;.æ„m7þùúUF&¬éÉ_gÙÈ@åðÚ ÕøZ) ݳ­ç�ÈçŸT®åª£N"> ì¯þXU Quˆ‡éKƒ‡©ÒÚouô<Ÿ­ßæ„5xœ; rHÈW__Á3¯¸ ³(5E!©R¡$#UAÝ%5)31OÄÉMUÈOSÏÌKÉ/WðMÌN-ÒãªæRPðKÊØ*(A”*hê™VhNtSNLL6Q²*q.-*Î/*ªžü†Q�Ñx2/S™Ž¥H (™ŸW’šWœY2ÆÜ"ê”Z•šž™R’4ž¼žé£ùd^.åÉû8¸'/bv˜|„IPÛÔÀ4Ñ4U ¢BòA.I,JO26²JK320J4‚ÈOþÃ$3Yœ%¬/Ù(Ù Ù·> #  S„¾9,LƆ“ï±Ä2*LdžìÇêÎdd8y:«/ĬÉûÙ5•R3Ó3J€ÀLvÎ/ÊK-‚yÃd„Ìæ¶pF²œq‡}“‘Áä ÝR@ „kõ€Y v¥7ÌF#C£Éqœk™ŒL�ag ™Í…"ê �Vxœ;$´Sh‚ëDß0F£‰KÊôÌL¬¹€À9?¯$5¯$8³*UÁVÁÔ "ê”Z•šž™R’4²æš\È&<9šq=£Ñd~NåÉع7[2502MÞͼƒÉÐtr)K‹¤9D3ÈТ¼Ô"¸~K "™Íú¬‰LL†&“'±/†è“ㆩ045˜œÀ±–ÉÐh¼1•�0[é‚oxœ;$tPh‚;£ÑDï&‹‰Kõ�&°êLfd”404²°æRçü¼’Ô¼’à̪T[CC#ˆ°SjUjNxfJIPÔÄškr!›ðähÆuLF&“ù9•'`çÞlÉÔÀ(abÑ€¦ÅhòEfe&#ëÉؤ�ŠÙ'K°D3L.eiÕ‡ëpÎ/ÊK-‚i1µÀa’ ›ØåîP÷aÕ²>@ùz›ÓØ´-&Ob_¬�Ë50Q˜€±�Áä޵LÆ@߇oN¦±cxœ�TMkÜ0½ï¯˜úÒKm§9”R¼NC PJ¡Ð„ƒ,ÏÚêê IÞ�ÿ}ÇkÙ»vš“Ð̼7zšâæYI8 óÂèmò1»J57µÐÍ6y¸ÿ–~NnÊMñ.MáÎØÞ‰¦ p}uý ~µB káC�ŠýéüÚtU…oKHSr£¬Ñ¨„Þâ6©Ñ)³V Î¥MÊ @!êÒ¸&;JÅö観ÈÉ>¸V³Àž…Úcyww•R¹Eþ�g·ÎüAfÛ­e¼Åôz¬}C¼f ËGÖK¦ëŸcâ“iðùN)æúòbœšò"B{+ÖPõð(tmŽñl  ®¹vèv¸Ž'ÀeRØÿ²„�Kæ½à‹Œ‘ï8šÓ¬¡9Â÷L(Á€Cj½î8.pï=Hcö0¤Ü!Ê@Q1( R2Bg ¾à³E'H&HÙB ÊÔèô\÷Yèúlì4?µZä«’uš·¬’¸’†¨“r’A´ù9ú¨Ö(´ÔjR¶!Xÿ%Ϫ©«2_nÛQ•ù,'®ª® Žqò¾‰"Þwè_b:pŸVÎý©Î4$-¶&ø¨�³ežÎŽu2$“n ÎÆYÞ“¢s~!ÉzÓ0;XjzÂLBÑäæR;fc¹UÉ�ôo_‚bBçµá“!�¤çº3«ú¾§ Q gç(‰UÛÔRÀÆ�´¦)DC_þî}@54 +ÿC eúeÀ|‹„Åû£qõįåøeâ“LÆUÈ8Æ×Üç¯úZÄ,äWøÕÂ3_|\”Æ ¢Þ&k2ªa\_Ë…|±Àò? ÖҺħá}åjo“(—þ �qZâåæ/Ð!a¸Ñxœ çò‰PNG  IHDR00Wù‡ pHYsßߨ tEXtSoftwarewww.inkscape.org›î< ¥IDATh�¥šlTWvÇ?ïÍÛã5¶±Çˆ1¶0Š0q0¶ˆ*pKÒ°¤$tC¨J) ¨j7!MT-‰ÖQCZ¡¦ÙŠlºÊ´¸mBZº›uHê‹MpBjdlc¯ñïß¿fðŒÇ3ïÝþñÞ›yo<3v¶Gzžûû~Ï=÷œ{î¹–„Ä!IÿŒ´lúl¦_#�ä €t½Ü ¨@“À80¤çUýSLióg€¦´…ìs€�.ë}lú¯Xä߉b:à†�N ¸§3aþ—IQ0¯¸¼´(J�…z½HõõõéŸþù’¶¶¶Ìááá´ééi»ÍfÉÉÉÁ%K–L�íÝ»·ËívÏYaEgä&0„ô²±¥1'fðfàv Xdm|>ŸãĉkÞ{ï½­III�Å‹�Ùl6Õçó%577D/�ÍfSóóó{×®]Ûqøð᯷mÛ6Jdu×�΀ÁŒ!5š ƒ�è½nìkcÅ“�ÍúÊËΗ_~y]uuõ†´´´©ŽŽŽeªªJ.—Ë’E‘£°¬–$‰’’’;‡ª;räH›Ч3q˜11`HÁÌ„jfÀø ÅtèŸxD_uù�7ÞX]UUµcll,3¸h²ÙlŠ¢(¶xõ«W¯n?sæÌ…�7Žé [€:� C†NÛ IkÛàs�ß\z�måÊ•ÏÞ¹sgù·/˲X·n]Ë�7еs:�3Ï=÷\ÍÛo¿]¯>üXu!¬’ÂPB¼ÍüݧƒwÙRò¹sçî{úé§ÿt>ÀsrrÆE‘FGGç--·Û=<44ôVkÕDl3’„aph«îD³ç€¶÷í˜, -Y²äùþþþœD@dYV—-[6ÐÙÙ¹X—ò¼¨¨¨¨«­­ígXWzZÇFTyÈÎìÕw�ÉW¯^Íþøã�—WUUÝÒ;K€täÈ‘+§OŸþ½���lcâÔÔTßîÝ»¿lhhXvûöíUUå»wï.Iö…Nd¦ÉE_Y²E¶`Ïž=›ùÅ_¤ !T}~Š‹‹¿9vìØU@ „p!R„ „n!ÄSBˆ==='srrF6mÚtSñ#!ÄI!ÄkBˆ×¾úê«Ó’$ %ár¹|²,›Íò9Ž™åË—÷šËº/¬PÄgÉB|Jäó|-º'?Ü9k¬Ç¼UqBñ7f×À€üä“OþÑÐÐPVOO�ë +•••y+**šòòò�•óù|)ªªÆ5›Á`Ð1K"ŠWB�ŽäSµ‘|ïO(Iû%?=¹9Ö�X}›$´CJ>yòäýuuu¥Ù†�71Áµk×.dddÜ‹8-]ºt0%%ezVÅâg¬ù�8°éz`q4x �ÍY1�666–têÔ©FË@ à¼yóæ3x€�>úhQcccQ<� .œX±bE—$ÅÖ_I’ÔY…îÝ‘ôt'x¿ÒÚ*^~ñZ¡¹eØ}0p%€üâ‹/–���§›[_¿~=+z®‚‚_<𥥥m===?immý`Ë–-ßD×÷öö.òù|.’î$H] ®‘Fžë|®k<þèw5ô&ÿÇ` H›™™±UWW?=¡ÇãqD—•––z“’’±x÷Ýw�’’¢¼ùæ›Wìv»Ú�&˲ջ ô²wY¹÷¿Ö¼P©:¢m#I«0�Ñ”Wzç�wŠÆÇÇ3¢Ųã²,“››;]ž™™é)++óùuëÖy***n”••݉{&D3àýzV“• n°tQhÛyÆ`À†æÏËçÏŸ ÖØ^¯×«>î27Ú·o߯כšp$`Íš5];vì¸iQÞ®·4­œ¼ì6ŠŽ=Zoé,I�¾É:`p‹{ƒòܶð<ÆåEòù|E=wîÜÃÆ•0 ŽIUVVvÖÕÕÝ_UU5 ¬`ô×™L5/`ªµÍMáàÁƒÔ~òóÞ‘á¡!!RddàȲŽJ`Ajd�Âa•èÆï÷§éžžž…ñ;~üøè¾}û999.½i0yù lžßÿ‡§ ùϵÌÒÃËÀXÍœ (jä(‘ÑLÂårÍÄë {¤q)''JÇGþ;’ž¬ƒ©Û‘üâ@ЬX¸m¶u<Ÿh*Æ=‘µ–ÑN4‘““Wa§¦¦Rš››çÔLGéê� ”ì„ûÏBÚƒ�½Ë*�Ð$x®Ï9Åoû‚áCC¦Š‹‹Guª©©Y4çÈ#ÿ¥Y3ynXóé`ý �¢\,{:¬ùÏÄãK®7Ý3¶¾$£E謬ì�çú\¼xñ»ó€ƒgµßäå°h¯–ôÏ«+¡IüEÂ&~¹ÿ´buÊh·~uÕªU^·ÛW õõõ+T5æ�!¡ÂÄetwŒ¯Yóoæ¢îÓÐÿ³„Mn†/6!ô ÍZô˜õë×·Äꔚšês:�Á .ä&}ºòÿ A˜¶·25çá@úÃ�¼,a“ó—ªMB@ >óÌ31ý•©©)—ÝnW._¾œ˜��sÐöCPtwÈì �\˜›�@/LwÅ­Vì‹xë_Ã÷#9ü¸ °gÏž¾üüüžX�ûúúrºººÒcÕ…iü3íר.ɦØîÀV©DÓ�ãÐôlÂásw53A´ËŒÓ`@úЬ‘zèСßÄàÒ¥K¥¡P(¾¦{oZóþöH:Ðÿ�j•r*ÿ®QKk— KTBE ßñÒK/5vÇdbb"­ºº:v°*Уùõ‰(ž‚vŸ‚¡OØõ:¢µC³1’É\š_^:Ђ¨œ:uêW6›-¦¼_ýõM1¥p¯11x€á  ú­e¡qèx=a· c9O«3Yx ø>°[Ï«�=öXÿÞ½{kc vëÖ­¢ÊÊÊG‰~ÒC IõϾëÞýqbÉÉNÿS‹‡jÄGÍSLÖSu@òûï¿­¼¼üvôx555ååååßïííu60ð󹫫à¿#Ñý� ›ÿÛÍ œ9?Kºá‡˜päyçÎ�;kkkËÌ­¾ãr(ý/ï[”:ñF%§I’ m�[Ž!އP§aªy~ Œ]‚¼@„ éÔ˜‘¾Üžã17‚@3ÿAç��€Óï÷'›[ùý~JŸ 8ÚÎeM¤Ûû4—S˜D韄–¿˜3JH£¿ÔNÜÑ‹0q%n,õÆÐ6ìÿ"^µ„~08.�ŒœKweµŒÅ<¾5 Z�Âè'q 9ø�[[xèéZT5îv˜‚7hʲïm¿ïô¯Šº…-mîQG Ú—ñü;«øãbn›˜4/ :ú÷íyýeŠÚáßduþ¿$§¨Ÿµoð»w Š3Õ±£/f …BÁpWâ<áÇ£†[C¶Â]WyâÇj«w£‚Íõ;KD!Õ÷YËÊ΂}R`ÛŸÕ¥LzózŠòûýSFÚŽþêaŽøÎ‡>¬i—?¬i'7;9ðŸ?±QèͰ©“³‚ca’dáW2'{2=ÔÌ8ÿ¹º'7jÍÿ6óBØ•€jX!µ¢¢â.ú '%¸Ù!„¢(ЬÀ'·´ò‡s³œ¡ï­qr³dÜ (0êA´õ*ò寽ISÓj8 ±ù{EýŠ¢(6›-nôÃœŠŠŠNLïÄ…õ¿Næû¢(Р‚ó~‰Ô)„æÓ¡�ùRø;‘ÿC@Oí4$B W6óÃ9D¨ /ÒüŸFÿù’1‡(ÿÓØÓQfá>IEND®B`‚Ûi„Òµ™xœ•jî‰PNG  IHDR@@ªiqÞ pHYs€€[M_ptEXtSoftwarewww.inkscape.org›î<"IDATxœµ›ytTUžÇ?õjIRÙ d%¤"[ ,“ˆƒ,=ƒèvCÅçÇAè89¶ í=péqÚ­glµgœ¦Ul<Ø êˆawR$CÀP`’Ê^U¤–7¼÷*¯ªÞ«ª ~Ïyç½û»ëïw÷ÞßýÝû ¢(�·òò£þV?Èo � ¤É�0ËOð×Ðtƒ€(Ç+OdXyP½EùAõÖ…iÌ+ß±˜6ªžñÀ8 È”i‘åFBÝðk@+ð=p`˜áÚBÐcXW† fZ«×�„3-c€2`�¢SFúèM»wï¾qppКS׋‚‚‚Öõë×ñØc��c˜A?ð à@bÞOôê¡5·„‹y“ê)æÉJºmÛ¶M~þùç—&Ú›±`6›ý>Ÿ/ÞÊÀÔ©S›^}õÕ=óçÏïb˜Ñ‹Àa¤D-Qï1ˆ¢9Kë1o—™7¡ïv»}Õùóç‹~ ï”””\)((èŸOÝk¬Xœ+l¹ë+íU¦ìe�ö!äZ[ظᗸ=¾¨ä“'O.ª©©9&ƒêñ¯¨¹ ¨D²ë…ööö¤êêêû¼^orSSÓØHæ•ï¥K—ž×jŸÇãIÞ·oßÌD˜·Z­žY³fi–ƒmj4Í`‚œûÂib€ì«¿æà›7Ū*Ä·zL+ªŸÜ Ðï¸ãŽÛ:;;3.]º¤ Ôåã‘G¹P^^Þ\YYyÖ`¸¾aív»SŽ?>Y3r°>š6f X²ÃiWßw#³³²uCe¬ê  ½É©BÖ†­[·N;vìX¹’£µµ5+²iiiþººº¿”””týXK¢ÍfóÄfî?DÓÚwÈ"›—·P”—¦•3ThäØ/rƒËå²¼øâ‹·¨sõ÷÷Û.\¸�Bó Ž9’¾sçΘº7¬^½úÀĉ[4#…û‹pZ`º>Nâkçƒ5íªÐ i”+ßëÖ­›×ÝÝ5ñ:th¬^ƒOŸ>�½x%%%Wžyæ™][¶lù_›ÍæÖK÷Ê+¯,ѵ23ÿŒÛŽžC/nú˜£,¬* …ÅõTÌZ#Ò^~,@{{{ò|P¥U¯ÃáÈDòÐD¡ªªªK�åååç�=ú¾Íf ¸Ýnó¶mÛ~/£fÀ@ F÷>À€#š&úø�ßPzÛ%Í"Õcߎ¬ ›7ožãñxR´2x½^ÝÍJEEEŸÅbŠÅÃË/¿ü¹Â<À³Ï>ë(**j�•'„�ÓHK½Æþ\#¾N3ÛDÛ fOËÀ M*¡6*Ì'ùÈcãÃ?œ�Pƒ" ¹¹¹.½x“Éä/++ˆÌS]]}B /Z´ètvvvlM= ’ò£éƒg´Ó‹>þµf’š2ª#;6vìØQÐÑÑeÐ(ðûýF½8€üüüN½¸ŠŠŠóyyy×"ék×®=o4Ç�/‹¬ßdI ’RBèɽ/²ðwÃ€Ž€y›ÃŠT>ä!8Û·o/'úúúbš°%%%º½·råJMÍÍͲÛí—A²ÔqåååßΪ($µ rî‡þ ‹.¤ëýºí2ùœÜ³´,Dò„Œ qò›S§NÅÜÔ ÄÀÌ™3;´è999]ëׯoÖŠ˜7ožfœÃá(;qâ«Ñ´¿ß= ½Çµ èÚ«Y<|g˜Å@Ò@ss󨶶¶¬è¬¬V«gâĉݱ*Y´hQ»ýþûï?!ú+äã�?î0›Íú]¨ ëv ¢]ŸÆÍZQ<¨†,Á1r@صkW^¬í©ÛíNÙ½{wÌ!2sæÌ~›ÍæQÓ,‹oÓ¦M ±òÙív÷üùó5Ö1’‹`tE4ÝÝCm1³Œ57¡2,M Y ºFÎHPXX¦UUUç²³³Ã—ÇÞc霘s—šôÔSO�0 úvtÞÃh¡=‡j—!ÐCYÑ(e‹hDE´´´hÎþéééý Õ cÊ”)ß«Ã+W®Œžž›Ÿº‘þ¯Ëðu„æ”… º***´wƒ¹+µé½G´é˜3%M=ÌüÃGXtvvŽŠÌ`·Û�³gÏÖ�¼´°xñâ�ýž™™Ù»zõêp{~À1ŠžÓè?™ªŽÚ°aƒö,—4’‹µ+ôj[yZ(+°¨+A•ËÈëõFÍð·Þz«£¶¶Vc3®�+V\6™L~€»ï¾ûK“É®ÖÍ¿©D”í ×�qê¨êêj§ÝnwFšz£~…Cšó®&RmáÇdŠãÐÀ /¼°LNÄoŸ‘‘áŸ3gNë¹sçrž{î9¶,}]&\ŸÍ …ëg!y BxeÛ†î=»þSÖFQ\P6W¿Â`tJØ$"†1c4ãnä¯^½šÐȾ}ûº\.WAffæÏÂ"ºö@PµH¸þo<¾®ñ˜‡§Ÿ%·¯bIÖR™™¯]Qÿ7#€/~6( �¢�””s#àõz“eŸ@LX­Ö`~¾†½Þñ~x8è�Öíá4£ Š~=¶d‰ŒÒ9/iÿK¼¦„¡w0l™$ÿ¹Òè(;] û÷ï×5–â¢OcŽëx/š–·¬²éš³"(¹."!BÇÈж“Jõ#»“Ç�דH!G�?•üÝpMcçÛw ü½á4£ÊßÛÈ_ˆÎ„T–7z¾Œ…3ÍõfN�®žP\\¬»•UÃáp䎨V‘ê¯@ Bÿ©hú¨r¸±¬eÚsSÒx¨rHKd"0>éRO¸Fé0Q˜={¶æF& EÁ`ô9E\´þIÕ3¤©Ü‡^m×_\ø:àZ|3ÀoÊ£§ß§Œ¥ò¸Š|f¶|ùòï•õ;úûûm{÷îÍŽ—.¼ö^è=:ΪK#¾¸Þ´htþ¾{Ž.ƒÐÒ[¨†vƒýÈ+AjjêPQQÑ•D {ë­·Êâ§R¡÷0ˆª¡ìü}¸CËï­ÛüÀñðùɰ ’D¤X,X° 1^AMûöí›’pÍ–(~Rrgk¡ëíÉò%M’‰À`äÕaÛ #HßÉoÖ¬YSïdÇjµ^ëèèsäÈ‘Äo…Ô?=G Eç.ŵËÐ÷UÂÅг<Jêgpêl¨Ÿ}Ȱ2!\F¾RRYYÙk·Û/Æ*ìäÉ“4�]¢àuÂà9è9(½$†§kûŸ„Šº¿�ôÆO¼w(LóBcQÑ€kH72‚«W¯>A (.óÆÆÆÄ4 û mº!¿Úö㣒ù{òèü0¡êƒ¦l6½ô�š$D}¡RSSÓ˜��­ëÝUðî»ïÎJ¨:þz<Šæë„®�â—'X%s9Aì­ŸŒ«GÚÈ'C![@-€ï‘îÚa2™Ä�7~¯`§Ó™»ÿþ̸-Ð:±ÑCä¾ >œZm9ê hÊbõÓúsKä•—�ßnÓ¦M�¥¥¥q­“íÛ·Ç_õ4@ �ÁÐUýø‹[Gäùãþ2Ú;‡�¡†ˆ^ÑE­@�üÍo¼ñ‘Ùl޾f¡ÂÎ�;«<¨¯¾Î„–!ˆ>}··®ü!á¢z 3X·%¶»Lîîî–Vä D‹-ê\µjUÌ¡000`½óÎ;WÔ××'#­$Ê#-ê#QÿP¡§µéž†`BVDc:·>é"Ô´-B×i¤ÛTIª'i�4Âk¯½öUeeåÙX•¹\®ŒÅ‹?X[[;–á»’ª%p`…þ“´o íÏ `à_>œDí—º;ÅÐõ%)Ã;ï¼SÐÝÝ� ƒF£Ñd0 ÷Þ{ï™ñ©=ÖÜtOÔ=Áh2 ƒÿú_�,ÏO^{vÂßüê`“œ‰®í*ô“Öw!Y ‹Ah|4Ü”Ž�/ZðäïöÇJâC¾a®¾öjxâ‰'~ît:Çkå˜�gõìøo«7‰ÎŒ˜µ÷=šÃ—Û%�NûΑ�A�4fËÇ·u}×÷.àg«÷ÇKfFµ‚þä¸xÅ�2õ!OÒ5ÆÄwšô} Žб3nRý ·‚÷;¸ðOpqKB¨ë{çS±¢vDը퀸BhvZ'>0˜Ü5TpÝ:B œ†Ã$AÄÝ$ø¢e!Óî:ˆ?�°ŸÂ€êŽ$ €+Þäñ·_Éþúriâ‹ñOјÆo÷V&¢öšˆ{¡I C¾ 0gÕùš?µŒY‰ÍL?z ÓYøx*OþNçÈ<\—¼øç–qðÝóAHøêïFÐ4Füãá›™‹Ô_ê4¡º%æ�{#$8[û…é÷dá“9\ôü­t}õ'‚(ØÜïm¿”sç€ïŸ>bÔ1rbÂív«ïÑÕðƒ®w8á¤äÖÃL_›êû¸qN¯_ÈNÌd‹ƒA캖×þòÇ�· ™ïz¢©°³ûÚU·ÐL©t×�ö»‡ã[—yé/]i‚`à–¹Ù]ßž>°`ª;)3¥+Ýô$'RF@Hí¿Ø5¶÷ãñïwd44_Éù±Ú§‚’‚ÈŽ‘ñãÇ_E1jÇÁ`0h�¡¦×_‚�¿÷XÀ ÂØî²"«gÁ¬ToA¶1062FƒÏ½ƒˆ]}N7™k¿é±vöøÌà“½%éýá÷ôê‹Ë±(ŠyyyWQýBcEñ1Âí÷ÈâH ø§’®#o<(›,3áÿ!'õ/s ó `"ü?eRÔºŸÈ0Q†”é6¦ú"öõ@aZý—òŸ`”ÿø”Æ„6`rXý[rœEŽWÊù!öF¤B”þ?þE`¹CbnIEND®B`‚"/·x¾ xœ5‹K ƒ0@÷žbnà †6©Ji ¥HŒ³43!ln_ègû>�#nK’†SÌÏ¢³Õ`ójyn킱8ˆ÷ÈIiøapâƒl”$ñ(â S†�x–¾—y¡SÕ¡Õg3ÖÝ­×M3^®æTß«r".÷Õº>T:„•œM$\ÔNXýí¡«8,µŸxœÕZ[sÛÆ~÷¯@™g†€ö~Q,wšd’v¦™ÎôöÐ7Š„DN(‚CP’•_ßïœ]€ J¶›:-=–ìâì¹~ç;´ßýþÃݺx¨wíªÙ\Md%&E½™7‹Õæöjò�¿ÿP†IÑîg›ÅlÝlê«É¦™üþý›w¿+Ëâ»]=Û׋âqµ_ÚüÜÎgÛºx»Üï·—���Õ*?¬šÝíÅ×EY¾óæ]ûpû¦(ðÖb¿Ä‰BÜÝMèÁ²^Ý.÷Ã'«úñÛæÃÕD¢Àcú›úJ~°Z\M 7ßäS/ÛLñ¶ös}S_CÆ´PB™RŠRįù•oñ÷rÑÌ7³;Øù¸¾›ý\ï*H=ZØ6»}y³Z×G;K>˜Pm7ãû?,¶+(¢*eŒqtÏÓÈ„gÓ^v;¯&ç¼Kº´ÛÙ¼n/ºçƒ÷;ûú÷»UÛÜïæõ DÔÕ¦Þ_|ÿ÷ïûÅRT‹ýâ æèôGÍç*!ÄEç¤|ØÃí‹;ßcë»Þã¤ø‚"MR$ûG)žE±�ÝÖófÝì®&_AòÂu³[Ô»nIðçh©�KVû'¤P¥l^éÝÞ.›GÝ.g‹æñj¢N7Ðâ@‚[Ÿ/ë9ÂÝÌvPýÙ–EÝþÜé·�ôçÙŽf~Woöåýfµ‡�sò#)¡ÞíŽr3[·õé{¿4Í݈ÎsÔ‹q•fíü‰jK�øáqµ�ýe®H¥•;³£/Q/ž�›·àt)c8³ ̹µ»Ù‡ÕÝê—zÌ‹óûÝŽ\´ž=Õð$ÿRœHH¥~9+½˜òˆî;wÅ©{‹¢Ù­nW›‡óºGOÃGTU@CÚVÅü9]|]¬ï¶‡ÔŒ±¶Ã•A^i!£>ôËgÞ¼"M¤—äPbÖåj28g3»^“G÷»ûºú°jWxÜåUqÁyñ¼$ùù¢¾iµIw²ó<@p_ïz_ìŸH(+_®6XÙ6ëÙ\¦�€†¿þøí7½}àÖ³ëz}5ùåZü�‹q2Œcz[‹X›¤òΛèúÇOÝc{»>ƒ¼·Ú ¢Ó'3­Hxÿ}^ywSÿ°nš>•ŠbW·÷kl½¡Ç“ÃózËß@ÜͶËÕ|°Æ{Ë#ü1ˆg;r¬w·×o¡Ç4ÿýzx ¹ ©$CŽVÖóÇÙ}Û®f›o×÷»çê^ãéGjÛîß×+ލ:‚ÒƒÃóNuùËÍM[ïŸkÑðó=NT[pu=;tñ4ú8é’Î;Õ⻥ծöõsEæX’1ͶÞÍöäüÕæD½ç�Vc†°ý‰Ÿ¨ŠU¥iùȘ±R§F�ê{�Þ]¤:ú-jWù“ÚU:†(\8©]e¤2VŽT¯ñÑiFª×04ÿ勯ú?S¾§ºü6å{ªÅoX¾Ÿ ÊÍõ¹@ü× t¢òî§Ù~·úðª~Xo—³S�5áa¶¾¯Û4t‰¢”…(^ºÄÕˆÖ½r§zŸ”æ°8üdÄ’‘’=¯úZ´Qœ#…4¦÷HížWûLõ¾XžGE4¦Á¯U·¯ÕÄiž†]~¥ÒýTUâU>ºóžÙ dÇúë» âÉ|u›¹óaHiî·wÍ¢ÎcL7ê,ú±æä…Ü`ÿL‹E?ôÜ›ì­:�àÜÉ«v y—«Ízµ©¿�jë˯nøÃ7]•\ŠÊ¤HËo’þ—÷»õÛ¯º¾ýõ¡¯ngûå I®&?&VNYíTTVZ%�ÐÅè±ÑA=å FÇ.*<Ú¾ S‹k¼èíùÝ9(˜ÂÔ£(\ÔªøcáT國Þÿ,¤«„×F{<¶²¤‰|éüQ}ÿuTŠì½�rW»ß5?×—üµ¶¥Û4E_žb"ùNÓˆ7C¬´òÚ™©�•…TëO ø¡wÎÇÙcå±[»ó›ÇŽÿb¾qŸá›³†ËO1\Žç™€»ñ.æyá î¥$‹c†/ïûŠ¢yNëñZy( ŒG±øbY(‡ÇB‡PÌÎgƒª¢’ÊÅiÙ¿ùË—2Ö¼hìݘ±g-‘EÙ›Ò[rÎNÈ‹¦~-Û;0›!î ôÞx �ÊÂi%ÌÇïØñ_Ì7ê3|ó|‚ár,±–€qé�ÎsÇ�`Ü&n‘~íêy惯ž|¬'Çv·‹Y»œív` ›fSˆ %ËŽÁu3¹©‚õÂv„¥ŸÈùÈI�Ÿƒc*[¬ëx*ø% T…×»¯¿¼9e<6H#Lè`N ÒÜB�ƒ4²‰íÁ CY#Ôoeб1 - Þ5þÄ+¥uì¿l†1�Dû뿌¡'@Ó*šÞ˜S{DJŸý“ÆkW޳Xy˜ö»Ù¦½ivw€{ž#ßb˜‹QG'åTПÃm*gƒ‹—RVµ.ã˼UWIç�™Ja+倢yëU1Çl[©)w §øá‹RWÖ¡yHU(YE?E²@(=µ@�|]¥B!¦AUÆ¥sUHQbDH¥ß¾¿5èEaŠI:˜Êyg¡:|Nû¨~Œ3 .%Ëð[9�n¡Ž"i4/‹D5G-¹ƒ 9Á_ê#û§-Möóù¼mçôybÇsÃ+}XjK§Z˜Jr�|2a0^ 0K€¸áDBzø‘ýkÉr�ÂÆ �«D8(z|‹6xEз»uÈÒý:~:¾‘X�|�% ŸÜ) Di‘+Q’(O)ªhŠ5üNë’Bù@IЏ€0r¬DýÓeWd1|ùç"ê �³–_ÑaH,W8eÉr@œ�”‘79uºÒݵ-'L RbÐVØ©GH‘?àÁ÷1PþR€ÑyÒ(SEн€$ g+ä8$’EK2"°B š°»E$EE ËÒú0• ÐÞ³¦•†nä8)Ž+sP=Jõ2Ø[²€ÈFO¶%EøìÑÜÓ¯çÚþÇñçó3r¾žµ8 Ý}•AiªP?}¥“V§l@‚!CU-œë+ÿ8rO©¦ lB� ªÍÓ:²¯:¿ÞÁ)ÒNò’Ü…uè3­K®5Ç,gÒÐw—OU@±Ó;^¦¬8¿Â@Àº8l�€yÍ…ý—×ó£¯}^ ×i”>; gÂN˜©�©ø¥ÎéÊàÊÉî ‰‰Þä(/1§G N¨ Ý`xžA=@ã }kàÈ_šj%í£Âçôë J<f‡Õ$æàð’˜M(Aß@ب·í¯ííöó=¬E$s%~%8æL‰ÇRy{À<tŠÜÂÐv2Ò&W¡Ö�"S€!Up$Ã10�/,¤ÒùBk‚†BÂ#ÆSd|(�4a;ZØTxÐʹHBÅÙM®¹Ptmi=e*¢å(“™‹� ¡r$ x€NùÉ>åE¾fôœÞÌÐ[*/rL±¬ Ñ$î´ô(æZ±¤"|!r9š®Rï§Øp«ê8‹É7š6‘ŸIMCì(e%e)yÞÐ×`©Ñç(¾+$±4VŒ¢æ.)A=¡£¶¬ŒŒ¬}¾›Ó«ØŠ�9qZfºº¿ËWpJ¸È‰ª€³žm™¸r™È2õA„Ž…î®ÒG\-µNX܃sêŸ^v$ˆ€dp-–¹›rh’CÓóßC‰ut†ß´ä £(ïþ+=uH:>Ôž,¶céVNÑ’Sr'�3žÜH|ÛPŠhÎ(„W#ë=½!\g|‡(¾HùE)Å5{À[‚¦Æ*5†É=d’êP"HIïøøq”¡ôv„H€%²�¦�H ‡BŠë€™–5‹C”&ŠŠ[æY¥©nQqÐ’PÍÒ©|µ,2+îx<åN׃¤ÁpÂ6�ØA5‹4¡à<¢ )C½<øL0ëɶ\ý’ÇEd;ü*ˆVgr£èÊÒ•&ƒs¬Sk¡ ÏòÄbˆ“düÓlƒN]Õ¥w]š® SSÉ74ì¨Ä˜Jn�C"ÉKf(<-$èDÁÒ+L‰b:—+"c7iäžši–%ŒcLârâ�‚ç ¾ò<ú2Ò¤“Ñb³‰¹ñXFý™›#0™md °�ÅH‘Bœ„ 46¸/ Þ 7Ö*ÑÐ,ª4©é˜€QSã\ç4|%Ç)ˆ G³$‘zÁÓ€"�O‚6uù`¹C)ušB�¦rŒ‘ÎSæ*2‰‹¨v¨@Üü‹ †X55Žt›çÑ7uå@%„á±HÓ-RZê«ÈC<,wChpºÌÜñ§ÁYw3"a!Á-Å6±+¦jœÚ%×G ËåL(úg�P;Jujñl$éŠZ?Ñ‘£!ÞÃ�×w�ÂÞ€×ææ2'Mz@7ÔSsŽ8XC>dq(׎­ç¹÷âó÷‡©‹Ã3ž&xºÈ<bz+qç9K]/j -º&u4û­ýš¦´$i4Âw|šÏÍi™=ý"‚ËœgPÊ�®Wåøæ©þ®«g +ÑÂAN°Áý˜97Érür椯Ó=¹Æ™)v6¡7µÖ±fè?aflŸÝŒ5´±Ïȶcamÿô?úë§B¡NeŽZÚ˜½Ÿ$¾Y?Ý6›ÃÛfµ¡ÿv¬0ÁÒ—ZxÊtgùŽæá¨\´ÔaóÝ©BIþq ^Tjø¯ïèÿß¿óo'óxœM�]jÃ0„ßu á äB×q‹‰J›’B)a#/a±þ�¶¸º}�Æ”¾ÎÎ73û±Ã,m.ί‰[©8–ˆºŽÑYl‰ç�,fýÂÖY.jd9$%XVΔõ¨üïÅÒ¬îß©µüº@t&�þKú§kbéã€Sxœëgjfš Áì:QZL9«ƒ©Bmæä‚Ò‰ómÀÔ#N •^”X�Á‰þ{ì�xœëgêaš Á웚;QZ‘-757¿¨r¢³:ŒU¨Í dMœox¤Â d¥%dpl»e±xœ5NÝ Â ½÷)d/Ð ¡±,F[D- "ÂÜGɦŸ¨«|ûfÔ͹8ÿçø> ¥Ü/d#4°cÝðž¬À€SòËeGa:ÚˆM:íPöÂÚŒ”¨5˜�LA>&%âè¨ÆÑÄOðáo�l^6Åš_«Í¾-êúºÝñeušÏnÊÌ^ƒ†´ÙF ¬°vPR…†”"À�ÏA *Äœ¬!¾Ðuž¥Hþ»‘Oé©$öïûjPi·RxœÕ“MOÛ@†ïþs,QãETpÈR)E"õ€Z´kOìQöÃÚ�úë;¶“P7©E=ôæµgŸ�gÞõ¢p6@¬eéëCo_‘Ç•{ú/£ÝöþŽL€‘†¬Ô£Ü&Å?\9#ÉÂÃWZ“Á’$\:cFS(†y»+É�¯D·ŸIãl¹Å+œuôñR£�,�ìnºÕŽ.”PŸÄª'ñbƒ>Âtðƒ^ã;íヲæ˜zù~õ»gi5çZ?VÑ>6’ãìÎëòú«1œŠÓB¼‰ýõ¬œ‡~$ìÑŽ¿yöùÑx»‡xœ{„÷‰PNG  IHDR88¨†;sRGBÙÉ,gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ< pHYs.#.#x¥?vtIMEê ö�¬ÕÑIDAThÞíš{PT÷Ç¿¿»wì»ÈK@Ø6ZhµI,‚&uªSaŒX¥Õ'ã�N£Ó¤:ŒgÚ¤31Mœ 35i¨L‚$Ö´¦�4F�ÖÚi�‰&båUÄ�„— »ìãî=ýã’-Êcï½»(�áËðw朽ŸßùýÎ9¿³‹ZÔ¢¦Ø|8í)ɶúÆXäƒX.‰Hd%’ÀtÀxxˆÐË8êð9Óá3f¢O’O5w.X@{Ivœ8Ê*!°mäEŽª—ÑÑuhPÏEP]òÍ_,@û¦ìqŒU“‡m‡CHV‹ƒ›éèmM$ýrÙÍ]°§4[ïfÈÞØtPÓÑËšhñÅe'[\ °{}ö rp $ û�$ ÍÌD)§›•¯‘R¸u¶­âwùAÁ ÈÇØ¿ºÖÙ*氫жWt²zÂx¾'„‘“½ÓUhÛ;/[´«Ð¶—\ì•QÛ ´Ïz¡é7!ì^gÛ:9†AbFÚf=×Ô4`÷zÛ qŒ]~(Ûrî7Ÿà"iUÊé¦ëªÏ`Ïæ,9XƒûêL:XCOY–A5àÄ0_ÒlI4ù‹�ü’Ù¾n¿ª-Zz¬(¥´ÉÝöØ?FUq>ïq˜6n‚!#ÚèhL&iI½ã ×èN+è‹÷�‰³Á,›ƒÚ5öÞßa6«Û>¾º&Mkx´Å�ˆ!A1XÔ3U0feÜ ›DgÓ™Á"S�äõ á&ˆ·ŽgÔšÃU²#Xz¬0®Í«ïÃÎ~Êÿ:(ûÓÂ÷ü KÊŸÓéüÏFtõ sø‘& ¬ × QÃ"©ýüE<òûÒš¤C#_áA�,à’‰ÇçyøÐ4@ƒ[Žá›™á˜ˆÔÀçpøÛ¯PÉïËëPƒñ€€> OާN-‡ +-ðIíŸ5� 5AÊÔ41¨ÔôK9I¦W®·£ÉFôv·K§Û£<-xÈò儎#Ê"„ ÷?œ–úšOuŽ~mSZ‰l©\Ï�®X¼zä&?-ÏðÝG àÇ\îõÉŽÜþ­ Òz÷µÔ#âî›J¯jר_‘UõŒê•zÿuSþÒxZÚæû¶DáÃÝflY>{4ËÓx|¸ÛŒ}[¢üpo|„¨�_¨ÙáÇe�,6×Ƶyô]^(ÿÖèç¶Øú­Rèø)#‹;>tõyïYhýÙ<‚ Ÿ¾�G†#7b d#‹Y�ÛwÞzêèˆÈíV³”«-6<»j'lI_Çæ.Ô"šo·âõ˵ˆw_ÅÁä›jÆç5|�]þÐ ÊŽ¦´zõ­^¨ú®¶ØP’ù=dƧ#:"áz£Ôn¸�B[;þôŸ3¸4r ÀG)ÝXf¸ұᜋõØ[O¹ƒ‘4ù¡Ó?öyó8vÄ«ú2·š/°ÿJv/:U©Z÷‹FFM¡d“?÷+‰#G÷«qyMñ²¢fûž"¾ã¢;Vã«ÐÎùìø_ˆA$ïVj6N@…®ÐîQ ÿøÜõη‹h>àžÐ XmQü"€Jm�½Eñui&]Üuö] 'î›�/Ÿ�„–‰JÍ~ÂØÿ¨ê>8›þ¹ëÌ«Qœ¸7”‘¬4º` R¹*¾À^£úÂ2† Í™Ô3`{¬¢Ä2 L œª‘Åßw�}7I#¬ 2»¾`¾‹¥zÙcÆkVÊÝ–AÏdÎì<÷ï ­;ω‡x`B©ýr�ˆ ѲÕ"ÑJ¾ÀÞ¬®4©MÇÖ&÷ šj'q;äö®¿�ÄsgN'€ßá%íûíàjoˆT\W÷¥ÀWº‰msË™-=©÷âpÊMh¦gNð€z"¼=SãüP§ª¤®È:(ðE.bù> Ç$ò@â1ýÉe½¾ ãÈ8€=“c†«D8l´µ¨E-<ý/iéésçIEND®B`‚RÇúE¼‡xœ|ƒ÷‰PNG  IHDR88¨†;sRGBÙÉ,gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs.#.#x¥?vtIMEê £ºÅÀIDAThÞíšilT×Çÿçm³xƳ0f<ï3ŒÁq •©ÕQUŒD"ᘉP–"•˜4µ ­Ô…Ъi›°DQ*ÑD ¨1ŽE äCB¥ŠTj 4Ä6c6lÀõ:ö›™7ïöÃx#3ï>��+ùH–¥ñ¼óîïžsÏùß#s6gsv/�¦ÃiéóÍóÃQý�a€å:c E"_œ1ˆD·ãŒu DWð¥DøÜiÿÞø‡Ò³°äÇ͞ᨾYcì10T23 !|&ýÕª‡[^_Ú3+CÛ›üªÆvéŒ=£3d¤cQ"aD²J´·uY×=,ªk’ã:Û©éì%�Á9©.%�öˆýþÒ�²ØŒ=ÛÒt�é¬|& …,ÐYE¤ÚÖe-ÓXðlãÃÑ8;ÌÒ”Ž†JËmjcÙ‡\YÀóå`]ÓöˆÆŽÎ40†Œhœ5×5mŸ–ëš¶‡cú¾ÙÐÛì²ð\Û�²ýi,ØÖ¸.g äYÒ¿c‘j®HW2RPÔ8û÷½HKgò¾ö7–µš>ƒEÏ5ËšŽúÙ7z&CCQ]“lÐ+bwLg÷¥kQ:€øÈo–†Ÿ¨Îʽ2v™JÑ}ÇÕà‰³Ýçþy¹Ûô¹ûA¾‚õ•,.Ì€Ïc�Ã.B BT†îÝ *3½i!âsRie‰|)ÙߥÉlïf»}óܲçF?zÔ7ØŽõ, eBH²…°È¯CD0ÜìÕÑxUÇ`„Tg°ô±ŸxÒp_?¦æ|Õ¥_ÑtÈÿíÄg-�†_¸g�›«s ÈãÙ«GC{§†þp�™!"? !Ë=¾¿qhêˆãòmÝLÁ‰zì”ÿ@©Üi(‚·Ø5=ѼnòÜ6tô§|Ñ›�ÍǺý£Eg/ªxçDõ­ZÒï×KØR•�ò�¢”ˆ°*Àùë:oÁQÂö$€½†ŠŒÃãwÜr} J¹Q¸!UÇ«ïõ`í[}“Â@}›†µoõáÕ÷z0¤& JrDú3©ú¸¡*zฺ¨?‚w(›Kü®I�¯ÉW°¹:g î¥C=xítÔðâ^;Å®Cã�ey">™‹£ôä…XqJÀ®öP2ù9^ؤä;ûüúYcÀÁúpô’Æ�†K~ÐÆQÊrù£Ž *%`$†o'-·’ˆ¥¹Þ¤si(@âÌñD.Y$Ï^T~—‡•7Šì[)5“6ölŸ >»rÇg5«=c­à�OÃS£>ˆ€C„ˆÛN&YLêiç»ÃoDØS}ÌM¨¹_Fh�˜rÀpñZ ŸÇŽ+‹ùƒ"áíµŠñ¡ì?®ÏwêçÌDq"èê�ˆB¿�“`¹¥Ez.wÆqòbí#)¹:$Âi#ÞÔ¼ëØð®Þ^<2ü›žaöâLhÆ/aÉBþèY$ì©®PvÖ¢­‹rŒJ.ØÚøÐ2ù Þ÷˜šÉüd�õÔª"©Üc£ßJ¸ÿAG�B¿`$j‹„=~U˜�3Á¯+ž+Ýl÷pŒm2ªz–/°`žp7°¨,âˆÛF¿ª\œ\¡Ìà¨í;®æÜìgϨ1öDdòÔuY ß %ר²ˆóá/v ý9™p¾§€íàÇê¢Î~VÕ°2¦³rÆ�gÈŽhÀª"Y™Ät†�pÀIć…>]½XžvÕ4gs6g3kÿí´À�t³“ãIEND®B`‚§‹ß´Ô xœä{‡7Ûïol5J©½ªööµ·Zµ÷ž±÷ÞµÕ*­½)Š¢bSÔV{ÇŒ½ZjÄ&F7~÷Ü¿âž#’O8Ÿ<뵞÷“x- E|J€¯¬$¯ žؘÈ'3ïöÏW>:в(Ë£œáÈ tu !÷Á yýÆFIGb"¢8ò­ˆ}À�8´Är2ϯíÚÈ?¼ðT2ö¸ž(�eÒþÈ7±|•Õ°Ñ)q_$—W›!o P–—Ñ \9êr ¤Þóc†‡5Ãtô×ÕÝ€“ϿҿýBÍØÙKØÎelè˜úøu ×õr@ùd�sª‹‚庫EâœT‘v¸ŠQ5‹I›ˆéä5 ðÕìÝÑ»€Ò“žûÒ*œ…IL¥ÑïòZ0)Mi€Ò·™µ>>SSÁZ‰À³­ßQXÿ¼8á'<% <;¹•éÂÝþÝ哜.ìõ4'J�£ŽÀ"¸y U7¦ó³ÕDK+|B¤ê P÷Ö9hi-KyBy™èF9å�œ�^G_øØX”¿,D;;ûFÞ}_º:½NвÕÁú/{›ª¶n=yö´À05r !Á t¾r�â¸ôPËýùmþ›úø-þâ3  ùÿq–Sq#½>Úçø›nG×Q¡;(žªYœÛjs¶ês¶lexY6&À;ìb+ýct^?yhhh¬ÝÙõðlÜŸÓYÕiNÄ·HP$' ûy†¯ÝåÈÖ’¯¯ýóâEöñãõïz‹&Y‘géŒe}`ÓÇ:å1Ól}°p /¬®ë`l==]ü¢ka˜6ÓèËacõÞG6IbíÑ‚‹k¿�I̓ª“ ¢§«k«×ƒ<Ì‹W䔇J/Ï«žnC[šá ñíRG¼ÁFÞ·ž:R{†­Œ²Ÿ7Ï)/ï 6; ÓÎ6–ɲ{@þr¾’uL„�¶D…ÛÙV¡ùçë®§h[X[..¸Z*h %·(èØôR€0}} Ylž„·ŠØÛ¬e®Ìé^â@£¶Jå‹^Æhò_“ÖäÇR–éÙ‘¿/Ëeèè¢zcª„‡÷8‹î‹3å±NÛ‚þïìúÄÝ›QôülnÆPŸÃ£Œ}õ-ýµÂ BÙ� �"» ú�— YlÝ‹{V€Ñˈ�7™*Ñ„r¯¸ÒGË�œ£ ö.Ù2tH"dÙô9RöD4Ú¼wt†(™R— ‚Q©zÌx\nkjjdÌT*±ßä€÷¢¢ ¥éèð01d?Y6u“\óçt¼¹cÅþHŒ¯¥‚/ûÉ]]=�Q°@£*CÙ œˆN˜í»ÇÃ…*äË]ô "Ë ‘¨UYàËÞþæÅ€¦nñµ<äĘOìz¿o$*ӛػ|KÏøfÜ»×›µ,ÃPÁþ!7¸#ÝeB„»ÀÎUÂÅû«„xG1†(°`³»*ó+sÿlSæ&Mˆc¶ê­?D +€p:|3šzszyçZ ¾éþŠø¾7k 5‰ççݘr|^¹'Š�a¨'Sìi+9ð{FB^¹r†ŽòœÀ‹wZY»oÊæ5±ÏÙúÐ,¹JY?¦ ®övÌyc­™A »?^[’c¢£ãY½ÃFAAŸaŸ¡jiíü¤ÐEÃsd”jcsŸÞï m§hWV+ÑV§ø‘ÆÈ6j;›„ê ðΘíÜ<Éz#ˆ$]Öxœ£«Þqg4$2={öàeÛÝK³ssn9›i,�J£ÉÓ©îk…z¼¢†ËÍøe‹‹;.ª¬ŠKl´XÞ-ò—ú‘ì£ê?Òµêä—È0Áo¸‰‹¿Mãá; &F§õê¹ã¤ß)PœŸŸœ#=ÌÄüŽBÏõð£RO%�QyԖÓÔéÍ`¯Ä(éÍêæfQ3µ¼¨¢åš ËóÊ¿¡‚vp<È*ò€@ìÚ*L±V;!R<£�œ:*Qxýèœý{k±uólÅóYóöe*Æè¨ÖMªFzŸŒ,Ú¨UI9e”°#í–d_÷ò0Ö–?þEgõû©¬®er›3ÃG®B°íü‡½K(ЭYjð.nÈGMBY=@‚S^›súå=‚¢Ÿ·1:E|™ë²Xk°­Qìfôl}Œ\[h`¸-pŽ\¿i$'ã ÐX k^ÿ§ŒYŽbˆ}t3þæ'òî�ë�¬õ0Œœ¾Sl´ÈWÓ¤Ný±yøŽx‚™8�ú²¯…ÑV_-i÷)Ù££�£ˆñòò~¿9Ü‹’ümñÞÆûÀW ‰…¦\5æñË�oT¶,»9ä$:e3x*¢í¢>sk­uh®¹±¾¾òk…TBkºËÑ„SSçlr‰ó-óczÁ (ïy*)ð™#~kIi7šŸ5M`Ö߯€ s-•ìß¼øŒ ŒÜIz¡åïªç§I]HtMËiùQß—MÂ%L±o�ûúze=ñ°d±‘�г–«óï™E²tûÿã´ò«l²ö‚®E"Vî ¸^ u÷x¨»L2,,,T1Xÿ£¡ƒÛao©…Æ0úêx`†8î}·ø… ]H:À:¬Gz‰³ú¯¡*ßÖ�YÿÇNeçÙTÛã a”$‚êé䱩leiæûMF›§S0¶2æÀé�“¸š�}S>L®›°EØux ”yæ%EHH^n…7kq,ºvÙÖÿrKdïñ+—ÃÚ«‚ÓŽ…`ue%D_¼Ê@±UþIW7t®ü›4ùÏ{œ¾˜ß¦ñiJÏgæ¡Ò!Ó1í]À°—®a�E"ZNËläW}òÎ*í¤1è4¯gZ!¾Ù=¤ÕB§Úºá2ЧPÈ»Ö ¸¸ ±Þ±y?m´o�hñ¤j7i×Ú®ód¿O¯í¾t˜ wÞžŠ'™ÞŸ¸Q¿e1˜zõË…½0¨Ü–ò½`:­JÒSÄаt'£ÐR)>d— …_�ïõ„­=[më®å�p0 «K9¬Þä¶Ñô% Å$ýÈ…þ‚<‰€NþgfSìæåS/uÒ‹’±2r싵žôαþ»¥EH;†²^t·¸ÿŒŸ‰OB=NâG™Urê¼¹Ýp›Œ‰‰y¤÷&*—™þþô®­7è¦6®c¾Ëþïãä5@àmjj*Bˆ-ÏZö €ëkN0ÞH�ù敬¼¾>¢Üé"šP–MÐ8¾¬¡v)kàpÀf^Q!fWô…_|za6æ“4jVýˆË�/¸¹P:ô0¾âÑñèʯ5i}£&kEg²×©Àg}¸¦4”ŽÉ?bF³$~ýœþ ÛÍôVáctJã+…ï%ʦ?˜àÐŽˆày‚ÿIHɈáëC´@tÉŒ£‘þöxã+í·‘ïÞpbË~"|Ë–@ô“±ã«иxþ‹_’d­èØŸª®ÛȬ�œÎñ@jÈÛ‹ý&ÚRä¿�#Øð©·»gã†2!ø`OÚEõÍG$Z眀?S…ž(žÐf:ç0¼áÉW³]‘˜jàÇQúòìGzzt’>†\º/T“¿k;üqž›�[Ç�zõîGM £²Î€Àä§r§Wý¼o… h„úú°W²"q0ÎÜ[ï`ïÞ»N€×–¤¯ÂÑ͹̗–[É9ûeç]Oa=ðk��û›!�ÄÁ=ËÑ`ISÍ=Ol÷(¦QÛ´LÆxaõtáZʆí�íHÚJÊ e¤°[¶Œ %Q÷ŠÄŸÄNdäR�x[Ã…�¡Œd���­�Ãx{"õVY'gïC.áÛJe½qi¹ë—5¸ÙÜíÔ9pþ­Iú$\j*.SŸã<òšà›‚;ÒŸ ‹ñ?ï�Ä¢®óÏu!'™dçBÓ]1#YKÔã|àËÎoÐvpÝ}*tÊ\ºkË×´pËyp¥DY¾hÓ0ü¶#®'þ¢ år›õÝŽ íki#[µSÞ$ÜÊ8K`+«¤¥¯/MÍ¡ãœÆÞ§ÙíÐÈÑÅڕبW¿X…ªÈ 'o™÷ˆPEŽ˜(—pèöJŒtØa¿&bf$>•mBŠ÷RCÅ#›”ò3µ½�]Ò­÷8å…î!$v�}f¿àÏãsÒM&•;Ç]F¢Ò�âÚLo÷/ze'£tu!9sàl\4¢Ñ­  Ô´'¯óZ!ËΘ1ú«ïÅ�åä¡i{pP9CS`ÐÝ÷Ø @‹|ã¼Aªd¥‹åÙ2z™¡ds8´ñLûd{ÐÎâYµr‚°¼-ëÙ¬•^R�Á�j¬.�Þ³-PÄ"ÓByø´tªãPí¶ÉäçE‹,²p°)€£ÉÌÏ{‘¤žHSÃ<ígo-.ûè-KãŸÙ£ôƒ¡<žW> ø®Åʨ£‚–¡s3Â]–Aù#.æaHæ©�£6'€MŸ„Æ�`•Mþ'Û–W5g8CvÚŸ0F‚f#“/Áž®§ÏK;FEIùlÖþÃcàHx;Úx„wj佦‘J€÷eþz>aþÏüË1Ò«ò¾7½õ•þÄBóîïRp?Ú!õä¥ÞcàSëuxùSÙã@è<À„ ã}VÙ<§¥Ê2u> `}ÞK4Œbœ=+ãó7i£DÓÅœHÕGü†—;År~à·EeÌš¾÷Ÿ´×ýøç•9„j­È)®*ÃÅ׉âïLaŽå¶øuÛÁƒXV­³:‰-¼«�o5C#ÚýÐͪbɧFʪ†Ÿä0,SC·-¢ÑNÆ3†Ê <(Y<2ÉÖw¯A§`n�8®ùÉf¾…•÷ÆcËÕt;—tZ1 .†^¬^BÌöb¨ápíãêøÓÙ]rxJR4G±$Ž­Jƒ–osy½G!øW�eÕ¶:Où?s`ª¯oШõ¸9ÓU Í+Å—L„¸Y¨©ø4ZìÙ#0+Q�ðƒJ )RS· ÿ÷Àrƒ€uϱº‡ü÷� ꪱûÞ}M3ÈømZ T,k‰ŽLOkQ¡Ï°Â{JòA@ Êúú•Ñ0&Y¥ÊŸ†ÆTl¯°%©M³]À5j‘0·â¤I¿ü†bû¨~xBùνûUèÝÝføãÓè®÷‡ìßÙ”æwÌ—k/2b^ÌQP|Ï�Ô‹ÀRs¦´ÀÙÚî¶z¶„…{ÈÛki¿–¦—ôO¼Ï´·“ 1 © §Ðw‡$ǖp’'&Z¬=!EmgQ¸¡YÚ«ueóg.aÅ—û&d’ê REà…'ÓîÞ½Àó 6HÅåjCÞåšIwÊcTN‹–�ÓD2B W}4aNµr9]¡E®q†1–ÿû #X3Ú‹‘~‚ЯvT Ölnsøuòã–"P¦x|r/Hr—Ô9«’w9ñjí:´8šT`*×+ > Þhx¶GÐ Hö‡B²ô%Íü.ÿ¤¸Lp)¹ªºòÛÚDªÛKŒ3P6I‘/3½!/$ïö¾]�{’ZHo•øZ~FF¢ÜLIܼUȯë`~7&‚*FW+]–Ñ@ç^áêÍl8…B^^ü‡vä#2WØ„ CW�;OÜ¥X+2TX¿B±g�FuLÔœ¥þ†8ïã¶]÷Ü©d Íaƒº˜¢›ÕêºB)oø²Pü†ú; VÐRÒÄ÷‚±V¯ Ü\BCCOjÚ›ÜÍ~#¨ñ_waÙ%Dü•ÜÃÂÎõĦžë !ËðôÕ%ˆç9Ó‡;Õò°'ÕÓTÙì.ï’HåøÞºCÏV1Í6ïX@I[Эr*:Œ7=¤°"F÷Èý™…¨¼ÿƒ¶ÆšÎ,Ïǃ,3Æ–Ú<ÎFž$¿ éäe•œˆ¿(³ RÄþ'¯-À—û¥–5�Y9-K£‚¤\¡¥’"U„–ââæ6ùJÖ MôµÀÅmÚHï1ú‘ãV�gá _Pìó¶Ôa5—p[.v„Càž>´]*”ç-¥³Ãhs}’¥äÍÛjµìl<š0U‡Ý„¡CJ…M“Í[qi kàâjö ' HÄèê±”´€k^]4é¨ òÝj‹¤… Þ­ãÙO>‰ÈªôÒûpd‘F$�=¡$M£â™`*œL²ÓÜ�Í$’mD6“bbÿíü·ù[4þ¨ 1†*lÈU‚RoÀ¨x�KÑ‹£�þ�mEàD†!c@ká©èf<;ææ[—ˆáI8jYC4¯s eÂñ7ˆ\PL>L•ã0PéqufHÐS¬Æf ¥-eÅëÕª�Xùa°ãì½`¢„›ƯD1Ö‹nçûÞ¦É5A½qnö»ÀÛWN8ÌÈF«`k“(4,º:¨#E/×ÝÅåï{MOªŸ‚xt¥S7µ×µ‚»BÂäë.öQ —D@ìèaóM�?1˜m[4+¶®"©�<㨇”tæèHg÷$\F_�âð9¤å³*c³%>ˆ˜iCmP�î6Ø–¥Ø�  ýñLê—ï-6=fÓÇÊ×\W°þž 0ç¾ÕÐo•È(C‡| í¿>ŽøàÆ•æm�jöw îAu4ìï:lÒ›§7 >¿/Eíçíëíìyv)�'ö%ÙżÝ\\þêëﲕu2*_»pϪE_å’D3)¼«ÃÜÚ®i“*Êö5ÏÖ×ÇGI1«¦óm�RŶZˆqiáoQòöÑy\tþKí?îùO‚1áß<,z _Rë”g|üãÌ‹W:Ú±Ë$§.W5Û훩Œ{íP·c±Œ€xr~ãÄãŽPÕXª¢ã ®QkÕ´eY‰k%8¥mâ1³™Âcñ4Šå7 `¢›/bÌ Ü9 ŒYtø'æ¢h-öÌ´t±±PMËÎ `¨Ï�÷üóW¹(÷/Œ²-³³æþäçÆ#­(¤'æö7Ûâ@þ~jïÒ§IJné@}Nu–cŽƒMÕØÒÞ»®´»?w�áÍȿƕí"j)ñ³¹Èq; Ö·…£j§žd�}¹MÙõy+Ü`̶7Yœƒ ˜*-âj¶ŸhQVä}£0Þ¿K!(a׊AöΑËîËàûñ@ÿÎ2�’SID6œgŽØ·ç©üÉD¬à߇Â=ü~Ë•ÕÕÜÇǰÖbÝÎæP@Àê ‘K’X+äl£ÒË×3N»aYRJɵÔ àpv5y@»Û*D ô4–Žè›)Ìßµ5_´SŸ³U90k“ª2/Õ.½ï’å9ÜöÈ šYX‹«t�…Ýc1H~Ø·t—Ȧ�ÞÏЩùSœ€Ó¦{²é€6š�2 Ò4$ .¸ÔÛ£ÙÊ`Ž×ÿ²ÓÏúÜä¦hYñÐ¥Xºö)¨²pÒè6xÙÞ­v+…¿/"Ñé3+`N(áEÚûýÓSžŠ1Ó–3ÿ�ÇV´šƒ“Q؇ x@RS˜újBÉ­�ñµñÓ0 ‚t�ÝmŒ%úÍ.`öî*�;{Ž÷ÞŸI™ªÆ=?óÁ“ð´Z( bëÍþ Å3šÐ j~çy.jdM÷íA•´æ“æ?²iú»Ä<ý÷«â^Ú+6±É~Â|W³i¼ÉzœÒB£N�o§C‘3�¾pL˯¯r"s4©(ÅàÍñU ÿÑ=÷‰?ÈT{˯€?f°`(ˆ¾ó.øûÛFŸÎ2£çÇS�¯2ŒäÛ×¾ÇËÍAˆCü:ÄVs_–ÚÜüêêjóãããìõvdžºðçíMëÀ2Œõ6VKzl—f\øß¹LNI¼øÝ·%Ë-Ã^t8hn�{‚d°(Ø�7É#$)/òløÎy¤õÝE­É7勚­t*Òòm™´¦ùºëÌè¿P¡{™�aWŠ<�统£ i"?ßæ�Ð7Td»IŠûe9ÆbÆòbß÷-™ô�èK£ÓA}dom…DEùD/Žr÷À!Í{Š’ µ=‰\ÿ‘ZÍ4*ü^Qt®v ô E0‹²Ž£ï:¾’í%"mÆu.#½’+y:»ósèùW¤>©‡ìk&T7Ô‘‘Á­ÄMW<­<2{—2²J€˸§�oÈïbî„Vo?Ä*úÕS¢£Çþ™îb½´|$—æÇ…®=¯J¤Î/O‹µ”J2VÚ¾}‚‘9ê¸:¸FµhUy5…èÅIrkñLÕ?&R ÿñÙM”kÛ^û9WYÅZY ¡ !èùöž·¦ÜJ‡•ÃRy�èÈu-ãCCØRc’éÞã:v'úˆÛf¤ÁÖ¯êð:ð�¹Á‘ËÑeh�âYßÜ,htW8f~ì²"==;ߨÇsã]Õò‚žP$²Ú´ÍÝç ;Ý» Ëb®\gün´8UÈܹ*‰Òá˜ÍËË“W©Ä~—Á8ªÉFR}Zq7ô€*':î)ÕÉEàö~/_öù¶'^‹½U¸fKû êÃ*µØšl>^Î:ÇT¿ÔU9î‚qÒG+ר73-2.j…½O]ß78…Ýb» Ùëvÿ'ß\…õÜtêïè"p‡ÉÕëªA¥hshÓxøœýo9£:>géqz]Ã*ÛAt=�ëh@ñ³6¨‡YÑûóÂÎå>û—»Í/bc1sÍG \°ªtƒÿàJ¢ÔÔð’F9õ�µMÿ}OçS€wT�¸Np ŽÝªåZì­Q º\ ~Õ�d ’»�níŒ-V=+у˜OТÁ­-v-°Eþ ¿%³Vй¶ƒqàÙxõâñFÀþ`#¦øÓ—Ÿ2QRi¯�(!©mŽÂPê‡På¾®6k›þXX©‘úéȤ¶j;µB@…‰<$î†d„¡<�eƒª—ÓëÄÓËŠù׬¬è½È»4ó �ž}j¶–:>™Zòž«I‡O¶è«™ b�¬Áð‘�`µ_Šàæè¨”|¡ÝuSüt®v5q¨@gv5ðo0ŸÊ¶è‹wÒ·ò’òî¿Ââ�ö›{סóía_¡7)Á¶fNK›H'¹ë-êО¦ã¬ZÆŸ¡“>O(Ì Ó ©®{ƒ¥aôEíKÈ&IóÌæ�‡àÂî¡Õæ¡D½#¯\ ¯§onɳö=#V@�¸æô¦t:°¯|ñ»©M‡õê—”WþÞ¡Ýß&oŒ 6FäœJ¾l kÚÐ]Gp*­¨ã÷DNÀ ð˜…Jªk»J²H'&ê•6îlÌ‹…>ÈåÒÑèìß¾ç >¬?]cÉ Ç yBÜoR©¹.=ë–¹×ë]˜‰^ä©1k[Ø2[‰|ð?tÅôs¬JÝzk¼aŒ%u†åy8ÝMgµ¨½»¤$Ê�º;N'§x§ôz›Þ6ҊܪL†JÈ‹'Wïîæ=lG:K)›>Ÿ"1ÒoÆi/Þ»yß`ùƒv>Ñ¥‚Ò‰½õˆ{güZ8éǃ À:4´+ ú,óÿÎ@ÛÝ›ŒL¶±z)éÈWiV¿A~À^Bù9�? î;È6­Õ?NŸM_E†‹zŠˆ¦|Y®îÙ?W8þ�øÛ­N½óŸ&»²Öt_ãÔXæÒÆ/> Ñª]s×þƒ n NN}5K:ó<Úh¯giCMעؼåÐ'Q¤cAÚÝ—ìÃÁ˜Aî!§ƒÀ£µùe«0i� wvW5ü Š\˜9埙&¢ª04Ï~ÝôŸ÷ÖÕÕ]âýR_ ¥WUᲦ)k’Ãâ"Ýp=3>± é.–r´§_¬»2´˜sç4©w´w3äœY]¼¿?m_‚·kjŽK캹Ýv"¡pìþþþÇ¡¡!ê2�"ºIMUé”ÂA‡ŠYn-A¦ýB#�¾J)¡°­=„ H‡�ì´yl°�CÑרÆ]­zÐßè�tBêüÕ g‘×8£³ßJX¯…luSÐC}çöm®—¥‡=�ôÛp~;;¶—ï®QHŠv_Q÷"ô÷9ò¡h)”””ŒHã¤R9 ç+›zˆÈ‡©/ã©5©ƒT%ºC€äFŸj³¸FÛ¸$ãU¼ 9î->à˜Ó½n ·áJLuK-¨ÓìæÖà˜CZ^…]œ#וøBv“Õæ›ÏÁÒ¡—#“Œ'I|`×ãöv’>ów(ßnC¤æð#è{XéD¨DM%s¨«?Wï§ßFØá»«ÛLB¨¤7�¹øÆƒ ·/蟷–‹µXw(ëþ±–颻uÚ„t½úÉþôæÇ™¡%r^Îð’#g"O WŠ;̯.‘=„Ûf…l²½Y®Öd˼¼ëÄ{Gª íÝÔ]6~%›s5ë+çíãuÀ�8)™]Éküõhõ©;˪ØôÞ{Ð}~,åFEÆâuUœ,ýóÞdk¥$��%Þù‡%vR½–ÖR©rd‹‘<Úr‹zwßp{Úb‰QÀÀä™åÒÞ*ðfýëIŽAÅ\�Aý‡‹Y`lò¥ÌÕçT¸:löì|{Ç4d“|m¥nˇ¢ý�»«+^~9…Ák&jœ˜>nzýà_@°à¿�¥•%ž_rO$™õ¶¿‰êñTƒë`�£[¿3nÄ.c }f×xKÛGGÅ­!)¥9hüdû¦a2Û a#¢šŒ ð5*jÅñÃ_}uA"ë¯>·Ð_Q÷õÒ¦Ë[# 30Eœ/¬bíþ¶«ûÒÝ·TMw®X_ê…e`Pãk$�©™hÞé{¼=Âåÿ†{ g\vàõËYnÆúÁ›#‘Î'RêÍ*¿†N¹ƒ‰¤]Zwc‰��À[î2§2XnJ¹½¬’Ñ"¬žæ:›¡…árþ�¶¬ÿ”¥…+–/N_�ê>äñxÄR ðrGg£JDTÙgü>óV “¨[Û†uUŒKÍ\]Õ¹óLm³�býd¯qIÅ/a6Ä6¹àf¸È1]«qO�®Ÿ>â*ž<�,ÿ'Ý«"®Gr6iꟆCà°€º˜ˆàít3¡ I8 …{°ÇXg9æ½§Væ|éûŽ?úc^�F8MX‹òÑF$$·°ÿÝ]ØËøªP¡TÛX;ƒ·Ÿ—^A[°&*í_°ÿ=É“!ùKÈÈUî«ÖÞ_„§.ÁtŒ2}¾þð•q`Ð]é/Ø�ÙÏZ„§+þÑ¿ ”‘kLZ}ý(ƒÚS%ß]Ó ýó¸¿ùø3)®2”qöl+‡~�èИóE¿ û¹U{Ý~'Ñ•¹ì€D#·Ab±÷Uï »øqJ÷qÈûÞìàZ!yQâç½â*O�æ07 �* ùÇ"gÔó5,¬²Ö"û¿xÌ×þPuŽ9~M½œŽ �ßV`È"ϲb#ÒѰ•Ñg瀒 ûŒëäXð„;nåÊÁf–TO¨O¨�u°P$¡¼©û—>l!~ý›êÞƒÑ+3)S��ÕDæîV¦I«clQÜVI:cÚ…Þçnç™Ë×G:¼o„ q¡VÏô·›9^�êḭ́µŠ•¤ö5—¼ÁôCDO�’ÎãHB(´šOŠ"Ίr˘vÒ›ÿúçÛì5ÕWêµàýû8)Èeiá’ø¼õõÖÊÊŠ‘¾¾± ‡2‘‹ »Y°ÿ¿î©Cã×WŠŸ³\2|Lø‚·£ òHK2ÆJ¢•Ó^ÐEDØ=TÛ=ä͸¢‹KÛâ”ÏEëo×õþýL«ØeD0'Ðì#¹'´�W»EKwÿáåV�¢‘È�vìiLÔ%ù@¬$ç4瓱fûÿšbD³õ8Ÿ”¯ºeðIòa¯‹Àx*’?˜c|ãÃ+­´,?_9¬Sëî[®æÁåó8î�é'µ &5n,MOãMšV×>ˆY³9¯òr'ïr§‹¼r¯¬jÒ¨¾!šCÈBS"ordÊê!€Ï‹-Ëk‰¾ãÕ¸óùî±O è±8ÄÍUUá?$©îû{œ\jP��RÜ%×ësžÿ€ÿ¯/âYû}D/“<£Õ}³*²¿goj$‰ÿW uîðÔ0SZ*|ìƒ8K«ÿ¹ŸMºe_êU&/GIÚFê�¥æxáÛÌ¢â¥éH0í�Ñ�îz?«{h…Q�YékMìmŠØíc­‰ŸÍºéù3 srMͯÔr§Û68@âU;¤JYUfÑ¡’—¥¼Nʬeåo°w £ • •ë×íIÛZ,�m„Û)Ï8‘õõËëÇ›ÓУà'Åè]ш¶>1I(¯}oËDÛ¿šmÂ?¼¯ZxÛ@Ó ôÝ(©CÁýwá>­¨÷?jÖ²_�צž>}Ê‚\�úpp„ÛìæÙêz÷�ëò\&CêyO�£Úw£Uw»TÙO‡Š=ˆÒ:ëòð§ÛÐ ý˜V}?¶2 úhÝ×1ôÑ„’Õ÷‰ß´Ö¶ þ4sKƒ U¿ü^·ÍߪΠ8üiÔsˆ$|kBßc¹{­j¢'e(`¾X¸Ó¿ú�š”ÇèËpI2ýn¤ŽóõÚþÝøiøÃ=0PüsX!Z`°QÏ!ø˜±ý!š°§JœÄ¯o—K’K’Á¦äK2êµXU#ËóÖQö{ê9•– ±¼��„{WQÁ«T7øí}½ç­& ¤Á&×ßdœsrQ>ty=ÄüíñûGåà>¥³�¶± 3QÀtuÆ +§aG D¨¹”o„9`ÓÿÀÖÒÚvFʸ‹Ë{k×?-Žb�ÊCÓ ‰‘RH{CM®èà8ÈÖºÿåÝïˉFù‡BÛÛ±qÛÞJ'³NÿsͰÛQ:)QÕ¤Mx@x@¤›ï«û¹†Í ÿUëì.Õ.Ù‡FZا0·½x.(‘¸È®ž|î.”ð_š„s„Ù ¶› ´^ïÆgÇt¦bÐoBó®½Yí\S�= ª69ÃÇÇ`º§Âü˜�QÙM׉p\ñüòƒ9õ³»��°p¼² g|“¿_-§Y…?Òãåým%Q8 ¼îÍ ×ü©¶¤9'‘ +ú–ìA,Š·ëíEB ˆ] ^`dІ'Ñ/Çß|t7¾' ;ª(B„Q8Ü·ôûXxví;âKf~Ø’Ý^Ü…8’Æsÿ^!Ö¨ËwW­S®óRõm‹�’�Ný+uuUÌoì ÌÚ'¢’w?¨ãAlN9íQÆ Çç·ó£�)]¥–ŒöœïÜÆ4aM…Ýn=‰ÞIÙ…Þ4s{×F!zÕä^gä9—­¸Š«§’5ðjM/PÑQlšÁ³©¶ \®eK20QnkR6íC 扶ÿ%åŸ.¼5©ZµRì×>rÚˆYÒ««rŒò;€Æ6¤7ƒ'¼!eýó>€K–/kšë¥Z.ˆ6ûÄ(±¬‘d V…¿º[»B‰êOÆvW+ B³“. ÿÛÑ~Åù_y-ùA¤O§Ÿ³§õ}¡.éuw�A¤€10`4+¼t°ù4pÓ³…#}J>N¨tss“RÐ~<”°ÂYËž÷õèÑUjˆ„0t[ht¼ î—öEÝ€¥ ½eû³^Ü�OZ(�ÙkéëéïÛ±‡Ã·¯fwfw¸ÇHÄî]Õ'ýòqI¹µDÒÔ5-�i{Îjpm˜D{î4,<À(_3T¹=a¹Fï§2+Åòo"·³-.MeÃUœz­­zÞ`K¹¸ü‡8´n¶ú÷"OnC{ÒõÏÐôL1§ûzç·‰£˜*:¹Ÿ8¡BÐU‡{LÓÑF›Ž!Z}öL»ëS HeÃÒ¡Êÿ-¾'oïɤDƒìL‡¶»K×|5ÓÂP—<×Ö  ïû%ì!ÿa!Ђs�ñ+RAküö¼Žiê¼ËuÀÚͧLDœ7·ÕÔð*é ht 2eÐŽ8æZ7¼\Ä‹pß7yRzÊ.º¿ò ÏT)˸πyfÞ˜’Üúyed•"ß²&ô­~ùZ7%,*,djd5ÄÇù'þVÚ&º¨XñÎÔþZ¶qPœµLʰ8YG¥y­ÝÌ'DýáÕ˜îŒù¾©aÓKá×%\±¼#.wzà…B°Ôeø¡î^pò‚º7ßáÝ6= ¿n“_*Øt¸C4yT…™…Y„s>4y¼•Þ#ú÷s@Ì[eJ¢íÆV|~â‚X™ÑtÀ ²Ÿ¶w¥©Å…³º’�Ã…þ¨bp)²O'’ùú­ÿøÑ¨ãô}M g�ÈEÕˆ5¯„öqv; Ü»dž»ŒËö'Þ.éöt³+tóËiù12´Á×Öv *GŽK^ª?aáyÒG¿‹¯´ÓÍq"©Ø‹aÃÝuq ÆÄá‹7ÚÈy.ÁcŠÁ %ËúM7M£ú< Hg _Ów{<5%¡¯;™^Wä«êëôu„¢*ÜÇÏ¡gz3Ìù(£)Ú&2Å_ð¸¯qÌn ¯××=è×}È�Ç1¢©«ns¯È=ë ^—|���¯ñÉ}÷Ÿÿ£&kÙûrÑ9wiúñzÇÏ?#éñ†Ê‡ƒøþR ÑÒFHS&&0Ag˜òa5%÷ô"3�|Bo“BÌéSþ,FvNãú�Ü__ì¬ÑÖY:¤/VžîVîom‘¸Iƒ­[Óìù¯Wó,WZ ²®ŠGÅfªµë’-¦¯~xù»AèhèÔéà¿­’’ÚAŠUþtjÔxšå ë © û®'¨Oûð‚“п‹á‡†ˆùD„ðlàVàFõ¹OÙ�(§�¹¬’’vFŠËÖ�TBé÷´³CY¿†c= ùÒylÔ�¿wA|ÎkjjÂÌ£ÚkU¹!­ùAGfEHRz>4è8¼oó~ÚÓ <3¿‹W­ÿ—PÐqeöãi¶”¸âO…ÓaªMdg¬\ õõÛl]I¡ØY�a-‚¦KyK¶˜ê34bÜufI‰â4\¡æ‹•rµ_ï‡ÚÒ… —(@D~U€YÏ·?‚ŠÕ G¸6Áý«A&»€ï• '…'›l©ûí@9pcËZ’ú3 �N[_ðËGê<~�†žÂ¥#ÅÜ\&–ãmÿ[:ó¹—aw[U€8<¨¨„U&ËN¡ê½MKOÖf\DöÁæaû\}ùßsGr§­L÷n;‹ÏhªÏ8Ô»PÞ´í¤ÈÏuÑyÝÞµµ.±ûWüG“Û2S˱*Nñß“ëF&ÿ•Ö “ä�uŸ×'©Pû ù/è HlLà óü}5ÿ“¿… _afamW [Kå6 •þKåòGŠÈ1ž²~ð Ä¯QÒG˳–YAå™ ñèÞI¢.Ç™Ü�bô¶çùYR� ÇzøŽëÙËáóFD”;•ÔB5j5±ÏM+°_BóÕË}V'‡}�lçEcå\~5ï·¯¾eã ?ØÝ¤•ÉÉÃZ\GÎÐG˜Åê‚€z Ý/dç^ÞËbç{Œ� ù31ìD'á%’|žY^‹ÿÆ¡Á�z> qØæQ nõ#ó�†�éy*‰éÇ�a—Ø9;”vuݤ‡Ä¥j±LbàSa†—ݼ†þ¢ºëîøuˆíf4DÇÍ©¥/È9î=Â^ ¯ÞW¡pN:&žà~lˆ,yû\uÊûwç0õÄ#«Dãóð�sÜZ‚õŽ­8È;à¢Ø×nü5‡յuèdÐ^{Åå�‘Gø©Cáý„x"ÊqÙ¯Èç~ù‘|I@"Éš&šÅÌUÕõƒ¿²n°«02XÌ�”ÈšK¼Ÿ>M=ÚØ Åá<}ÜÅýÞBÐŒèk˜o�Væ6¬gi�sYÓÛ¥á3ɾáÁÖ… �f*#om]]’ßAÎF‹~ùwr¡ƒÒôÑûçëç‰AJõÕœ#K�_øýáÓ°�†¿6s Ð{ºl±V„e½QÏ�J,A¡€¤ÃÏŽÚÂPa%ü@F¸ÒÌÑ –ëþB)˜±ÔæQ”ÿµ®úÉÝàaïjèêáAvŠ„ÅV¥~?2%L·¸É3_,{=ÝåI„Ƀqp¬ig�¦íR3)F÷;Kµ#ŸŸSQ9 ¿éÅz÷.E×ç@u‘ÑZ¾wàñç¹êªX8ÜC%óïvápxJ «•‚v ¯�fJŽñj“˜é4îê÷(žï_®…¡¾xe³)yMÌá]»€ Óï‰mÌñçó»ÍqTt¸þ~Ù‘ÙZZkÓ}Lü@䆌œBÅ{ù4«~Ñt*CþËÏuÿÙóT2�Ëés‡‘§4"B!–�Óöˆé-ÏÑp³‹Gâ\bA]›—Ðì|ò¤ï¿­ˆ“özÌß‹Ûûýß\�¿¶¾(ëªì†ôýç¦3µ5F`æVÆl;Û’]Ä´¾Ñ›I$¶,’�Ì%UøÏÕã=7Ū`ŽJlþ’èù�2N¿¦0¿zÊç|¡ìžwܾ*'qš¡”°8y Ý|ÅJÆEžú°2öaÿA¡’ Ì{fv²Fàa›@ÐθâËJeA‡uù±  y·ØbáêíóH»§””Ö¢A] âÖ4 µ¶÷˜÷�ÿ°ŠngóÞ/� &à>“Jõí,/è­ªŠÞʳeVë’ô„–Ȳ ¯Wñ�óåmöòK@ êkËÍ1Œ�‹óå²�g­´_Ûeo7!Á]YL÷øT÷YÚù¢œáÇáIßOÓ$ê£7ø×¡�XE¿¥§Ò¡'¢âx’ã‰ÇcËf6�æûù¼ë…a?]³q­}�üU½¾�”ëú^üÙÙ/Z´ÿûr¬¢žV´˜ºõÓ»ÝÊ6½½2ÊFijתHVõ mî^#üöŒŒàl):¶UpvtL¡ˆ}‘[N¡£/hKQk+¬c¬"Ž'’R)©KsWþ@�ÐÜî,(–/ŽLRJåiËrš´AYϧ_ÇŒ¤Äׄ—y?Ÿü?2°ލ0ã8µ jÜ;Ún¢ÛxºCtå°¨KJƒ©®ÝY¯&‡ài PZWߨØF“ðÙRµ‡a!Ei_J¹¿†'ͪ[ ÕôÂ|ŒXåÔ|6w =?sÛõÖ½… Es>€ôÞpl~k½æ“çÓæz…5�t@"FÄüü<ãj,Á7ÇMú²ùÔe¾�˜Âw£&1ÿ¨'ïDèÄŦµ7Qè rìcÝ·1d=Ùûšßi*¬ÌéT*k9 A6ùÖɹRüGaI úUV<Äv¤yÅ•†±µ!Û›Ò×§áŸïÌV�4tÁüSòÑøûm‰S^Íw›‡,ÔǦƒ ¬dÔoÔ/£ÈIÌ‹ìËÃW:<«”ñ½«Rêã“àòºE©ÅÃûåÿ9H7_af¬½×ÐMs±à£#b�‡c*²¢P°‰MCËþHËùçÖ6ƒÂ¬û�ÄÍøƒb6âøNï_ˆŽ`=S7²�ôõ¨�mÕsáðƒ¹¹ì bVV4cII=‡¨jÑ6oݶŽÑõ1¤·xç {¾¥'AܳÚro(}s¨¼öôw©}ý²Eù0DËöjõ©PXði!Ç"Ï—ã±÷o ÏE#|w=a’a[X/m•‘$C«©y [=ýû»: #¦Û¨ËS°EØ=n@•?б¡)u =‘¢[E´9sâõ�´”w—UœfÉìÕØÑ1"´ÚEÛè—·5šúºy7ˆC VŽ·ŒiGõt–ïw6Š4 Šò-°˜¢s 8<mdZbpZÔÿšƒ~^º™èbÓ궺õåIÆ P2ÿ÷GGKsóèõQ+Doœe7'Àk%¯¯�–1ûÇ­ƒcV}4qÖ”x¼B¬1J1 ”{œMæ†Ê ¨T~¾9Ô¬ ÛAíš …·÷ÃIq|…HÝ­?‡Zç=µïIµœöœŠ'ÞÍèˆe+�qÍdq%Ú\i©Ð˜ËC?iÖ׊£½<Ò[œ� Q KE®^"’èä2{p¨Ç­|CÚnèn„óÆWãKˆ#Ÿ•Dý`èê}‡G[Ø—»�±{÷À.ù§|¦« ³b%}}ñÅÖªÉë�®¾¥Zy„®x9„-Q¥©ÃR�ä‡,2”lïO,�œM_=²ÜЩ˜kº5$ýSÌϨù“d£;üýgžÿ�¿¡^�§=à¨a=²!2<ô_rÑ]é•’˜4Øh³¦ÞÇ ~© ñã9,AïÝǽښgÐ]½™MèÅy®7v䈿¼9\¨"¾´Úüd°Xãb˜þe A}@þA>s>( ¡^�ûëÄ/ÓCîR÷³Ò]WâÂÉkjP?´ŠF´É†se‹ˆƒTÒ^—d¨DÞœoL´¬.·d ¼Ÿñjƒn­?ûýÈs. BÌï^jH£½«ÈuNÌ!ÁÑ\º¹Ùó+¼}“b•K-e‚)nʯè~ày{À²°éÑMßïøHp’Œ3¾Å $�á®\«êî«ZªÜ½>ÙÑ€ tà«tðãÊ9mx¿€º ›à z1zù…Ž+’¿3çǘÿÞ€Åó“�H�‰O�¡Déá‡eä\øtÜüÉà¶PfÚ·Ä­ë2ß)CºçÇ2pRØú±ùbŠ-M§|èm‡ §1XÔ8/Ü5ÀµÔ¤B¨­´&ÏŸ6Q*á}Û·ºlË¡çS[ËO!OLíšØ3Bÿ‰¬ü×ÎÏTn¢æLÚ|Ñ.L‡Òkéz¶Û¿)þàµØÝ�U^¹·¾8Ùqݵo´¿–kÑI|(�Jô=/–Õ%—ý­þl§5˜·<,�ÈU܃àGÊ9Ó¬e”d[?õéL›CµhÍüÙÂF)Vñïd—6êiªHk½�¤Ë¾óÀO.ÃoT/£CÇÌ}Lõ|¸S3R@ Æª!"NÝ}›ÆñFØ\~…0Yü÷z©ê‹Ïn‰«_S¤³>íZ„^,,wÅ+0Ëô°¾éuöX/gqì9mhʤSt¼·Áý›7ƒÏn•I‡Ã[ ™i¿l¤£ýZg4Ö6uâÎN¯1“fDZKõx·Æê’aÁe¯�ê’–.Žf�¼y}’�5Ô4loÂ}ž¶+/umüczüUÝü&ƒ]üérň4X_(V÷× Å»\¹NÓüé· 0ìxîR©ÚF²çY•–ª” o“ÊZF„Sú+ j”‰Õ›Sr�¿"Ù¬\Ãc�{Ç$–Ÿ°ð…çÆ‚VòŒö޵oêî�€çnf „£©ÜÚ+ÑÞë[_¦.Œ‰ï4<>ø|bv§ržÏ²ÅZɶ™q¿Æ‰‘eŠ‘*Ò÷²ÿYÏ}¿~Øà=%™|pK}­Rù�U;KÀ$^)¡~7¢`­XÒÏ•f-¼‹›·œÑ¸ê¢©‘>›bkójëöˆXEÊ<Ÿ*ñ ¦®4{G Hki,‘þFwq÷Ó¹»Ón(!MoÁ'Å_àk»Åå°±ÕìY öŒr�ÁîíÉ­½dµ¸[™D¿Íó.‹û¾qÊoBÌQ_ɳu4–Ø—xæÈ¼ß÷š9Ë¿(¾·�/ Ò!  ˆt®H7ˆäÒ + ÝÍÒÍW‘–.—XJš¥é’Z”î†]ºá^Å}žÍ̃3såûsÎy�j·åmu=R4QLK21ËͰ³~R ÿçûÀ�ßy/nÞ_ζÆlËŽ]ë›@œMÞ,*ö]½7QrŒxo;„:^µ²´NšYeè±l–Ý+õI þ[1Ž#*p³ºÓCsEȽv-Â_Fýº®ñļÙmµ(à1éžp¾]‚õ­²¦ÿE0neÌÂïß/)åºßÞÎo�øt ;È“³’wõvŸ¦ðf‹Î¿1iÍôµ‰$3=ì;ï£YÉg›Ä[Y·ðͤû¾õA�ïãŸÈ:•-j b}�=Ùí  9îY¾Y�åïü%vTš:Ëu¢ÌZÙvï0a‰Âéâ  ¿úâÃDGîß‹˜-'Ò�ÃÑöÊÛéðÙ£+ù²+j†¥9pÎXU>Ñ´Óê_+I~Û2ßSÔŠ°§5课ß}¼9ËK¶áÇX°èóÝ¥Tø˜‹o�Î3urP„æ7éç§xØt��¼5*õèlÄz » e]ºŠ&”´þ·;k—ù.¡Ã묰!L Ýí<ÊÀŽÃŒ¢’FÙÏ£e ÑœI÷·dR"›VÊç ¤?Wä4^ ‚Uxå~HªÉq“WœïС Íôžê¡°˜�zä,m×:c3CoSA132m˜4ø½ö1à6�”‘¶CŸÿÀj¦ijä>áëþ×Ï%›_­Çýi&Wõ6À¦óÙ"Úð ÏïŠ ’¾Jë²mÐp@�:8ÁRÿØaÎ×qn„õ¢S˜i>Ç �HÇ[uÐLM¦½¡eCø˜“ð,ý3ñWë‰]ã75�V­—PH£¾9C“Ë0qÚgÕáÌbeë>hŽ)¸RwÓ϶Mì�˜’{*ÙÓßáð5m’Ê„»ÀÒ[QÁOa§ík«&”ö Ô:³µZÛ,}¬7°¼óh­€9üÙÛ#üTH[+¬R¶\§Z·¸–m¦553Ô®ù5HÌmñ8(ï'q´çÅ“!§SðYTš•œ´:<Ó8—Râ‚yYlûy´ÝN?è0æ*¥n啤Î>KçÎ ´�¹›2)Nÿ¨oƒhžâñ…mP+‹^­G”TwÞÇŽÞD²î­±S»Â_��$“^¬9„’i7¦ ç€úýoüƒ¯¥ýìÛ†M Û­XߜԴRÓ Yz^3©ò¼a‰µüÛÇ`†è-«*SŸ“«œ•™p£JxǨÕ‰IßÃT²1R$-9·x©&eRÀÉç¹Y)dmxÑðâöÝÅ5›gòë2ÆýVk‹XùPjwÈm{B2oKz­î,Ý€ü-¦Íû"�$¥ñd�\òß±¯ Þé½…Ù6s˜’�çì窂™iÏ«òÒr~!Ǻ޾¸UÒHñÖ™£–ì\,¨—¾ÞÒ›Ê6†ýq<ͱ«Eá¢Å1nîXŠÆ± yÖö»#O¶VéEþÅUnb†SÕâA}…Ük)þÜ ûòjFµ3V6ǃƒôîÕá—K¢yï~¥æ’í'ó>Ã^PuhI•©HÀjQõÃf¹íkiæ­9_Ž”È;’ýVÏ Sc-*icÈ”ãäÄFˆÿÐ8êÊHúí¬s_q‹º·‹ùà¾RˆÏÙÝ&#øÈÝ0� zh0‡ F�Tµf´rî“Ƚ7Ò[jK�›Þ»‚«f‡n_Š„N:ÿ€öJ‘‡:=½íŽ$SâDi¾2¬Ç” 4¹+©1 $íw+E¬$¦)eO˜šÿ¹_ýyô=øZ@+ªR6y²”–öiT³ÞµƒRȈ+ôòG7¯¶VKÁþl_UÐzHò”ýT³Âª*(F±ÄÙP¶8i×D„ú?5È:kšd¯qõˆÁZ\¢Š~þüù+¬šAä+ƒȤ¯wó®›”wÁ{I‚qÔÁèÛtvCª¯ ¿ÿÚÕðcHø9ý¬wÎõâ¥9yeø šµÒ�\³<¢â4®=˜`¹Y¢è¯IŠ}÷´$É}”%C…UG m8CMQÙ©ÿS›Â'濵¿Þ²jS)¥ø™óãÈ?0±ƒ³¸|›Ã¸ˆ\-ø›ÏÑ›¦¸¬gvè¦)¼—sº0æÛá¹FXÒa]=g£½t+‚±À{uxìèèøƒöÎB©f\,¼…J°¼Q7¸[½b«TqôiÌœëvþƒh#fáKô3·Ï{€ R3���Ï3t–‡ÈêE9æzë�å`– lÀË‚yBñ&à`v+*Jøá ~ª9¤Ï'§"tKWn¹Øç8;¼w1¯âoçj‡y…Úî5\þ±ä#¢­Û{à×¾¾¥á3sûÑ1wgè9oètP@§“2™ˆO¹dxRÁƒBö`kÎùdÖÒw×Pò}·7.5< äˆ]ó¦•ʼž |ÝÀÛ0�R¿Èh§Ùß›µ°>TiµóÚ¿¨<– Ø‘)núðg¢­ÙÍ…f‹�þâ.Þ2ëf?š-ƒ±~%›`ì$U�‹ô¼�ûŒÙÎ7Á, xªC&-›~Oö±PËü]ëüfƒ•ƒ©E –AŽaÀìúÆ·Î9z-B�ÜS9Ó3º“L›Šë®‚.¢ q%‡>› ¶Í{K¨Ž´- /‡�IO�X\b½b:=fMgó5 X噊1*þ›Ú¾n³Íl»µEü¡j�Xž_-ä×xåUÎSR%›w–x7$_s½H98Â�>XœßôÛÜß]UÆ™Ñt¶!;(}ù"šó —[öÓ}í2mD�V¡ìŒ£ãï+Uù»êJÈ3ÕuÐæÌâ‰ä _Éeï/ÚT%—ÓчÄìÔêNmoܧG”9]3Ê©“O¤ò >´èç-»�ÒøÂuõˆñ0Ã>Ô\}¼îÑQ)MœK2–š«AÜœé÷B öùFP£¡]„Ã�ÍüŽ{�ÇñÛV 7ïý}Ëa鯻‰ù>ç]™eŒï©‚~Ý ç Ô£bj™÷Çé¤+—GÉ•ÕHûe ­!î§Ð¶1 dÊE.] ¥™¥–TZ¡Ë¤žð±ÕÔˆºN’M O®øI;³kïx¯R¶öÒÖ¸`uçN‡úý[Ê¥|.@%b9N. HÿN>¶P `Êg@+S*xEüó5%£º“,…s˜iKo¤±UÙ˜"($¸ƒ$½?¸N„N7œ ¦¶nWgtžV45 0Ûæë¼À` UWÏú‘ïV©8Îõ×ïró¨|?TànrÝÁ¸Rsñ¸?)Ý7ÉÚKï ¯àLpÄ×Ïœu”P¥bûÂS/Ûå]d¶”Ë@´CMðáÜœ$¾í(Tøœ+´URYŽˆJ½4 ^ ÔTMx¼H¬/}KH!'Ç—Î�ÝÙÞÆøÖÞ^ Xcœ‘¿QÛ êñ¯¨0³W•À X–ºY­.³j^=kVÓö Q_±»šgzK ¦LÑ®�6®©´¦ƒ^tkÌ1"C+i–n&±^7Þº\¼˜3ù¢E›Ê×|r²’ì˜|êáéA˜#Ç5Ák)߃Ë3ÿøØ®³Îy=pª$½ÌÀðãIPvÖ”UÞ§Ê#/ Ãp¢ÞPV¢3M Ÿ™pù€Ž™¯ù)ÐlÒÒ[jµ<ª€ðBÜ8%��ÄkŸŒ³¢„ÏR[g¾¤Í˜þ¶Pzrou÷Â?|‹>ꆻ"ï{ÚÇ‹´Òû×ÝŸE¯>©Â?ò;Šc�~—‘y]íÁà£äÛ¼’Å—Õ…¶Tû>/•\�­ë•œ®u‹u›Í�mƉOM¦§(œ2’¼Ô§¾ßÝ?oà)å¶]Ô9Ú¿ÖÇ ÁäàU�`\ŽJÎ/ä�5›U% Ö%ÝëÞa.b¸{—Z÷§©Õ[¸p•[£+òM¢Ô1âSÉÒݰpÆŠëB³NÄk/÷�¶· ºt÷ WCÒ¼,)cÐæŽ«Ï}¾#ö¥RÈõ" ê¿Ìâ¿aÀØÂ�c?Ì4šþ+^N rµÆx®¡H�WlŠj½à&æG›ÏäþK�D­y°“´�›[~ ™%à·È©™Úƒw3ñO{à?_j›;ì ‡~×ÊKóÀ�ÍÔókò™‘p’O0ä¡þjm„ýRì{ñE8Ý+Øk|z…4 ÛÏ aÕè®$AÙé³³)ß¹t½8VðæS´X,Ïý…hdÅJªgPY³ˆ\è¯W÷œmÂóãAÌz/εnk;foü.Ùß²#ôXoITÛIdÜî÷Î/lçÊ8cRšôNbI‘bÐlØøv± DÕ ‚—áÚqŸÜRwz=¦ÕwCLþ{PŸOiÛk™(&ÈZ?ÖÅÄÇÂwÈÜÂèÊ·ÿlyVd¬éXÏ0ôŸ°6~p 9&pEÿqÜž¿ýõ{°À鸀“ø«œj±¹D,¨ëèï¼­K­¤ðM<åx.-W�é`œ øðFÛ<É(Ó~”g´‚£á÷¹ºlÝÿY²²umó_W9®ª¦”KÙ w\wÀ’DCÊ�sƒ›ª»¿ ywǾn!~¬?°Qª‚›Ù©âßq4ªš¬TIú®`RÔºëe.K¼²³”ïàëx<È»zÜØOH¡c.³|ØÑ¼#ØÔ©(£(Ô£zȧm2hzŒvÎΠ¨¿ã8k³ñ/;�¾€?¥RgP½¤pʰá¡$8Tz=.d2¶»�ééO’¢=?brÚÈyK3ÕS¢Ñæ³vZÒá sÍ­X€ŠŒ ÒÐAŒÜë¿}#°•)IÎÍó'æ<åFÑ1{ŒµÐæZ.:ö˼¶?÷®‹4øL6‰ð™bIõ‰ £":± ’<ëAÉœ”˹•†êNÔ<0ðÅTµˆmØ#€=t²¨ÐU¹ß‚j0ë�׿~¬­²ƒ=‰$+ÄÕùÈúDœšf:î!>¸ôšâg‡.Í·K{`wšo=¼/]ýíë÷˜_ ÇÈ•‘—�øÎûË=û»}æG#,Tl¯!-OSEtʵ%)+™{>s³… LYöõs|´‘¥ô…zs*šu³òQ÷¾Q£ÊSÿm^Nʉ?wM–âË#î³;2!�çøè ü­t[>Ómi8ª±ñ­ì¬·0Ó¯D¸’Ñ~5�sg•ª¾=œ'íÛ[šú³•ÑY¾VBgϼà#UE¶ÉazæåQùsÙ€«¥šNt›h8QKbÜôøkóâ×&+ܰêí½¦ü®ûb¢©¯Š»žºÐx}Ücõ–FCGetmÙ¨rÙxù«Qöß?L¾ØB‡Gh®sêU Ù³ÈåLéûájS^tÑ«¯"ñc Q‘d¯„‡žÇ¿†ð,Jx ´…Ó”™:¬4Ø‚o§GB¤¥Õ�ƒq`-ªÁš§'ûVµíý|S©sÙÆI®cã>þÇJm‰âÅ Jï”jÿóLäü'mS_-Ü^kóâsV…Èç’5 :ÿ<pº].aÓ¾ÏÉÉ鄬€H²”ç?(eà1ó‚êRyã§ôý½1ºR<#<õLãdžZu˜BÏi£ òœ¤¯{—„¦¦t#W:m�ÁêS*x~w¬… ¹ÚCƒZp±kM�Ê]aEî²á-Ý‘ŸÅ}êø¹ä[’„\¸ÿQÀ¶FO}¿ËL‹··À†ÐIñ—@%…ÔtÉIËy.É<’šÎPŠ…€ÜmÈ” D…S @ÖãÚœ*µ YŠc�Q}é†YEbzï~+ph«Õáÿ˜_ÓáFƒ ¥^LÈ1¿{xµR�à ‰£à†.Áe4�}Ññ†š’¢�_ß³ž(ulT¢Ë)Ú<¼y.FÞÊmõ,ü‰+<ˆ<ý¡€ëîäÛ‡¯Ê#Ìæ¼žŸ;ƒãÎSß±&-| ´€Ü�vÓŠy/:ä§Ý~]/M=zµô|%n(ÝÑa&d¶p¡kºŽô/4N$4¿Ãdÿcþ a°´ä‡Œs•¸í i³=“„RÈ é¯¦C7L_ܾ™ðÓHô[}ø¸²ÑµÆrÔ©·uÙ}}Wêëw<\Ës"ÁÈób.?d‚ZËKÏI6öð-Y¨�Tc+¢X]ýRpQ�¥Ò±z[ݬdu]*á  qæüÑpñùß§û5É"ýJ¥ö™lýîl€dm Í’„¶,ß"� ,ŒŸ‡óìZ°ƒÖLjG, NW2_Ôó;Hƒƒºš“JÅ s‹$�Ðh­¶é^‹Wù:Rü çzìÁדÌÌV º’ý,‰MSÈð{H†4£*à’þ=.Ó¯`-b@ñ �"gd~Øê2y]òX OUŒòçýÉ�ö)@•޶Ôûû‹é² uözÁ4¥=�[ÎÉæw/›ƒov&:ÅýuŸé°¡ÁÆóÉoØ8ACéœ ï?­’rþ? Ã@ô�ó€|pg°YB¯e& æð&C—Uy°“=´ILE;Àìw~5F6h#–éËyXÇÊÄ^$t64èM ªcp�ç}zþA{Hµ6®nt}¸Ï@ûR¦ì¾™Q1p3/4ol© ±=ÊxÖ¾èp_vÇ"ÊYÝØd€¶ñ\¡3@ø(%ñµ ‰ZÁ •㜹dœ…Õ³©pVÄP^5$*òòò†--™ãè< ÓŸºéZGû§y“¢=8x“Y,j´¬È¹©6qöÙ™Á¤ Q¦N½©¤ž¿õ¥Áú^ø6Yéáá}€ËkᣔÄ�ñ("®»·ñÓÂ/qeÐ7÷àn!+äÚ†æ[ º&<~ øÆËžÿ<šÉ1“�0ü¥cf±ßÔù³7}©îT2òV=S‚гn%ÂmQ`ïWÍó¸¹â¸~…v]§µô),Ê^­‹¯ž`I˜Ç�W–Ëû˜7ò׉ա¥n‰—3Ú0}ÇyŒÈ¶“ÉÔªWûû€”Ê+™0‰ÄY¢n]ÕF„ƒB23Ï)­ÖÔð3Q|Â/¶oÑÕó>–@®é- ŒŒU`c¤X¢ù?Ð4|8æ_·®RíÁœ<ø�CZý¢œäΣRr{#÷0�è%m\¯\-O ¹úTwK'T�TlŸf“¥=”e>•ü'ÍP´ÆÎ’s³ì1J÷¶¼¦Ü›&mÌÐØ°³×½Y(Ð1ð±îv™^BÌ®rN_”ÑV0¨Ãþ:§ºÿ.…C]a†Ê«zØÙËB{ÜßÕµºî\wHy)“û5PË“F„ÂÎÆ’àˆõ÷˜0U÷7ó�¥é4q³’�ÁtrZø/_ÜŽ•-æµ§={jf‹N~ïý=› ýí·9#yŸíTÂÎ3RÒWï%0Ôú“$~OVpdÝÂ_Ò2ÈÝ߀`c?íx2}åØåNhžªž ŠèÇk-‡Áì¸R¹ °ò‘ž+�ÉùýÆ>¯,÷º“ÿ+ä°PNêŽüzR�|k6¨>øo™6uZqQøcÂØ—Ù"ôÖ1áK¼ÆÁãÖCOÐímÖk(r«¡x\°²è<•/±#lv‘û²QÐõgtñE¢43TwÅNáé¹�»Yúz´ûuبaËÁÉ®U�2*°‘Ñ ŽJ”)û{#ÎäŒT#9³é’=.j]ÞЮ§$œ3 35†!'þõ--™lɃ»Ñá;·gAG�6óR'JE_9b-¦{`°wD=øªIT¯"±Ÿ*’÷üXð7øÁµªèç…+õèddx,¦Û`�Æ™&n=‚êþ×KòãôbßèÆâ±~0©œOî½|t¾T^<0\š·cSŠAu™%´Œ¥ækaÕ•®Ã¡o_=bèè„-§ô#Û-Pô Ö¯Ü&¯ÙÁW“„�ÜVÿ>áò¤™ùÙÇ7Ϥ¨ž¸KŒ{Ö'§ür걊ÔÓé­@ñ LW­ü|Ï»÷¬…—:ÜG�ûjÑiØ�ÀrJT•¡4u\ZÙü§ý|B�·pßß0é;@YOoâ5‘4Ó/ý3Nb–ñ£J[1#iê/«5F B 5où²Û‡ 2"ð„v S -“ nRêË“÷³%«â=ÓR|‹)$£á©žh'B³>eUwG_ÐßvöW¨÷o~Ö3D¡CªÃï ƒ­y™mûån߉>¾/欓¨„Fõ}`îÎ}�Ïyç×ň�½�·}ư'infæŒ'�’Pð†¼DŽVLò¿Ÿ/ŽŸ*¤mþàˆ¥ÞWÍ,Æ)þ!…‘gžœ›ežnnëO¨!‰ÀÍåU7ùa¿ÅÐ~íçc*yu÷sÝLú@ ä Ãø�Ïä±ÞO·M d\ú[r®(¨SmÛ§CX) “è^quQ2Ä«y]ú„œ_%gü|%²V Üp¾¥%²”UTlÜöþãj5;òªbk©,D#§I>& Ñí{lÖ Q›¤[Aaa7:Øl2’»€7C–8�­ôûMõõ‘wÁ!hó˺ÃíÏ#4@6�J݈c¥5ÆD“ Z|G¤¹Î‚„1ÿióLÀ"MKÌEˆóãÃtjÊætÑÍ^`à¹m¹¯)¾©ab*\y4œÊ°¢'w‡�§Ôøè‘-FÒh7þ³SÄ�lóiÐçs#Ì«æTrå$‘ÜmѺY5�Tze•Á·dù²N?ï‹|Þ©"Rshé}³±9†|ñ‚É+À¿™©#¤+öýÑ/«¥'tpz]lJæ_J»yïñì5kþËMâf,Cp'Ã?ÁV‘3¼]/ <ÕÝ&éŒÚAÝAV<íLíû™>•sÆ-»OÛ Íšž“6I}~öðŒLV5d€[u×{)uÚ¾L'%ž­p�D}ÅhÙ† ôúÖÑ~SQ�‹ˆˆ ’Dk=È‘�KW™7{3¥*ó>µ°1Ðïþñp¬áѺ-¤-ä»Õ¯]j]—,d;÷HqHÜÚÉ`ª¾VÛ\QË L…ë÷â~×o¯×ßÉyNÌ:t{®Á±¨ÿáè›P—•2Ä~†½¼ü’Æ"êJŠÒ½áªi°‡¥çƃ%<¯F¶¿<ÌLpÐ6�¾]Åþ€ó�-™©@ó?”Ä8Ál$ÜÎå(ô-™™Þ¡²lÍU¦¨ÐpÀØY6>\ÍvÅœÑ˃´Bz³jynîù´·$ÑË ˜Ã/äe ¶,>¹‹ž/â ˜ªµ•Ÿx?©GŒD_HIè>’ù4)@«)0Ùeô?®ÿ)ïË»A”­;qqÔ_nÜ8ŸÃ+áJU´O‹V¦×É% µÛ±eÍòIø–pzëËå ò‚VU��sŽÌ4$‰»°QÅ ßa°Ø� ¿!fZÒ¥÷‚¬i$CKMîÏb?IsÐ̬WòP²ÿμS+…4û,êu¼L² òøð¼Td òùw@^^þýìùk&–ƒ” 9…˜Ò~4©OœõùöÍnmmQ‡ã¹Ö«çãXü(…ýpÈb¦¿q½`¿ ²Z[ý²*»_ü/\ô´ÉÆå¶gL7»Æ :­³[ý«*ÍÌPÏàÛßm’ON…ÛÓ»: ƒ ¤4º7¦S_16Sʈ‚B’ÔØYò¡‚‹y—¤lÏ)‡•6µ¤¨¿ü}cøŠ©ý[xž¦õoð©‚ËQ°-8«CEèkuo'øœÓð�O‚;¥<à<ˆKEÉ#Ë€„�­Íçý>Ÿ�=É.J KO¸QïÏÈuoaL(LA3�„û»?Që!žw¥¹3ÖûUàSMu¡{ŽÅEk/¡! Í¡QÓ ÆxÉXÜ ´’3ûâé–¯ÓK7g�Èö…qüÕàvÌj-tc%—ûOªbÛ”oöÊ®w§�À9¼>Ú¸„ §œ«ÜáÌaQÜ-pEÛÚšÕÛ wÇì[§©cuÊ9Ô£kYÊ�&e£åÉcÏâ~* qJÇ:ºa�Ù±DkQkM› Ü3”ýôf¹-Øme@&Ï{–&0�ʈö6Ȫv¡Õ�¾G@tÝ_Ò^.7/QãÄo÷»æJÜf¤#¡®üœÀ¬T@¬rèû$t¥aãjŽêáÇ ÷¯Y¢&À�Q—­¨ÏÐøböœŽkŒ(¥ÜÃóÛR|“î({Cc#®¢¾c&õ["fžXK2­„eAu tZï.h ÿDè8f]¼>vPoÞÜàçÚ]"BIˆ 9Ú‹–½õn¸§;ŒÙˆ”°øäÏEm€Âo®Y:jŒ“?yBƒ”ÔýzÒiìÄéêÚvÙ›÷pb”¾O(F(Cž-aE’vÙºI•FGää`ŒÝeßfPOî%$¯rµ=ýZ–"Àoërösê¡a‹f�vÍk[4¢8ÓÿáHbÃò­¯¸ í­¶7–Ñ¢ªiöQ)òËàPœ±k>~§.œ¦þ¨À�Ìwº(ÑŠn1#ŸFj„w>³öyÄt\Þ™Þ5öÞ´u9Æç9˜z³zY»k+?a’ÅÛ¡¨qßzÇSa*²;T®ÆÂgÖz†�·tKñ�ج¡½ IãTUˆûi÷¢G>>>žðôIõ4KïuŠôZõí¯ЇbAÏ!å—©Êñ�'ÛÕwŸÞÀˆZ�¼@ µ Ž ÿ(97ÇÐaȳ•nýÄù4Nß -$Öv»Ð9Ó>LÞâÖ¦Ó™ÿªeQü·Æ¤'ß6!¯ÝçîÉ®6*¨õmiêû· OɃ9�TóJƒ3eàƒGU›0¥08dyéÆò2¡•ò j•ÏʧÀ¿ûÍQõTñ TæEf #C0ŒK÷i åeé‰Áß m³Ðëf$èò^ðœSî½�››frGøöK€[¿%?) šg8˜ŠÞ €ÙÛu‰…2®ß}fÛ,ÍXúfžo=ÊmZâ×-ïõéÏt=!ŒMõ]À~sxØôeé9�q]$YåñzüiÀ^â³½ÃpÜ2ûÕ�F—C*p 7R:?œ÷ÉŸöh[“�@$ÕWÿQ²‚CEaöü ÏÏî_7¿Ò³zF»×�ÐSøØ---c/îâ"–Ñ*Ê@ ø˜> u§·ä§hÑæ*…í½+ðÞn(>à ÍÓÞ[+çˆÅ Õ4µÂU)³x›¹¶=qØò}Ö¤ìA¥Z4[4ëøën8¾�*ÑÖ`^ýÈÿ¸U¾0Cm”Nˆ“QÈ�D«g;*:ç�΂iM¥~�¸TÈEÞ‰HÕk§Ð˜ÕŸ@Ì4Î'…µµIdr"AšãO̙ğ3˜¥LyØ4¨éêÍ gå½è°Î,ÎZ!…N;ÒF/ ކZÐZÐRô»ËêéEÛG°±ÂB±eX"¯ôôÎ)äL#É´½æ'Þнñ¤,§dj/æÝ#hÆüó»�­¯8¿,n±oXU3@oóZÈ"vèûŸ…¿,wœÓ¦Ón 8Ë}–it¶Í݈ç«ä[4&£Ì–éQ†œœíxü‹ �slÎÐî HØ)víÆ†Ûdè`à…înZÖ˜»a}mÝzonè6òv<˜0�ÃG8�OQn—(Í;¯6¥YoœP—Ìb}¿;ÇW8‹²JSe°ð¿eØ0ª/T)Ú°¬ÑbZ0årET½ û¾‰ön†Äª,þÏÑ‚¬tÒÀõ.é$÷Bšj~XI÷z:Cú…éߨ87 §«¨ÁÒ§îv1éaSÈ3)5ƒÐÖ§8•û¡•åÕÕ drÝh�2™§öHî.P.F ¡¶$»O`ý:(¤yÛÔêÒ¯¨%H?5lS—Í+§g§ÂZ^’Ràº&­9® {ÆÃSW¥Jh3o„¢:¤Vóíb�~Á·Ç‚öö{çÂ@zz sùgxŒGÞ‹iÜž´CxðæVÈIá7e½Ä•hùêbiyù–=CíÆé¶ÕƒÐÒ¶ä0e2lŒnïÕ'ËãP±—£)6rª>’]ëÇÍ{éÓãñËŸ—µ“aZôÃ瘨–E—̼çõB±ýÜL�J™}‹z¦YVÎ 0Ír¥>LËM±Žd,>M“�á“”eõÐ~Ìê†Ûå1#CH-r¦£ ’+ìm& ØYúAÓ#óÈáÒÇœ–"¼¯vs g7ƒŒ{å^o w>žøê¥¢sLj�=ÔCQÖ6à7C¿³�mäþÆ:�^­?¶³™2KñoèúqSáx^gû,'¼8„4”´ú)¾d ‰Á\��HÁyà !–»™^OE0,@´ûÏæ›Ía‘nÙ½sä¦ëÃÎ ã½ô¶­“½ý1&Š%�5=øìQšŠ [(ìy`Ûáúà«Ås›åÃŽ�ÅË‘Ž¥±„�ŸsD<jYØÙ0Éá¯Ì´q½hì’ßÅÙݤtdxùüÓƬs~lÓ0NeÀe©%Cb&xoò7¼ Ë4vø[ø ®ü]UZùæ;¡ŠFãñHªF¤Bù¿�†(=ÂÕõ x·™t¢úY6d„&!ðð½Á±€1A×Íg>§ÞÀ@ìq “£~ÉhHêéK7Ì7*[>³Ðªåû¿ŒÞ F«h‰Š*d®¯÷Y>|Šú•–F­<ëÍßÀuNn�q2]›i½€.(T®xp�ËíÑÚ@9ÑG¼ü¬–öøÿÈ,«a4û5ù[úcQ1§ª÷`s¾­HxÞÝè¡ÿ×@äÖU¶~=’¾#6$ox1›lz¢¨ )ß‘±\™ùwØwÓQº5ͦb¼‹¢+@sì|îa-ÍŒ¿ým»–I…d;Ô;ÎVqôI«ÀJ¤²º]tô<|¾9þ©> ' Œ*š4øÔ"ó"<÷¥ˆ›ÏŠŽŠ;wPùúZM\gÀYõsolgø¸0|,ðËA—võ¯YìÁÄο·­àŸß…Á¢Ì”¸¸i€%&üÞ¢F¡�u¬•„wE�Qs‡åó¶®Õf¦í~±Õì_½.3:a|µ‚/¼¹ÝºÞTua~Ÿ�7wÛóæóá…î�µ@N+Å2PÕ7ºv…1)£Š9ÈgMVvf³ùâw#qËw Œ+îw*ê(Z’fý£ÌôGŒ‡z”…• W? i:îº]½; �´wS×û©óÿ9µ«<š‰vbââú`úѰ‡yØ0¸ôãtéÝÖÌÙ}ôÞˆ›§v™§¾ˆ6Û¤£Œ­ÚXËðëÿ=ZâwÎ]ݱ‡¿²k8}Éâ{};éëgûáÁŠK˜¶8p{¦ëz U%–ò°½…ü&à8Öçöza�z!˜yRÖ"Vµkl)—»£%a|DÏ®¥¯AÔž®©Ñ\Qù6IÓ]˜ya¬¡à‘m`:¾Ê2¤\Í7êu1¸Í7‚™Ž~BjH`3ð§Ää¼~T¥”�an⺢~`»å±`jS 6øà¬¡¬´7¯ÿÝ d÷HXN­ÃR0!ÃsÂþÿŒÕž¸GÒÚ…i*%z(+j(TÉ}ÿ?žÅô±Òxœ! Þò‰PNG  IHDR88¨†;sRGBÙÉ,gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ< pHYs.#.#x¥?vtIMEê $žò  wIDAThÞíš{�×u‡¿{»{fgwf_,¯eW‹÷C³ÈF–PÈXÂRL‚UT–+±d§$ÅA±KN9Ž lÇv•ÙV^ŽK±SQ%r\1Q ‚ˆ²%âPB°Ae�X´Úe³;�îéÇÉÓ;Û³ÌîÎb䄊nUW÷t÷ܹ_Ÿsçœ; ï¶wÛ»­š&ûˆË>âWº_ýn?KhâÄÙ|¥ûVÿ«`»ÐÜÌm¤¸'<åÑÏNµ•Þ«Þ‚ò›¹'`RÇ'å)Œ«ßEg²ƒZn»ä| ëhaãU (ÏÒAŠß˜ð†÷É~ÞsUÊ^R4ò™)”AÓÄNÙKêê³`’�h¦¼Ï¤•Ü+�˜¿2@y CžbÕe[¯“Rl¯ú 5¬G¸ãW(�Q�Çç¨á¾ á%1i'‹Ð$§5Âzî’cÝ; (»˜E’¯`²Ÿy²‡š²ë_eƒ|hžÂí–_Ö(Ù)hGå÷˜I_EX€�"�¦w D¾ÀZø&šE˜ŒLaÁ¥x€L{œ |^öQ ˜uEå~’Ìà‹­!œÂFãÑ, ä³ü3x°tÍÃ�4° KÉ6LÔd.õ|zTtä9n¨îk“¥Q}Ü‹Ábl°Pø.­üKhà7ɇƒ5.òÑ~î§3i!ó¾øñ¡æ5|â€U—åš[h\¼ƒEÉùÌÄŒ*“Æn À6µ•½ò<Ûå_PáÌeå¢r'7±€?ÂB0,({dp‰£Á%@gyHý�î]tšiŒ÷5“¸c.©»T…ß›£Ïµ·ß}dÛ6IâUƒöóe4Šz>K?_P·1<-@¹•zÚxœZê1b!œ�„�Åϱȱ�Ͼþ¹o¿0<›ÔÃ-ÔlªÖûò¼y즻n9¼úÖWeÑ€^²|‰:¾�Í>Ä_+UÙá+ÏÁ& ‘<Š:2ÿŠsÐAá„‚c‡çMŒ`¿¿šçî�@‚ö5/ýÃÉö|h]'9dÊ9ª™lÅá§Ô²™çXVµÈÈ ’4ryN¸Q |Ù±ÂFk¡ážïïZÜÈ<.ÓÔJJø»ÏÿíæíGȇ3y2Ð$Û1ÂŒ¨Žßš¨¹Ô‚1ÖâRG!´T>´Ö¨å.-~Ž<„_ÿË}ëWÌXú˜O¦7 @dBoóò6_þó,ñ}‡tWÀ7Ø»�ýØ€3 ¨Bc±6 "‹i¨lE]áÌ–œ]N€8>q|À)ƒ+Yö»Ùòñ‡:^†gžÉ#ž�xâ{HD†ª1š?ܱiçŸùŸ>üþ·®Ý¶ô‘7Ýt ½Êùœî ã@Udüul™Rdd )fòt8ÑÇÔ3F@6­84âƒÒæ¢IcÒ�‰ƒÆ'D&rêǼx ó¯æºlØ QÚDi ´‰Ò:ƒK¾oÛck´ÝȲÀÃx‰î–Ù\ƒXa@‹î� A®‡Ï¨�281à–s ß+Á™a|›KžkÉ”�UÚ\4#dÑxh4B¡!…¿û£ß9kòá�˜ c!` ¥F8yèÁóŒ8±P^¡;ô3³h²ÌšÐÏŸª­™ØE…9%·t"jÙM‚·ˆ‘�I7! —kqXIžë±Y�ÃB �|’ÎqËæ,âå/žƒø6øëÜ¥~r7#ò sB÷Ì�Û á=nxì1–N>]Z.�+*©æ ¼=äȸ}†NÓ»sÝÇ�éýþL.îmÄ‹BvwíÝ4N-¡!2óãr%¿ìÜšÉS5�4>‚øajš“42“<³È“Â#qÏâ\¥÷uÎw>Á©Óé;êÎq{{–ŽÕ`\§Ïm�ŽƒÀJJ!J£”•dɆ‹ÇùQËJ”Æ5x+´–U²V9`Ñu[¥“µ »2 P!\P‚ƒXø¹‡Z.’À–ÑOmQt^û'Ž=yúÉE½¬z¿�t¸JŠ)®e�Š�áeÔ… ”2e‚Š¡Ô‰ÑusÐR@_8–P³‚{@©ðL@…�ÂŒÀY!œv8jÙtñaìÿÞºãûø›Û³(ÉC‚ï@Y8(&H¢Üb\4B�W&( \T`B`‚>ó é›w�óµà\8žYe�Iøa¨dÛ €}".­ ÆÃù@óçÏËy|›�ñíP8¤bPGð2 ˜X!\$@ÄÀ,ÒêÂyX,€5v˜ ;ãÝòÈPXðb½1§ J0“Áù(±z¹½¹€¡<Ä/º%Ád9šÇX¸xŽÄ@<$ðQ2ÿF¿®ba—ÚÐBˆEš  0O0¡Šv3o7TO7LÇ ¥M—Ž�ð8M¢�ÿðD| ü*rÐ r¯�„©œˆ„×Vn£�¸ÔLq4IŒRº˜Cq1¢°�›—Ð<äHk ?„ŒEÃF!LËrXí+¯M2œÂVÛÊ¡D‚ð\�·OÏ[Å’R¨qBÐйX¥Ü7‡"ƒb &²x4”׆e€k9:œfæ[%_òÆYn|ŽZ@[YšÖ³¿ç2þõQ• ÒÙüñ¿“E•Å׋(~F<ô®±Ü·w\ðïç´Ú…7! ¹€yУΈ>êRÇNIJ¡ +›Ä&¾î73â(5 @4J�î5 èÞñµV–Áõ¢y…ChœÐr£‰ý‚² �×§¬&F^`MS%ŸÂ-‡µlã¬`á§xtÀB”QÅ¢ƒQŒwÚe”ö"›[n:³H•àÞ$ÆëÔ‘áÆJ7EEw(0ùЕ‡øù”€ ïµ Kçh±*J`åù©W7ÿxÞƒ<‘M ¥ a‚úLÕ ŒX)áF™|€{²öSÜ@èÇà$IÎ’ N�ñ%Ùy2÷Ì�§ÀÙ ³¼í¡×þf Yº¥ž7Ò“¨„"ëâ0ëùµGjWÛù.VC$ÔøªÞ•@5`Ô ŒÊl`$·�íÏ~ð¾+ÈcÒO-}Ôá @ÂÌ*pñw‘“تž€^þE=Ë«U-:bÍòy¬;ÑÄѳMªjuÕ ¥LÄwqNØ¿íü”�ê¿X¤©S¥LPñÐzÍäÜyºkëʯùµíÌAˆFiqË »ÆêÒR�é¿�¼‰£cÅlAÇP ±SæBu«j Žqã#�,h6Ïþ¬–sv•ªQ,�u¤ž4ûæŒ0_rÔ‹àù)Þvç,·jy4JpR´5`¨Ü节¹n!h‚ü+ <C©âDö�>vÖ_àâ‚V¹í¡wðnèJо#ËüT-Ò¹ Ê(^LØÇægù<’ðZô8(e.*rߨur¯’v»)H±ć Í©NL¢Ø·»YrJÓ×j1÷† jè6ÉøÓHWÊ…hT$¤hP.òÝÌq†œ®˜‹+"¾€¾ÃÑúþq÷$éᤀ? ËÛAêHµÞ ù½Î0°3q.ªÿ×sLù&„ó'°\±—ô‹ô;ƒ3-05Eë¹þy)ð�Z°§ˆ¹“·Ò›½“Ô¡Öƒäìó[‚Zzò O¦ª"€*bÑ ÝÒÏàŸ™wý¶¤`è" Ò‚Ì`þI+n5’PU{že‹f±ðÉs;Š‘Á¶›8v®�ã#ÓþÃOED¥\uK‚3ò s(ÃuÍ¢ð}EÁSø¾�ý…Ëà—®!w¡ÚŸªºur]Û Z­cá�c.�nâxOŠÓ…;=RQZA#ÙtÊfYƒGÒRx¾Âóžžç3x8�óÍ%\¸8�gÉô W$ë¨ýݬùVyGN¡–ÿîIr.SËy{:î[ Igi‹åhKúÄ p=Eà+\O½M÷wM‚'ÐeO×Y¦ÿ¶¢þ“5 ÌÚ�à=·^Ú©ïÇè°Hçc ;¶¯q|,åSc¸ÔYõq‡IÁ2À÷Á÷À÷¾§ð}ð<—žç3¤¿u=gN\ÎX©—ñ:ÙXc‘ÞÔÈœ‡´o¼Ì—KÄW!Ø(h�îC9ú¾ësòp¸—;Æ+ò¶áÜï¥ç}5Ä·5°òa…¶.4 ×?媂 äö™Ä^ëà¨ûˎ튿NùküÅ&Ær“Øõ)ì0H¶Uº×eètŽsOÛØ/{'5©7nâðÈ•Ï;þ¾¨°KaHÖEåµ|!•ÿ ‡3êr^*y·ý?jÿd+@†c8œIEND®B`‚t~S`²ÍxœmV <”k™‘±TÖ ICH–Ì d-š)C”d«0f˜.Í`MšÙ}ÙÊÊ3|Ù’¥˜4eß"B Ù²”P|¯Û½Ýï~÷{žßóœóžÿùŸs~Ïó¾¿÷Dœ±8¹�_Š€íX4”G¶l¸?`èÉ€A0Á™@ "„†Àh �_$|rÝ; AM¯¶Ô�m.f'ÑÀæÖ̬ÍO-|T;Ø¥°µ¸°8ùÏ ‘—†5Çð~‚Jñ + tG.¯@+[Îù2‚êM!‘=ˆZ•ˆð÷¤‚ÕDG[™y?CM‹p'‡Õ³³¶ùeL±Øó¢L{5;ÃÎ_—^[_ÍU¯ËÖh‰g%&ç9õ¸àÒŽ|£8),<|u8Å^û±Ò¸Hé2iGy…ÑçA3—^)¯ø4ß éç•XøÞä]ü¼2ëÝ/áä ãø6YSÒÞ¸"σèHc‘ ®¸JŽ|™úœ¼œ«ùr­VùΨǙgy?ÏfpRâ 0¦.ôzýû¯©Y×FS� ð~j£dcxÚ v’ÆtÀ¾+ò¿Q/Ö ù3Bldý‹½È]Hf]óà(÷&²�ºÌOQñÆkW–áÓ.ÐÄ­ºO»<®x¢«¸}ÙŒÜÝñͰ°±%¨³R =+£^ûÒìŠj`ú×G‚G&mšût¬¥ÄÆ…ÀH¿ ~ã¥Ë²sß4'ªëµZÑNR[q AõQÐ󍿇GeÖ¹ ë�¾$€÷ÁEñ¶°âj˾œ ° ,ÚÈÊ}xÖÞÁÊ-¹ñÑñòÂp”çƒs”(—4X‚ï^„õx÷®ls©,-LÜ•¸m‰ÙrÖæûêÊÖ™°‰“¹•1¦i»øDd³…ÜÃÆáT*¹þŒ^sûÓ¢Zí{C>¤ Ãô¡/R­º5Ï%TWNèê]!}Üü¸>qOG¢êÌgfÛ(M_[Ø9€ r3¹êá9¹[¦¤â|OùIy¬2„À�gÞŸö6ŵúTÊ@·.•£^KcêbÊG ¦ûòÇ×m�¶=(k3X}>#´´¬#øN2¾/8Ÿ\ËžR 뇧Üyp;êæP„í)ÓÜø‘ôëÐkÇ�£�† ó”·úÑšÛ¶mC†¢’[ÝœD®ËsÚµöpô>ìL´Xû~m0^µ¢)»£¯ÄÏ/gäSV¯r®‹~隆ï–üj°è>ËÎÏ=}[µ8ÓRÑá»À}b~øJ‡�Ã:&,Q˜hzï{÷Ü!ú�B"®gö„Ý*Xo1»«Íó„û�è³øçç°JæwÅUš¤. \t“2ü~zç°~pk_‡vûÚFÒo%üfÝóÝ›lÑ)�J/Å/ø¼ú×A3ÚW\Å›íoÌ�«ð#›v¯?Öåî®v,-² îm®ÍäÎuãÓîÖÆÄ6ÂɬSÕÙ±� >¤Žß/TžºeœªšÐØ|_[ÿ $°$‡³V9ùîé2çlbמs7j”„Giì¤ý?®u¾/®Ó+]¤„¨­g?šþ¼D­rŒu•ŒŸ5kª1‘EÐôi†3B*‘rj „«±ËÓŸ8¿g+ÏŸúíùªaÇÔpԻͅ6Û¡R›…Uˆºß…§å‹Ý „7›ú2;• „”Ônpfyä3Vâ·²_™ã¬ÝS·ýk0úùˆŽ,ø³^>Yïå£ù:*­¬ý|Z1e7{š=\“éMiV÷Û¼Ö9n$âòl}>ýùóçÐûåÞãââ`,4T*�åNSù2'š�ù@ù¥Š²Sªb|‹|6S©õ®nD{õ5õ™~»¥±¥£3««gØrge»øÒ\ž¬uµF¾ *iãÅXˆCÂÑx¼¨•õ¿óaʪöW÷ ›Yòµ†×­{ï—et²Xl×›Þñxbjž�†ðŽ6ãælJ*ªÒñ *Ä�””t„6)´CF?¼¹ù²vÇ'[’âž#’‘løÖǬ«\*_ã6TA1‰M°$�²¨Sà‹÷X‰QåLÎÍ5�>»i‰Ç_ïíµ™®Ý]hSüòë¢ãñxâÀD*!³Ï)*IBÌNˆ OVØIwø*‰Ò³Ï»'µH?rm�žßÈ‹¸}î/6väœìhodŸ¿uU-Tœ�nÁBÐÏŠFd 9>>åóÒöæÃÐpð¨\˜pÜC1ǃb˜†'¶Œwr7_–Ž0�'t‹˜jw™£†A·ûîyýúuµ¯Ê§ééŽÞ^Fí••ìü|Þøýnem(ÂNqž ~y˜‘q™j——¾W«0ÿ�Ò�Í«Žµ�â5~³ÆnnûOu¥÷‘ ©µŽ¡>sý¥9tº�bå’m¼<_NNNeïÀà`œ‰<+ò�&$'Gm]Jθ�…ÁFßæ•ÑÓy¤¹³å±“³³MÅyœØ.­í`M0(÷J@}’ÊYÖ8)fzs#š®#õÑçä¹>fÀ©»·×¢ð:ŠÑÛÛ«#™o>ËãU‹�¸r)ÏkW ›éÖ&ÃO5‘Ï;`¨W*ñþÉžbÞÔ6 Ð4Ï´mý~(­T¼œÿ MÂg®zï]Ð%ÙE•IýŸÛA–ËÎa#«ìÌ}FWD#$*sX¥Àê ½WΪ=@ðãå^éëÆTF²$²•}¶aFX¬©J©BÕ(2¡na$’. ‹]_ˆXÞ8ãÅï¹+‰N—�põ·oh¢/OàF¡<èrŽiiE}úÂq™ËF#ò%Æ“~[ $ȲÛYÆ,kL�J'•Žõ ÝA–‰­Cæ±Á±«íö݇³Ç9´ÀK#N¹HÕúþDÃñ[D8ë]š¤^YR m$g¬œ$¨r˸ ï�x+2\"ÌÉ" ü¤ I¦·ÅÑBÀÓ¶îÑt‚‚LL°Y(Œáìªç”KD :`êTµp »ŽÆz72R_onmÉ((,, è0�&“[W¯‹ê4ÃU»ÐZ"Ì% ¨¬Zª oHRJzÆKyÂäó«Í-®¬`óã�·Sø\W�ˆ¸ÑQÕ–ÕŒ·/>S¿&ö“m»ÚͤµûÅų>ùÞ·ð` RAÙÌѵ­= ÆŸtnr€E69¥~<îVûu�mim-.—þ¶>ùÂäÕÃ÷Kö68ÜØ»§Á놘¸�/Ã5ùãÓÚ—N]±sñ¸HÑ ¶Ñ.|Û ÕÌo©²LºûzÆRÌÓ„–?í ~¯ý~t¡o }À¼K+û>ÐüuÁË�êêCt‡Îþ~¥¢twe�h.CÎPnpl)°–ÓwXKßÞÎÎn~!jm­–äú¦ ŽÓ�[óF„¿s„ç‘o³ß37²²v÷=ú±"**Jp¥ê‹Á¹£M¾E³½30Õ}›ž�%æ"ø a�\]ýéfð§¯9¶é´>5æ®(6%aD[,ÉÜPuÀÃ%@¶Zp\RÏï:˜ríþ¥ã¸”þðရdú‡Ýø9bÿxþ)!™\EÌäþÓ dBþÒ·Fø@8(h[š«3�¨Kð&‚Ð@jVCj©i¶Bêè:¢«¥­‚Dê"‘ågdRþF𤸒I[¤ ¥†Ò´Biéj!uQÈŸ„cóc{@‚Ò/�ìIô¡9ƒÝï%A�m�ö“#¸8r„·8D2I÷8Ńâ�§:ˆjg7E~�p;M"ùi€Ò.j8 D%¡gÈþD[4˜ô’™r @!5´›löþÓÉîïNf†Ñ+æ¡‘I$ÝÓÞdâ%š3íw‡ŠÂïz *´…‚�üß*äÙñ¢Äàbÿ¯çÞ¼¶øüOýjÁû·. ‹±@?2v¢ÿǯ4µÒxœWu\S[¿ƒÁh† CT”ˆÂ ix´t—Âáˆ�"!H·„¤” ÃGI Ò­(H×�Ç»ï�sÏçœ{ÏïÞs¾ñûÝgzŒtÜt0jiª�½òÿ�†¼Vºû.€k¤¡™í PWŒ.ê“èÏí©À1ÂQÓH_ÂS8§k€CÀ†(dǣ܆hðí#M ,гþß @v.'8 ó×ÒU‡m@©i8xD.f^K c4·’j`2yñÉVŒþÀ ÚXªÒ›È¬e_²áÅöša s=™€Cæp¾v¸:b L¯FË‹ô»~¹;ÚßËAzÆ…Jy™ÒØt¢� ùü$‘Q4ZJrü¶ó qFù7å%þ8??ì:ü'÷øIþÓÒÁʽ”ÒVð04“¬u@5h‘‚îoÇlj0[iÅØôž«a¾]ÐŽgü¢§\€$žÄ: ÛZZxKo¾-ZBG"q˜µµ,ð§‚6زƒšÝE,Dd8]ˆ¦iáƒÆµíÚØ¶§­ÚŸ6pZ¦Û™fcc뎷¸`a„PhßôÑåb½®C”dü±»«ÿMrÚ™­2Ü/ão+|ʶ:ke‡>´½#Œ(|!Á‰ˆ§§Œ¥oDhw\0ÓÈÉËÛ»»ËÝ#:ÒD=ƒÅ³Q°°°¬ù+xxzÎÖ;ÂLMM'$'õ9ÕâÚZß+Õ˜‘Íß[ßÛxxx‘ËßÃþl·>!ÚÉ‚´ x¨˜MKæÄ„IÜóç‹ošŠšš2ff̈!§ ^ÔaýµµÂ Üï-uMLØ´´´ª-ÞF?ø7 Þç›BÀÎW-}ýáUåbO–däåaaÁfªæÁ}<åñ>»®¹ôóç¨Ç{·÷–€ËP¶º¿¿ÌúúºuKPòPŽìË‚‚$ÓªžOŸb„óƒ(ÌÌÌØÅÌ•CBγa%p¸¯TK´N×·ÎN B~>ÿÖö¶a…Iƒß|£÷Ï�ɪ�³õ2ÀÄÌŒ•“3¶ @ ­­mbbÎÁ¡¡­=�©ß=Ö¬×Iy�:"%åÕŸ£m!)©äk÷Ó»‡%Ó÷�ŽzÁ—ò¡ö~„S¼ÈÏ×36fÅï”VVÆ�K\31 „‰‰ }¾×åè ${ä¤cì—á}Ÿ? )*ò133‡„†V”—?Î~o:d¥‰šùmN‰·�®òìÛ€õêúl­ØM†»¨¸x¶-UeV»:�& ~â^‡m¬�gª8‚‡Šît ÙÒøZ7瞯¯??�ÃXZY5“®éééPjꃟIJc““·ÝxÀ'@ì�ë+%§¦ªéè0 £I ?~DÅ:ÒA ‹‰¼ì‘‹väóS'ggßå>nž}IèÚ5õA³ªI�\¹ä���v/ÇŸ½ W¯]Wuu/Óÿuü=\£Âäà“@x<T‡ +}GêŠêXìŠñ Ô(E>\@ì.èzxTfîîì0ljÞ‘#©èáЈ[®_€º€mK#cã`Ò�¬–¶vQe¥ƒàH ·ïúØeT;é‚�¢»»ºùá¤�¥%÷?Ü,p¢Ž]ÇÒ®~bæIÞ?úmê+ ?ɰT¦†¼ååwø•——çü• ✚–öjÔ­[zXéMý$Bÿî)”�a–UTt%;;»üÍ›•Ÿ æ+ZMídßö” Øâ|Üšïø)‘…‹K¾QMsM2†+Ÿ—ü{ÚVKOïú­[¬x<~ll̼-ä”à·³³³¼¼\hTÑ70PTRðÃb{š]>§‹—”” }—ÔÚþì ¼¼½g?઼ԹpW&šØ2C"ýÒdRMlmõýü¤�¶UÊÆYøøt}|jªs›šdµKS–—�˜øÑ4ʺºŒd2™øáƒƒ ÷³øø’š&[[[ÊP$-WKs‰•UÝFÊN†¢øì…oÏÝ#Ÿº¶´¶Ö76¦¯Ü¾s‡¢.ìïckkk9ü‘˜mØé5www@ìÁ6QQѶ³ Q0dmgÇe©gffæÑæÔCˆ‹~ƒ ý×R–¹¹9ƒl”A!††,¾¾¾þþþuö]Ôà>+ªª„„…aà�€‘7w÷äÉÍ­­n;dü™ô€¶a!™Ø‹�«ý³³Y ¢³˜�¥ƒƒ:ìÖ±99<‚‚‚¹r¾‘ÐÙÕåîíMÅi'ª­¨¨îÚ_®ªÌ©Ã”„¬0É;sí öˆÜ×§)""bÞàDËÇÇÇ«–Ïð‡ìaDDE×wvVA€8VÞ—–^ fcgOü€×aW–2^\TíJÈÌääåå¥abré|J{¥¦´¢¥%7I‰œ yg)F„Â`ã ™ííAô[ñÇ—¸¸úG‹5äqlÏÙÅUÞ˜¾ÌËSÜx¬LÁÀ Ù(@Ê»‚àL¸ÇÍÍûìWZ©iµ9ÆË«š¨3 u1FÀïh˸ã=§†ÅÞ5­V.™›Óvp¸RN>�� öߨ#>ze ܹ;Yöe©xÇSÜÀ÷5;N< ÿ¥cÙ¡Õkl­ÌÏ툎éEÊÃî±sdÞöd?==•‘‘100èȕǒ]óþ©2­w½2ÛšaóøYCƒtØé ÇÑÖ¬I4ñkåeÈßâ.UÝ€€ÐByùø@}©”%çåýÍXy`m·ôvøîK®Žžž(JÓ#Òª®´´4 ÝEÅP/A‰&NVn"ÒÙÙ9²îRÞÎÓóyTTàø8(èÑùÉ>fޢы ä-‘('aÓÒg¬¯¨®¬ LMM…¯ßoÁ«ËÑÁ3å±×G8ø=ÝÜ ËËc³n¹ÆåÉF}šð¸�Ãá‚‚ƒë½f0Ѹ×Ó›õõõöc%Z %a´£4( €ËpþЗ<èà[óMúNþ’¶¯A盎ONª‡†*êêêRÎ�ì„þ8Úžo†ñÙ "ìâ˜n6ÓÓdšÏáw[5p¸Û���!{aK’ù¹¹bÚ«í÷Aì•¿I­©‘Hމ¯dà·{J¶´ƒÂÝ||¨¥Q(Áþ¾ùËK&U“Ke£n%oÞxÇ "ÉQ½$’wÂÖB«Uü½ábOooJAÁðrÿË‹F¾7¦÷eA��ûŠêèPSb·ÁÜŒ2ðÖ,¨Do4=�]EUu}kk9AváÇáú¸ššZì)HI9z‰î©©£‡óÍ~+t‹‹‹æVVÅ£n#ï»{z¤x¶6›zš±PÈÉ«B6¨æÉÕÕ¶)ŠRá*8¼¼ª ±ÿ?VzµD5¦ïâPr)û‹æÞË4~‰p•”µLI›#œª1„›oq›¦tôôîK”E¤ƒYׂ˻f&&u~+ï–ƒQNê»gkBzùh-ooqZZZs CSÓ{8Üûr8#õâûë(TX0*))1PS‚¶ñso±«åïѼD `Ѱ�œ¾«ƒ\•UUûÇÇÂbbñ7�_¿ÈÉy®]zEP0bsºöerrDmín¡w é *äâï±5ñQDyù ÓZ]w÷›Ièã×:ÇÇY ÍØ_q yzN‰»íw™G kÂá:£öö'ð™öp¥ýý}¯™:ŒJôÓ§v®®Ÿ½çgÞº2ï¶ý‘è(,²²ÛóMÏç7)((ÔzÖVVz¾6c›ý×/aWÿç±¹ÙÐÒRdJRBb5ŠÃæG_²PW Á.’€DÓd¦ôoÈú|]û9AGUSS䢙ÑÑÔ>_‰‚ »_ ”˜·¹<¼¼œ–ûÁ´fÇ®–`ÒÞááU , xlðÛ·�¬’;ÛZܳ"ˆ´®âwÐl¹V÷¾»G3Ž`b~6A —N%šuÇ›[\\´ÚÂñõ=Éqc¢‚Wñqêêª è— ërï†ZV+¥”'{åä �8ùÊÜÂÂîæÜñvø{ö[NN®ÎÝ`âS�q;]ùœ1>1ñ4#ƒ,BþþžRÓÓÁœ¡jeÅz]e¥x7ÄÅ{Üd/±H{§³Sï§% oËʲrskƒµ¼NžBÚÚšµxAZZOUmÛÀù) ,æX𯲢›õÊ^bÔã.þ½¢’22SWÜåìì,‡B=ÿ¾¹¹éü]?Ýÿ\²M®¤<¡áÕ¡ØW%ߥ§ÕC©©l³6ä]äWcÂÍe�ϽZÖÊ—øLµQáµ Ïî³Äœ(NS¬¶6¬bŸL·lg_žvަæÑ\†FFFî pÀák0�ë-Yˆ_üߪ®Ä.ù“¾ü ¼XçœR*rVÆI™‚¤¦ìýïòš^ ÷=]¢�–�{@b’)¹ñX÷©&ÃÊÿh¸;ob|NúÍ�úÎÈGéGÞ�¤g�hpúRLdqjFÒðÊs޶ö ¹ßíV½àŸ  ¥®§V£bù—­78´‚xœíXy8”ïúû.[QiCöYÍ03Œì"¤²—e–w˜˜¥™ÁX¿*%R¶ôU¢HEBÊRThW(ßK*Š¢Ò‚”ó�¾uιÎùëw]¿?zçzç}ž{ùÜ÷ýyžç¾æ�TOg%ùeòDÉe�Ãði#ºe¥Áï æÖ~ðq%ÖËO°� ãò8 fÄp d2 á#–ç&å+0Å/#–âzb—·¾|äÒØš±º»ÔÛ?Tn„B·»ü)æÒeZR¶äuïµ&«Ä»J×;½w7Úc}T¿²¸h橆,‰nR7õµä°‡„ ÿ‹Z¯ºwK^²™¡9,ºáÞßß{p9¦ ëÉ‘ÇõTf@U°ß9‚ý›dÛ£“Õ7PãÂÛ-Ýj·É2–,OêØ×²ÂEJÝ©õž*w˜¦fú| H95Pé¿øiP‡"¶ƒioïéboÿÏrÀ"%ì#¯¹b£Ý¼ñØcå(•WYŤ‡Žé„ÆÅvå‹"Ñ>(ÔÊÖÐtn­ì Þomó÷•ñµU™…l�ânìS›ËÓ¸*ä>ÉŒþ �ž€{‡¼Ÿ'Žë3çÔúVî.öMV±×²ÕKk²윘jpNªŠ–+zñÇx'¦<×TZÒaHÃÌ­Pø§0¶Ûœ0¡îìïí“Ú™jàÖ{§Û»8s€à»y‰�ÑskdxwÉUê±”(Ã{£®‡Z‡·{Y|ñ`õ�˜ôΙ;äiÝU 1©c8Úµ±ô,˜�­¾‡¢k{Ò#m§ö›²„Ù’Ê`xž]å2-âpµ˜ÒÙ1™¹}/N¸ Œ²¥g‚µGhвoÄß¿ÒÒ㋤¬µ„olï¥{â kNTÝ”*ºVióæh<°èü1§"Ň�Öl4ÌÛöÙlæ8¯�ìÛÜOúBH€¼ïñþ$M H¶›hžßÖßÿ�ÜýjB&o/¯.­$¼ÜuB&¸»üÜÝH4 I¡s¨RÈâBD—Õj!—B 0*Êd“á�—à0&� ÷Å»£Ý¹ö@sM,ØëáE‹ §éðÕÖòVBÀ˜�Áæ“„dø<. ‹Ä(8lÞDN†û¹{Âì9<†Câ�h„£�……[ËìxtiƒƒÓwpF†‡ \ �Œ6Crx¡( ‘HD¡±(,Z ø1lEˆ`óuE ŸÆcrL&šS¨œHõ°æÇ⺻ÿ€gó¿³ò�R¸( �b±P?{ðŽQ‚ÿîÁ÷)jÀçDòh€cÀèþ A§ýðçFò"æË¡ÓP@ÀMù æ—�Î.îž¿PÊdqç½Àô±0Œÿœ›H3ŸÜ¿Tÿß+™7žçˆäÀ¡EŠÒsq ÃEñIt�I'Í©h<MÃQÀ�À™Ñ"�NG`©8GÄŽþ” ›/ °i€ ” ™ @ âÑ <€ˆæf–bŽ b,< �A§a~‚XÇc‚»’ñsV"(:…¥ˆæD" AµÀÀÀ#(D4`)8Š•‚Á[XÌCÑi$'�E—“É¢„(.;t^!"œdëéB†c‘è¿%žt ÃݘìHáß /& Ø( ˆØÄX`±s šVŽ7#ÿ6òx|p'Š01h¤n^#ZQ1` Á¼öG•${@px^N.‚€‰<¨ÝÁãD§(‚¢±8Ú‚„%xa°$4Ž„Ç™ ÁÁOö:“ó¿¬E‡fµ@ò&Œ3/Z8Q�­ ³…isa›?$ m¡>% XXï*Z… €Ýõ«œù¶‘Ž¡R©D ††§"p4:A°ÂЩ4À ‹Ç~Eás‚h ° y¹÷äGšËöƒ9î>¢ .ºÅ i�B�‹»£ÌKmyIs«Ë ”tº8Øz ½Øäî¶Ñ_ã›°©z›ÚÛÐY[Æ®•:z;%SÚµ ¿]½å§¯Œì)O]«Î-> Gêßqk?‹8ey÷¼×“½;R{ü¯iÝÈ”V7°³3üc½Œ¤BZ­xn¿íDsÜÀº�ð/®©ERŠâ:F>âwNßI`êêî—T…wd½]}yeÚ:Xµ!ô£s$É~n™“²ÏGïuè´ü‡Éšø£ÂD•AÛ{nðÙ÷ ƒRŒ?æ.Á’“½aw¿)ŠÛ)&:Ñ+÷u†ŸGB¬–¿”Ð�¼~’W?Õx¦¿‰þ‰b› ½ÐÖÉÛ¯t§ª÷~ý88Ì2?X°¶ þ<’œ‡ål <ŠñõK/ï<IzÇþóÃØàÒ6xã·Þæ(½Bóû=!û¯l‹É”™ìëóS˜ùZsj²¨ð¡¶G¥UÝÇë}� Ç<$4©Ó=¦ÀÍŽQwüà‘ÜuúAä*™ iÕIíœJû硲ˆÆqOإó 2”›Ê«:ÜœS†óqEÐúå£]nìXMN �j×ÓôÓÒj„aåTµ–®_QCÀ%·—ó@A5�°{ç6±î@›·b˜EèÇRA8œž©.KMÓv×Ìz5m u«Upø0\ªîíj|ëø›ŽŒÖìÂFè‡25¸Qê¥ä.˜‘yVsœ…ü²å½)ª*×J2ÄZ•Üë—uoØüWâ,ÁÔ2ú`c5æ°² ¡½ÃaXs¨ÓßñTöË"–]kÈTdv¬· õ‘>&üú‘ ýW–„ºö´½Ý³[9qWÇ~¬|q‚Öq£‚ ½dBr…’µ±qR=çùæÍôeM¦¨*¦Æ®~Ê�ä.Èu¯ìÈ»wDw¦2èLÏÓÂU2g÷øÖàKôõgÆIû$Œ{ןk÷ê/½«°=ÖÖù5lLE¾èÏËç廞Œñ‰yÕ;q¢bÃ"coïõÓ}99GTmîŒy+m×&_,ài|Ö‘ûzòU_*iðÉ¡Ø,õݵšf¿•?û0uü†ÎM=Ù1ñ:�¢©éš�Žãh:á0ú)3{åP–íï‡tÝâ¯cGÈi{òh-{ð1»½ŒªÄ¿ªªµK@ÖÇ«Bv�.?Ž#9tzÇÕx+=k‰l˜s È;a™]d|A?Pª_AMô÷«‹£‡C¥]ÈöÈ�ˆº`xœ õù‰PNG  IHDR@@ªiqÞsRGBÙÉ,gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs  šœtIMEê 0`Ü$NIDATxÚå[?�ÝDÿÅ<!�(R]Eu4¤‰( ¢ ÅE¢FBâð)�üÒÐBA4�‘4zwü""Í]Õ»D<%èI–¶ ïzgÆ»>û¼ïnß{+½çµ×ëõÌÎovvÆsãôôo‚-äk æàþX;Á7±6öìËîöµ×/ãG$Y×#ð÷—íÔyðýƒPì2ñóù3×x|ü'ˆ¨!�¯ƒ@4°ÛwHý ǀߛƒ(çv‰øæZC÷Ì‹}ó G�ŽÚºij%Œ‚"Ml-ÒÏhÑ—‰w;.ïËÄ»—"}ÝXî{ïðçÏ^øwa&³\ÿâóÏpa©Ôñ¢û2(Õ�OñÍ·G‚93MPxÞ/ìy à¦m`ßö+üà-Û¾²÷ÞpbÛWì°ý`¯/šê/t!Pxñ¦Žöξœ0"¡ ¤ qylgè™:³uÃî]Øs ¶÷&gŒXw|à5ölÃú”ìÙÆªF<ˆ¸0BÌi8ÎMbæ>;&uqá”øÚ `,~M`öÆ2mÉ®ñëµ:š8³å²ØÚl-M �•ý-Ž˜U„µb”Á(}ïK{í¥‡9· ¿¢z£b­Å$è[_®»°EÀ!ÐâÓe  H€&\Ï|Œ)uDÔ/*u\*Z]×BÀ+aá]šø�R4Vaõ`«¥¶L%~ÜÔnÎ �‚À:J=·© c )bÄP x 3A˜‘L«uE|k„69£ ­hø‹à¸N ·á*8ñ4„øŠÖ ‹T°êY Z‹—ø^`*B̺ÿo&ÔÉ@Y¼Ö%˜(1Ô‘4ž_w• öu£‰SΦϨI«ú! ’“ËÇ_^Ý’:f;ÌLÿBz~ïÞÿéê|=„xâ­èî’R•�ný•™Eß@ àÌLUÞÉHô…%ÀV¼BxuCàÝ ½hb¹'¾XÞ;¹¾™Žémñv �” _g(<€‚@Òeðq>ØoÉg d¨6Õ2úäv‰=bOs\I„ÆŠ˜¯,I1jF¼w‰aB\  u¦P¡18ç@âí0r&ž{„ˆ‚¾²í$¾¾�Ò\®-Ÿu@X¼€hg 쀵E‡3›iñ†¢ÃB«o¡@,4¦£Ãâ¡o�õ챃@«ù—V±5¾ÇäÝ0xap.1Zsh,/x8§è¨èðÞÊÏúÍ<Ë=³z¢Ãc>�Ù€u€Ô·…]l1 BŸà¸Ø•½€ �©è0(¬ñ+Úê#¡1(pb�¢ãJoƒí€nhŒ; 4ús«ÁÍåæB€\t˜¹‹ƒË`E[‚ÿîé3þ9yki±7eó+�ŸywîÚ7ÔÙôI`G¶ÃÐKó¬b¸Ͳ–Šý°Úó÷ç¾VF_!‰'¯à¸É˯9¸º6�7}¡±m7uhlFÚWFhĺ4 Lé!ñáWÀûMýîCà�ÛÀé›Í½û àì 9æ¾fÑá™$>ÑìwÜ0¯æ¹`ÉW>_€gRìÜðRpïðÕ�×!3.m&}zÛ=í ©‹ŒÌÿlíOð3ýŠnö)có˜ìTÝ7ØÞMǓèè0àüÙ‹6µlR²ã˜ÄG$L²:®øXšp=/q]Ä3£oæ‰].­· ™ÀQ7™Hekr1ÓÛkêÉ0åŽX�aªŸ)üµ§Î6 f¶†2LŽ~Ä|¾›ÄÀÿð÷Aç$ÝпIEND®B`‚ˆ�¾m³Ëxœ³ Ló‰PNG  IHDR@@ªiqÞsRGBÙÉ,gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ< pHYs  šœtIMEé =X‹r IDATxÚå›kPTgšÇçtÓ 6 M á"‚±A$˜1ꘉ;Ñ qf³µ»•ì‡$µ_’”`RV™TåCªò%ó!V%ÅÊÔnmUV“MÕ䢣آ3j2Ú$^qѨÜ/Í¥»iú�tŸî¦»¹Ù‡ãûO�î>¿ç|Ÿ÷ò<Ï+ݸñÿoþ+Äô?¾?Ÿ ü|ð'ìo¾ö·Þ{aŒñEðõLB ýÁŸ‹ûPsòòÃ,Þjµ¢õ}XW׈bZ‚ ò5!æy�÷·ó¹^B.À•ËV„Ù÷æ&ñÓ÷¦uËþnÿ0‰Š»hýv>F¸&�\¼xžk�Wèé¹�$Ajj:k‹ždƧ‰‰Ñ©*~Ò5I}}6Ûuú{‰ä«Í…­G«ÕÎ[¼ µ¶Ý¸x±~N#®\­ãÈáÿbxx�p-11‰—þã?)*ú•*⛯7pôèÿa·‡å�‰ìøý¿³Ú\8«x!W¯Ô µ¶Þ ¸p±~V#¾ýîKNÿF�eeeQPP@KK ·oßž~ $±½ìžþ_£*¾öÔ1Îþõä¼øÏlyŽg·–ÍÚ ë¯þ„@ N Å{D_VVF]]]]]X,, ]]]X­VJKKBp²æ;åûQo9ªˆŸÿ‡óµÊ÷#öÂipà: ¼u?ýȉߢÑh¨ªª¢¦¦†�7†t¿M›6a±XØ¿?�‹å{®\©{`ñ�õ—8{Ö²`þ�?œ¦¹©>ò€è½–}ë€pF ôqøÿˆ,Ë9r„ÊÊJæjo¾ù&‡F’$¾þú\´øá¡A¾ÿþ«EóOÿvûð,c‘²oÄ—_þ7ãìÝ»—^x�]»v‘��@BB»víÂét†5⥗^âƒ>`b|œo¾9¼è©îر¯q8&Ø»w//¾ø"óm>¾Ã1Á)Ëшâ fÑÖÖB}ýÏlÛ¶�wÞy‡={öpàÀìv;v»�°gÏžˆF¼ûî»”””ÐØp™›m ßy«�–æF…¿Ðæã·Ú®qûvg˜i1пðq²æ[t:Ÿþ9’$ñÅ_„@ÂÝó5I’8xð :�Ž¿œ9±àEÎù³– þB[ ßzálèX¤¸€PŒèí馹¹‘�;w’——ÇbÛªU«xã�7hmm¢¿¯wÞâûû{io·E�ßÙyƒ¡¡�½ï4(B]àܹSH’ÄîÝ»•‡½òÊ+!€p÷f¶·ß~I’°^<7ïåíOu? Iówæâûæý ^èß ´ÔµZϳcÇrrr”íÛ·�ÊÊJŒF#F£‘ÊÊJöíÛ7§ÙÙÙ”——séÒÜ““sŠŸtMRõ'vìØÁþýû4îÌÆoº~÷¤;H¼²ðO‚æækØí#TTT=H¯×SUUÅÈÈ###TUU¡×ëçeÄÎ�;µÓÞÑ:çÆ¦£ÃÆØØ( wfã���ÒÕuSé¾øƒ(^�­å™”””­VVVFzz:m­Msº@GG+ªðouÞp�iݲÉña³]§¼¼ Y–£f€,Ë”••Ñy³mN¸y³�òòrdY^ô¸‰çέ �PYøÞÆà`?÷î݉êÛ÷µ’’îÞ½ÃÐÐ`Dñ##CôtßSø‹w"ñ{ºïMï$ÂwA.`k½Ž,ËlÛ¶M• Ërä^Üìh â?ȸŽ/É2wnߊìwïÞaíÚµ˜L¦¨¿€ÔÔT ÖÐÝ}7b$§»û………ªñ×Ð××4Ë�±Õžî{£V+.~‚þ¾žˆa¬�þ^Ö­[§ÿ‰'ŠPøÞ1À¿èí½�ÙlVͳÙÌÀ@_ÄÞÀ`¯ªüü|3#ÃƒŠøið®Ün7½½Ýäçç«h@>½½Ý¸ÝîñSn7ý}ªóûûû˜ššRÒÊ^ ¿¿·Û­ª¸ÝnŒ   ö/ jÊ�}xXIÐ({�±1;™™™ªà{öøØXˆ 8&Æ—Œ?áŸH’D||¼j�Fœ.Gˆ 8œŽ%ãOºœJÒLq§ËIll,�F5´Z-z½—Óâ“.×’ðu:.—K‰�(.àpL`0¨÷ö}Í`0àt:BâöN§ƒÁ :?>>—Ë©p•iÐᘠ>~i p8&B’Kõ †éàËÈ�ifÁÒµð骥l" $èu±ŒÚGUÇŽŽŽ¢Ódžˆ�Ñé]þب7˜�^Ïøø˜êŒ��¡×éCÁ˜˜ÆÆÆÕç�Ž¢ÕÆD„¼;#½>–‰‰ e•¦Fs»Ý8�NbtºÐëô8êó].:�nFDÐéôJU«ÙíÓ‹­˜]¨ xï-_«�QÆ;Åâââ”,«Í÷츸e!. ��]2¾N烔u@r² �FCkk«jØl64Z- ‰!.�˜˜Œ,/_£!Þ˜ d‡dÜLCjj6›MUL)©H’â²,“’bR�Ÿ”lòfšf¦Æ¤¤¤ªû?ÐÚJR’)D¼/P¹|y26›º=ÀhLœ‘F ’bJ£¡¡Q5Xž”±hbyR �� ªñ1ñþì°7D”–þ(MM×éëë‹:¼§§‡ææfL¦´ˆE)¦GhjjR�ßÒÒLR²ÉŸöE};£Uyùx<Μ9uNŸ> @öŠÜ°â‚ìì\UùB@ú£™AÑ.`LH$-ý1jkk£n@mm-ii�Ÿ±VÀ`0bJMS�oJ}„¸eE¼˜éBòrÍÔÔÔàñx¢÷x<œ+rVÍY.“•µR5þc�­— Îçæ™‰�7R]]5¬Æ`ˆ'+;göZ!!ÈZ‘‹ÁÏÁƒÑädÙ2iéAâƒö>Ðj´¬{r#Ç�§³³ó�á]]]œ:eamÑz4²vNÐÈÌùEQå[,ò_ƒ¬‘Ãd‡•à€,XÿÔÓ|òÉ'lÀÇŒ‚'ŠŸšw¹\¡·Ô6šüÇW†°¼ÞÒñ€�09ÙD^ž™C‡ÑÞÞ¾hx[[‡båÊÇIHX>ïr¹Ä„$²W¬¤ºúÁùÕÕÕddæ„Ì>þí°v߀øO¿-err’×^{mQ!+�ÇÃ믿Îää$6nYp­àú ÏD…ïv»Y[´> 7p;&W—™•C~~gÏžå£�>Z°~ø!çÎ�cµy-�ef/¸P2=-ƒÜ¼ÕÌ_‘ó8©�<¶@*hnƒ²½ü_ˆ�‹ã½÷Þ㫯¾š7üÈ‘#¼ÿþûÄÆÆ±õwå‹>ñ›-Ï¡×Ç.š¯×DzaÓ–ˆ\oH,¼x!Fc"Ï?ÿo!xùå—ùì³ÏæŒô~úé§¼úê«!x®ôŸ1,3.ú4ˆÁ`dËoKñxÎ÷xÕ|¬‡�-màÚ>Äf¿±þß°ä“–Z¬Ö‡S<ÀßÃ?å#p¬òýÀ±å¶€ÍTV½î]AÝ¿Þ/¾ý#0ôp+ÊÄ���w:Ÿ<™BDJ$áu‘uÖ)�]Oý\àùÄ8"`9œ‹ñ¥¶ÝñŠØ—&zôhØ­{EZ\ “@‘e¯H Fkcñv×UÇ*âí®+!c�µ$xlkK >ÎWöˆ×+sý�ß{1±tw�c`pXcŽå%^a½�Å«"Þâ;½�d�z& XêÕ öJ¥¾¾žB¡À­[·Ü¶ÖÖVD„±±1lÛÆ¶mêêêBM$ R(xóæ Ùl€X,Foo/SSS Nž<¹êækjj°m›ÉÉI^½zÅÕ«Wµýäóy^¼xA*• !�xDT=À�­[·244ÄîÝ»'þÌèè(—.]¢X,råÊ•U¹™™2™ ¯_¿¦±±‘ÅÅE–––˜››cee…††¨ªª¢¶¶6t‡¨ååeÆÆÆÈd2,//»ý[¶l¡©©‰ÙÙY­==@¹K•/¿ÿž¾¾>߯›ÏçÝ_>›ÍÒÙÙIMMÍšøK&“œ8q‚L&Ã÷ïß)‹:tˆT*Ekk+‹‹‹kγmÛ6^¾|‰ˆ°}ûv·ýúõë9r„Ë—/+W�.*ñ¨g€ªTüHùúõ+±XŒM›6­ú½¦¦&>}úÄ—/_Ö\snnŽžž_{*•âüùóœ={– 6„Àa‚  ëàÀ®]»¸ví;wºÑ6~ðàAöìÙ£�%ÃÃÃô÷÷ÓÕÕ囯®®ŽÃ‡s÷îÝ5™í@3¬¬¬¬`Y±X,Š}—Ê! ix«•wïÞÑØØÈÀÀ‰D‚\.§‰î©S§xöììÛ·�;v�L&±m€¶¶6èììäøñãØ¶Í™3gV]÷éÓ§ŒŒŒpÿþ}–––|ý7nÜ ‘H�N§ùöí[à!XQµKÿŦ§ÿ€ÇO "<þk”Û·3R{òœ{÷þDD˜œÌW €³A®dä˜Ê¯Áe©Ä‹˜­û4^Qm�¨A@Õ¢"šÏ�HB@Ö€€Ç!i¬9¬¨þ–îù£ÏÕè“2 ¬ +Éx”‰/A À™i6 ôÏÒ¼º†CÀ§ñŠ`à?7ù ðj¼>HT @Œ·Hä  ôéæ0°4�W5‡=¾2£!àÖ�3€¨„ÆT¥š~âU��ø£ÃFÛžð}Ä à×x}ˆ„9¬\ý–ò„Â|sاñŠóDƃÿn“�ýº�»œQ£Ã݆ÊÁ/ãÑa�£ÀtŸˆ7:ÿ33Ú5”øqÕ2rmå´©‡@‹Ç)�¨„ÆœsÏ"à5¦ñæ°"ÊSÙÈØzhÌ6Z$$4F4 àÓxÅõI4! NtXqoûHˆÆË�9Åè°W¨ØÊ†›Ã¥ÏÒ‰�Pt8,4fº* Ȥ0ÚÖ¢ÃD34æ<®ä ¨™Q€@Y âZF¨”R˺kΖq©¥³©lÕúÆò“Ù©Þ±�ýþt<}OtØõ)‡à‡�ónjÙO%;þHâ#ÿa’åz×Uó‚®B£‰W”¾¸“HìäÒV4CEàÄ/Ò"žlMU̼浬’aª:b½¦^(IðkOo¶i`fkP†)`=¶�&ñÿm™zi`X'|IEND®B`‚Ó48¶\xœÆ9ú‰PNG  IHDR@@ªiqÞsRGBÙÉ,gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿÿÿ ½§“ pHYs  šœtIMEê .7Nž IDATxÚå[=HkIþr ÄB‹ˆ�ZÙ"X ¢+‹`£�h# /6Q°ÓB �»Š ˆ°[ˆÉ yy†Ýàjx ,úž(O­Dñl‘Ü{ÏÌ�ìfñ‡eæ:Þ¹sçžñ|g¾sÎßׯй-Pá—ýƒõÜ.ÖÇ~(Dzç•c‹÷ãÎHb[ž�¿ØOžïûÉß`™,|&“�ßîüòåOQABé6De¶Q[Nûç€?~Ï€°ì•3IøÂ½‚Ü~Wí /úü9å´e•&GÃH©ÒÄ&‘Ušà.´0–©·3/ËÔÛ™—JŒµç"õØþPß¾ÿp¿¿ XqÕþi:^±OQüòkJXKž-½†—–«Þä±ÞZ.· X|k {…«¾¾ù|ëëëe½|aa�L&‘ËåÐßß"B6›ÅÙÙÂá°glmm-.//‹ÅÞQxƒˆó/jjj°¿¿�ÖÖÖ²'‰D"ˆF£ØÜÜD(úFGG±½½�D"�@ ôÅãqÜßßcnnîC `cÀrVF�ªª*ÌÏÏãââ¢ì)–——1>>ަ¦&ìíí }ÕÕÕhiiÁíí-žžžœû���ÃÄÄQWW‡££#œœœàààÝÝÝï—°½”=|uu…��ÜÝÝ•=Áùù9zzz000€‡‡¡ouuÃÃÃXZZr>Âï÷cee[[[H§Ó€††tuuacc}}}8<<|3àƒÛN*^{åóyÜÜÜxî‡ÃaÄãqÌÌÌ ¢¢099‰`0ˆH$âŸGåKõÀàà ò=Ùl½½½ï³�¨Â6häÂ;<@åä躲Ãeqá‰ô¦ÁÆKÜ0 œ˜¢BÌFB€þR@R[w˜QKŒü’Ö6€;}T„¥ò’´‡@QøÁL½—@Üñ,!ª«9<Œ—ñs�m€Ìx= S €ÐÞ` ã   }¢; |�ñrwb¬Lk8mÛÀ”Ô'}`ø1€˜îÂóˆy³Ãº.O�‰!1}m€Ìx=0Âf[¿ÅŽP¸+Ó÷ÿ/2^UvXg#( /ú&�py?ie€; 9;lHP” t#Bô±Ùáÿ G„@Æ¥Æl»ç…é,><Ù! øBŠÔ˜”ÖZ¨Dj f@ÀÃxɉ‘™ ;;ÌÂÅ:oƒÂyHÇ& Úë¿Ê61;,ó×QЛ�Dú,Qxƒ²Ã¥RcºSAujLQI¡µ;,d‡¡H�Åôµˆ× ðJ  PÔ¿PJ…Ҳا(JU\ ål|Y…~ÅX¼²:U«ì÷–ã‰ÓHÙa»lއо}ÿá”–½ªØñ¿>â ‹,Ë�W8,­Ø µž‘>¿]Hl×ÒºÌ�)yUšHªÖäj&»×ô¦<+W˜ÊP"õiO¹ÚTYÙªª0`%SÈdÌþlRÀúv=ÅIEND®B`‚(DŽ"¾¤xœ½Xmsâ8þίP‘ùW‰&{·3¹�©sÀI¼!8gÌdRµU*Y Âo'ËÔVþûµdl0¹—½º/ ©[Ý�úén ÎÐ0I·‚/–õh}¼úøô¼ä!OSôHXÆê­ô÷ß¹ï3ƒ.û�³Ú.Øq�î“d24;g sÊâŒ(�0 — ™)¡ðUJÎÑ7&2žÄè£q…zJ¡[Šºý¿‚…m’£ˆlQœH”g Lð Í9ø`ÊR‰xŒh¥!'1ehÍåR»)�`âµ4‘ø’€6ýfóº"R^J™f7——ëõÚ ©‘ˆÅeXhe—c{hM¦Ö Õú³8dY†ûGΜÓß"’J|€’5J" Á@&v-¸äñâeÉ\®‰``%à™ÜÏe#R48p]bEbÔ5§ÈžvÑ­9µ§ç`ãÅöœ™‡^L×5'žmM‘㢡3ÙžíL`v‡ÌÉ+z´'£sÄ Nà†mR¡ðH®bȰ)c 󤔥Œò9§p®x‘“C‹ä1¥LD‚3UH/ ù9íu(’Dfeðé\}~VAE=`+%BÅ´ú é•цfì:Ž7=ö^˜½Pû¾~QŸýŸ÷&¤M±ß9³É¨ßA迱:ø¬¬>«ý§ì¾gùtØö.>iŸ”)ÈðÒïTßdž¿oOÝP_0ø³ ÿ÷—‚hqÆ![U¥¥*{IØâï%ãhSRúõ 090àì$0deùLe"¶=ŸÐ`l‘ð8Íe˺L’pÅA’1ÙKs ^2°°ê}žPÎXKé¹ò· �ž®ÃH­HA(Ó šz1Hâ9_”C3¡‡ABWz ]éM 5_èð ,léàÄW MvˆB’Ç/QN¶€;%1 kólÉÂrž¨¸. eÅ3ŽXœëYQ&Å0÷S‘PèVÀAÌ!LzY’l…Ch�Å,ÜïÝ 08×–ZJ“4Ow³= 5“I²%ÌÍz÷ 䈹Ž^Á4 A€&ˆ,41¡æhꙞ=ìw$ &q–äÀ6Tž]û›éY{òèy´I=$�VäÑ=ytO­È£;òè!y´"�¶’GëäÑòèy´Nm�G÷äÑvòhƒ à"ßò‚€º`Çz}1ƒ«ŸÕà�X•s*q¸ï“ŒíG8U™ n‘È„&aCÕìÊ~Ø„¹«µ�Ç«²&ÿχ9}ˆê˜ÇÈ«ã©s}øåÃ/·Ž3¾ùðûؾ½5‡�žk­·¯G *ÕV‹¡†}s34m×i.�ú÷Ñ=<­‘í6EÍÇÉ�¬¸z›‹pmî‚ËcæÃÕåÔVº`ñ =2–î^/s.2‰..P”‡’C1î^nú=ÎcÉÄnZÔ;ƒ7Zß �½Õ‹¡Ên8Îô­[È ~µ†ž:3w¨Eo—%À�[{bº¯…üf„�ÛË]ôßÑ?ÔÑÑnÅÓz«F3ö­*íÃõ]ôÕOh!P­sx)ëB>nNúVAÝ—ñ“ù>öÁùò[{Ì~ëvÿ�‡}°j1Ԡ׬#UK c¶a4—êgQ_wþ÷œÿ ÷/OOÖdvoMð³é=hm~ Uσ�êͶŒ¾ã8ÁQåÿÁüfáÊXÃC½fÿûå š¶ê­{ÄSû‘A[:|)k퟼ò¦¨­=YŽsyx76ïO%z!ÄŽ÷`¹oÿn¶ÕÝU±(¯[üdþêéÖ*Qd·n³'§¶)É©mw³ñ¸u—4êçäå£Zêî8§/Š!ì•ð?|!ðî¼0ì?•¦;#kêA•«tgTÁ/ðø�Uý«Ú3ÿ1ùkçŒUxœ;§r�g‚+£éF·¦úâÔ�‚Ò¤œÌäøŒÔĔԢø´ÌœÔb.…¤ÄäìÔ¼½ ;¿´¤ ´‰ŸœŸ—–™Ž,’›˜—˜žZ¤—¡É•˜’Ÿ“™T”XT©5F!8Ä1ÄÓÉÜd�VˆÁÉ'cœdgïÁÞÎβC~>w=Jìiì~k OØÃi°¼ÝÙ<Æ¿ŽÒ?H}Ú Ïž<ûž¼™»ž»\’×6�hHêìÿŸgÉdB[ÓyC�oÉË ˜¥^ïHé¹SêGÔ!‰ïŽxNI{iOá?ñ¦IÞÒ0¾È³ÖRÇ5ñªÖ8@×ABö5ñƒ˜$nD˜@ôã”.câúd,–žkûSJ®ÜxÎè,-Äñ^à&± Ím‚²“à\mHìX0=�ãe´¿·wuuÕ²·­ œíy¼]´×ëuú£Î.p, Î|�F 韉‚´“kb/�£©=>=ûŠ!±g!…wq€_…nìú³&‰‚óøÊ)¢qÜ(ÝIk Kù±Õ 2Û'µöˆtG5ò¢=ꎚˆä]wüjp6&ïÚÃa»?îvFd0$GƒþqwÜôá¯Òî¿'¯»ýã&¡ . C?.C”ØtQ•ÔazQª±pp–¢%�ºçîDóg‰=£d\ÒЉȒ† 7Â.�€AÑxîÂ�í˜=*È…„ö<ø/ןz‰Ý/ìt^Sþ·çN&vD÷Äÿ­ùsÃK`<ŠõWQìL‚À+ üsw¦?BvâÅ—„vÚ±²‰ëx4÷Ä_&ñžd¹ønaû`r¡þ* ‚ØZP?ÑónÊ=K&Ë0˜‚½[ s7rïc0˜ 7Þÿ3KÜCÏLŽéÔ³CaÉwòÌèšÉMÝÐu*œtÔÂ6`~É4&Ö•·°/hhñwÖÌÏ£VL>= ðAD/\,z5a¨ùÜýB7ça'¼¡ïïÿéetË$²ð^4AÄÚG€D’˜À.B_ÆL"%ÖRå/UŒ;âÑ2%Âwsʼ ³‡<ê³àþÅô"èKV¡[ˆñÀ–°n¸†_Óës^62ê;`XétˆXq¤à  —z탊°˜.Kºa¿È¡T&*Y‘Ÿ)] >ù.$‹ÁPtlôŠn/1 ûWGbGQ0umœ¹Ô®\+]ÁŸH<ƒ%þm{Äg Œ¤8s÷¼%©SàH޽C?Ò)LEMÒ?ëõÈÕœúàÝ#72âÓ¹š»jGð´t[È©„¸17˜ý4ƒ-è&“lGy¨¡^1Ü Úʆ`߯›ò�†‡cæFf§Y;ÉTd-í0¢u®! s=íðé)4Ù»&©ožõ�EàÀt �’| s†Å§l ¾ÁÛÆ: àË�é¢È4“D&ÑÒ³‚É¿è4F*â[öú2p²AYèØ±]I=UjÝšÚž‡óˆJ½Úäw”]EzwAÁØŽu cźÙ-)Þ¨YÐĹo/hJPøóîzæÿÑ;�q{Ap‘,™U:.è¹fñy“=Ä‘^3;UỷÎ,@’CRéìPNqœ éÈØ€ ɉ Öû0£.À›ES+Î�|úÛï@„;Ì£7=«Ó?;­×ú�OkM2NÛ¯;C«}„‘§Õô;�f¾í/‰ÛþrÖÛö‚éÅh a´_„è Ž^[££a§Ó/vý¹;qc„Agnx·ÿªû¢;¶^t^v«±t|gŽNÿØ €�øÓùCdð´ í³þÑ+kÜžvûí^ÁhN=¯Ão|ôªÓƒ–¿vŽÎÆU—ÂIˆ<È» ¼ˆ` Dß„ôÒ ’¨ün0|=zÓ>êXã�õfØyÛœ�ŠÄ3L}úÑÐá–~çWCïgÚŽAùAûØ ù ø˜‚« ®{vTÉÅqg4ÞC�ŒÆ½Œíè¢\%ãöèuµ6Þ¬ˆVè   æàjh»‘¡ßÁšnðζ»#C÷sÐ^pEÃRÐÞà]gX:f°¼?ID’RÑÃîBÑc½ÄhT£ß}ÎN ,qjP´KE¼ÝçúÞ¡Âã9Dóu­©¾ƒYÑ´¡Y){«Ùãg2?±][_íË>…½dþ¥ˆo¦Z’î2¾°®Òˆžyïìf>;¬÷µ‡*(›ÿ–½W·Ç�ì¥ða‹ H�óœšþÔŽ¨iŸj?“iÎîBà0µÆ�ª}ÅBÁû�§´¯­˜ïò€ÆUcTèV³ŠŽ²¿%ÉeÂWªk“Gë‡[ù‘®ö¤Ö椋z%ÉíÍf|kkª¶ l¸|—m]N6¢ß鯠žøÛ¦¯oéIòh›àðêÒ`w~û½ñ‰ÔöÀwìEsˆáj»Süw/‰Böð®5qÞrsP¤x°i…‰¯;IU*E œKŒT\S�¬ùýíÉïMÆ8g¢±�´è÷Zäo*}Aîϵ5]8žë3îy0µ>ÇÆm]�pÃìÔ�{V+¬¥Øª‘~[E¬õf£§°-¼‚}ÖØõö±ê8ÔY Œ7?› “)íCÌÉØ>Œó|+öf­O¯jú£‡*Š(¾NC�‚;E&uJÐÍÏWz˜1“…•“™ˆ§Lö0ÊTÐE1ØPì<ˆ"¦ÊKàj7æÅæräåÕe^&ÑœÉW×ô*©DÍÏAQhiz¸Rª¶·f®tt7„z`xEVóFåðs‹z%²[ÌÏæ#iì{{d<8ÒÿöÉå=À‰ÓÊ0¤Ñ2àçöJž8ªè2 ˆžŒ\¹žë±Epɓ͔\êQ–ˆpéÚøJE²´}ê1ÐOIŒy€ÃvýÓë´„�4ÐÝßWz·eò#BÇ`žÑ½Hþ4(ïÂd�blz‰~[:RÝêÃNaxFcÌÿ AI«ømfLÞ.£ÛÑ……£CÁp 9MnÚ #wÕ#ùÔÓµuÆG=¤ ̦ä6œD8,Òœ2I4Â�–:¬ªßµ‡ýnÿ%Ä;ìt�pÕì“3?CB�Vms†Ù™Þ½2ÌηÇpáøpÕÔ[a+¥“°a Ë¥¤aRGô©%9©Ý–9cmΰVar)‹ÉÃà~¬óììÐ0Ùf¨šä+ Ú�T@…ä­Öl%§®ÿ¤nHO�� {AÂm©Ríÿ3•¯ê:zMÊsû/º<·!꼋2ÿñÎy›–ɲ1þIzdé)ÛÝ_´¸…q]HÌùÛ)´xp`ÀÈ2ƒw>L‹þ†·’òèåÕÊÞ$jDÒEVKú=k’ø {iÔ@SµžƒJ$ Š"%xncHÅd¬/ÆTjL¸äÿbLU9‡;㹃kŸb fýŽÊ˲×YüyÔöI²lÙ/è s¡J­ñðP— µVÒBl¿F"Š-¥Úh¬²mÅ,«¶o7Ø`*îT߆ïêÁÑtKÌZ‘‹'ÕÿQ쑸àÖ²{¨ú±i>óüÜÇ-à¸^¡FvƧ&˹ÿY4rÎÞbß/ Sˆ™ þ³Q7�g¿f¾À:w¬þ`xÚîU3,�{PwgæÖ¡¾)¿Zú¶µ.‚)¹.¤˜º³Š=5[ÛäžPK\˜ce™ÝšNÒwFz+Èñ\î2j!K_ŸØ*j2ó»Œbd_R‹.Q½¡í¥gzüòY´F´DÝýµš=[¯Ù7ë5ûv½fß­×ìûõšý°^³×köÓzÍž>Y³Ýšýðô™ktüdÁ8£²6Fà]C^äÀaÙZ˜–æ^Rï«a�+Êî#bÖskG~x8øvL儆ò–¤¸�<ÅÛÁe›å\Æå± b¬ºÏv+'�ÉÓu”œ2ÃʙϓÓKÉäk§…i/<]Íè2nDê „[½`j{¤ÎïÅ6n›µ}/ùÚ¢ ÞBÅŠ ©xx¹<ð)ÁäßôRö#å¶ê#vïµ%*¬Œ’%&¤G$¾ ˆVO-ì8ÚÇf»¤†öµx¶OþÀû¯5Ì/jóTE,TÁ.°×ð’â&,K¬O¯¾G-ŽmÄ®dÑqlCÄŽ»4Dàu\¬âÎÒí7¬U+ÜzçWÁù�û–Cxׂ©üU?€°³CͶÊ*3�©• �!jq`ªk)oîÎü ¤[1²Xv„~‡y×¶�µ°£ ~dá.,ù8­Â‡¯åҙŢ£ê‚³%‚(�WJ^¿*—.Þͻ޼‡¥òÊ6Õµ¥Ö2àÂ}¿ƒÞàÞŸa”¬á ”;žJbºéjgÓ¬õJ¥²zº—Ë…bAÏfp˜œUÊçï}2.¨>&ªœÊÕU± ”‡ZÝ3} -Ê�ÝmÅx¯…ØÄ¢Qycä××/¬×�÷V?]ƒ5{¼Çt¼Ðì Î8/3÷‘ÞT½Äö -?jâ�õÓRbx=;ÝSAò|cðQ×å´ÃXP”2¨/ ŒôáÉ£òøqöG‰qá 6Ï6ÑV9Vå—�³F3yø�hÏ?j1ñä@Â) 4ìQôô)À®‰g&F v|DX}—nAÓåu=kÛ”øšHJišµù žcÕ3¦…?E™Eî1ôñéUæ–õåÀZ1SbV‹£ZJ´ªˆðP%T8(ÍÙÚ_‡%|‰•²§™pц 4‹WÇG`%Ë% ‹r)�Øú Õ öŠ 9zjµG«Ûuú£î¸û¶£%ך.Ó°ñSXÆè^;#•[§}µ£X½¶óªzZ V\ÏëÍY¯ÌxÎøHL{l�³Rœ²È%Î†ÔÙOkŒîí .QAT÷Ïlò-ó¸¶we_½í]ë>fdSwºi \Z® Ã\Yª€µj L¤êóKâ’hV ~š•0ùkõkG!Ÿaæ'X g}vºÊ ±³&¬wŽó}Ü*µ‚íWæFÁ†jŠCR)?êAÊ‘ÞÌdkÉRÈìFb#¿ñ”MÎæüš¶‘déà�á`#µÕj¥`»ä8˜^È¿ŽKÚrÍʵýZìÅ¡÷˜%cü²™ ÚIÊÑxØ#OW6ö—œ°ÌyEFä¶?©•}Ÿ-ˆÞ‰¦ö’n&uÊ*‡]a@òóëÉ�ß·Ç ØXzxÉâ–Ü™0åt‹ÇE0LÝ8fkçÅ2¾n’á!«²ÿ²B<»±}–Ô¤žVæ¸8i÷FË»R¶[am?~q?ˆGs÷<~ŒãEÇOKRîÚÙï'ðéPÏxyåË"*#eóH#+˨T uÒãoö@ÔÊ -¬mÅOæ*Ë‘V!<ÌÎSô7¦2<¸ðijÿ:êŸ LH®µÜ'AŒ½U£Ä{¶Fg¿©ea>“$Jê8i‚žìðÚ2þ΃inƒ›¼f;ìk£—K§”$Õ“.´µ¡ÞQ¢4©Ö_x¿H «1¢—Üïn¬/AÊNÉSI†™a»J)t‹±:�ô›¡kpV¨˜ªZNâãb.UÇ�‰õÒ꫹>ᨉ¤`òiá#qY¼rÏ~Eä¤ð4*™É¦V¼­«ÿ=ÉAù�3š‰ÉÂõ¨f_Ï×"õ tgøë)âA£UØL3±8Àa&bíùÀ³iÿü€Y¼øÓ3é#�=’¿0s@njœåÛ9IqÒ†«‚­x§¡1…çL¡Gýºö´d 6 O}콚ÉsÝÆ1!Äzîç¼Îº^e•‡Ò-JÍHå ùDÉ¢Q‰dØr›âÚÌŠêY÷zñεƶíJå+3+õá«úwYÞX×°üÏrð»rNªŽ^Ɡ†ÍY øÅj>“Õœº©“^bÁÚ…¸~W_r–²À¦Nke^‰W×½¤2™Y–úð‹e}&Ëb;"f�Ày`f¶ëíŠbCžçor@[7…^f!ʳ–�Üuí¥÷y×çiÍêí Ê¾w9€•%Ñæ»¿Äm_ÜwÛ²è´3Sб†�­á^V×ø£@xÍì<ÍšÞò”×ÊÿLtḆöxœ­WmoâFþίq_Dá.½^ÛK[�›`aìÔ6áÒªÚóË«Ûõ®CPuÿ½³¶ /gâ¨*Jdyç™™gfvwÆýüúý Óoõ;�tàÓœG ¼@ò$î-q¥X ’t“ñÅRª·Áö΂s¸x{ñn—<âi c� –ÁÙCñü´È}Ÿõ‚åù¡ª¼‡›$Y 'ÃTN °X°ò8DrÉ@K½•¤ w,È .zoáLÚ•¨}~©Ll’VÞâDB.ÚàŠ€ØSÀR <† Y¥÷â€ÁšËeá§²ÒS6î+‰/=„{ b‡d¾OV¤—R¦âc¿¿^¯{^Á¶—d‹~TâDßÐÄtÈwȸҘÆ2öwÎ3ŒÖ߀—"£Àó‘gä­!ÉÀ[d e2QŒ×—<^tA$s¹ö2¦Ì„\ÈŒû¹è÷0êÂ:ZyuyEX9Ÿ�Ÿ`6Ô†x`\Õ"‘À'�9¯h©@C&‚žÊ’ܤL±`q¾BŸ€¿£Œ˜–Iº­:ÉïSÝíÖ k0¦ÎÀ&Ä<-×Í‘~¥»ôŠÜ诀Ü[µ mjFÔ%öD75£㌈aPò™ ¦.©E<Ëê„3Ë;·Ú€P×¢·6¹Ó­©Skæi’ÏõéÙ¡´a}P;Ä�8®mÝc˜Ž[ÏÎÕœq#±-¨äTë¯5£¶¦;õ)ª†5#öK׺¹1½žÆ [à<Ñ>ëý2|‘Ûõ’Á©ùÔëÈ9#mØà¬20¬—óYeWòð¶Ìû u¶e¹tBÌé‰]3"‚ÅÔ†ôÚ¶&ôZ7N›ÿvЦ´S¿¾¯eWAl2œžà_!íŽPÇÕžOÞmf"ÙG3–ª;[ÞD>S=/À›�¹ä�,Úôj6¶&uø-z羫?ú{ˆ‹FÄ÷�ˆ÷�ˆ?6"~jDü܈x÷¶ÒœÕw˜ÖÖ×ã#/[Ûn¦Ê(!XztŽP(šó y )v¬c;U�©èWTÖu±?ÿº,[âu’áÔBÈ‚ÈËŠÙà#Œp^ˆÊ)£l�Ï s¯÷á”è‘õe¡û­×ízéWM#W<E1�Tæ·ã nbœÌ” ŒT¤ð2µŸË¡9E²+À9ÇšÊloSâ£iœ�D™£C±†4J ìýfKVŒ_8 ¾(ÿR¼E¬’P‘ÊijóŒÉÓ­dÊ»jv3ñ¦Tƒ!˲$+«Si:.®¢®ª*Ί-´•V‘"~s¹ÉŠrq¢°.ö †ƒÕsìç»*Lc¿¨Ã¶8>ò$ѦŠgh x~\ÏgžG%©+sÞo=&<þU¦^³EJ¥ý…lñ"eV:~mMŽ�uå°ö;•Ç*8,—€‘I$,‹svt¦©¢KUæoT²ËÚùøkÑ1vSòɹZïwpy7Ù—°øA ·~¬m?`ÿã7²²÷/-q¹:ç2‰Oxœ}’ÑJA†A½Z1 R¤…ÊêR²x-BƒD¼X¤ˆZ¼�qÜ=i'3áÌ$Á«Å7hÙ'ð ¤ÏЩoâ™I´Y�îÝÎ9ÿ7ÿ9ÿ<¬ýküºXTòIô¥Fõ'»›ÛJ[çícñ½uÔNÅ¡ÑFŽ�B#1U©’ù�µ(‰c8”:WHÐ1 ¬Z]ü¼9Fª'¯ù¿vº[ÝΟ•†F’rÈ1S’¤+ŒÈ‘"cœ�€í€:2Ã&ÄId 2Ï|âMŠb¸³ykİO(Úº³v]„ŸÅu¨”ÿÙ,óÊVMQØ (z}…=ÔnlÐtü#­9ì¢R/YßúŽ‘®AñÁ®>>ñññ\Ê)©i™y© ññè2\úZZ ‰y)9© iùE %© `#ÀFNç–UA²"¾D¡<'71ÈGµžÜÈ*Ï¥ ¥à\”šX’Z¬�ˆlŒBȆɻxåôAjŠRKJ‹ò!â© %ùèCeŠ&[ó(¨£:@ › ’�ÖNãSqÕ´û d�KjqIQ~e1.³õ€ŠÀÎ)H,JÌU@2n²#¯˜5²ñ)£4°z^A ™[PR¤9yŸ·ZP+hé¦`k«àš—¢�Ÿ†lŸ^†ÂDAC�µ ¶ÎxœåZé�ÛÆÿ®¿b¢©p%׊À–wåFˆªm$m£(ŠIƒ¥8*9²vkøï{sð¦®Ý�]„t�3ïšß;æ {òæ©.Òéµz�N‹tÈ» )ñ|Áxä2A×]nË'>ß<Äl¹øïÊü!–o“W/_ý�üsÅB¶Ù�_<šÐ˜XwòûÝr;ŸÓ®¿²‹Ó`Êäïœ/�Ýht¥™Œ˜O£„d@C¬(éo<¾ô‡üFã„#¯º/‰…ÚúQÛ¾D|KÖÞ‰¸ Û„ –©½÷éFŸ¯7!ó"Ÿ’+ÉGSé"��šŸ †{u'|‘H<¡…^ ±I^÷z»Ý®ëIi»<^öB5.é�†Wƒñtpë·QH“„Äô?[ƒ¶óâm@"ß›ƒœ¡·#<&Þ2¦ðLp”x3Á¢¥C¾;/¦H&`‰ˆÙ|+ 3ò�Úù`2/"íþ” §mò¾?N$òûpöóÍíŒüÞŸLúãÙp0%7ru3¾Ά7cø÷�ôÇÉ/ÃñµC(˜ øÐûMŒ€˜ MIi·)¥\‰”l¨ÏÌÕ¢åÖ[R²äŸh�FdCã5KpI0@2![3á y«¢2êµZa‘nXþåwœ6z–­á†ù®,dB…‹ñ€˜1aÓ~ü´r÷K!›‰ÄÅuóó)¥AiÉïr²Û<*ùæ~ˆ?¦§æº·ãÛéàÚuË-¯S³=¤€«›ñ¬?&î͇,˜ÖÓsj£½¥ ºom5 Ë^Î!”�ÜaLgŒ/Þ¦Gõ1Bík`É74ÊtÉë›QÀ�ªG4ss ôd`øº(OÛÖ×êGâéÑÆs´s¿˜×|Õÿï‘«�zêlž€¥‚˹£Ÿ¡À3F/„i€Ç+xÞ¡‰ÊóÀ'ÌBO“�NÌ'V½is1AŒ{Ä!Ÿ®cž* Ó˜'ê¯Àæ¹ÚLs0!ÇJ+`…ó(*¸Ml ]sFqÇç%à�XHå¼-Ñç‘’‹Ú:8^ž’5ë�ÿõoX<å@Ÿeeé�¶2LÛ9h;òÅ)ÎLí±wr:ª2-±w*0³À�gƒé̽êOît0ž�C�,[žŽm!ۀ͆€Ç*,HrÓÁL…-Snx»!*¤Ö,¬¨×ª›”ž¬�€ —†Áb׬Þ¹ç°| q‰+Dg?fS{—‚4¾áàûÒݪ¢ìéû5y·ŸÂ¨�5°Ô¯Õ#/å’ß~C�Z¾Ž‚Aj…˜lÁ�Ïz Ô ,‰ÍK%ñÇ€}²Êðnú%”´]¼×¾1'0’ijj{WµmîÍÄ� f·“qwiÕnZm,c|‚gãU�:Æ›Œ'�¡Z“£xA ºôF÷úĘuó5¡ÜŽÐ�2>™³â·î•X„`Õ¾ãñ�|ûŽø W´ÝøúUZÄñ×]rŒW6Ú!ÿw©9ž9ò¸þ(šU?Ý«J]¬33[ý�LéË•—й]Å`!}47ÆÏÇ+¾˜@Â׳eêú§o…rÿš=“‘¼Õxœ½WmoÛ6þî_qp>Ì1\©†}h¶!®ã®F\§ˆ�ý¤RÔY""‹IÅñŠþ÷%Ê–¥K±�£;ò¹ç^IùCøåßz`è÷üá°C¸X‹�q#dƒ/!q©á2ß)'ƾMêðS8{}ö|LD*ò®jT0¸//â" ÑãÉi{mù~“2&sóùÄ™ Ž™ÆŠ," “ ŒsÆéÇiFð;*MäàÌ{ » ïTýÓs ±“lØ2i ÐHBCé>rÌ ˆ ¸Üä©`GØ “”vŠg1>9 FËXßA®› �G:1&×o|»Ýz¬dëIûiµNûóÙdºXN_c·ã6KQkPøG!yî€åĈ³�x¦l R‹’ÎHËx«„Y<-×fËZ˜Hh£DX˜VÀj~ävs…ŒeÐ/a¶ìÃÛñr¶Y�»Ùêýõí îÆ77ãÅj6]Âõ L®—³ÕìzAoï`¼øW³Åå�ÂEvð1WÖ¢)l(1*ã¶DlQXËŠ’Α‹µàäZ,FˆåªŒ<‚ÕFh›RM# “Š�0Ì”¢'~YC~ïD¬I±† ¸›_Mo‚ñÄÒ f«é‡à}ôNH-2üÆ ÉxZD?§" ™Fßýzɯ ¥6Q(eúDH¬Ì¶¼“вA„Jô'}øÒ;Á,kßo­)ý¾DÍ•È %Ú&›+d¶N©eö~BÃyVG¤Ëù/= Ç¢®Èþ¾xÚ³K8†ž0GÏÐÐÎ 7êü€•Ôó̶’㲇:Ь9@U’ÎôÑ(LŪØ]("„€�È©ß\;X:®nA¶÷*-ÜQ£¡å¬)ºU_u¬ŠaDÃ$¥Ð?”;hm�ò9B]Fäó�; ²g‘Ê b°VrÐVî9 Ê-þÄf��5#²²ð¹Ö罯�ð²,]wÞLJsº]/ 3u¢¬¦‹«ñêc)§v£=.ï ‘6»Ú{$¯žêÎb´é xœíÈE8 â<ðIû£Xä4õ¦ð‡·8ä/A–‡ILŽú‡¤‹:¼©Ó{� î“’,½{')ó`„9¡3 î¦AZ�0&Ód™F¡O² ‹‡Cé#Œ_9ŒdRxÐÝ#8w’ÌäŽÄ+8Ò‹¢HóッÕjÕ÷(¶ý$›D¬_~pqþ~8÷cþÆMyN²à¿Ë0ƒÙNFSoxFÞŠ$ñæYmE‚¯²°ã¹CòdV¬¼,@0~˜Y8) …`?˜¶ÜHæÅ¤s:&çãyw:>;äÓùõ/o®É§Ó««ÓÑõùpL.¯ÈûËÑÙùõùå~} §£_ÉŸÎGg €\0Np—f8@3DR>¥Û8f C)Oƒi8 §0µx^zó€Ì“Û ‹aF$ ²e˜#Ks@ÐG0Q¸ ¯ �Œyá@ÏžýSO£Òö A]t¤§ˆÂÉÄ˃þ·¿xciÌóBmÊ ’$‘ñ0ŒÍŽF{vŸ䀹ú´x_Ââ€ÿUËPðÕg+ïåïÃò*íO“,°w(‹�aê³0ȧ‹+÷fh%¶`¢M&ž©Ð®�’õ2±€ª%PÕ3•[‚p1·Í»©«�OÙl0©åÍJL�N^vOüÔ äCÅѤÊlæA'&¬j~õ3eìg¬›§¼ªèH+6&plSÍ’ˆ +t@¤Â‰?0I¤ŒÀ©#?³_è"Ö¢•ö•æà½@ƒê[šÌq8{y—n^ 4[wª>€å‡ÔR Ô 0;ÀøŒ²PZ6ÇÇÁ-²óø8Vn§6qr#ñ6ÿ`_!B¼Ië,È©›Ãg± ýbá°�ÃØOVдLü OéÃ8!à]Ì`逃V ^‚~yĦ¦~(¼×M£-ô7·8XOOÈ×)Á©£ü½p*c˜ha,s‡°ß%¢Y,²¤œ/xÍûBµ¬èC•9õ`…䊾ga>E?ªš,xP:¦0HZ0ßqéà�Ã4¢8‹Ù9[Û5ž•¹’Úšä®áÌ Z)€scQŸ‚‡Ž¾ä¦TëÃJåæ@ƒOìEáßjí/h—S»E½þ2êÁï¬(S‡„Àá0¶PAX f1tJŸÏ ðurðß½ˆj¡$®Ìd ²•ƒ”d¬„ SS®brEd¾2 3ܱŸ\"EV!Æ9.*KÔ,L›áåc,@#ê¹Êúä:cWäz°L2 '`´µ�8?0”PÅ0 Y°_w’å©ÌÑO§€· ¤éà³{³‚†Q:Ñ«ì%p­x¤\ýä¡líø!³éÂ0âOÍ03ÆiOïÙïRò@¢‡”tÞFÉ|YhZ§;p¨Ã‘̺Úk½s Ãéâ*F!�õÀë.Ê,¦+œõ”Z÷ß(ƒQ-ΦÑß ¯!�¥¾�DÅþuÚ@igT†J �“ûŽ+uª�Ø]E_Y¹ )1‹QWûÉ„ëqÛ`²Jø>rï×U_ƒÞUq‘x¾�»0'ïÖ #¬:õú&%4©©%÷Ë´«KU«à¶8;1,x�!¸°Ž£Ô+n˜wÛPpÈØ=ÿp5ü¨�Zw¹aÞôS¢£v—:f·‰‹æ¼ë©È¤JdK+]jÚP”0ÚÈnÅd¥¸V,t†l¶Ý¤×"βKÓ,ͯ­.cˆ]¼J”ï²Û]“#wA˜»ï@F.ÒÁ•Qgvx 33Ÿ;ÏóÏ�ŽCì|¬×ÖiY$ûLH”0»2ªïYî㯃Ný0X–�VýàªÍRÖ�–Oe™æAáÒ .L/I»ªÍ’˜øÂÔÁuícÝJÖ0T� ëðêêò û4 ¶©4Ó‘ø¶%%'3“Ê6œþ¼�EsšÙºÜÕpt6¼^‘ ¾%·^ÖÀ¹¤ÃùR=²1¦•jYoc*Û0Ú&™«Eª †‘™[‹[‹>Ô'æMJ9Scè­Ë”�¾R�ã‚P?{¬$8ͽØ@`Ä6¦£õÝS±¡ôHN¤aE/¯(¼éBí¬Ê˜Åñn$«1æ&´m@wc*·Ld÷´V«˜\d¥‡-&….Ñœ¯N‡�éfb¹mmf²Z/ûЏDyÞN³�DJgsa²aò-–lC½ÚX´¶~Í'ЩiØG,¾„vK7³O¥ÉR·_³-jÑxÜ�M°2óΈ9‡m§£øââŽ6——!«ªŠ‹;<¼¨¦‹é¼*؆~]®ir{-Ûê®ëj@=žàRö+=l?wEëvB·¥q¤T� /­c«KOʃP ùî ¢õ±ŠÎk»[ו!W�·ù×J›Íûg]gZÔ` ØŽc£K§66b©¾ß‚¦îÔJ¡³Pkuæ –¾æT€%ü_AÍ�i D„g+xë�3î€fCÔ;D„Bí¯‘Ž–dÕ ,IkZ©‰Èf>Ívï[é’|à–‰{[´*‡”1êc émáÆåRh`ËÇÒË|2�¼2Ž˜$¥ªíiµ‹3‰£{-œU)±ûÅ;?Ìû¬€‘•1ÝHNŒy—w”ƒ½èoàWH9«'q6|wóÑ¡UŽb“QRc„ÁÂ`¿cXŒ[°l…½í>‘KïÇñè[Y#VHߨ³5]Ë|êEMÙ7ÓoÔœþ¦Šþ7›>X�r|ûÙú‡äàƒ´8õˆmŸ‘F)‘‹ª¬¨ñí(jýkƒªVKÌ .¤E³Ïþ}<)g€û‚›~Ô‚WÝ “ÙÝì‡CæA‘Ÿpõw{ŸÿûÐýùô¯=½:Æ`Ôe5ß” /ÈHi/PI bÜÅæ“#þµCŽIÇsöÃö?ˆÈÉ»+ùgì¿${¢˜ú,È6HÔáÖY»ÿæî}ÕÅc×ÓtÚw¸É7,è¼QyuÔ•9@;pÞq—ñiu$§gÖøÚäçH ëy(¼ð�xÜßXöIíðüÍf[_ |cS¾í l“¹V 4U Y'ºG=�ñ"=OÈv-›¶ÜÔÍ·“¿ §iJÎÏê¨4V˜y»êà=ÝÒ¯ª�  òíÞÚ;Ñ÷ƒ+UTŬm76Õá•"!=ü•Ñ+M¼˜íÊg[‰·Lø)0ãÄû@7ì>È’†Ó/ôà£~&G:|Ym£òóó öéb÷µ¥®+ÎÔÒ(‹êYÉZTšâ ÍQR÷fÇ4l³ó©z?•ïf+ÍGŠö¾kwœ»õ[½JËB…G¹†Šî•Ð –„¢î“ñ—0eüacõŸYP¯KŸ™jE߆¤»pÄ$l%×r’o¦ e©ë†á5» /ÄŶxlZlÞ[æ�0Üê2ZdáÝôF£ ŒYšÄ>*v̘-&‰5"M*–�¼ŒðLÉ=]yò�àÞÉèªá×–ØC¨zñˆ\‹Lúµrm]P&éÍ…e'~É[Ñgå›^±­4üÈ«Í$€}ç³¶ð�úòò30¨á­×FD« cä@ê ·±zŒ}-Šhö •’ô†Û$[ºYöÌØÈ¤¾WŸÁ1Ú˜ËhQ¡ö¨{4ç9”«b™e]f^hBC =TÌ:�¹#ŸðŒ–ù©ÉÆS‚xÄî7"ä¿õ›?Û;]e�ë#ö{B'3o‹÷”w&4d‹Íó ˶©†°Q¢åçùÉó¿•ôN�¤ WLL‹ŽCÂMbâ ¼‘Vå+Û2zŽEÏcÚ}=µÝ}⧦÷V ™‘ÊmNµn]š‡1“f´xlZLªÑ§§�sø�ÇôXºÔË2,jA~cGΛ%žW"d‰ÔYv¹ÚAG¥9Lú…IpéK1E“ÔlàºÖXÔYûši7$·iëêQ™Ê†S�0ñ9V’WÍÙYWãèéKÞšÇçd·;—)0s\`ºôª›“#�.gML~u—ƒëÞŒnÆÃ3×ÝÖµJ&]ѬªÅå zQ–«ŒJ-bO®©„ꤸ¤Æ¢š·ø˜8Y±Y¢–€ã*Q²ØÆ¡‹í] £ÑJód2§÷Sö@ìHì>d:ÐZà¯ã=iü‚•¶K˜Ø%kÕu�¼ÙãÑæ¸òâÁû ¼é¢k3&›ÜR÷FªîÌßÁ:×y1îÂ3ÓˆÔ1^,�H½[ˆ«UÕjM[ áuÒò¾¸XÈW¨¶|BÜ<ÿÆlAo­Âð�Ó¬/aÃì‡ ¤ŒËœ]új„¯ºªEÒ[6Kg£€ºz#Ö“ª²71ØnН¸4“We[î¯ÁXP 4 ?`òÅFWßÐ^\Ai[!ì°ØÝÞàåÀjó~ö¶ºæý�¢)—®(ù� ÀoEü;NãÔȃ#u¶bÜ𶵯®óõp|í¾?¥[>è%ê„hªQOÌò–Ø¥€ÆÃk&Žìvq‹pn”üÖ më Ê¥\Îxéw¦á%%³¤Œé}3>»Þ°Q· YCö{H{RÚ®–h ˆ${ÜvT窙îüΣBÞíhM×™ç`–”'#ü½&c,¸œ ú‡¯éý`'Ÿ;g¿ìï}î<8€ÞCG¸{‚õ^�øÕ ÿì^âÉß뛫QWPÈá— i󶃸¾ºJ0ª‘+`*Uä3÷úemD�“ gõ¹ù¥ÿ“Ì9R™shaŽàC¦âõ—êëƒ-òV¥ÚÖY«‚wZv• ÆZÆþÙ2&9r$óI7…„~Sò¸’™¦ÜkSeJúÊÔ‚( °+k}Ä}B)eÌîàÓRO}¬óãeA(6j²½bI3žV"‘vÁ>I¾’F–Jœì:ks±=^YF>¶�tðÿ$Åž/KR.Ô—qtOzè3tÙM¹Â1¦)þ>ù„GU<ŸA¡wƒÐ’I› í‚+�åEC‰â[°cðv<ö„*Ð'ž,Ó_­ó@Vû w2„EëÌ=¤Þ1–êx ÞTînu 7 šüD~ŠG² ëQE ø©–ÚòCiN_\Ä58<ú^z\Ý¿õ㫟¤ÇTLà)*Îú)ß“Ï_‡µ<¬Éu6¬î¾X=ÉsôôFËÞ¬ ôìÚdù 6ôè¥éIÜû¤NXàcgÓÀƦWßÚ¸ôýO‡. ¸ôrI0©�GG»åÑÑ·æÑQ¯2â;9— p} 5$V¥ñÄ‚ÅO–6�c k\:¾Ð k�§­pXw–¡:·Pï¾6.µ îRvçy].ëüþ9®c�!R ¤µ¢÷ÅϰZehýÂÒågL¸��Ï$Í躷¯û$ÙúŽØÚ¤ÔÖ>æ«îs´#ýÿrØK ¹*‹ÚŒ®Î#§z�e¹¶™‚¹uñö§M&:hŸè`‹ÕžôÔ.Ølן‡?+�$‡/š¾ØÎW=`§ìÌef9q²¦6Õ–­”[k§�œÿ‚MÇBè»éÓïlý“t–w·ìxœÍkoÛ8ò{~Ŭimñ“.î—lzugkÔ± Û¹î ¨²DÛBdQ'Rq²»ýï7|èAIVÒœŒñíúãßàãp|Þ‚êB:ä6Œ„Ȧ'TI\©·! KªXb!q¼¥ç hÁ*¶WVô†DJ!‰6[Ê�AW ñ½�Çm.‡Jr B½½½½Àñc·_Û麑üÙ 8¿ ë®ßæF}o±°éé¿æ$ãî‚R¿4ˆÀæ÷6…¥×]{¼§ÿš“[ûÅvTSÀˆrêé$@è1¨˜‡F¤ æžZê’¥ø<šZW³�u5žÍûïGƒü?ê-lçšnO//àÌÜ•'#"´ß³}Ÿ:6§QB*¹‡¿¬dvC0Ü?%•ØÔe™uŒ?Ïàœ8¾éýR< > Ü¾²E[z>eÊ¢ã¸ä «+ÀÑYb‡ƒµõ7 ¢²8ü¹øT#ô' ,®Âæ¡5F‚Ká ï"”H-Ô*µøñ±ÂÁ$v�dÁ,×÷‹%° îôq}1r²gÐB}E’Ô— sôî 0oØ>ÆÛc*r|É´ÿ%%¤EÙú–ŸàÑ�K#L§Ov^Fö†<Y‰î^¢"0"Ã(ðŒÄ ´9&R.Î s"/äÞ �Åp»¦[¥6v/m‡Ç‰:°Á­òE´´ÏöSœµ�5Ÿ¶›à¦�ò”jÞŸn�aŒùŠn%_Κ3ì¦\NÉs H ",–-:"+$Ò�eD7ˆ$¢4g�©’ýefcŠÁjzÒ‹aÙáZäW· ³‚11Ë1ñ\¤fEñ5CA‚¥·Š•ã§X %(í6msPbþŠj»‹©æ†znÁ•×2$¦ßÜe4íä—ÀÙ‘PYÛµ¹-ưи—ˆ4ô—%a˜ó“H%�Gž^PêÀ+nC‰âcCøšº�Œæ ƒçzÂBŒo›v‘²JS‡)8J§–¦K™¾V«K4lòV ª¼Êt¦N�“´3ÏP`Xa½ëòuöº&¢oíýYÆÃSCp >]­ˆk9RžæÆFïB—MsQ«¥üÛ[Bs|5 3Èд08a @L)Àlòà­©X¤Y’Š R]høB Éƒ—".ɇ�z~ÞºÒA ÏQiM!Î#8&}ÜE7—Ê¢ÄÓø½±Ï~oÃe.±ƒíÀ¥L0ÉÛL&ýÚè¤HŠìÉ”µsvc_øWõð14®‚ëóO z•õ*0Èñ¡P鲇žÈ!©µ£’š[ºJ+Yqìs6x’B–Œñë^âüÚ¥¶°•€8<�§–*F²�|Ug0äK~ÖÕLtªí¨È2èº,¦¥ynŽeúªç×,åîáV¿¯F¬ç¹º¼‡wcQ++Ü.D}…§òíÝÖ?†3]¾&Ë$�+T&‘5�ÕGQK‰Þ�£Y–4DY‰�ä‚.\ÆxìXp)Æ8X—.ʲøÛÈÆ€Àž Ò'±Ô}~ÈéÍ <®³L¦äú°×1àvd´üt>‰µòމ�“HóýÌL§“i¶ç£vªD[¢m±Ï&‘ZvUÌ9ùÓÃZ)ã•IÖæº¿ÂDºo*æW/-jV˜h–�4g¥D"Èÿ�2礛¯ƒ­a„[$ 'WCv ]KÍÔ-fX �t)nêeÿ׿Q÷0�F´r>3d,&ðã?ÿq 4ž°¶9üzt$ÍžGvÀа7úðïRÙÆ“Ã>’I°8qÄÐSJ#× p‚Á–øx » ‘CÀãÂÁ^�"ÿµrÄâ0”Ö) ¥ÍÜÖeR˜ Y^Ê›-³n�Žš¦)¶àÕ«]�ΧðydM®æŸ®æÖ|ÚÏ.&ÓKkŒÿõGÕNó¹?Ç¿ Û}éPr s1Aƒ•ê¦h䌼(o ‰ì4�Wj&öÜÆÏ—Çf&Oøšy¦¶mßȾá&‹Y™ò×6³ÄB3ÃW0ã`”¤ k†¶W…Ä`n®€ïæªâªP®Á3;FÿµÖõW >†øŒiŠeÖ±È&äwÍÚì&µeÄÏêh à -þ¯(š#qv�°l/«E<»éç,Æ´Âáøb‚&ˆû)󎤾ïÞî»ðö»o–þ(Gkñ$ "!©ìÜ€Ö-‘ƒŸ �›05Z»ûR¾Ä  ¹…¥í¨”jLµ‘Ú7˜™d7~Gª×^) }Íça ý•5¡·~¸••=¯à~Vç¼äEŸò*£Š–|rƒvâ¹bK“ƨ·!�êËc,Ùo|šãŸÞ4n“_%•è}ÕÄNžæ­&¶¶NŠ*Ì×5®¥ÕË D�z{5>"ùÖúÍ·ïÿK[-D±í«n4³ÚÉ)]}®ds6¨N�×Èõæ“;áäÕ±¬YÅ–Y¶k‰Äçsâƒ@èV¡t|ê\ ¶ÅTól49ûh]NÆ“ùd<<Ã&î"ÎćVJ�âvdÃw·ûÊ‡ý?×§!/oüµÑUUÜå–§¼ø3—B[ *žrÆG´6ªz~Æ“’#»ºÇU×÷´H^ï³×Ø[ßÞ$é”EK½iõ4÷”Ëô3ñG~8$ìø¿Ÿ¡ð¼±÷ xœí]msÛ6þî_�ê&Ž¤Ê²$ûš¤Š=v¥õœ#»–Üæ&Ͱ”HY¼P¤Ž¤©©ÿû-Þ€_$[Ns­fZKÄîb±X,,f¿ŽŽîëƒêû;ûõúª£“‰ãÚÈ_DóEdŒ}oâÜ4Ǹà¥?_ÎÍ4BÕq uZ�¢Ñ ]N×™ÏÑ¿L;´ôüù{r³�ìæxz¼ƒEïüÃñÆî²QE<­HeÏ'ÞÌŒÆÓ&p‰‡®3™¡½Ïþê ç®FjQYŽŸz Ég�ãݨÏ>™+×ô¬=hÈG;Øû�­'XDŽKJ,{âx6úåüʸôŒëþ`xúâ¼'³¸Á~´šÛá>|3¨B5†k®|F¸ð –t’m#ßw?8Ñ>ûK¬»�½½²Ç®˜‘ã{áæ^CºÜ]åíÄ•ÛÄ¥`ÍÅ8BÆ'w6² ¥¯�}ÞAð!~†?ut½†<ß²Èví™íEÈŸ “ÚI%ŒÌñÛ³Œèûïaaù—¢§·çS�˜®ªFØdlûäï(4,;��Õã�åâ'Ý®3Nù°x"ËÇ�3�މ”FK UúÕUšøßhÙ¡^ ³� aâGwç #è€1A¯',?÷C‡h`ÙchL•5y6ýÇG ¬Î¾Í£ AŠ¡Fè”…l”ën|4Ý…�êâ»`øè;ªÇkÝX!µ¢ ÕlO¨¶vÝ”![ƒB“ŽçDU**ˆŽ€æúóÙ“¨õÀ¶”L‘eG~Ìȼûd‡ŸWl4ùšÀT¦Âï sM6§¡w4Z •â÷4Z‘–цÙÞbF • bªtBóî=:b1íÅàòÜèõ¯ßT+}LãV©�‹ëáåõÐ^�ö¯/®Þ}øßéy­‘dºòAûY+ƒíY+‹¥ý4‹J²˜:O²˜ $Íô&ø ú×çg——½WzžSÏ*hc×¶O‘�ÝL.BÛ\EFv«¹ Më�A¯?<ë÷ΫPÁ6ß¡Hñ%žNâˆÏ|Tš£tDSïŽÌácdòê<ñUoðÒ ¯Îú?Ð`€?•¾9³ÁÓ aÂÍV¦³xÀAšš¡�5P¥^‘�¤¯ê�é-&æ8Zv°^•3‰ST-?ÊhAÉ]³jÌ"Õ‰–«l`޹fm!áÕÑßúúb$ó½&F16Ñ‚²²~ÅÐF¦K‘Q%š­Žh> ¿dë9ø — �T9E1wvTmGì”e_]\†–{rlºö- 1_Có¤Ýl¥j}qqq.ÕÙóÌ‘k[î“[­MYÚg¸ ©º_^†r'^2@´IõL)õcgŽ b®\VHF�MIi‹Úþõùy±p¼ØÄ3²zI4–£¬šrIò›œ„^ÍMEŒÊz˹D¶…fv4õ­ †lÞC{÷õÁõëǺîñ$ð²‘ÅÇã‰%]�–HºCCš3AUl(l©6L1±!]§±/ÆË‹þðlxe\¼® ž,_abßÞ¿yÒM¤J0µˆI…ª™+Kàeß$ó°V½c¾ÞBsèÌT¸îÕv¿ ¸‡6*+ëûo'Yé\Ì-¨ë·…s–tY#l©8ƒ“9^„–âëŠ~%Ë:9dsûèí’˜—#¨UíÇÝ<1dvnXoNßVa–l¤$šZ®6×iÔ`%,ƒÇ»ÒHmrDsÒæ¤/»¥ÉW@¾*$—•ù'å½ÝÉk7ÿÍOŽM5M'ÏsÍFا6Iæ¦ùiA±XÒv¨Ó€•äŠàó$°c ò°ΜÏÝÕ¦ã­l¸’~Iq§ì¸R¹¥qe 'Ycp%Å­5¸’ÌéÁ•¤Øp°iêR‡Eù!E�JJYjb',¡"óÕ¤„™K¨¦ø:Uk ¿p6žtX€\-7—p7ß: zWCŠ£¾IæZEd{Ç8«P,ƒ`C׿¹±-<ØõÇ÷(t~·ýIU+¤Æj‘±Ÿ TìW”ЧTªŽ “’™~fQºÏ¢Ïh<5T¯[]Hèï°‹nQøîP,»ñç3jâñ·Ëk£æ¢¸³j"/.F·�é‹VÐÌüP$H¤Kôpy¾9¢•A ¸�[jQœð¨âž†~p€©Õ…?Ïy߇5´Wl_ë¢o¿uä¡.»BøÎyßfè”ÈñÒ ®“"K8ȳó*ãèjÅ1&¹¶,÷ÆùòÀ_U%gì*L©U âÿ¢ibJ$É�|=þ�¥¼Ü<[œN¿äø‚´›h9t‹�ü•&Ýœ¿Q'G5ffNŒ8•j’üÉš+(n+ÚÝUØXÆš¤º0QZ(gm¤J2ÓÛiR©µÉ ­ñ^tÇ´Ó!üVv>À·’’œ&ÇÙ`ÝïË=“½’ѦL´¾ .ǶN5=”m”2•ìzÂTÇ“k"�Ñ;DþnYºï3½2ÓÕ²|„—ëø(•Ã~ùÁY:¸¿�Þ˜ó�>™Ã² p™�†§‰Ï¥˜ž…&ŽíZ@á#z²[®€•}¦[PT@Ôeé°ñäŸOä?}úãMR2…5™t¤É‰¨h®Éª@ºžO�úŒP—m¤rª«œVʱ¦bíR©»rZâTÞ�´$¡J¨'Kê¥Ê]_1vdªX3)ÏXNµ¤d‘€äžÉÞŸ�@¼Í°ž"®;š²u ìÔ?�±Ó›ÞÊ Œl¨Š &ÉÏsNÒyN:¢DšSY:Mp>’[Â]÷H1D-N‚KZpþodº4)&ja� Íx6§²Þh”©Ec y·ÿŸ1&)¿J¶¿ÃëßáuS-ÿ¯ëì{\lé°Å7mä�…þø)Ïiðʉ�$ø±¸�ý¨¥ €J|ÛÚ�mðÉ\À°äæí¹k”c7Ù”`GÉ1/f3È!úºëxD:�8¤º•µwL…MÏ^FÒFZ̽´›Ïªã:Š¿B`ɲ£eµVá•’D’G y­�¢ZDÁÄvä: þQ{Rþä5ƒ/+3Z†W¿»©I‰Â˜<÷Ç0H«ôp{mÓ3¬[9½JèãÌEˆLD2$¨²l¬*l'Ÿ¿ó5WBx‡g¦×Bîé^Ÿa\÷¯½W†q÷+iâ(“8¿À÷h©„ªü„žJ%ZU…rºSa2@r<Ô[FLÐ!CÜŸ�0sŽ“&:‡Ÿ6²=q3%áÂDgýáAÇxsÖ§èˆh ´ïÚ�÷,—'Ï4"Îtɉ,ÿÚzŒvwÅïù)ÍI ¯†öP;5ኌ¨0¯èã,\X€TqDª¨aYü„•}whàã›üh!™ÚF!¶-ôÂG;ˆ B„Ui ] l·j0…Åšh‰Cô-r€|EÈÅCÃõoªŒ~9½êŸõh ÊkÓÁ;Ü`kêv(ÞZþµò(üµR�%…Ÿ¬9«¾ÛŃgÚ%:æNßâ«$ÅJ¦(R•_jÀK.¼B Lü÷ëZªæ�NT‡ïlnˆÇe—J´*BHí3¢'Lˆå(hÛP©žGƒ”t}(îS3d1‹Œ.ÆX£ã·ï=®uË÷.‘HIU‰Guš:‡ï»$àH‘¤²×i>9|zðÝáÓ†ô½ÒäCñH÷æ`¢hBG¢ À-Y�GøžSaïx)}_I‘$=G-ìö ×àÉñ‘,.û²‚Ü#$èÒNS‚1ݦ‚¶æ^gÝtAç°Ìl‡ÌýÆ/ÿVrîÓ^<ÎÙÒ{ç\ô>àhMAŠ_–?ž\•äÂP& X÷RûŸLȲ& ¸ðÀü@ƒœ˜ÂP£�èá§BØÁ¼™Øc™Ä¤ =ú µkñ‡T$À�#ä3á~ Ub,2ë”Òò$©%m½^Mf­žrYT:y¬,Ű kà¿6“„lè\• D%¹*­ŠB¿&"GÎbtA½®b(Øî¸;é/F®�‚¤GºÀÛ§rX�Zè§Eô´þj´Áú p;ðf~Jb¾O T(\&I9Í¢ øD(û`ƒV«¨qøR(E a’,ÝÆì¸Œê3vÀ@ƒ%¥"ítDEÔDSÄN•ñiLèwéd%DFbî~üí£Ù�¿7å<ÜCÎi8ÎÏÁ°“ÍkàYŒ8SïAùh³¸Oc¤yð„!Íž˜J„c;Ã]—å78s¯-âÉ<òÚâS²²w—€¦ËGÖÉ#«‰j*ñC?¢{Ä] ¹Òñ—EoŽÂjšç³µŒ(]®‰›7®– W-ˆ(Pé_¤3�.Ð‹Þ ”Î5'�ÌËÆÂì³ÿ5h…"|‡w¡±Ô!{·ibâõXq¾`0ƒãWú‰W$H`†äKŽÍ„°³wÂ4Á¾“› Pw$ô"ˆOm ÿÍ\:ýþÖ3PšzÉ?&È Bé-(ŸñúŠÜ¡•ÔG’]Î7¶(o¢+õì "UB¢õDZfômù½¬­;›S"P9•nÓó*$œ Ø°7/O½ä,ÔÎ mì™] Åô6,7è )2 ïxQßH™R/åì­f÷‡¶&úaÏáPgã¸RæÄ>ù-/¤%, Åì΀{óÎø¹wuöúßF¿÷“qqe\õ†×Wý*oEƒL›ÒÕݽLéh/Ù,sÓ&qò~-eÆ\‹×`xÕûI¢®@{Á›ÆÊÙÝfâÜ`Bˆ­*$å¼²L€™qÎu¸ÌÊDäŽø8}.Ïäñ½E&?ç~Ö°Ë�Ìá$d‘¬ô/xÜ#¨9w•™[Ë¥çÔ%2•»ó½}1o.Ë®x'%“v| —ƒ™•v£³ÙøôõLëëžD›¿…ésa` 2þZÖBn¡¤�¨�úÜvë+Þx¢l|-ö‹3!eí/J·mOysåÙ½ÚóõéùàNÕ7w«±�€*ÛÈAÀòÑ­ è—aë�¿‹ ;èËAccº{)yÙÅ)Œ[žZV3ú2ìÈß2,ðê|OõLt®NOµñ“ÌÍ>A’ÞŸ?aEÔOB>,æÅ{J¨¦�QÙ¨§o¿V£J1S|½‹IÿÔ°"s�[œ™éàD*¯²;+‡f4\–XÉvRó\ã=A�eçä yøpÓ¨¼ñ)"þ¬$)oÙÎZ–=8l%Œ{‡ÙV;¼Ò0f)…¹öõ«è)L–ïiÂZ£_˜YZÞ;O¥Þyv²%¼™�� zè¡àÁòëë8Üá]Bk¶�|Æ»s¥QgÑä·âäs¢r/%ÞâY”Å[žºB±—Ê›žå1•¼ÓXà6Û©Jôæß›÷�XgE[-É® x5Ù§o¿BûJ“ãW€Wóÿ‰%Þ;‚‹wYR—5,šk®pŠ™~9’žò¸¾Yæ—ˆÈz낸Tâó«æék¬z!J/ ùmºže¯O#ÿ\B»ÂãØ)ú@M!UAq¸ß®q¢då¦eæ"ò«LCpâ˜Z£BGUáoPR‡ÎZ:t²u8ÐèÐ)§ÃÁZ:h“ˉ8 +s»F²Ì›óï/¨¾½k)Õ‡�[+í}jÌ0ÌÙ°qêîÄk÷ð®ZOãÎ�4V¢Nû€öRj¯wÖ«ú`Óª™ÛÄ!†oEˆAoö÷ðÅëIò´ÛäžÐÿž³[g¶ò xœíksÛ¸ñ»Nû$�,]Òi?Èg7ŽãÜiΕoühzŸš„$4ÉòaIw“ÿÞ]¤|ÉŠ’vÊG ì ‹}a™QŸœïë"ýÑѨß?"}òfÆJ¼8òãÈXš®9§ÁЂ'ü¡åù›€ÍþºJ~�®Õ#¯xýWòë‚9Ì÷É/& i@ºŸøç›yüôD‡Ö¢§Nƒ)!?yÞ0ÞÜ\I$7Ì¢nHm»6Àˆ”\ú¦òÉ€üƒ!ó\òzøé‎|Ôé�!ˆ�“¥¹!®‘8¤ƒ…„3F×õ#Â\byKßa¦kQ²bÑ‚ã‘P†ã7 Ã{ŠLnä�x³ì@bF’èEùáx4Z­VC“S;ô‚ùÈãÂÑÍäêzz} Ë�®CÃ�ôß1 €Û§ 1} È2Ÿ€NÇ\/ æ< ð,ò�âUÀ"æÎ$ôfÑÊ (‚±Yì)Ž�%ôÛÙ 2Ó%�Ë{2¹ï�·—÷“ûù0yøùöñ�|¸¼»»œ>L®ïÉí¹º�¾›ÜÜ�÷ׯãôþáòíÍuv¦ŒžLëuíHxPä|d:Žg™‘GD´ ¾Bõ# Ç„íÑd �æ’º‘ñüªjüÚž'sä@\±Y‘íŠñ•ýŒ­ˆ¬Ã]=;S¦�¼£–cR^dÚж‘ è(g†CL62�/�I”gH’Œ•³|¢ª<#"¸ê¯s£7(@ò<øú1e'ÈO|~õ‘CÇÉU£H_{Û𣤓 ¾aQ6î0í„ñ˜>ÃáxlSÀçmŠØ¹È9yq@KCNHŸí„ Z¤|Ü qE@qT8,ƒ©b!¿Ï,QFÃkS;R,¨ö‘XÔR†µsvYWH·®µÂöbœ €T*�ÙZ€ë ­‹i–Gä-u¼Õ˜L!JðV®ðÁo0\úm?L ~@žHCK`ð$½úé=u©· W4ÒD—Û 1€(†Br[�$q7ðò‘B»¼'>Öô2˜ÇhL4šJó-lQËsgln,¨i€¾=„D­³§»K¥N(”0€­DEÎf€a Üà$’N@Áçn:). ˆ¸ 2hάas]ZˆnÇpô^%öm3Jï H±p]�º“¾ÃÜOÈØ€?}ö˜Mú1_&‚óøê¨ -Ó¡ÀÃ¥²ˆ ç$ßµÖ@¸rÕƒ—™ŒJçS˜”8Ú¼‡íýÑÒ„‘sâxó9µ ‹ç‰‚W¼^ HÈ~§Þ¬«‡Öë ŸÈf¤;}¼¹Áu/Âï�A‹âÀ%8DL(:½ÈñTb‡ª©©xåÄ\ÀT6­ h9]ãý›ÀQÃrržŠR?F*—ªD�„Z�µ'£¡R-Hì‹f©ÎÒ™…5ûœ¨UôɈ#æ s©µÍ…Œ�Í]ÓÙªÏI+²O/D”>”ä À)¦#ÛY-ü¥dû¼qŸYÙšË6dˆO:è�kØPòä†\àœžÜi­¸ÐnÙ–U�l6ä@ d—u©æHøéV å’Ø�ø0z5¶°´L‘³‡úqY‡»]ô²YHAµ°¡¥Ô~ûv´”ô–š_RÚi¨"êìÞ¶:2qYÄL"ˆ�—JÕ2)™Þ’Xq`@>“úÑNzY#±†Û¡JŸäb¡CN¿ÿ8•‡Ö•š×2ÒKbÃ]s‘]M'×þdO´Îc«÷€No›ãÓe{í÷Iß, Toª!™rLÛb†íÅx€È!$³‡‡>.Œ”æ_cÀkàgÍõSh@JÓ}{o\ßÝÝÞ Hç½Éj7ÂÜ)XïÄp'†í= ÂhŒ†–PÓZ¬N}L¼ãp€Ï2įË!–|Ý�(SÞ2n@æÙÆÌ ¨zÀº8-#ˆ[¡ê&[× K½¬Vø9gÙ�©¨§(T #�Š‘ê´¡Ïg5>-SÔJ®²}ÒP4ê`ØG I P>;1KuÂߺ\<‰ˆ<<™SÐÐ_Ï7ç°ÂxÓ÷ÀZ¡C–^6uÄ–Ãx°ð¼nb9?'e£–‰tEùª·kñè‹”�dË$¢¥§} šóZÉ­øõ ÅŒB”c� ¡”N�åöý8”}—¶-â'eí¹½Ii˜3ˆ÷’ �%àžxÏÌÆ¦ ~A)øo|30—$©žgµµ†³ñ' 3@D¹�(×Ã6ôS 1A'™ñ“Rqiä•ß ©ÀÞÈ0 ÷v¬„±eÑ0=:@)õ›ŠuÅ>ùKõ˜àÉ®n§—“éõ�qû¾›b®Ã0àë*-Î~½xlgêbu ¹YFøòKÓˆ ®Ï [Þ*A'VJtdÈ­ 0TpÕÆ9¾Žý9ö{� V<¶c/¹Sf¦R%ÊÉVš½5�¿³¼»’ðÁÑà�®n-5$ˆ[UéV‘àLú ˜$ù™•9½@A‡krŽ”×¥#6rÄFÉçÐP|‘ø\ºŒKlË“I° g¦….ÄŒÈG±�>&V‰N Ù•é8Xçf>é+( ­JÌyÁ8kŒdÎ2~™£Þ†™„ÖPÀªå-`ªU¦P Z1‰+¤ØÞ¿_þ³ûjøÃ |xŸô»"qê÷Ò3篤bÞ_¿3Œ|Só.-µe‚ â˜2h)¶dsÈ›C“#í#©\©%‡ *ߊTסôø%»øe ÕîÇýÀ,fÕ†ÔÊVV´r}õ "ß…ºr¨‰o8û ÷ÇKßÚ¨Û Ëýè¿V vŠ¿$= ™ T >E\â6?Æ/I  î.D››–:‹§ùÙZdî-àθ¿®úõ•5}·ö‹¿ës ýÔY¾oG=yøôýÌëgå»Ð;G»¿1öME%Í�m"ŽŠVê}Få”V¹óŠ®ôC¸ô�ØIÓPÅâã±65o¨‰ê¼ÿ…̘­bæ³»’&ã"ãL{÷ŽúÙ¾ã­m@»C»u³°V©Dÿå‘�‘FwÅnÿ’ê7ehëôƒƒ�äA¬å˜1ÿÿŠÚév¥ýýÝÜÚŠU‚½µskú¹ÿ‡Û°uÚRmkcÎÈ6S«v´U®Â~Ú Ó6âkׯÿ I¥zhµì#Fãô-'Y_»ÑxœÍZ[sâ8~ϯÐd«z€"�í�Ú‡d»«IB÷Pa +�íê'—°¨b,¯-‡ÎÎä¿ÏÑÅ’|�˜\vÇ€u9×OçbÓï ¯u¡Nÿ¨ßé¡ú´¤!A ìß­–EAχQ9á³ø!¡«5w—ù jùmôþôý?Ñ×5 i£kLR’ Ö�üþ´Ê Òó×íâ6Øò úÂØ ¸�Ç—šÉ˜ú$JI€€3Ðàk‚1öáKÏtÑ¿I’R¡÷½SÔ ŽõÔqû\�x`Úà1޲” š"©ùᓘ#!Ÿmâ�âÈ'hKùZòÑTz‚ÆwMƒ-8†å Ý[º æZè5çqzÖïo·Û–ÒöX²ê‡j]Ú�.‡“Ùð$Ö;n£�¤)JÈ2š€¶‹„c�ÈÇ �3Ä[Ä„W �9΄ÄÛ„r­º(eK¾Å dšò„.2^0X.¨í.“áfh4;FƒÙhÖD¾�æ¿NoçèÛàæf0™�†34½A—ÓÉÕh>šNàî3L¾£ëÑ䪋˜ ø�q"41©0% ¤Ýf„DX2%RŸ.©ªE« ¯Z±{’D ŠI²¡©pi ‚LH7”c.‡*z Fý££¿ÑȳÜï`u}ìLü+¤‹NI_÷Ö�É” ÆÂÊ ,–cYÒˆ oãïv6ôn'³ùàbèxÐsѣ͎…‰QBf9 ÑHÆÈGPh${îõ§¤wÖY-4 ö*PŸÑá«–?f)UAjùk–¨2 ‡!úÉ$Ý¥Âg'ÂÁ~/ä䠜٫O¾® Œ(=T`ø´[WÑÅ™ÀR^µÃðêËÐ?ÏÑö~>ýêÞÞŒ¾üZ˜¿˜ÎçÓߺÚ&ÜÃ1KD‰¨|÷ÌòBƒ�¼Ö%VŠ…N�q݈ªÜb2§ÜanÌ)¯ ]·b‡²›L>‘ѧ}ô{‘T¥fê,d_…lµgú0æ·þE<`Š-[zE»­’)]¢Öäv<>Ñ3�&y–DH «Ejâä£)u€~Yéáª>R6‘v)ÐðÛüÚû6½¹ž}\ˆ߇7ÞÅàòúËÍôvrÕ”ý!/‰ sÕºÆHTšrý¤|µÓÖ–�½*¥Ú•ÂZMöéðÐÖi[¿Ë5ËëÑÇ×ǵL35r)–ŤY=z•±ŽôWžW,€èÅ̃–o>MÓÏÖI¯Ý„ºzYnIƒäŸ>ìÁI[GA{nKkr3ì!q^‚Ë<Úóó(?—ÐZk²íÿ‡ƒKEÌÓ.þkøµÖKÐPxûëVWì<•5õ�]k*ßÇ0}®ôéR�N}0xCš×ù#C-y� ¾'î{´ÆQQ†bŸ7q~ÙT{+?ßy; [‡|!Êì*qÍš=áJßkÒu�´‹ƒš'<ÍÎ|»Út«Ø‘� »'å8SÆŸå{Pdk([ž‰ªÏK›åŒ¢u«$•fbíN,–×Ë’Kóì£�óÚ¼°»¦q³~¥\BíÛÄ»o>f„§¥·µú-n!¦ˆ?4Èÿv8/BzsôÆY£î}Òë$AÂ<èH�ô*ͧî)ÜsNP±wLwÜŠ§ÅJk»y¦3?³eá¯GÏx!`ð'0eàߺüxœ½WmsÚ8þίØã&ÃÈe¦÷!iî ÄI™p�ÁÐL?yd[MŒäJr “ë¿•_b;�9šæÎ¯víË£ÕÒkÃÅ[=Ðî5zívÚðqÉ" ñïµ$>íú(L很·’…+mކŠX~ NON‡Û‹Xà ¡ŠJ°îÓï�aâyˆ²jÕÍÐä=\ âfãñ0ßdÌ|Ê ábè…~L|üÊW:ð™JŇÓî XF¡™/5[çb+X“-p¡!Q1˜‚4&úàÓXãà‹u1Â} ¦Wé>9J×`|É1„§ ª0±ƒXV�èÜé•Ö±:ëõ6›M—¤Þv… {Q¦§zãÑО8ö1zœ[,xD•I¿&Lb´ÞHŒùÄC?#²!�„’âšÆã�dšñ°J,õ†Hj`¦´d^¢k +üð« ˜2¡Ùw`ä4aÐwFNÇ€Ü�柦‹9Üõg³þd>²˜Î`8�\Žæ£éß® ?ù7£Ée(¦ ÷¡±4 ›Ì¤’iÞJk.,Eæ’Š©Ï–ÌÇÐx˜��B(¾QÉ1"ˆ©\3eJªÐÁÀÀDlÍ4Ñ©èE\Ý�ô{orøŒ�ž1ð�†¡yãWÆý( �8›h]ò|Õ¬,}`\ëmLU­JiÄ<�(Ú˿닊…œDÏd:ð„x)D#CáºdœÖÝø¯þ�=s?õ?Ûîx4ô‡7óYh·*¶’òz½C0ð|c¿€KêGDæÉý©~q¨ó¦±€ƒõÌø�æ®Âà(Sj�©‰¯ËÆãªÔ¤íb•È=•®§3‘kyÞ(̾ €[ZQ)…t}EFf5×]LŽ}麙z; š˜Nºìc*4ø+"¡½Va¹€$ÄãÉ[K¾Ÿ‘U¶[&QtÈn`‚Öˆîjˆý—{›&ÅÉšÖˆ0¿.º°C?ᾩ£Ñ/Ì’‘±tÔ2PF˜ÆR0çǨc?ÄBšV²¦z%‚WÒ'¥¿ÕcÍ!ƒ'¢”¢:‰­}9n5æpZØ]L„‹ e}I�8]ÌÈ`žZ]ᤳŸ®˜,Æcs�™â/Á2¯&õ;·nÁãÓžr#Zǵg³é¬Í+‚Ûûœk5ó]Ì#1M’Ã’DŠfÒïégF"Ë]÷³yç%«rŒRo°pQ»ºµQ�LJ¨9öõçÝzÈs )«^™¡K{°¸Æ MD­u„4wRÊ›´[©f§VÀWœžJбÇa†¿'j,°ò`e'·õÚsõƒÍømOžiî¶!1$~ÑåÓ•öúÿ©[7+%Ç.€³Ó[l»ÅO<µÅQ)Û ür‘#À»wpü[ùÞ*-ÿ.L'Óó*± $Öà)ò4¾³ŒE¹³­¢þo^†+¼‹êUˆ‘ÍflÄ ¶V�¥D6§y«ëLŠ1]_$\Ó•ú©»­VÎj�;jÞÎF·óYÓÌÈG ¬Žåøãì(hvžú>;,úse/ø³þzM+á÷\lx«ŽX�ÈXU_÷[çM,o 'ÿ'œ´) 'ëˆ"o #pІú öïCÃÞ IbæðÌLÞË9#µ¨‘«¼w^iæz,5öðò� tçý…?ÜË«â~%N3Vv_3IÛ<0 +ÿ”_3™ìÿÔp¾Kàˆhxœ;%¿C~B,syNîÆX/æÍ{˜Osƒ8“ÛùmX´ô¹Íƒ ã´ƒxœÕ=ýsÚH²¿ç¯˜õ½KÀ‡!ÉÛä]Ù›Ô›d]qpðå¶®®!PY–x’0öîæÝó!ÍŒF apö¹RF3Ó=ý5Ý=ê’7»ú#‡�'�ÃÃ'ä�ü<õJ¼À_´=øÍмhqû³yŠ¿NåÒðšäåó—¯Éç¹õä£K“Æ5ûüy¶�iÛ›7õfÐä¿É‡(šœ‹‹SäÂ÷h˜Ð Y†è#�SÒ]¸|ˆ'-òO'~’—íç¤�Ä£ƒæ vq-É�{OÂ(%Ë„B~BØpè�G)ñCâE7‹ÀwC�’•ŸÎÑKûøUô�Sª»ÇN¢©Z‘¸©@zž¦‹ä¸ÓY­Vm—aÛŽâY'àõ’ÎÅùi¯?ìÆ¢ÅUÐ$!1ýߥÃhÇ÷Ä]Fž;<wE¢˜¸³˜Â³4BŒW±Ÿúá¬E’hš®Ü˜b7?Ic¼L5‚Iü`Øj ™’ƒî�œÈ»îð|ØÂN¾œ�~¹¼‘/ÝÁ Û�÷†är@N/ûgç£óË>üzOºý_ÉÇóþY‹P À¡w‹GhúHJ:atRª¡0�8JÉ‚zþÔ÷`háléÎ(™E·4aDdAã?A–&€à» ü?uSVTêoh:�À^02�¸âaC D’<#4 7 ¿8QêÜÞ¤Ôºv8LåñÉ“ ö;`(Æ5Š�aRªö̈‹ÕÁŠ¢E+Ý!­É$ò®[d4©¤¢…PXÓ$Ô¡òÈ:yœ wé2¦d¼œNÁ¶à RÆçŠÒ†Y7`“(¸�J!e»x!‡X Ù³>â0ijœŸ])Sÿ§ž©*JÞÜ�‰õï�ÕvÐ^§*3ì¡�o�Z{ÑèqF/öh›¤SS ó¸ùkŠò¨Si�'ys€V,T }™S6_#ˆEä‡)|¿À[Æ1p ¸×"ñ'|Ò~†Ü”b�AÆyÍN џûp˜,ðÆ'ÛÁm¨Ÿ©Ä:øÚøÀ,ƒÛEiX÷5óJ¡/�¤ŽdHÊXrH¨ pRh°Ð½ú9†Y—Ë_E©Ã|”ÄI��3…r@ÈÙM£�@v%þ m²'vð?áø˜cÐæT€©5p7�UF-g¨|àR0n"Ô† è v; îm•Ákõ«BÖ&×ãc¹›œa — �õV9ä‚ö8Iïõé—û<ÌG㮫ֈ}ž<ùý S .?“rÝO’ƒvïü¤añERr(¿‚ál™³�Úœç}[96‡¨p3&\תâOÒl.bZÊÁAeí7¿þr1qSʦΜªCyÈÑÔ!g´ÈüEFváŒpVh#¿�ü‰ãÍ{‰=V£�2Ðk)(F ¦ß��Á x©Ü*¿*¢Ìfæ=½è7êxx1•ÌolK6<Õ SŠU'QC‹0ðÀÆ)XÿF™ 9”ßrLX·‡@Yw£ì p6ÿ_ˆº¡X̲ï”fÊJÏPòð¼b<+3 ˆ$Ãèø‚On™ISk8+BnFÕøêÁ«Nåy#ó¶:E@iù´ÔÒë W®¬ÜbÓðåÙå1ÐEÊÄ2�l˜ZÁ$‰`Nž,ø%>J$†¼È [¤»Èë`Œ¡ü„é–,Ü8¡ŽÏ�B"'"ƒ‚ˆû_)¢Abi›¯E²Ž“Eà`-OiŠÍþýŸŒžï†Ÿ/œ^ÿêSã`tùù ÅÒ?½³=~6[f�w—£Ñå'µ/)ּ轩õðw±ÖàüÃ/Z5V`Ös†½þè¼ß»h4s�é”Is å9@¤ŒB+5°Ð$ÆYox*°ë±9à5m)üáØ 7ÛHäV±ÄÖ,xÿ²ßkÙ˜¥CÁ­zó(Þ;—µlÙÊv„a‘]h’zw‹(Æ<6· [æô„I"G»úÃMïÁ2ÉënF–SÒabÞfÂæ{Ì•d^ÿµÞÊœ Ü yòûZÇ„6ˆfèø{nD^ãE‹$þo4š^`³ÉcJý«‹ dEæ’˜¦Ë8$ø€W“�ŽÞæ#XjÖϨÆ\›7$Ãüè->aH.—„ýªöí¤H(I"xž—Ïhʾ4 2’LàXÆ%«4…Bs8ªñnèôƒË´îGäöðŒÕë± '¸BÃÀ´e¿⃱Hã辡8Õ²^�Šß”!R\nàɇP´C-ò)Ô´PG&ˆÖI!Ï%«_ ÐoˆÑ“J'¬öCi–ÊýÔÁ”·Ã¬k£`)žæQlÞ*V!ס´•Ú9Å¿=1¤SÏ&å”U±Ù�þmÅ,K©Æ6ÝwI€·~â�¥3ލ×(ê™Û]4æM¡:’!2†@ètWhž¯/èF\>Õÿ@ñV8n]nȨ�%YÐuœ˜4bƒ“4²I!ÐÓRQc ®l¡m )È[æ5'þ,t…á•8z+rbzü¡à•7+ÉEåu+6�;ଋžÜª8ÖH“æ5©“,`ß›ÈÖÓÜ*R)ÕÕh…½âbè,Æ™E¤6ò*Z«h“± £`6Ã�ðµšR‹Ò²±X†ÙÕ„kk1»4• 0³°À@®¥YM%gÃçžYóO6Øg¹©'0Í…7nêÍ­³Ím¹o„|0²é UTOEÇI§¯ÛEì߸ñ½x¤ÏUÐt O!‰² ¦Á ¶,�ކXõÉÛ6TЬš)°€w”ùuÎ�bßì¹"*F ~}t¾\>?wO!œìþÚ°õÄiudá†4ÐI•a¡O“ªú°Fæê"³vj©:2 IÜR¶óÀ%Óe\Ìl±uU »?õ)_zõaz�[¤ÀH'mc‚ÖWB9vlG†s7™9,‡4õÕùYe.¯ÈýK„–t°÷£W¯ï^½n/ÂD¢Cçüý ÷aM¼a"bñÓ5ò‚Óþ¥;èŸ÷?èd?xï:lg„>0‰�ÿúý_gœ³î¨ëœ�†ß:b€+®€â_ü)ßíò©û$ixy5±‚¶ 5Ô‘ðt²\è(;$�dîÆt3JàùSÅVÖ%áMsùÍí�4E—£¡-Q˜l*™óÚ Uý!–ìåÕ¤ÖrÔ”µ^»Ócfó­>ZùŠï?­Ðp¯SEݲŒiÅØ¾p¹[­ÎöÒJbª¶°Uõ˜Œp×(üsÑRÞ“98d÷då²M Lžäî$n¿eoc:�b¾��Íϱi,îßDh�]°Ãn’Džïb:‹MI› çÑ2À�²+—„å–ÊDx4Uç™”°ÅwüÍzpRÓh¯]Vo‡(Lºò �ä2ë 7_Ð8µ}«3‘…b@&,íŠKVV¦¯ îEË\8®‘yø^6/ˬ‰Ìõœ÷ß_b¢ˆ…ø¾í¯‹ƒÜͲ.4Z–ž<ù¶ûì"[£±š˜ÒõZ‘üSS7¶ÄœjÃlÏ‹ô+ ˆ˜²OüÄÔ÷FUMdžÞÊ­Ví®ìæD vj„Æ —y¸ߨV$³ça½ÒîZŒÜz>Qm,ç³:�/¹%RÖ�‹B`„›óFÍb”V…øö Am–$MŠÁ5x¨~ óÔº"z4k¡£""E5ËöýÙÏ™Ž‚ÚäÄ¢—ÙSC7¢`qõ¦1¥EÀ²âè¹ku¼Š8ñ�‘1–õÕ‰ÌÍYdxCœeËgÁ��K|—×4q©º¸V£±™’6š…ÆèÀÆ»B»"ïÌL’‘fO&q´h”7²!dÔ©,Ðë¶WÃnm6T×5¨Œ÷ºÝÚÕð^Ûƒ ïu J&ò²¤–a U|ͦ4¥+±çÄØið}Ím×o÷²r+N»�ß,ø¼žØ¶¤°Í%ùÎNÜÒç¸>E"¿‚?’½ò…ØQŸ'¡ZdBžCÌ€;_ 2Û€ÒFAp„]­æ”M2ñc˜äp۪ĂëI‡¼xY­îKV—ü­êçÑÊA´ÕaIùÕå¶Ê(lzNŽÖ"üÂÝ}ü�u!††Äýß/žÿ‡—$á"»1…Hzš2�ïÍ9øëä E¤–n  6ÑH’­…dZ¨m§:BT¾„g™Ú*©°m6-l/Ú°¿¾æ>Õïà±6 �ÜIÒä1¨˜ó›Oýhuž_Rn[9ô³-¡¼HXóÄsªlG,[Ë,—}1bf½ªÕ²Ì¾u"-ßǬŽDJ…¿v³ø†Mö+/^×[²¶/Wû�&»,©ËøâÅ2/Ë%é!üÉ$;õ)nOd´î�<=5�äÙé�~ãBw¡ëpòð&/“69‹ÝîpŽ¼ë£•Ï®ßµfEFJƒ«ÐßU�f•:ÏáŸV'ðC³N*¢Ñ1—Àºõ¹ljÄkÕ­­�ÚÔèyýúúXuö*|FKÉÇž«ñ±$™0f²¯ã¨ê¨•²4†¢ÊÇ[ÖVc”^û¡­+±MŒ¿NÝâøKY}滳ïÿuS¾ý Uøˆ ®{QŒÛ"Tõݧön¥™uH³íëªþÚQTbD‚úw´¢;gB%…«kW¶2^[i_õ^;ŠR.àcxŸò�p[v�(Ìxüº%èvyã‡ì¸ÏjNCáUnÁ©Œk÷ö¡7•g¾z“™† L!/Á¯zH•UHÎU%¼ÈÜðŲ́͋ǰ]ð;§lÝê4«mÂÊi¿�ÍÚ»"ìDT-¬©Eâm¦£ŠlA›6\ïæ`ß gxâUBq�?н‰Óú2ŠZ¸×ñµ™W,DÂHI‹…ÒXÄP²Þÿ”ÔË‚&Yñ…Q±°RÏÆ®é»²вªà Zìÿªt-b! u›âG,¦¨úPÍ¥üÔævó»`k‹½­Fѽýúä[“øtÊšâ—Ìñêi,kõ·]ôR‡yj<¥óî�|«ã_oɶ�«ÐËß…Û–öY'ûbó ñ%¹_huÒ ôz_dF«¹»ª®|]¾†;®¹!�áÐWc˜Ì?­õË·!ÿû ¿¬€²NöÂ;^`Û·OÅÚ—¾6¶óµju±úqc“Ü"ôµpÛÌWÂo²B1û*IÞêýU®<�Ñú¸è’¿ÈNìÖJòW°É£Êqq\ðŽéÑ�»ÀØk›4_å•�enõJ±|ÅQþØÁÄDíôkánǯ|}{O¯ qœ«þÕ°wæ8æ›(p’Tc%]ó‚+!nÚ�Zä�Y˜—?o¸ÆCÛå—Ýa)wµï²4oÉ jHíæÎÍJG´ ÌA8,XïêNÖ†-è Ñ`Ëý¶Ý‹fãâÅ;–ë_ ­ZÄ$>ßîòÖ{ƒQCí«äžPþ§an{;BÉöpµñâÖüÏÎSûe®ÙÖÏj—$f[k^�¸·ÝÉÜ:'<—ôòá®ÒÉ Ð#|Åó2;LÄ«áE¨iêzóöz£öÐ}»¶éz5ûǻݸŽdÚ7íìžãú¨|³ÙÂÍÖÛ°:5î}*5SŠyªÝ©~ÑVA·°oL*×Ù·z4(ïA¿%YòfÏö¥ÎkB‡DîLÜ`]¶~1Øw0+¥÷ó=Ñ5jÍYYí€y¥ÃZ‹:Zÿ ¡¼tôÞC·wƯ»QѨ“Ý £ýæXH„Ip´5L¶ó‘/w–{0ì0›¾ÍToÙPv›6³í¦Y´±æ°�ÞÞ‘·oøGZŸÊQp[ËŸ”†k«Þ#�ç›êü¤Â6î‡Â]3òµË¿oÁóÛO”Cô%”xn9þnz �ÖÓ ¡×(ko��ª“G—`…<õo¨Ø“9ë±;Ú¤Fó·Äã%¸${Otð’/ÀÄß•.2ˆáû¶«Rîͼ•^ú«�+{»ZÑ =èý42¤]ßѦëë�~õ¬WR…ûúÕŽŒÈ¯ôenŠ»ÇV"°’íx¦î*'û�·iÙŸ¨oªa¶�¿è�iZáÀi¸3×›Ä�â]ʯ£Ù’Ý%~èÉëÝÓ”�aÁëܳÎg(F¾'ŽºÞ5 A�¾Ì}ožA”}!CW±ŸÊEšú¦"¤«Ð‡¸–&é–/DT.×To˜ÁL\ò#+cpq°”€}.äËbɈâõnBÛÛDy3|줆ÊêûDç2ÐøŠJ òM¾/Òõ†#ç´;ì)¯ŒdoŒÔ¡#mÀ¡œÖϰ7âÂ(0Àz¦áÈpæ£ß½m1N¦s±§›g}qÔ~"6ï{QŒ7„÷¹ ®À¬ìH�Â�²ÃGR‹óI°�y{­„•âExPÊS{Ãѯ=°Å—çìà{v<ÑCº6ÏEC?¤=™ÅîÄljˆýÄó4x§ÈÝtê¾v_�_· ûH”¼zñêÕëà`ä—F}Ë{n+çwðT�òDøn¯ä3ð‰™±‹âÕò“Q@Gñ-·÷…ƒTåǽžŠ ™å¨¬”½öçïuú½4$Çøýí%÷äèhG¸*J2`~xÿ¯wWï�Þ?®ºCçsÿƒrì+C%‡'Ã;Ä«g Ь½3¡6ݸ>*K§ÿ©&÷Õ![fé{!{ó¾¼­�=�ÿíâɲ xœ­Z[S;~çWt‘ª¼ÆÎæTíl¶â9qÅ�6!yšÈ3²­e<š•4ï©ü÷í–4÷18§Ž«(ƒ.Ý­îÖ×1ìÁÛ¿ê½áÁ°×;€¼[ˆ˜ƒæê‘«Á Gì`(Ó­Ë•¡¿.ò?à8<�7¯ßü¾¬D,Ò>1Ž[áøÁ~¿[fó9„«“ú6Üòü.å9M&žÉD„<Ñ<‚,‰�†Yq¥,Ä/?Ó‡¯\i!x3x Ç´àÐOžœ‰­Ì`Ͷ�H™æHCh°âO!O ˆB¹NcÁ’�ÃF˜•åã© ˆÆwOCÎ Ãå èì Õ…ÀŒzeLªÏ†ÃÍf3`VÚ�TËaìÖéád|qu=½:E‰ýŽ»$æZƒâÿÍ„ÂÓηÀR”(ds”3f� ØRqœ3’$Þ(aD²ìƒ– ³aŠ™Hh£Ä<35…åòᱫ Pe,�ÃÑÆÓCx?šŽ§}"r?ž}¼¹›Áýèövt=_Máæ.n®/dzñÍ5þõF×ßáÓøú²Õ…|øSªè(¦ UòÈêmÊyM„…t"锇b!B�>]ÝÓ«Û¯øõ1^áŒHx÷$nMÂ8‹8ü+ó9Ó|è¿«wLâé´©OiÍ¥Œ[ƒ"i,ܰ-ž8:uWé4”Ê1ñòÝOnƒ»éUpw=��ÞO®ª;c5œ³ð�'Qƒ$N˜mŠn…¿3,eb”ŒƒÇ¼¸4â�¨Åç–¥J¬™ÚšÇ<$¼@Vsæ�Y{´ŽTn<Iš™áš%èˆ+‡„70‚ª(vÎÂ`¯ÙZÜ£Ùš˜môå,4ø%�[hœW¹z-–Ú<¬L¢êbÙS‰•`8„ñý÷;H[®ÙAè/OŽ.ÐDK«=‡›n²AµL”üñ z¸I·ÀºÐôò_-ÁÒ\^O˜Ë¤^5¯¬ ‰hgJéʱºp×l]¨_#°qc*Äü%1šÌ�"&ö"‡+¦šÆ'&ŽF� Ù†ÐSn3 ø„ä›vʱµTo¯ªÉ=¦ð˜£d“ ¡ŽR¯6ÔÐæäߤR™ŠH…2UPÎæç¬ŽÕû ˜uƒ Ù8j…î¢XصyìÃüÂ1Ù¹ ÆWb˜Ñ`óŽ64^7“«S™‘`:‰‰¨bé F_Æ�Äì’1ë–É#úþÖEÏ-ÜO¯6VµÖ.°žšK¦"p@á32ÐYJÖ|QòQD äÙäýe®‡X)� ü«{ŠÇFÅ¿~=é´B 7ƒ“¶c¢®‹ f˨ ¯XÓ `¼Âµ<Õ>\ÝqŸãœ„ÿø¥hFSIKóUçÝZðyl…cMká%9EÉð‚¦ Iu©¡+nœþ æ{lxY]mÑÿ¤²ÚÒ´•v•PÍ©+6òrÚj (ÑÒp¼‰O©î#ÇS«­Ý^“W#Íë½c²�Ot1¿]þSÊÊ|€kc^‘·ÐÙ+‡Z�è•yáN8®§�…ˆÆÑ’òjÐÏŠ]ɽ½à½êP�Þ{W®tÑšóÀ3õ ŸÖc• à± ãèbÝ•3a ±åFçQGɪ…ÙÂBɵ½pvÃîÐrTÇ §Óá™ñ8g˜Ú%”8¦€Ö¡®8WM „£ÑdvT$n{pŽ ’‘´óüÇá3ÎR­Þò¨îò gƒjéÖ"×,îªî·{²ÊÅQ¯ÈÿQÁ¯Œrä{�^9§–Š‘Ä[ÀJ„z`˜F®–ïê£-•Ì–«œ\ÞAù8úz|»}ŸŒ®/Á50¢A¾jDPá6jUÁ;LÒwf{ggõâ#§Ô¸’›¸á�=jCÅ_@”ÃêÓT•k[•6\¼gk6*ª‚UÅѨ‡†E>V•¦Wr“ìQ”NÅ-¸V*«©Xb~sFÜ’6+¿|Ç È\Äð¤íÐÚRy†aÀQä}Ùb®\ç»ÂàÈ�qN#ç\,¶NVp^¡‘Õ4eÕÉ’G¶Å†ö~}›ƒOß]õ,tµ¢²†ÒA[!›@çÿjÞ^t%ò|=¹u[Û½ º¦Ý ÷gŒx(Øßr §úãh…ÞêÚLú¨EÝMT…íù¡Zr8»¹¼iõØÏ�ߣU²æ`¥ 0·€#Å—ÔÞyMS/9'&P–�í×RáN¦…uxF…¹¿Ø”²’CªÅ[®5Sb‰�äM�¬m3ÏÖïö)ÌÁ÷2¶Ûø »<ùv˜bž±ý†Å––ƒÍøÓmL¤Îà‹¾R$Mä‡(Qáfòâ’’© ùµO�Ö¹™ºÓÜœQ'ÍG:¯ǯï&7B¯"y”•#æTôáÊ쨪�ަ豅²2ë*^|•³w8/³û.Ò»nx­CÀ”ÂB»v´ ?7Û�óÒ�ý‘%aÌÄõ>ÏŒÁàfÕÿ¼™Ú½�ü(ñüÇQ¾›n%ÎWªX¥Ù ⊇xà¢>š‹Åâ8…Ð çê!ëÔu�ô ÐK™bkh´rìQj½¡>0�÷‡¨,‘óÿØɬ`LÔžùl(û .)^Ñ¢qGïQˆ:YbD\¹®ÁŠžU…”kå}¯Ï0„¥X8ÌË‘¿B¤Ð]…ÈÔ)~�Ý•ŽT±»Ù¬Í£M©zC YÓ¼ š”ð̓8ËUºÅ±)n2•TÙøw”Ãâ*tÁDœ)î„ñÓk:&û¾&ηD,DÏ ­‘�y3Ÿ¸7¶V²×q}Üj6ÛƒýÎödÙ”ì?wJ÷ïÿjñ¤¼-—îhµëâdÝÛG‚™î3ž4n�ŸE&VU�RD»´ÙVc¹¹"áÈ×$ºž ý:ßvq³¯)4d)†®òv–]ê¤*€Ì>|¡)¤ŠDb…&ìq.JKÙ³rWýÙFT×ÿ°=cÇ“o(fŽtÎ.ÅzÏVO•fyÙBk¹ç’›¼»ÆLàˆ×¹úèxÌùió¤òIg烎ö:ŸŸÝYÀÛ·p… Ãx›ÿ—ÅŸøG¢õšj÷Ëì9”xœ…SÝjAF° ô&JzLý‰KÜ\Tra¨ 1Ò+Ūw†a²3éÝÌ,3g­1à´Ìèxãô |Å™n¢ÛmÕ¹:|çïûÎ9óùÙ·«G“F¢�:f“K¿ŸŒ/¿½Ü}1Þ¥;”’M.fR  ´’~Ásm˜áÀE’1ÃPjõ^#Cz©FE…�¹VµûÙhß(Šp�ÍÙ¾·VÀ�¸WkwÝ»µõE&§SfEœ>&dSª$+¸€2»O3i1N;„X4EªPñ^(¤™Öù°‚Z¹§XVA|§ÂXmÎ@ºÀ¼ðÉìÐ[ÃÐmÆ…;º2Xˆ`d„×bSÔm\_,åpáIèÃîù!,-ßãþ°<ò·~hà¯Á ŒÃèÄFù¢ÊƒSKƒ“zçG½ôü¾¬É­Ñí?¿(ëG°½ cÅ×)Óâޝ Áû •iDÝè5—rxœ�S=oAŒt@(r~ çâ&¢ ²ä(EÂJ�ä2ÚlîÞáSÖ»§Ý=9i)%Ñ6©BI 5¿…†ßÁÛõùCq ®ZÍ<Í›™Ý»|ýøëaCðJ¦Ã+~xoûçïÞýµþîà`o¿÷‰í3½Ê0/$cؽyÐu7c÷«ñÔýmÄîË£ÎëJ�¹Î ÃTpÍm¡ä;èYÔ0ä2èþ¬¬]ˆcÆbÄOè4‡v¢¨�$$°§‘[4À%ð²E$a:º ©’yñ¹Ò˜A®Õ8Д±�©Ý"/Ò-ÉÊŒ=ÈJ« {;ý\�M¡‘’…Uúš�û/gÖë8 4 ú} _¨µÒ~m;ZNÉ–†xÌ;gÁw+ú(E0VWi¨Çž0KëYHC2K²Æ�M)XB;Ü·ø™?_wßcpÍæêÆ]½Ýjf»óæêü^2¤ýê¬uWŽÙ™,lL. ºm¥%ìB© i©«ÀÑùb V•¨é}*‡£Eµ‹£­º¼ùørmžøocºM 7D{Ñu<%{²nùØ0O2"IáŲjMºÁúÇÖâá•Ú t:Г™2+wWO¶W<ÿlj+à&›KxœuRMK1-¢ž¼µ£íaÝÃö"‚–b¡~¡UA­7 éîlwén¶$Ñê© 'oJŽÞüþ"ÿŠIÙV¡m.æeÞ›7“—Ãו÷ûb’ù}šö¾»_K¥öUëœ^œ\ÓSJI%À0æ”æií-W÷HÍuá8C&ÐO˜`*Îø>Ü(¦²T„ QJ“Ë)㬧 åÝ\LPÃ$e}ô/U'¤s?y¶“}šÄRyÑ–~+6ª\h 4r� ý ÎèvãAbîLÀe§Ýƒ£™ð u�ÌöîLÎ7à0gJ�8~>Žô¬i·¼êÌv`…¬�æÀ -…œU�Ê£©B€R‰ìÙ™×Î4(±m¦eWÑá±3e<+�·¦ÀpÎt=kŸ ~ÆMAWR[h ¨ÒÜ…ä`�è»Í3çÿç°\5 8â�Ýù¤È‹àc}§`ñ_EÒCè2…AxœeRËnÔ04ÃHâ±€"ñºLYTÑ4Y°€– M5  $YžøNb�ÇŽl§VWý¨²á3`ɾ†o`‡ã¤�ÞùÞs|Î=×_®}¿úùc” ^�ᇠ�¾ýÜ¿xCs�Ô¢["8±¦+ÔТR8‚cŽn ŽÛ êcÔ{ UK¦ÖKš£™zµ@sÛ¾D PpJ¯Lå›{BåTSù ¨ç ZPÑuŸB(­ÑTJ2. xÏ%S^#ž]¯QZj¹’-¸´¶:È2ç\ê28N•.26YQs†ÌªZgaŠÒ®…çeñ_J†K dþêå[ò‚�8Ýñ.qXó8™‹š! ¾XPƒY%¸±iù¬I¶îÝ�³$�çJ;ª:Þ`îÕš+ÑÃÃV–Øó,Iw}:xx’+¹äEZNµ.ßæëÖì¿­ÌÃ6Þ ²îÞAÁ*X ÔU·Ÿ¿áе”>É4îâ�UTÓuÏ"•ÕâO ã¹ý׈ûΉÀæW4¹ÜÊCéW(pÚÜÝßïL6&̃áÝü9oô’ÚO÷i´ýxSr>3H,ÄC‚ƒ@V~�îl‡$6Çjś٥›‚ †Æju²»>$u{|þÃÆï¢váÍ�ñ�q÷Kàìú“¨ý&ÜÛí‚sxœU�AKÃ0ÇÁ¥ëaŒ©èA|0A-Ø^EwPÜAô¢Ç³6¥aiZ’lº“A%‚_B�~"O~“nÂvIòÿßûçý¿›ŸÍç>JËdøE>–<÷86íÆá–¢�“‘Hr*!+%œU•2ªqwåtQ® n%qÐa™Hi÷®Ï/ñÆAÇÖLÐù–•‰„�R ]΢h<»ÍÚð*Δ6]´�zv¸é£Ý¶sÁîyA†TbWšW4YuȉH95-ïm ¼{{k3 \ ~¼§Ç9"²_v‹�VD’j¤Ò²^d\²tÁ§TiYNöšÖá;8 ‚8 áV0 ÚŠë#rÃ胦R@R e~—7ë�ÍŠƒ\"†øëþ4Exi!üÖÆƒ)¶ÝxœÝkoIò;¿¢Â* «¬t:Å—»µ1΢¼8Vt{ 3 ôz˜æº›`n×ÿýªzzÞƒÁ޽ÚÜ|à1]类ª®™n Þ=Õ­n­ÛjÕ ?ÎyÀÀáœ/:Þ17=±ÞI¾Xjú׋ÿ@Ãk›ïßü~^ò€¯×ðÁeŠIhܘï›ÙŒu¼e3�†(?À{!Èi8ìY&Cî±P16¡�4ô’ÁéÚõðË®´á“Š‹Þt¾‡ÔíR½yB$vb+w¡Ð°Q ipF!vë±µ¢j«uÀÝÐc°åziøX*¢ñÙÒ3í"¸ ¤;ˆy\m…^j½Vo»ÝívÛq�´!Ý ‚SÝá ×Mú¯Qb‹qL)�ì?.QÛÙÜ5Jä¹3”3p· $¸ ÉpM ’x+¹æá¢ JÌõÖ•ŒÈø\iÉg�3X,ª�@“¹!ÔO'0˜Ôáìt2˜´‰Èõ`úÓåÕ®OÇãÓÑtПÀåz—£óÁtp9Âp:ú £ó604òa·kI ˜œLÉ|c· c9æ"I­™ÇçÜCÕÂÅÆ]0Xˆ/L†¨¬™\qE.U( Od¾âÚÕæVI¯ŽµaÏ„çF’ɔᴠVî “èm¤d¡víŒ÷´…‚¥+}Oøh@“2eŒ€ŒÑá< v€1û ºŠÜîj7 pgb£a»äÞ2Áµ›}ÇÀß…î �;"æe$#i»µÚw<ô‚��ÁjwÕ²ž¹ù·€Ïf®b]ûÝYþ½bqMä—”ögB¥›ˆR¸·S]…&5w}6ç!ƒëáØ¹šô�«Ñdzz6ìgÀ·�ìêÝc97l7h7ƒLþ˜—q3v:]…ff]tÚ u©t»0¸þ|ké.Vî[¸al�Á@Šî&ÐNb8r=™¨ BJ°ü×Ýõ6R iìÚ¥¬çÌ \i£è«²b�˜bðÎÔ:pÄìWæiGC˱ñæÇ8�pýF ðB5”m- 欵l—VæUKê¿ñX_#0Rü¿ Ws‹t3_VFŸ$Œ ëÌ¥XEÒ6*%A2±ñ�ò×—c<¬)è åI¾Ö¸Ii£ú · í}“¾{X5ê@2£«\¹ëšU'3R i R©Nˆ†á° ˜Yú׿áüf¬t6ùyèœ÷'=ç|ЛF>¡«~!B]Çœ 7XG ¶É�Óm”2Œ?tDÐ7„V Ûf»Àòtüþì‡7¦Sv«{"ò(Î žF4„@¼vî÷÷%Ž“>fíQØhÖîNR{ï1·a„VfsEæº(½�íËF/H|Àê%œ¹ÞÍB L÷"±þ,�·Z'¤J+ö(8}ŠÉÿ G�@ö³àoº—Qø·sáÝäæ#�ý,p£{÷që]M¦—3ü®yè‹í!Ž)nïSZÁ‚¨œìÝ¢D9 IÈft5èð�—6PÖ#~X×�,Ü­é �‹z¦÷ŠBe ïÓ±V©aªR¿bœºêfˆ[ðp¬" C›5û3+‚ÁJ–¼&bOéÊ~&˜û’òA†¦hfiÀ’אַ|åDwïãZÊ�Tâú·k!©1^1½þ#{[âàõS]D°ÐhÌbÒ~#^§6WBô£²ëØß�¸„ýË·þ"i#ž²s©EգЉ`U¡ýwbÖøô�¼–“^½2`ðâ$:7m=¢+K/o¥¬:˜�Ò˜4h‰ý Rm˜8ƒ‹qÿ}3’ï®f¾ ^³¿"æ{{Eºòæ¤+V*¯í? ÿß¼ŒZmþÒŠísæ%+&­ ¶Œ5²­!ÁJ¦72„T·“ÚÝ3ÅÜÄ¢ÿqaŸêaãÍ*r6qN'“þxêŒ.§d„Ô+©¦mŽTÎÇÉ¡�YS¥¥º¥cŽõ2D…ŸäèÜ1�U�ÊV3æãy×™qj®�ʳ–‰‰ÇâšPj6ÿ¸H0gÀÿë@0>0&ædüÐ0ˆlù0÷G8Ïêv!‚´“¡1Â#Ý]QÔª› h%½âƒJÒã*�IÁ÷Ö�ú”´VÝóÈæ±_�«<‡ ND¼þ$õ¦$‘·òU óˆ”jçÛõñ¤ÒVïVQd„­D5뼬=7¡dóŸ.›1Ò`°^ØXze;b:)ßE&¾å—DÜè”@¢¤NÊ‹WnðKgñäUÂÓŠ�%R§ º5,çJõûïÛ(!Ô)ž›±æn XÎ5FŸdÒRìû†”Z$gº !Ã;×zÐI!Î9(àPxn�h\Ô|l/ÿ,]< b†¸ïT<\±mS4p¡YŒ™KÛ¹M®@;§Sqø3Mm‚ܳk³ûå\4°¶F—˜ÔÈ4ˆz©¬EŒ)Œ~©¿T¿ÔË•+mº‹½hUæËöÜfÍ“ŒŠ•]H &²oýl¾H‰guÌëÙ�/ÇåJ 4|t‘£¿T�È M3¶{©*êxVÎ6TH|WÌSÙ^=(e¢û›ùW«ôå•"OÒ“4Òë5MèZÉí4±^Ѿß7D(õM)8+O2ÏÐ…”70eL³±^•T”3ELJ!;¥îõÁÃx»ËÊ!~||CºÎû÷kÇGíKÕ,GjÕnÚW9sv:\Ë­àe•ªkyQ§‘Ðy‡bèÒ^;˜`l+�cŸZö~,U¦j]…F$5uÊT-û ä‹à¾¡èÄÍV577Цæ;ºoåHXûðc?†Ù3‰v๊©ªç9.-—Ÿ.$K*ótá7;"¬ÇêÑÐ0«.ܵó€s;÷�u,¤ÊÄ`é�ÃfÚŸL�Þé¤_#æuQLïQWP�˜Ò¤o VŠøÌ¿ß ö©Ü“§¶OLò9=ÈÖKW›$±e$P“¹¯Û…éåùeé�Ž·p�ù>[û¤ý× šäK‰pn`@ÄZ­µ+Q·V«x‘æm…PÀ|#Íû /z@eß»0{)ij”êšž¾­ƒ¥&zS‚ÝâªodéÔŽÜ÷æY[ýŽÍºùÊõ˜ÙËf.'¹ýÔ.>;£þ?±jö!Æ�<š=#ìÉtÏ`ƒªÁÇoRçG�‰ÿŸ©i*g–»uWk<�ƩŌ:ÀÅ�Dg‚gI3&•}®!R×ñË>‡rÍÞê˜Í3Åfç„^ ™ÛWk>ž~è��ÉåÕ¸×wÎãÚw¦¥„úGzO ›nó�å-#¼¨#Jèó¹a˜i¨îéA“À,“ƒz—i¯›4¤™)C^ý(‹Cˆ§’¶Ü·%ôObõ�Iœ�:?‡°Ï{¬ÃíM«TæÝÚÀ&Ý÷Ú�½gè);ù{ž-^õ »JEoåU–+=7_õ3®«˜ÇGÞœ·á•§rõæ3@BµAy¬ä3î†Á“3]ƒ ]¿5ýȃÅÝÿ8ç2–´æxœµXmoÛºþž_qà~Xb¸öE/0 Í:Ôuœ\£©]ØÉÍŠaPi‰’¹H¢FRq¼‹ûßï9¤^(ÛIº®8Éç¼=çEõáÝ�º ?:õû'Ї÷±H9„2�E2Üàû0”ÅN‰dcènRßÀixo~zógø¼©( øÈ¸æ Nïíïû¤\¯ù0Üœu�ᑟáJÊ%]_O*!×"乿”y„fÃa\°ª•üÊ•2‡7ßà”6ôª¥ÞÙ9Aìd ÛA. ”š#†Ð` â�!/ ˆMËŠT°<ä°fcåT(CÂøRaȵa¸�Ù2ö73•Òc ýv4Ún·CfµJ•ŒR·O�®g“é|5}�W'nó”k Šÿ» ­]QÈÖ¨gʶ °Dq\3’4Þ*aDž @ËØl™â m”X—¦ã°Z?4Û߀.c9ôÆ+˜­zða¼š­r7»ùeq{wãår<¿™MW°XÂd1¿˜ÝÌs¼»„ñü |œÍ/ÀÑ](‡?Š,@5¹’GÖo+Î;*ÄÒ©¤ ŠX„hZž”,á�È®r´ ®2¡)¤Œ&™0ÌØG­]‹Ðè䕈q%† @U/gWÁ/Apò ŸˆœwâÖÅlJSXcÀ¹!âN®gÓùÍ9 AZâd¸UCÊcLÎ<Ü �:MXwןƧË:xÓÉb9&«Û««éê&p€ƒ—%¯¦Ë_§Ë(Ù¶’ïH cø»y$·º5y€å+ˆA¯Ãvà xgó�kóMr§óËÅr2=°ø)¹•~?L®Ã;ù¶iÆî¹ \;ZFæÜqfevXµªêbø“®šG©¨ éw°"•¡ñØs)s2,Ö�~(ÔÜš€ƒ×ÕEwçÍÙ$6ÊJ‘ª¶Ö`ê`ŒEÇÖ³þç7-B{<ØÓ燆’!•]CEŽ¢²Ã¤80ì ï%±‚9H<[÷©ö)ñœ*{ïNª{�mƒ÷‚þ НäZýÆ!ƒÕ¦©´r¥gY�7íÁ Þƒ,Œõ[øÄT"òãnl0èæÜã&ÑìâñÃŽ°Õñ¾Æ3Ë�vE(e‰ íö6•ßCïеaº#¬…þ„Åë93\¯lÿI½ìõyåZèv‚”Ûy†”6Õ¼È#÷-õŸ4Î5Ý¥²±¹÷Ô°MéI‘®mUîæHu’ÇNw’EØäÁöR®þÅ1V2³•#<¯ ‡ب1ÆõÔ¤y\¦T*RjK e¢= [°BḚ́r–QÊÂטþ£0~E\‚c”~¯3žIµÃ>cßÓvÚË „‹^ã¹ë,¥›Í Ìøz.˜b™“J Þ³ZŽñ9iájÖe43•IBÓ[ËÒ8�mË÷p0ƒxlúHS¢0Vpî�É+„Ih´nÇ»g.ž [‡¡ÍŽ_)ŽOCX(˜ß^_ûÚ¹³g±µ~àhHLÉ’¥æÒWsÇ‚*ØVPÃ$ƒÔ )ÔuR½µß•.°`˜FH*�/&ži�¼ómSq,³Š¡qu§Å†i_‘#?+ž°ŠÊ9�°ž·)…ïgžû›WXÊÃ6KŸ¸üƒ‚>“Y†÷�'f"-ß/8Áq¯³T”¤)µ‰0ÿ+Ç×zfí7jWŒ…%O9Ó\û~iëOóy®�F¤ˆŽÃ+uúM²«O"Η˜\‡�9Mkÿøg¥òmŽ5Üpµ¹±:í�´àâ!\½r~ô;Íïö«LûµæÉo5ÕóQ¿ó­Ë}Ã…wï`ŠÌÄæSÃýÎÏÄ„÷Ø4Œ‰ëk‹Ðs-ߦäÞ�ç�„ÒDŸ$Œ`[0Ý"È; ¦úDbÅ’èy‡ì¢Õº¿¿oZœÚfÍZž˜ÇZýÞUw0êžÅrÅ­ïQÆHDÿ·t#àv²"VÙÖèô¬{DÄšEÆâ)¾�ÜØõgaÁ4¾·"Š`—Å‘;YÆ�)ú€m}ˆÌòI­3"½Q�¼éŒz#�|ì�ßÝÜŽÉÇÎpØŒ{ݹ’«›ÁÛÞ¸w3€_פ3øD~í Þ„‚¸}#äÈtQ”ÔárQš!a’XHmwêÚÀš?[Z3JfÁùÀ i´pn)ã¹ 7¶bþ¨À"jýÉõmoéÀöK=�×´‡÷ÜÉÄb´%ÿ6ç¯K�pg‡XìL‚À+Ω�hž¥ø?jtâ7Î%¦Ð'ŒL�?ÚBDí,Ä7g!°Ø](î¢,rbƒH�àÞ?A›q-ÜŠ`{¤' *ä0°ÇBÁ©¢M@ó¿Ã ¾H Þ‚ƒ38›à?çHÓ<Ø�’W‹­C…¥ —>(—z*pÝSâ0Çg®4iA_c1r‚*ö6Í96¾kiÇļ÷Ö³àS͘|="ð�}&o`�Î<׿C7)Õ‘ •[…gô�ÚàþˆP_ÖD¸¸XÁkfò9•�Â8º=;’•ÆÊµþ‚tЖ´ŠvØö2tÑÎ 8ÏfÜMØ|éÎq½2Y°Œ WAíä&•yœÓÔq¢àYl¹{2F’6q§<3’ÞŠÈüFŒKüK°Ñ™Ô>r�´ÅD²GÂtÈX8M`š§ ߨ Àx"çÜÁ¤bn9ÜI`Áá(pº%KY‘�Z_色è2Û0Eƒ&x°^¾‡Ê¯Ôѵ?ð§îl)Óùh¹1§9q¬b úÉ“.*‰‘ 4ãå¹Y¢9r•éP0sÁ¨�ªhÇöŒ\ñ‚°¹¨‚ðÌ£Ó8¿WÊ“ yèè`…‰+LGN(Ç„2}D|kÖ"*°$ª��Pñ%{0µ7ªÙšq,v”¡X´Ï~=Ý~ìí(M‰pŸ½{„YÁÂ6ù¸N!Ãàþ£^H;Nå7ð·Pj®À=üªž¡° Œ‘õ¶*îl€.¢µL¶E’‹Å§2oÁ ýbyK@¸S#•+FÌ< ‘y›Üç×9ªS`ÄÊ­,¥A.-MšêUÉΩú–J¡€u ¤ù,éIøT¹Ož~t£Ê¨ž˜¹aDIÉ}ò.ˆEô:ÁÌ þB~ÏõߊVM‘^sÖ1!�É„…Ä#f›Å}Ëùè¿ÿC^ÉôûÍèCß|Û]™·½Áøå¹ ~jcßÞbx«a–³„,+¯8F2¿�Òg?¶Û #‡»;¸}¯cÂ>¸4ŠóÔòØzÔe!Ò(š,úØßùµ;4;Wx@bn]#ïˆPX[ÛGß´±º73*‚Ú!úÒ�d[t÷FÕ󃼓|‹á¨zþì¿ák'ý­ÊŒê‘’½µ¤«²cÍÐ�èÞM§+³cÍÐ!)ßUäeú]=ôtÊGÝÁ¸7èöë žu‰è×}ƒϤP&Ξ~2ú‘³çú Àbz“†Mr!4!ªâ¼yç&Ž0Y¼{A"tígi¶–ÉypAîQé"y×>æ »ädUÀÏ;‹�qZ½Ü�pè9–@E KU¹rèÎåìõCõôUÉô•˜¾¾ºÕÊX�,åqâ;o²Ðþ| †šT`µ¥zz\ÊðÙkqnߥdºiÇ:ë�ê7*|éòÆåÎ,lôÒÂ7óo$=¿RI^êbŠëòèñùc1/ž+Ldí1D‘”p—åå[ßOfÅ�ÛözEjhaŠ;Àïvu€¹û3¢x XßÈe‰?^ NhQšu[�*ÙëóI]œ64öMù’ìñ~<ëcùKb~ù‹ç�xQ!ï%Õ;G†R+q{Ûùà–½VQYŽºˆh‘·üj–¥w&Í£² &Tv‹m7H[Wº A¹;‘¬šåÎ\ÅЋ’2!Õ,vïÆö<‡&ÇwŠÄ¶˜è˜è¾ý¥kŽo>�oéÏ~÷z|‘‰° ®«ï%ÔgéüÝåvX‡½_Þm‹¶êâ`+¼onÆã›÷{0¼æÌû È·å{ý�Br‡N­¥_äŸ?V©›¼ÑϨQšêDUµè£*üZ^¹�^?+—V˜ŸÑR˜Ý>H¼KW¢XÞs¯ƒwîÉ­êƃ*ÁZhTjUìS+ÖS¯(¤7ß4[Z‘ClÏZ2Ê.Hoæãͨøgj2oìBÑ3�]+³4åŠhž1A’øA5œô;ìè?;PCM§T» ÿ¹v#‹»b쌣‡‘®M¿§D¨O£@»wŠS|®÷¨‰`’CTá㵯`°�yyÈÄ ;’ãb1nºà7êÇ%­z\K:ÉÄ _IäK­ìÄKTŸ·kqÅJ^WŠfèÒ¨jìYÓ\‡ŸãT@ÚÃ<§Ú�`nݰõ�Ê­ìþ¦Mk”³+Ñ+ $D¦ R@¹?�ð#ð§Ž» >oÓ~H^Æ: |½�—Ê�ö',y—¬Y®ÛêøvŸÈ³ÙWäå9À�±ðóüoø“A:Já×÷$U“øN¸Amׄî‰�ª´Þrœª”þX½×Úwã[ËnݹGµXKÀ_wú£®¶¿jiþ>>Ý“÷�O%'êq�0-ß±Ç<æå?]柣+?ÿëOy륋2ë5tÝßK.–ÍÈòhæ]Sñê»|7tíÑœ­yå!ãáíþ{t‘ôœÈZ§Y¶e?¼h—nÙ‹óö�·l�xúžTu"mòË<Œ( ÿªëÃ7oò“t“…‹eŽ_gêƒ4ýyõâÕOäó2ŒÂ4%=ÊhFšWü÷—Åz6£]Ù2‡Á�¿‘÷I²:g’ÈEèӘр¬ãpäKJú©çÃ�ìé�_iÆÂ$&¯º/H²«Ñ:E›dMVÞ†ÄINÖŒŽ�>úݧiN˜øÉ*�B/ö)¹ ó%§#±tÇW‰#™å€{çN’¹H¼\2½Ìó”�ôz777]�sÛM²E/p¬w18s†ç8–#.ãˆ2F2úßu˜Álgâ¥À‘ïÍ€ÏÈ»!IF¼EF¡/O�ã›,ÌÃxÑ!,™ç7^FM²< gëܘ⦭€È¼˜4ú2˜4ÈÛþd0é ’/ƒé‡Ñå”|é�ÇýátàLÈhLÎFÃóÁt0Â×;Ò~%Ãó¡ . C¿§ÎØ Q”4àr›Pj°0OK,¥~8}˜Z¼X{ JÉ5Íb˜Ii¶ .)D…«0÷rÞT›êý%ŒýhÀòs-]6´¦Ÿ£p6óíÉßîò�¥Øf¹ÙÅò`–$Q­†˜m7Þ¦ƒ‚Ã<Žý$£v€u t�‡1%_.ÆîåÄq/‡“iÿí…£‰²^¾IAaàÍMÖyº®°gƒp#4~' ¹UÙd­Gƒe¼=¯ó§ zæùW4zòe^ï”\ùI<&ˆ­ hzë(w¬75»Ðf™ÙyëÌ+3[sX²«0ïÉ_® =ô‹äœú‘—IMz�_DÇHÎAÓÈ–5¢]lãZû9qo¢•wE35ÑÍÉG0 GŠ#° ó+kµ1Ä©�|ÍbNN]�àšÆpIwY5kc…NN@è«’uÎÈ ˜£JÊ· s4 p°è¾* P? €Qi;¬É©ÌX¹Éìwêç°ŒmùšÒáÝ`« âgŒJÂAq…Ýk/ZSÒ.ßË×I�vÑØ*Yâ=&K†ÜÚ"6·  ­Þª/÷$½½älë÷´�{)åРåá _áЦ£óÑ Ó4‚Eà™×B  $¯¥?ûœ°mã62góii ƒ¾HûÔLDéb�€ë˜—eÞÆtŠíBme/÷e·$õ2F¡iÁNÅTœx½"Ê %Âø¾QlDËçaî›tÚ|Ñ„‚s²%ô»êËù°ÿ‡¼–2x;ù|á:ÃËOÍÆtô¹ÑááÒ9ï¸ðÙêTaÞŽ¦ÓÑ'L´Ô!/œwS¿ëPãÁûo¨Â¹Ò¸¡sÑ„ž[)žsS2Üéà Ÿ3"Ò ”Œ&«4°±*Œsgr&¹s@XÀè déåúî*�¦]r§ÞbVL|8:ÛbéÂÐxëƒ%Ù}¸óøÈŽ­í‘8<»œ€N€(M·±I,v`e€ÜrwœØ2hxyqq¿®ÚÌ늊>Îùž&îXV4_&Á=7éãÈñc=ˆ°’èµ�o?£�Ã6í9™–ŠuJ‡„"úòìW|U"°B$rgH“7Vp¾`l8úà¨XSI$˜k”,0_ð½(JüæKØC†ÿ£É¼iiµ„†Ü¯‰«…K¡�´`«š¯³˜/£S]ÇoÊ™­ZÆYê0áÿ�÷¸=­K@ÍúËöÍùK³"ÒÀL¼acX‚´¤�t\�@óíÄuÆãÑFò1<çðàaï#Ê(䵨0tþštÁæY²iÒ)ájbºÕ¦*ì‹O¨œJÝÅvÈ3”TË"%•mï–&¦‡¯ ê‡×¤‚I—‚ý°5åã0wçY²ryàhÖÌýY©õá�a�ò]$m•v)ñÛ£Š–š[+�œÖ [.ŸÊ¿«y|X’ÂàŽßà˜ÆR¥¥Ïð�z$Ó²Êö̰â×! g‘Æ»F[Â5·2VújŒ7-i±E†ª6LèçŠÍS!<»zÌÃŒ�~Ä+/÷—šDw+Eé5�ÇÊFM�.n“'SÈVnÓ,\yÙFv™Š{(R­„Ëí Š¨øà4+ëÁw¼h¡þ:Ëpí ð¦Nšƒ•ÔÄX`B„ñ¢koIqó~MtŽ0iü4ýè~�?N>÷Ï ÅìuÆè­«/V둌…ÿ`¢ÛÄX Af/4ï’Ÿõ=B5¹Õ[²'ÝëüøchÆ–}1Œ»J±»6å¨Q÷ò­´;$liô÷Å:�¸ïá–{žiæs²Zc¾O9&fÄ»»ˆßºbª¨Ô…6Ë÷B¡Má”·†Ý!çaÙ²™>©G7¯8sØíâeÇÊu±ë|?Q𒨔Ѡ!âMl³¯šYV$�ˆŒ9´ôÓë‘wƒß>9'dŠç&ð/¢ÞÕ¦[K ¶KÕ:DÏ:Îèü Û*R3p1q¬cc~c«(¥°p{šëzV+=Ú=/ˆ³Z•*%ª…±íÈü°¢—á윌κ¥–håØ^=Üí1He $X9ª�ð•Jã`oC–žµ!7?;[§�‡gX¼ø \¾Â6£à«ÅÑ/Ï™,³g`&«äšÆ("c‰z¸7ä1†uÉd™¬£(T‰�Ê5•»G$lÜy)¿97ïVòÄ�EÞnœäá|£…÷ƒ �j‘9.È¢ApšGÛ›¿1«Õ…jðêML¿Û×|÷lð@A­i5Ãw#gÜÿ\4ä¯i£C*ŽL:°ò´âöñ7ܼjõªÛö»Ú~X™S²ªE5•QÝ8÷`ÛîîŒÊn°jÁtg|ÈÖC‡×-À’9jÁãÀœÎ¶�+Ia¶\²„¬òrÒõŠÓë¡×{«A¼†¡D°c¯i‹\ÛöOqhQN DRœg”ê©Å­*Ÿ]$¾‘¦(s·î[D{’ò™(Dc¤¬BkeOƒ}—œ‰=R´!¿c‚Ê eÃgN¡Ãgu�uÒ店Ãˉsîº?ô’¾Ç8Ìh·‹FjC/-¹ �×·%ç–í°‰¤(ØÍ½ˆÑÂÅ—‚ÀlLÿæt8Šf“éí±Pð$žucL�½<Ç 4¬8¡Ào|�é iÈÝs£ÜÛv‹(ñø'ŒšfTÎãö×\!Žž�†Óþ`›ìÑ»Òíd*:;޵�è!sÒ/µ‚Ö¯wª§ÏEyA•&êe†ªs.¨L=N9ÄVWÞWݰpýXÅ‘ÂÔ,zM ÚÊì,!º6¸Ré°GËÚ¨© _ìÛ@ßú“‰3ž6(áˆÇàÜvlSP²u•�}Míu¶VëÉÜËî¹YÒXn@ˆ L„µ±StAx9p�K$ÁÀ Ï´Çß<ôŠÁŸàrö¥¿VMø.JóÄÕỳrksSû«-=,ƒÝêA4Ïqg¤æ–a›@ïáz¸Vîr=w“Áv fmY­ÍÓø¼¹úAV-Tvû;‡ûvB.±"� /eªkÖ©—y+ÃfµfåŽv»¡Çºw$œÎãûœ­;[�ÚÖo «ÞIÓ<ã€'{ˤՓ=kVMõMÖeBP€½á®•ÊM–~‰ 1ªÂ·ÚyΘ˛Aü7U÷ÁÚ%Ä÷e¶ëMb,v»yeT‡~Áéyó¤a2�7Rꬑ[u¡Ã�:“©{ÖŸ8Ú�~¥Ãä—ÇÆ´ eâx&ÎT,ŸäáU%*¸——ãۚɔ¯®�7¦À¨H/§˜,i”’yø�ÆY™ºvű˜:�¯›ª¦ûÛù{÷¼?í»çƒñ„ßöúÔÿæ5]Ž!5…VÒè±¥—ñ¿x¨†g~Ù[DgþZÄõ-»¥3T2ÿÕÞ}u‡Î¿ÜÑØ;ÓËñPcŠßì!Ò2ð˜7]ÄK¡Qøt·scø€ÃšX%­Añ­�G5Úù‘h“À ÙÚ�£nï1iyÛºz,tæô Êò>¿ZÑ”¥ý®¥ª/È,é1µÊé*×vqSOôä¬'¬OäKx}ùâÕß;¤»¤üOy^“üôOød¾QìUr— CãJLS|2¨òŒéƒ·Î“æ®ÙvH‰¦¬pÜý8–wKÓÕë!üØÑÅ`c&1Æ €e™…1æŒÆ1pøÎàñì¹u­*€•¢R{å"Ëä [•Àj‰»¤þжñ¦¶q= Á›äú=1[u�Ôª&ÛgiÑžÆKp¯Ïrv/_ÈL'¨äŒŠ[½ÜQÌ[f8ÏJ®#„ $°o×g»‚(}ˆv$.“™{iY½j´óŒ»bˆâàWSÕŠ¼äfå‘Ù”¡8Ú²ª¿®€Ò÷*XÜ⪕±Äü&ŸòÌ0"H3J»Q ‘"„Téq¥H/�˜\¢T»þ½3LLMþ¢‹�¼½¡xœíYmsÚFþίت/#†Ô3í‡8É„€l3!Ð"œ4Óéh„tÀM„N•NÆ4ãÿÞÝÓ I¼Ø©í¤I§šL{ûúì³§;ÒnÀ³‡z Ñ®µ�4àÅŒ 诤å¡@É<­c>_HúÖÍ¿€éÕáøññÏðË‚<Šà•˃ù^}¾˜§Ó)ky‹zÕ M~‚3!æh0èê î±0a>¤¡�>ä‚A'r=üÐ+MxÃâ„‹Ž[�Á$C/õr±),Ý5„BBš0ôÁU °+�ExžXFwC�ÁŠË…Š£½´ÈÇ;íCL¥‹ê.Pí feEp¥Nz!e”sx1n7uÜ[d‡@+º“›Å£ç¥”Š€H,?�Ìrº7D¨8©ã<#+X‹XG+!©ú!—åÌJ> ºÕ÷øÐ5lôQ¬–ŸìÑg˜¿X›'e‡ë‡'Ä¥àþ�\·9'Â¥oC›iªâ Á< vRhês†©\×òmà-�ØÍ}É,ª¡Qø �ÌÆ¥ %‚Ã4ãsgF¥˜·ÌO3ÛqJã7Û¾ò¢v½�0`W ÏQv3&eŠo9Ú7Z2Mô¤ÍÌMˌúc4·=5ôå¥Þÿ Ýà¡�fdK<”ôjcŽ\Gl_ôÚC“¢A6sc\dzRÖªüýÄYR`úÑ­Ë}ïëÙjAÇ͈6 Ýb*5“Ð=÷qïwèå§sÝ×ëí~o|~\§sTº'ß9�ŠÊs7�f ârÞ%Nî�®«ÛÏ®j)©G�òb*ûL×ÏHULÓýBˆÚÃTþ§éWGSÄýO.0Á7ú=Žöú,J×¼©Ãöù8Ó”xÛI1%@B©Ï,ýÛÌË;óÝ<³ß^]O $Ä~àõV_J”ãlr[ZFû2%Ãßÿ@êe}þx©Á»½‘#€„» ¸nVíJ¥0-iìXoÊ>`»YÏ-_ÚÎIJ'N·c[Žmá­yh ̺ºÁT1H˜Ü.`ù¹#Ûšd£§3RŠ˜Í!ì²^<ì.J½Íx®`¦»9…¥¡¦Ÿ*ìð¥ºüáeèFI[R¾¡µ6GáLì�¹í½dZjèÑÎÁ|c�û§ïœ¡õ«3;ckr1šy¸¦šïÒ&V×{¨Þ÷w‚íœ@‹·Ä{ÖŠžÈ»e±ßÈžŒ_w&Ýó’þ £µufíRØïŒÊ =¯æöËÑ'x ¢a!¹ú8{3=nÛ¸ö¼o£ìµUŒ'mó%¸Œ$�"=ïÔ”H•f˜…—eçôsGw4<íŸ9½þØÆA5ÔK2·¼—?™ í2—Ý–¼’X„íôOÇÖÙg¤³Šü¥™X¦€¾™Ë7¿B¿H&÷:“ÎWÈãéÿ‹é·O·0ÜĨèˆ=Cí€8ýæ1P™ŽÆ—ÁÕíÍðÉ_îǸ¤a¼|!œÝÜvó‹<Ž v¹ä§w“ ÷ÔZé:5LJÑ?FIPÇ8ãígöó u[µÔžsÉØH2¢NV›ÐìiÛó¼3’”Ê�,kß•v�d·MâNª¿�DÍ1©Ù‹‹ý’rwÄò™Í.7*VL™R†óÆö\iQ‘H»áž½|�£¥;y¸0ÏæOKo§ j-®»ËøY”)Õè’”‰w €‰Ëy0͆—KÎ ædjù²ÿ4$.tåGÅšsYòoW˜í3ªÞmEjÌP˜º cT(_+9æDý_eîoh ^"ñÃŽˆ¯’E$¬øïÔ0è-zG´à¦ÿÿJ`îÿ=Ðw•´`©ƒÁúª:–Nw“7—píaE³t* S¯/ñfáø]þänîçý³÷y3ïwŽ}–ÔŸÞô)C’ݬͧ÷×~ܳ½?KÁæF±– xœÝksÛ6ò»ª›6–G–’ô¦âk¦Ží´žzìŽe_®ŸdŠ„$œ)‚GRRÔNþûíâAâAR’#{:ÕLb »‹}ààˆü¸¯9 ŽŽÈùiÂbJXÈ“Ñ.è¤�ð‰Ù‘…BzViþn0X­Vý@`ÛçÙtË~ùàêòìâzxq «÷ILóœdô –µã5 RÀ( Æ€g¬ÏH0Í(<+8b¼ÊXÁ’i�ä|R¬‚Œ"˜ˆåEÆÆ‹Âb˜ÆÈ6;Ë‚„tN‡ärØ!N‡—Ãùty÷ËÍýùtz{{z}wy1$7·äìæúüòîòæ~}$§×¿“_/¯Ï{„»`ú9Í�@“!+i$ø6¤ÔBaÂ%JyJC6a!�–L VdÊ—4K€"’ÒlÎr\ÒŒLÌæ¬ ÑäÑ… þÁ’0^D°ü–´Î:Æ£±¤(Ö)Íû³÷FkÌÆã §õ×~˜јóØk„ÎvÛ*X5Ñ1È8�ròLBŠè„%”|ººÝ/F÷×ûÓWæÈ8´ðm„²ÈsVðÌ�oõúMGùŒÆ1dÊÄŸÂ`J4<²b þ"_ª‡«x<ÎȹãE’(vÇË7š–4ã‘ æ(ùÐn3Ðƃ°-‚¬âQ"‘'F ˆ _d!=9[¶‹œÓ02µÊ_e»Ðx‘! Õ*Zð4¦K KF´l`_…ÐH±`d Ϩ ø ¼Aøx³äPȰ$�` @6ÃÐòs¤šÒ";i§Ä‡œ³<�ƒu ·âX$ŸTp«6 »~ð¦11™ñ8B­Â)Lâ_å`E šM‚�ÖÌ¥F[sÉ69Õ—‡Ë�ÍãZÞê’É&sï4v`£‚8çœTR€¥6™\<ްÉd°@}‘â ðäÄ‚š/‰„ÆtØ#cªÙóÐTʈ?‘lÍu¤†•mÖ{ë*%_˜všznÍŠÖ«ëeÛ òdÛKG®®Õd�FÆ�Î/Y~âò¼¶‘ùþ­Ëzü°ÈýŸóŸ+�@«¿š±pV¡ ë¬|x �Ò¼•ãì6_½ÐÀŒ…áiÄlb…aÊf®gz9 ®g KŒ³iëE‚<ç! СªÈæ)5À^Ùr�±²ºÍ™ãÓŒ ¿ªät¦ iÁì€ ‰@%] ¾Ši45؈îÊ_!ü˜Ý+î iÆ@׋YP�" ä¨I=ðfHi‚– €3h¹‚ ƒ&@Œ^“pqàЬ…Ô¿‡Û5Ü‹5õÑÝ%°òñI˼h`Æ×{šXA3fF«˜c 6é�­t“ŒÏK 8l’WLº'î$®a=ªðäéô|KÎ 6eId‘åBR̶[=ÑI8Š‚"¨ÚJ‰ZÊ@Þie‘‹À Ü$8€Gµ; ñªÕ¤SZØLýúY-‰0QðÑ ¦ÄÊÑ0BËqÙ; W˜Q°‚;°c+ ifü¶%¦É{Ôúoˆ6ÿ®Ýwå žxKg7Xš,`ÛÏ7É÷®¢+€‚Mi²Ëbm1­³NÒ 8*TlÓí¢QÚÛ#ý­É€´CoàÛNà-ø �¬ -=!€R?¦ê¯ÚÂU7ÅÇåæ`oÊ€“ýAMà* >­øáŸÐnëVYÐöõ•¢2˺„d_Ð%ƒ“¶tËÉ7 C^èÕù£Ö&,ߌʡÞ�URÑW‹ îBr«ïôj„÷¨Ìz¶¡zjíu·Èõ}%½–¨zûÆ¡WR«“3Hég)Qmƒñ§ŒN<9_²¬X@49‡ø•ƒIÃJ‚Ï{ÔrŽ#7htª£v;�¬ˆ¹l'DéÔ.Tˆ!­$èþ®Z7Rk49¨ÐŸSžaú!q}béD)49Þ×6yøÚöúøA×<œZ‡A”%£ÒÒ=øs§(ƒüx ãû˜O!ã…AóððM� Ëùä°T·+36!‡×÷WW¸&.ð.ä‹Å"KvPÝ�.Çï+ÜAÜ’‘ßÝf ©­ÕŽ« 9r˜úm®€d�Ïrü|·ÙLW�ßT_ë;üx)Å G§ÃáÅíÝ¡Ù�á¾B_À”–BR}j³ û³“á1†µÇŒÚ€+~šx`¸ÈQ÷ j>ôÓ¬gÐÿ�bô‚âãD"¶üÝ_ß/ÎG£ýÃËdÊŸó%-™Çn¯D£¿=‡Ëï®E3ÆÔÖÚí.uÅuoÿö» OOÂ7…£†j$Y™Ç�6¬´ÄÌšš˜b[íZ€^s;=õÓ¦}›} ƒ•æ{æ–M sÙ6od"7í µ8ÕæÒ€–S×ü ê(ïzØ*½ÖÕéÕr©â·é®ýí›ý9ìç³™gb þ# ]9‡t¶3’îâ4F?¬ã¸óØ`¹“™œ’ÔÎM<üñ´¡XøJ³ #�;#_B`Z¸^å¥óÍÉ|‘xÀË ¯"²dA[ޣݓ²Ÿ¿ë¦¨c×·2vYÕ(ºõUZ]�µF­â�àu¨Šã2þw�VÕŒª ™ëÏNÕ��°wÔø|[—Ñ6‡gú]ÍjeÈ>S¢m²iK>¥‰ßf…=\Më;ÎG ^‡†£‹ÛÛ›Ûé| XŒg²|j¾M{Dü‹à_ç·ÛËÅ÷o;ÝŽ—îÛÄnG¤OœKc³Ú耺í4¬Â©Bu«Œm3S�EÝPêè5Cmzäæ:ÎÀuÿÆØLc‰>ЀŒÜ/)O|ÚÜþÎÕMsk¾k¹]~üÇvë6ö¸0æ>§ØtŒLu*IV]Û 5‚™ï6o—*ôÌY¶ekÓdzcÓšIc€‚¸d9Çt?$ƒ_¢ÖLGA‰ŽÕíë:�Ê‘mâîbÑlACÿ8°Œ†�óþµÌssl£.†¤mãÓZä^PÞ[$@#¤…`KÜ=a¿òK>¾#guCMÜJÅÝ>_°Ä9ak IŒD”z<^L& ^ÈŽTQaiÐÂÆ‹c`›â&y€°JeN�°Pž—·¥$Jl%8šc"\FŽë›$^“ÕŒ&5÷ºð:W5o‚ZK©i+ØœŠkÚÏÞÒ—Øš5•’êÜÑYóà{Íî¾¥Èó!¹.Á>øÅÁs¡úžVïí¨ÿSJŽ!œÝ\ß�^^_ÜŽn>VžÜš­ f¯éŠ™¿¯Ô¼ cl®ã˜ê@Ë6u<¯�gÚŒê�ˆË57í¸ÿS<‘Î ÔqI ^Á”Š£¯.JEQ"##¯0HL0`ÀRž”<íèïdkŒ¦ ý\¨vég‹z¬{ÖÈS›‹v9 㺚–9[µ†s}Qqê‡Öùígí8Œ–C"†¹²øÄŠû�#ü»×ß¾ªfòl±c8–M›A|Óˆ eÕÍáýÞÒ2"¿�“pkJÄ^$É|‘¤ÍÈBž)g "!ñ>‰o£áUx Ëf¢:‡1ÛC°#_%òÝ7â}•@`‰}[Ž1”Ú¼�´èA¶‹'Áî/âû>,`m�ÂJv˜k„6¦²Ÿx ŽB‰ä³E= •Nûå¡É MÅ+ º²E‚/ª‘/¸á=ÀŠ„ßGNó¿ˆhD1«^B0£Y^Ñ`‰l �AUÒ®ßáÀ#H‰E€—¨7þŒñðØ2˜§"6\¯‚5Ö5ä»qJiõÞ"Ü&Ç×/På›[ìиŒXEl[JjëÍÆ½* ¸YÿÊÞ¦Ï_Ñ‚¥a�¿8/†úf—Ò¹£§¢~ ¡‡[BÝfûªÕlìs ?|­³6¦‡Úà˜Ô ÌÿÔXÅhªpj0[ï®xGp-w6œÿº6NUŠMÍn‰ÞtÊèœ/©³ïÔ@“¶£š—Ô¨J<“s/ï¸æ¦�ˆ{ŽïÞ¹ûä>�Ä1#/Bx/vÍy¯æ§œ°Í÷o±C¸sÚóô”g÷tg—Ø»> ©oÝ&-Ùk¬­wHDÁ«¼0vaÄ-&ûúáÎWdQ†ÿõŸ8æÇž!xœµY]LÙV Éb“áß`Â5Iül@)$N Y ,8»‰Òvìk<ÊxÆ;3æ'Ù•«>®*%›£•¶êÏ*•ªVÛU¥¨ÛJ}ènóЇ¾lèSßZõ©/«î¶o}©zîüy°=Òž÷ü~çÜsϹüí'GþxÿÑ·Û¤BQãäu•*›¼&ÈÒ{ÂÝ#çŸþáú×:+wb¹!/Œ5„ ØÐ RC„RZ,f(¹¸Å”‰2RªD ЬÉiY„÷&<ëÅl–*±Ü jž…–¶Ýë‚”á„JNøáÈü½­>9þ!ü£ùø¬õè…**[~O®H¹´‚©I9+VÐQXï±´( _ð£}MvŒÁ‘&‹žù»ÀßÒ¹¢•,Ÿ¦aL:•ažFòü=$\sj¸. ÜðOÁÍ'§^Tºð�–D½b0 Ï[.ÂÇ'š+ýñÀ%�÷q¨ ~ÝÚ:¸Ÿ’?µN[`Ô÷è¯>߈»3Õ’—Oôºy»“ÝðûkŒ÷Àyn?°­P;sÏ`>yÄûh”ö~Eu0 � |ÜîëÀ²\™s°Ûîk©Xz8ä‡cão¸ä™&/š’,jðV»¿ljÈú¶k3u¦lj5›cœ.–å«·v þÕž„/»~YéÙ�»{*—~Ó=߈?Á*¾]çár¤«Ý¬/ÃÎJì:]/-°m­:ßCJ)Ö§0V]­¨HdéÖ‚q§8v£3Î+¯¾�ª¨ËµÏ¤w^&: ŒöõÁdïdÍDøÊ˜‹x­rê_^t©ZO(:ÿ‰ù[6Dy�Í‚'ñŽ!÷l1Ësb¾ Èù¤ÊàüÙ¨ü�÷ ¢æ øÛïMØfê~>íóÁó¾Ï[+Nü¬?uÌXyìïN¹K�yÙ�³÷\¸È_�ç§Ö\`ŽÖ:oSïû¯ º7f™(H¶ª>ÇuO€w‹+| Â'�l},cCkÕ–MÁçCKðúÌÝ_mž–�”®êaU _nA"„WU9-àz†l ZN÷À¼Nˆø�¬Ð·Š‚¢÷xÎ.+4K€ìÔÔžS ¨ Ö‚0yXeHŽ7ÀZ{s�[�K¦¸«Éåä•ù…ùÔnùæüRjne�¤ù¿.ˆ‚¶­g‡á/ý­pëlW©F뢫X1¿£Ž™ ƒÕ1oÈå�Õ×rð xĽ›e!óÂET�:×6£AÕ=ÞdÙŸ�ê�ˉ¦…º™ë8ådÚ>?®,ðn"7CGKñ8¹^ä• I‹|Q¥SäMŠõL�ñÞ.ãϽuJBXRsü&ÅÏT MiœCÖa‡hÀÅs}SÈ’P TQìÊY�áÓéb¾(²üãluæZÐvˆ÷ Ã÷fG¡yp`ifçW—’w¸¹••›+ÜüÒÉ…ùYnq.õÚÍÙˆC!C‹‚ª²æÙꑞ¢ßú-›„IOÇãÐD9,ßOœŠÔiˆª{ˆÂtÏ7#CBFqÿ04ØU³rÃn¨^lƒ¯B­§�ÉÞë’#— j­¼U__ì„ï†!K®j'+Vµ‡’ÿµ{|Í_>Òv8¬ñÞîse³{®T½Qû2¯I�q+<ÓÄe§BZ“më§É8kçê(«ê êô f‹P�ÉXlÁçÚ�A©×Çn—“µøº`çÜql Öa-à ˜I¬/ëaÀ?�s‹‚Õ1Tëûg  ¾ˆÝÅ’]¿·7è–hܨ¥&„åó±_:Û!3 Ä9 "“ã5¬¹79Zcõ7N�”µí;àðàƒèäXít¬ŒmtFŸnÕ˜>7”Õ]¸�S' ªF%ªÔ´îú�§•°™`æA©Œ}Þw ×/Ïbe‰�¬"çõÚbÁª—”Mvkî?Ì%°÷z…]}‡®³=nBßMD_ #¥�oEsè€Ö³ñòÕC˜¬Wï/ìÞ8=z¨X²ìD¦öj&LijÝY…ÒÚþ”Hj§@£*ŸeoH®ªúË—+vÑÿßî„EÿûT£o6wÕÞ ü4O^ÓSSµ2¢‡¯˜ftSdÖˆe�kÍ|уy�SÊFü[‡KÄ–äd¿+C„ÀÓƒ@ÏtººOï”l'ÍV¾ñÂ¥N‘Uj�¸­°®Ëja,‰å|}‘w‰<»–:´Ë¥:.»å°Õ=îmE—dvÌ´{vˆI&ú‹5ÉS^R‰ �Sq ûÍ¢(îàµX(”hÆh8u^Îê×Ð\ë£uM»\at&Ãk|Âî`Y_@¦§‰SÖÛoÛ·gõý½Ê©ñrp…MUÌN’3ÔÙ~$àú)]Cy"Ê[T‰Št±â-5B¶°ÁÎÉE1c(Kˆ6àf¬æ|øU˵xCÀrtæÈ·JiùeÂxjäJŒÁZ¬¢ ÇÊá¢YÉ&c831ëQ/ª”™ÀBË"ÁQ‹å,QxL_‰_­ ¡p¢{¿1þß±±*Òu©f—üäü)=<áÙbG‡%:hȇÉChsÑKËyÌTj� vÀ¬¤ˆ` pA ¬gm»Ìˆ?R¨6vK²†ÉšÊ!Ä8@óæc¹Å Ð> Çä§…ë1â:Øð.?Ù9¼˜‘ñì�¼»˜�g=òi|ûËÍÝ-ùt1�^LnÇ£¹™’Ë›ÉÕøv|3�·÷äbò™ü:ž\õqö5È�é¢(™#å6c¬@‚+’€Ùîµ�5Ó%#KþÀ„‘€‰µâ–†@ ƒ`'NôØèÛ\°Æ �à·¹gžG®qØÂõùt=µîf#ën2»½xw=Ê/ñÄ0z @©à/ 9·\åÎÝÈzx-a ¼U9y…ñè±aû¬5÷݈ ”^6qîÝ»ÑPÿCRÜCt=äŠÙz³þ‘ëAßCÞsÚí'{J~)�]€ý�#ÓD3 ¤ãJ”r ÊÛ±6ÞšÞ³¢|¸°"R7ðV!Ÿ�Ò±ÄÌ%žD@ˆÅ]OØxÌ;jß÷=׿Gەꯄ†@pN@}†¥és¬?‘’xW®�rð©x$+î9h5öhfƒ‡ð¯¹}Ê_¸ËX‰,E5Ïr`} "ò`‘¥Ö¨ñ"Ê)[0ÁÐ?jØó#âq¤°µ<6ž%-ÔÐ ßèò-‹Ü5G¡òXج¬–`åºÂç"ìO+&½S”à@?…v …LºþŸ(Šq NÉ$ÚOŒ�¦BAÎÉìÜ ó)z½/>Ûdö•ªb±kêƒ+`¹_ Ü{ 8ý¦£o è1¤,qƒJ:9<5p�ÓþÓSÇó¹ÃrZc9b(i !Å¥aŠý@í;Ä÷Ø�´ô´p@nQb}ˆDË¥¤ •œ½€]¢÷¨Íl±`À5÷½ÇÒfÅü:š“�R4‚Ÿ6oOº¨nkYD÷–àdÚ©K´T‚�ié«]„Ó?W¹ä àê{5ÀÌL6»}01Z£¹8T†Ÿs°UÞ�"nvÛ”Àz&`ÛTÏLqK�3óô2èõÏ vmùÜ…ÝZ®¿� §B99Ù¶95šñ$’Õß Ôßß>k‘‰V#Ù­ór�ª¤vZQžÓ?/ç,/¿ù]ÌyFpÂeçØyµl·¢¤õm7÷²î¶æ¬ÓT¶Ùm—€{4šZ=Û3�$͉}ÌZBh"½&Ϫ>’ �Øi¥îOŽ]ˆÃY(/†Vô�J¬ýÕâ¤Bb»Ïl òr/C0K�9IN­#&–Äâ�´u£mÕ¨6F¢­�­7|çÅl6šÞ‚c‡Ð(’xY‡T�¸ñ ê…ìI¶FVúFùËÂr×�Á#�ˆØ+ µ§½v*VxCç/­êÅ�’7Y„�K¾ív ɆÆh[Ÿ.¦“ñäg] h(Ù5‚>ÉÝ„ÞT™p ÉÃÛ7v”»X’2>OÈRôrm“.�¿þJ�ÿÅ–¬¨”çe´dœE ø—¹ÇÖê 9;‡|Ø?ŠÒ;c_T�“‘ø¢>ª&vE"rw¬ Dù«b³K&Öeƒ*±0ʇe˜ZžpU®~; ÆnZßÉ�ÜAÖÖB°å1–Zõ4e…nÝ“—ídû²i$½ÔÍFùFޤ±/ ‚®Ir’7w¾l­!wåµ~Ä›.3S€:Æä¯£ó­x�ëS@ ëÛ+fßËv<„¡vµXhÈ�î)9‘k¨ç¡³y-ß}°´CšBCõO€¡çÈ�ö$QšN™z#Q ))B9…+{�Š1ò\5�íx¹åJ¥¬S ÑÉÒ¤ZÛZ e&°<Èe'O©˜S‰#Ì.ø‘Gò«�µ‹Þ hvÂ’r–É.iÚm d¢ Y‚Kg§KyÇ¡©¤gÇŒz”ª[B$|`Z!6U²r¨Z'õâ+צQÒŒ%w&�v2¹¹Eå¡b­z#�Ú³Óu´¡¦”s´Äž¢¸ÃÄ™o*0Ëè»9=’“càù‡7zRBVK‘€¯à÷#KMì”öÈ+ý†ç¦Ý|@ÔßÉùéŒ'·ß¿±>\ü— Õñê¶:ë(‡ÚdtG96�NÉ˰Z\U8­”I'†ãêó³„Ìniš~CbAeRé<�ñ]Ê¢WvØê8˜ øl“oŒQ�7IÓÍVóK†j;J&Šž {�ÃØÆG™Äÿcc…ÉkçÛ–ŽK_ö¹ÏÁ3]íV1t Õxu¤ŽÕ˜bn�2-®•‰SùêZ¨½J¯]÷íA½žd§m\�Ö¸LÇõíR³‰í ýÍÚ^öõ÷m[Œ\ó‘Ý×å³ZD©ÉÛhÉåÓ)$ÀªüVÑrö6–�YeÖð”5={ëÞîÐ>”BìÕ4Ék•Ù3#0Ôe˜À¶N[’Çœ¦$O}BRº!,¹wã`ÄLˆ2ž†èš¬-¢þtm]�g¯/>«fkrc}}¸™~.ÎK®'ÚpnnBÙWål;ɵŸÇÔÛþ‡�)ëC¹fåàþfïwo{Ý-ð=©ÉV;*Ú‡Š­ 燹\ºá6¾é}Õx{œ2°÷þßo¸ÉÿY82=·™xœ­V]oâF}çW\‘‡%ˆÚQ*íC¶­B�m¬EP…¤«}²†ñµ=Âö¸3ãTõ¿÷^$¦K´*[$„<¾>÷œs?°?†Ÿÿ¯Œý�?` ·±ÊT”¡—Òu}$uy0*I_ͺ ÉK¸¾º~¿§*Se ŸZ40ÚÕ¿·IµÝ¢'ÓËãÇè‘á7­ʳ\ÎÚ$K%±°AUD„áR„i)$ý´w&ð«t×ÞŒ8`ØÞ^~`ˆƒ® (´ƒÊ"a( µ|–X:PH�—™…DØ+—ÖyZ�1¾´zë… `í ã~ ×’N�+í�ïï÷{OÔl=m?k⬿ f‹Õfñ1nŸx,2´ þY)Cj·%1’bK<3±m@$éžÓÌxo”SE2«c·&RÖµ­Ü‘a?’Ý ËDÃé‚Í~�n‚Í„A>wëÇø<½¿Ÿ®‚ÅÖ÷0[¯æÁC°^ÑÕG˜®¾À§`5Ÿ’]”ŸKà ˆ¦b+1ª}Û QˆuCÉ–(U¬$I+’J$‰~BS�"(ÑäÊrI-Œ&S¹rÂÕG_éâDþàBÅt#†0 æËEx†ƒ ºVö�(¬�Y!üd]´Õ:óÒ_ÜäðQ²1‚e&L�ìjxÈu¡O‰ õ?gs‡9¹YIá>ËÅMȶñ¡ƒÓÇz4†NØ]˜QY¼tÈçq#B–Yeù;ÀgGÆÀp6„¿XD*ðý£˜nJg…C[»£zÔ½ndKR–M!™–Îtç]EIîj…]_÷¸°«Çå’»�i ýÁºÇ'ÏeÍn4út-zä•�m'hŽä°>|SÑQžF—?xÒ*:]‚¨�½Eÿßp=F÷´ÃÜ×|n¸h 5?í¤}м%vxà©«G£™ˆó9N|ãÅ3Êªë ©‹X%ï˜w3�çTðwõTfZîhúÑÜÌÊj�ý¤Î0­Ñë !{†Iµ!Σp;x¶ß*M¹jMcdј ‹ú´ŽD±1xÎO¼0��êSóÍÒgÌјℕZ«(+–³K›WV½A…áv½¬ ÆR£ÛÈ’<·–,«ÒZªn’øÁu3T ¶6çíH‹¿ùž©ñˆâ™avJJ+G›K»FW*e`mõuÑz�0yŸ°ÐA‡ú¹Æ°SRæGaU˜©1΢HäÙšRui÷œOX’Ð\ìS—Y»’D±ûî|b˜Ê¸¡ZV ¸îÝÛÐpb¯c C¦Ð2…W¦ðÊFL¡c ¦Ð2…W¦Ð3…ž)ôL¡cÚ–)¼e =Sè™Â˜)Œ˜Âˆéhd™BÇz¦0` Sè˜Â�ép@ú‰)tá•#Œ8¤g% È«„ÓDÔeH%�Ûë™ ÛÝ^ˆ81ýåŸÇuqDý�»Þ­<ºòÃèej}®ã‡Á»žmüé¹1�‚]è6®—e»éOwþÆ Ÿl>*¡%Sæ²,•4d®§ÿ_vâþ[Á²]PgÆ›ˆ¶ÙqG¼íÌ›¤ÁÖ ë{'»[ì3>¤m½÷œ•Ö{¾s¥½LÛ¿'á%ÇÛ²€qþæ`ÙJÛuº/·~ÿ0•esÛ~Xê£ó´v6+y! ¤î×µó­'ñ®“ñ½¾ Z›‹âØþáoÚ:ñâß×2Ù3W©Èno›0œhOK½È*9GRº|ß'mP‰Hgâ|©�¿9P|kººçõ¿ò¦@âöçòñÞ‰éS°£»È«;òî&/Óùù †ùä_†ÿŠà ‡$xœÛ"tŠg‚+£ÙF·,¦ìäÒ¢âü"½ .…ìÔʤüÄ¢0'71/1=(¡É•˜’Ÿ“™T”XT©‘™WPZ¢ââé TÕ�Œ¬Ä)ÈÏÌ+I…HÀLJž,Ç.Í2`²={”>Ø$ tI~~Nvf‰&WIbQzjI|r~nAfNj|~AIf~^±P XÏUöàÉblâõ0}¡N>`7(©T;û:z»Æ;‡¹ú…Äû‡9»Æ»xÕ*Aä}2“ÀÚâ=ýœ}B]ÀrÁ0ÉpÇHG?—ø`× 0× ¬J"¼�œý}}ýýâ=yQ¤'ó°)òÃ]äæâ:ÙšMv²<‡´%†K¡^E(²`N³²‚±¸&7pHù‚Te§;çç¥e¦[Y…ûùû‡£ Â�£LP´ã))±8ÁŠ/ÈÉ,.™¼’Ãô/8|T8Ÿ�ë‰{xœÛ"t›c‚+£éF·&£Ä””øœÌ¤¢Ä¢J�”Ôâì’ü݂ĢâÔ"…àÇOgTQ½äÉòÒš¨‚\ AžaŽ!®@д¤ÄâT +3/3c2/›‚¦êP'Og CI¥ÚÙ×ÑÛ5Þ94(ÈÕ/$>Ø?4ÈÙ5ÞÅ3¨V_OOi²»4ªöÉìZhŽÊ˜¼’Ã]ÝKoQN?³‚ xœÍ¤Ù=ë6›g¤I~™{>%Î&ŒXØqà è°õ.ôËݨ¤î4È›ó7?“ÏKÏ÷Ökòg›F4$õ;þýËb3›Ñ޳l˜Ã`ÈÈGÆ€i0¸‘HžCƒˆºd¸#^Rrµ¶ø’--òWF È›Î9©c‡šlª5.ÄŽmÈÊÞ‘€ÅdQ€áE„Oˆ>8t/ [­}ÏJ¶^¼äx$”ÂøMÂ`³Ø†î6Á¹6×;;–D/ãx½ív·ÛmÇæÔvX¸èú¢_ÔôozÃI¯ Ë·�O£ˆ„ô7^³�툽Š{túö–°�Ø‹�B[Ì�âmèÅ^°h‘ˆÍã­RãzQz³Ml0LÑÓÖ;Ëì€Ô®&¤?©‘ë«IÒB _úÓ_G·Sòåj<¾Nû½ �ÉÍhø¾?í�†ðë¹þFþܾo ì<ôaâ €LYI]η ¥ s&HŠÖÔñæžS {AÉ‚ÝÓ0€‘5 W^„K�.‚ñ½•Û1”›"êž�ýÎ ãÂòK9]Ö´‡ò¯³|g<ˆãÝšFæSß›Íìˆvåwq#Ì/гMÁæ¡ëëMܦ÷4ˆÛs³À£ØÙg!LÚ|¶µwÀ· ÚLH!­ì°YÌæóN.�{%_cëvÒ³n‡“éÕõ §�öÃ.Ÿzþ³·Þ•öà³²\zü®êwGw3f‡nUŸˆÚqUûƒ ÇZÙH… W|žŸ�¶â)æšö4fÌ¿óâ®üæÑEIÞSÇ·C)PGH´�d²IQV ¢Š¸”/á§�â¼õWöH¬˜XÔÁQ°è'&´y+¹VL¾�ø ÄÏ Ä3BU·É×t•¾òÑØIBH›ˆú4ÓgÖ:/Êafx]<ÓG@Ï<4ÑÜrØšs7S›±ì£ú^œ%@¦ ÞrÙÀÒ�)ÔÈU{f"� Àâ;k�ó¢È¿ôÓT9i Øè- $,4!_W Q%\gÑ WsÛòùã“Ç{QYö,b>à£q*@!“3O1þ Töƒ=Ž>Í<´WG0‘×U`Â]™F1Ø®X!È E«vRìgåÁ‘qm;wm0 wb¿ L”MÔÄtIûDü…É.ÔO°£œŽ"mÇç:õÌ„0f,V¾:N,òbf´2„NYÚšü¡©’_–”;Ø& ‘­>�Çm!qD¸tµñÁÆ‚C@„·(Zœ3°|€²¤ô*(‡qqötDèW8äžynÆs N-¡mõ²•mªÿp^-Þ‹Ãjºvlã3pBÄ‘hôi‘ fœjñi1pý= Å~%êz*¬àÆ9 ì†Äe<„fº± øØæþðZbÌÙ*¢âp–6PcB_ý dÐ+@Î9°áÆ…»rï æhò/s5WÖgá»$_ÄŒ7nJ£@%h¹¨6"aÅ-÷¶¿QÄ(xz|°Ë}8�i­}ðr#'c¶òÌá½þör)=¶ëÉç�õ¾7¹±®G£�`~j#ðÎCÏ¥“ÈÅê†so±~g­Eæ¶�ñké´@L±"ÈrtH­}ÍO£•!u2÷‡5b‡À}  ¨©$Š$«DÌõâ@ÀS·7~\Ë¡»í§?ÿ¤¡›xÿ:]ýÔß7?å§ÑƒuØÔÜœW.e‘óY¾–½áí§z­'öŒ1î×Ür~b.„®4LVðŒä?înKífbR›ÓJ‚-W=‚œÈUl”t�r<3ýbôÅ \¶=~†+€em9°ê‰v©L´¸P Ÿ�Ò­œ¡&í-¨ 1tE¤…Ðñ“A·o*fã6P·J¦-4”Ïâ�.9aµî& » ¡]Çw¥–taÞô¡Ã‘Ô.ÎTàãÒ™giŽä\+¤^�iLâ9ÔÚ>K �Õ0(§±ÓÕ£îC[h+ª¥9î=¬Yˆi¥øsÌ}fX-CjÒ~©Ìî¡Æo'„Mžê›m\à>ë;Ò!{æ¾þ*òm*+”ŒÑ¼q¶‰‘ ßÞÁÂ7â.¼á¸·ò^yâˆ7ξíq3À0úl± ®åؾϜúkaŒÙ¼nj4D„àÍI}x; P¤`$¤ñ& 6‰Žicû]Žá€¶$†Ñ‡éÓ„ùpEï¬æ Ó8$ÛI-Ì¥±4*^é‚›ì¶þ[=ñâaT²‰=ß‹w°�A܆iÏð+LpFK¶  ¤$��Â9ì×Ha­ätBCnlø™AhÍõë‰Õ�Gã©}°AÛÝ"5‰%'°aÆ!ÛÕ5iÏÜ*?JùJàÛql;KS’S· |­¼N(‡0G"úê\ëúxÝ£ÍâÒòEœåzbI7{h,íS�2Û³ŠEúµRÜd�¿ã'k`.fޥܮ(E•ôcâ2Âñ$7nÎÙ&!ZҀ˽$�÷�ÍóW¶¥÷¸­oA%®�™ÔieLK*h^Üâ¹TLýy¾OQñëbÒþdz/"Ï FInŒ+ �ã�ÂD¿±³d,¢ŸlmgbFdØÅA´æ¼ ©Â+�.Ç÷8V¤ f–QrÁƒùÄBÜÁcÐ+d?lR¼HÁB§3ôf yž`™`F’4ž�𣸭¡ª£%7¤[`z‡­ºoÎ_ÿO÷ü�Ý×ìöù!„Õö‚6,3¼¨³ŒW~¢- àh´Ð (K¢X+ò�í§âþª\ÞÛï‡;"dm�ÉäHÓ^¹)ǧ¡3I´TœK°Dy2ìDSÎ å™4í‚Eïщ) *�ï±ï‰(ä)¤Íôðygm#z>•Ê{IhÌgŸJÞ—ZÞ3…’ájRr/Ñ�•_ÞQ穣 ®Ü?Õ¦e·èu´öPS!j¦×u0C¥ž #c<ž ¥Ìx.Eº2e Sî;in]Þy2¦ô–;Š9çªJTŠ|@ƒº|—"«ÝÉ,½%½ŸJ{™˜‡å®hn`9�F¼“£kÞ§éâžÀö𤒠â LüêÒd‰´Lš“—ó¯LÛgc=z0!%7OáI;^y²ÿ°Ò"άÌdƒ ™\�UõþE È“ð«âÑlÖOOKú{íÇèïµù±öyÜßüüS­QË�9ŒÖDÔ2|ÌET’¡Íù¡ˆHŸÙn]N¼E^w·ㅠ<æù1Jç{Äd« ŽZ¬cXS>êxã�Ê™¡½Ì�·ˆ9™ÇÿCýX˜ù)6Õrº/†''±„ùÒ"3cš6à–åǬÁÿNޤÌ:é¥l‡™u�ú`‘bÕŸè³.Èwå1“¢õ/Ê%å�Ÿn,òÀÿSŒ+ÅüBŒƒ-iÀÛ'uqXÒxîÑÅI-dåõ¯âhýЂ5Uš½¶!¨ z9‚öXU#ä+•Õ”N7ÇñUÔמ°@æ�3Ø#oFÃéUØ[£© Ð30ZÙò½Ç¢‰*H9wŽlKHzõ Ës-ôʯ§CkÐû0Õùˆ£ SÄoÚ@Ÿ/ëó¨?œ‚X_ßN§£¡5™^M{Öçqo2齘F^¤ž®Ã«q¥?¾#4Š8AãþÇ_§F�‘åeæ0^„êúP�œKòІ´Ï#¡>ÞØ;pÖãÞ wU0í'zJhy¡ƒY k€V½dJ—a/TYO‹¼¢÷­Ã÷Ýjªî¿¼ÐFÃO{ÛfެÄ?á&StdU`dÒ¹šÖ%}~_/'m)³rŸ]¡'dRù}±ò†Ô“$q�Œ=ù.†eÝoÁ^YÖw”·Ü9«Ø[›dÞê|'Šyx��¸®ª˜æÌÙDªø–,Õ.<)×A.zÀJ¶T8ßQ½Ã&Å‚û¢þ m¯àóú.[ñbƒ{ÏåW²dš˜×‹ñ*1,‰tðn=å×·±´ ÷2�ð0²´#£^ŒsúXi¬DÁÙþafóå¯þœP²,cÈT ïe’n4¤ØgKhòõ¦®•i@özéé!÷�: š’:›•X. «}ðe鈀Tr"ÎÏ�7áÜ6òÖ*L]?ƒ"�ë(KGkØÏ^J’U�îlÑÉç—«ñ°?üØ"µŠ7?S.Zâuƒê¦Yê9tJ<òCæ€�LVxOô}S�½/ë 0‰ûß]AB[¿ÒÞ(ÝBŸ´}^Ký×Þ¸ÿá7«÷�„ó\Üo2]kÜ8©w]!ª(Y-w­ÄßKm£ïT:LòT(ePG«OÞÂÉÛ¢ íiÅâ¯8UòÅÀù$Öœh*£[ŽB˜Ü·4ظfkùf(lÓ_:÷ {(UC¼=Äsx£ LN`b±Ù?aÏKã7Þ,ÊÑe›èÇ_i ßÌÕ¾™>YÅ[F.lÁ.2Ýó¯ê*oâÐÏv|¬5Š—Øû‹5[ãÞôv<ÔÄwÅq«^{k üc!üéø¶§]PðMž¹_’É9[w[…XøëYÀ$ÀÏ’™€WàÁ·F~½ïè÷ÆÖ`ôq¤½TgWŸ{�ïf:˜øŠ^w¢x�JÙ&€ˆµîꕪ½ÀÅPV0§ã<Óé@½ø÷J˜Ü¿óxœ½WmoÛ6þî_qp?,1«H‡~h¶¡ž›®F\§ˆãeÅ0”t¶ˆH¤FRq½¢ÿ}GR²%¿¤YQÌ@ ˆw¼—‡ÏOA~þ^?è� ×ë@^/x†—JK5HiÅ-ƲX+¾L�}Õ/pŸÂùóó—ð!å/ ¸b¨QÁɽ{¾^–Q„ƒ8=mo£-/à7)—äi2UN&¾M�,�•dŒJÊ´$™5B‡àÏ .²RÛ¿~2t®Ðuás犄/‚ ¥ãœ™5Q½*©ªÕØD6^k§û¹ô³{ïRt,¤Ò°4R<ñ´Ókm0?[Ù÷XŠ_–:gÙî¶Gµ¡Ð [û/6®§,ßDýÇÈŸýO‹ µ›�§8eê°§ž aaÔÖêŒÿC�m•¢ ¶%ÎtÌ2ª¨ÚTI\zù#�âÞOÓ¾‹Î— éuáKƤÌlò¯Á…SöÀeyfYíß}…ëHZæ’’{È)3AÌ Ã…j.†¸I!Jð�½Zaq y›Ä‹óCI`^fDãÐíýî°vH©ƶ—\>Ø.¨‘‹D®<ˆ6²³D±å’dO�Œ`è 4€ÞÞ&‹Ÿ×­‘ì×¹™ûÐAIòÒÓ-Þ Y‘ë§°¡JÂIÐy�wáR$v«?O¿ñ ØÚû‡öº™ xœÕýsÚFöwÿ{ÜÔc'½ÞÜĵïˆ�[&|€“fšŽ"Ī…¤“„ŸëÿýÞÛ/íj% ±“™c&5hß¾}ïíûÞU÷ÉÉs}ÈþáÎáþþÙ'ÿšù%7ô~¹É´åÁ3öØ‹âûÄŸ/2üu&�º× /�^þ�\-üÀ�còÆ¥)MHý†ýý×|5™Ð–·h˜Ó`Êäç(šÃZ½Þ™X¤ç{4L锬Â)àÈ”´c׃?b¤IÞÒ$õ£�¼l‘:ÔÄP­qŒ(î£Yº÷$Œ2²J)àðSÂX¢Ÿ<gĉ-ãÀwC�’;?[°u–âx/pD“Ìp— ï$šé€ÄÍÑ‹,‹ÓW‡‡www-—QÛŠ’ùaÀáÒÃ^÷¬Óu€b1ã: hš’„þgå'Àí䞸1Pä¹ 3pïH”wžPË"¤ø.ñ3?œ7IͲ;7¡ˆfê§YâOV™!0I°­€ÈÜ�ÔÚ#ÒÕÈëö¨;j"’wÝñ/ƒë1y×Ûýq·3"ƒ!9ôÏ»ãî ¿.H»ÿž¼éöÏ›„‚¸`ú)N� ÓGQÒ)“ÛˆRƒ„YÄIJcêù3ßÖÂùÊ�S2�niG$¦ÉÒOqKS pŠhégnÆY|áB‡;;õC/XMaû•¦.jÚ㟼ì>¦­Å©öÈ}óAàO&nJÅßòA`/ÍÌ¡4›N¢(°ú¡ hŠÏàÛ|vçÞƒh¦`0 —/JèZ€8‰²ÈLéÌ)y×:×£ŽsÝ�Û¯{}v�¢8ÒCøæä;­„I©ËYAùÏÖáþt3sZFá¡ú†µ Ê@T7~v(þ²}:DßEΩ¸‰Øé'ù.t^ä�`Œ,@TmáH{åeĹ –þR±îdäa‡À§�EáÌŸ¯8`3þ Ýä¾IþX¥S¾šÄ]#)e ;¢˜¤qàà,@ªö=†—ÅYr¼£Vì�FÑôZÈÇe4×ö‘¤þNp’ýxJ÷Þ\Ƭe…u�Ow¸¹'|åÛÈŸ’}ˆüIŽ`Á޽…›�}a¾åƒÈzùÈ­¬¨ J’Å–â‹#]õcÙ—ßVAÜÔÍ\“A“²âÍøvJì'ÿX‹I¯ Ožž92¯Ë%Ä6›†«¥3¥©çd¤°UŠ£ß~''Âñ¾]õœNÿú²^-üYVk² s98ï^t;CgôK÷bÜà’37NÿI.Ýû ÅÌî•>ÇŠÓÏÚW#1[‡Ì’À‚{6d;°(j÷Æ6Üe4}Y„//K!(�üÁ†ìEó¨Ùü<(Åùc ΋�Ψ™]¿Ó«ÃÈ£R„Χ8J07\ÒlM¿0‹ðKžëƒ­p½_4ÿ| ­ÁÄMÛ°{£…èܬŒV̰@YX0‚A³Ô¹[.½±ó 1 s«¯6Dó9¦Ý ˆ¼º2ûPøÿ¥Ñ¬^ÄÐhðxäÏH½Ýëávë8P|@Ì rP}øàÔ%PQ™Í‰EiÁ¼ò kO“ÒSÌÈj‚% Sìx)�Ç�BÊ �—xz¦×²u£ €4žÓŒ}É¥_¥IZ²X² è†ð‡œ6ºþzät†ÃÁ]¸P¸²0‘<|PØ?ÔX²JfI´h[rÅ­ßœ%Ñ}ÝPƒÚ҇DZÛbÛ£Qg8vúƒ1N®kìHÙ%tV/òÝûSž}ÈŸÇö&%Ëà6ÒIØ&ýÙÈJ“ìÊ5K6Í\sýžõa³ ÁÀ ™PXÀïÉ]Ûc›ö¼%}D³�›Æ_¶ªÔäg“wäKì²5›= §ùúÀÒ�äd@‘8‹6î¦&C)|žÀ­2?À­ ¡rT•˜Ã;'¹ w×ã?8•? ¬ J'v×á`õ Oii¨Æé9ʨÖ(dÞ ¬I^5ÈŸª!ö”œ’n ¾lÿº^Ý&@žË§á�„=¶ÚÞ+ò]j·?‹Þ ²ÃÿæÛÛ-Ä÷üò‚ê}3ñábO’§váI“*ÈN³é¯éà eYð4ÊiÈ0[h„¯ª…¯bW,y)Z=¿üSâÀäÍ ]`WÌH1qÉGÍÉ|liµ‹ÚS™šh�ÔU�‚ó;²<ßSï•8ÎuÿzÔ9wœç½bR™Jëõ•$Å7}ê”f‰…è³dCܰy7Š^›;CÞåp:�³q÷mGÝvþzé›´ßÿüöÈ)Ø—g¶’Ôç\HÚfÑ�×ìtß«.=\‡�úƒ»Éžðö«¸ô ¿Hf½ì +8¬²®C­Ë~�í³¿±õZédõ »¼HØ+¸Š#â¹X–òWpvù®Ÿ‹Ãö›bjHo6>°à E9cªðjþdOM1¯šjIà Ç�ÑØ9ƒ\M{н>e²‚ªZÎ Œp¯Ê0�:c®TÚj¶GUboí=w,$c¦tê*&?]v¡ "w  몹dvÈÍ{ù*j}¦Ž‰T~‰CÏÃcÉ«Sãp�õ|xÿ§ð’ ïI½!öÖ¾ìN§ö ÏíOØQùÉ𘵬O.�y;ú¤w,:Í'o�EùdðøXSn\néÛΰ{ñÞéwþí †�Ù�¯‡ýºdºÉü[“È~Xé͘ ·r¦xóZÌÿ¬Eõ»…‰£ñ°óomBm˜ð7¶œqÉf° [Îè±<Øn9å-›"n l9gÀæ˜o¬»¦R›~µôs;“ËÔäÿ�}Ò³Ûì�ŸÃüÌ+RŸm9za¦°€ºÙSÒÕ-íè $‹<Áu³…®Ê­6å´Ž‡×�Xµ�ÂQÑ›žV];k’¨¡ë“à†kTdðþ­Í+£‰½G?~¶E¯Ò¦·ø|«†8h~�eÏ“øUŠoŠC`Œaÿ£ùœ‰^·ö'˜;¾N±W©J4ï‹’24Õÿ¬ôÒ±ˆxœ­VK�Û6¾ëW œ‹m8R°z¨›"Ž×ÛvìÀ�.E!PÒH",‘*I­Ö‡þ÷õØõ+95`œ~3óÍCò†ðñÿúÁÐs¼áÐ�!|Šy†pÀc ™ŠÜ”dµ8”ÅQñ$5ö4íÐp÷áîgø–òŒ,jTÐ?ÔÏOIè†éàü]ù ~—2!_Ëå´u²ä! �”"" “"L Ò£ÕŒàTšKwîè[ƒ^«ê Æâ(KÈÙ„4Pj$ ®¡N _B, p¡Ì‹Œ3"TܤµŸŵO-† #s6w�ñ©!0Ó�Sè_<¯ª*—ÕѺR%^ÖØio9ŸÎVÛÙ{Џ½±j ÿ.¹¢lƒ#°‚" Y@qf¬©€% Ig¤�¸RÜp‘Œ@ËØTL¡…‰¸6Š¥9#¬‹�Ò>5 ʘ€Þd óm>O¶óíÈ‚<Îw_ÖûøKœ @LŸ˜{ÔncŒf)�±ëÏ|T ç+ð64�p‚F(ÆÉÒOÉ�¦Ä/™À_ú™“ÑG%½£öÉÉ_üÐ V ¿ðßÅ©ôôŸ~˜e›§­Å;éiàO§NŠÛüóKÐ'ÍÔWiæÁ[õYEÁ½Ÿµùÿê˵³]½spmPô²š�&ë!…�¢­Ó\ xë�%õáCäñfÓn ˜‡¿H‚Ü1g‡¤ÎüùŠ9¹Ùƒx\ØQÌAû^ k—†Ú>$YPæÕ¯’”œâ‚NNJ¡¡èßàãL6:ŒgA Px)ÉÃ`G&"ž12‚ÊÏp‚Iº†lHŒé ùÍͤ ˆ›<)�Kƒ5g/Â¥µŒ£ÔÏ¢ä¸"H++›ÝÛä‘N²ARRßÞ0„ù˜81É`8*KТÄöW5 Ix5‘Cûã/*ˆoJI¢�Ų!êIwYD¯šï—äÒ†BŽ1JL)–ã€ÊÇdtîÁ¥7[9�Îä!ò½r¨P;2÷RI+xA¨ö0ÈmbÓ|ÖÅÛ.8:\’œ~xÏ .¶‹Ô`Pƒ Â;¨~Ñ'K„æb-ö ¤8Z2� À’· “šjBÕ;$ì=ÀCd ˆ“¸D¿DTldHyŠØ,‰eÈ{ø?ç‘î«x¸$9„Ö4B ý‰:Ü]HEÇépSÌ¥%ûRðW*<€„Ö„jS™§ÊŠ?‹±è$ó�-ЄZ�r;VjoÑÈIæÒ€| ¦Éå�’ÈfÖØ…¦Â¦uϲ`À˜•ÓŠÐ=e¢§¤vYqaxÜèy¯V ñ±}³ˆÊ†çdy“§j6"nž�•à5…ÚYðâ(“à9éŸÔ”˜+GXs+~ÑK¦³æ¼×*Tå)ETƒE’N~åí&ƒA08lìLa*œù8}Ù˜e�~>ùW!!I <’¨¨ñÁ!ž¾°Ak|†Cy1Aó0“Ï(´ÁÒ{ô1±Éç—ÛëþM¿7²Çïû7úƒÅjõ¾�™ EB¥Y·s7n•{w­Ñ€wVžw–é1üqQñüGÓóÁíÏ·íÿVçØÖ WKD§–>)å(š²<¦…8.-õ8ÂÑÑeÖcN];Ç<oŒÄïÿÛ1è»ß ìëÞ¸k_÷»VíÔŠVî"v¼Ó&šAI�Ù°kÌM½Œ-?¨î-Zò’.ã|ëM£X¬ˆÝO(µ.}¯î«6¤,‰3î ­þ°7¨Õy2"þßûG Y›Yâly�œçò9.:®!˜ã.ûCE_7� ]Où¼�a¯˜™™�¦X„›l±æÊ#c':SPæÍRÙ¯úͪ…Yšm †mÈߊnJõŸüõ“¯;ÄHÍçØ£µHäÖ^ÃìÚÿ/Žfµrãz�•þ Õ†“Á€¸E¹Jp¶JBDZðöz›ów²i@†òLÊÐG³t»Û�ñ¸7²ìá­EøÕrÇ/, ¥SMëš+Rf"LÔ‹i³¡Í z›€VíÌЗ­š´ÄÜ’ÌÓ Èxîôê‡Ò�ù‰ÿàg›ƒ;z˜v% ;ƒB«�:Ÿ>�±{ÁÁÓ%i�ؤ¦à…Ñ/ÅLjµjƒßp .|QmÃK¶Z¹ç›¼_É«¾�¨ÎøBu#tv†ä祠•…2y[¾B@ýS DQ* ¨&1­ø˜†r oÁ ¹‰ =¹6¦Ýϲ%d“=e,�ãÉÆtËÈJ(­9@ß*óâ�!T yyÏFª3�V6ùüd÷èì\Ê3MÅ<¨®ø3·‹i’þü™“–ºÛF±:sˆÜRÔÓPÄÙ€<‹ÂÜ&ÓISÿÂHåb½Y›ÊåÌ—¤J\K³§=x˾¹wÈ((R'Jï7{CT ‹ H4›ii‘õŸ%Ã÷hÚ‚hÈßöð@1Õ ñ(’Ó ,.Ńöý–v–xyš>ÊÛ¸YTª¤{ù– e…lÂÙ$^ >®›$\á$Å“ýE±ˆD`W$LæW¶(úöP§(ñr¿J²IQâèª/3m+íùº퓇@Y¹Êí+ôñm¬#è©-ÛÌ©«æ“‡¨GÕ:Ò$ZYÉç1Fs2¼x4ºí·ÈÞ”Èo_#W§µªÈÒ7Óô6Ç2iž«v1MrµjíJ[«-ä*§P)„´Í�€Ð:[jÕ¯Ò-dQp�éüØ"(×�ë§¶96jðâooGÛ=úå5ÁËè?»½hŤñ>vÑ!-šëöZF2lñh(›¯;ËÅXlí� ÁS»Û×½«ÉÏÊ,Þ]8 §g‡ÎÿþúâtÁõ[Q±J¼ J’f5.‚Ü5DéY]*|AYŒ&:½cªÓ­¶W/ÓW¨F6ü^zÍBôòônÔÿòãÅiý´´ª0v� ›ê]5‡h‹Z|·ñmN§UÈðV’ç›Y…G:á2÷²ìzÕ›#Mtæǽª+Sq£azz`®Û½Ü!”èD0Àtí¿þØÅç£,;óƒ©ìp�t th¨òÌ�8Ä;‰³DòV¤ôXìDžäxy´mU>NíS‚—‹þovø[±íÞ­NØÙ·7+²û;ZÄ„\ß(UfÄéÚÏܪ™ä?GÎ ls�”vìï& ίýnÏþÐûtuÛ]_æâ*ŒŠLDKµG¬îž›——÷r~³$Zª`Ò³¾“cõš¡´»Àü˜&KùÊbÅÒ¢´‹­­#¶Û¨3…Ùi¾Æ0!9Í쨞yAÆí(TŠDqÓ±:H7Ž`¯b|ê§ÚŠäžßËk‘Š/¶íü0ÔbøÌûýòG²—n°JUAHƒŒ%U5‹ˆµ(åœAaÍ@[ź�tß_Ò�œ×µì»Îõ!½înûC«7ÚŽùÞ«!wÛß”£´}ß?§—kjÿ8yo4º×ÉÈ5„ª©vÄ!¹æàɽÐÙYI ø¼@WÈÈß;Yæ¸ C2ßjù굺ªQxdp�?ö­ªèægV¤—ùàõ‡7·0v}ù˜"z_¢IÈŒê!ÈîʉODR2ôåÒ½ìw[rº²Ñ×g¨E6Gùi&rb/|•‘ómlB€Öù>¥‡ð×à=ØkµZêÄI> ¨óq`�{‹ºé\õ}ë“€)}Þþ<»åYÒlU~œoóìNVç”s\»J¤Q;ÂÄ£ª”ÖÏÒj…´z¤öYªé§ž´íÉp2î]Ûö®âZ:`ÝPÊé=kd¡féÐ0üíç©ËÈÑÃ7 ’ß}§�!î]šÆèe+$„N¥ÜH£k#_’PzgB éu�ò‚¤´³An¥¥+—ÌÆÙ½®?Å�нݪòœ�” kŸÒ[1�§çÓràßâÈ� ß”ºUÌ*«�ôË�•ÊO? ÈÑ€3s"É�ÔиjVÄ‹u%ÐÞ.åÝì=×uK ÂÂêzîѶçx°NÂ"DIÀruwÅkqþVzÈŽß–PúyN:?Š%ò@,Ûë’“‰{�X©Ø�[µ¨¨ˆÍñ$/Á>#«®¤+¸éUõÓËi?9½k\Œ¥µj‡C­ÎØêåuñ¥MŽ­}�H€êù©Ž©µ­ (ï•(;" Ñ"™á�R®OýŒœ®o¡>Œˆ“Eüºg¹F׫Ê-X¥: ½7™’»;NÅ”AÈP‰qÏt傃].DC¦¨aiø]P1/3ö:RˆTâ› eåþ0NÍD_BÇóA±Y…É{8NŠ|á|à%’ïA�v€Û™ U¿¸)ÌØÁ¼Ò öš4/³pàûˬùíèY®k ws¶úŸÑ÷öÚ¨ß˫ʔ´T[½a×€L)÷ÑÐÛ\Ä}¶ÿ9»bã•]ï15=WÜ"ÍŠªüùZ}'�t�Ö/�ßX e·—^˜·ïÓJâÆ_N¼Æ-$±Ggˆq„L¼KTÃá3>S(Ò!ÕÁLêñ(!xõÅÙÊeá;¬ €Ðh_FûðÉ øÉæŒì ZCŠ+d8¤ï*Žå¾úLJR¤êˆQ™£%O(å ³T†'£À‘ßþ®@¯"sêyí©Wñ¾?˜”¦0Išú� i¿zú² ôyÂ�ðýjÃmF(f2z‹€0ýÊÓÿcqW±A¹Ó2=Ýú"Êe:FŒt¢¯ev”’t›î+ÍMtJä8m–DCßÄ}2Ûê�IÖ÷ô+e*[²") W<`cH Œ{?ΗýL�Æ™J©X`�ºKõÊvé"»œëräsg5}YïÑùBÀÖw˜�s”.ɧ¥pÜîKÚÍ1)îÙ®»øæäC>ʱ˜ÒuÔ¦¡�¸ÿj¸»j¸ +QÈ�äüöáŠzvGz)£ÿ[:,i¢ÙTÕ¬Äo*HVãÕôµ²Y©ž|#Ÿ3ÙDg¹-Ëa&û(‡+0¿öFý›Oö°÷¯šOvò€‹1VðMíâ Ѧ{‰¶·xÉH�vÝø A ±Ý, Î;MöC\ù•1Ñù*Ü‹ÿriÙ2™ÅMz…2¹úl]:^G.œ©ì‘uó<‘“sLNÊÊÆN¶zÈuV] ’&ÊMg0þ®²Ðð¯°Ë#„Ñû‡ºä�c èïîh%þ|Nœ. ƒ Ù Eâ9E—WÓWÇð¿)éÚ^ÉÚ–jÚ´Ðmm^ëUQŒÍŠ_o|üåñÿ‡G–彉xœ­Y[oã¸~÷¯8ð<Ôq{1ú�´Ei)‰– Ë¢–¤â¸‹ýï=‡¤®¶'É ŽÉsýÎUšÉþùÿúÀpÒ› ‡=ÂÅR¤6,c WãÑéãýÕý`͸æê")Â��£ÕÉ܉L*ˆRβ"×gDy wlÍa›ª@sf`À²´Øˆ”©`Xq­¥Ò# «BsÄüED¨WÆEÊõØIšÆ1°PÅ"Ó¦€­0+`Ô¿áf%ãqC»Yqˆd¶ Äys¦´Èú6 —öÞú¹âŠ�½�‘ÌwJ$+C¿.Ë0ˆNàóOŸÿßV"y7ØÃãÛ/R&¨êööÒ:Ü–âuߢk™æG£h²oš³¿üÍþÍÑ™ÁçñO0 ‚¾¿êŸœ“ˆ�,0l;Ȥ± š•ÐÎIþñÜÊ‘Üä©`Yä4µ ߯^† CrI ˜'f¼Ñ+c0è“Év»3kíXªd’::=¹�]^Ï×§h±çxÊ0¸ÿ½ ½ wÀr´(b!Ú™²-`8Y¢8ÞIo•0¹h¹4[¦8‰‰æ„ Ó¬´Ýn d,ƒþt³E¾L³Åˆ„<Ï¿Þ?=Âóôáa:œ]/àþ.ïçW³ÇÙýý Óù¯p3›_�€#\¨‡¿æŠ<@3AÉ]Ú-8o™°”Î$�óH,E„®eI�‰|á*³¹ÈÕFh ©¦¼ 1)–‰aÆíùEŠ&½Ob‰K‚çÛ»éÍõC0›{z î¦óé/øëkô>!�ÈøwiPP¥EÌáÚÄ¡”éxõ¯ö¡ÈLû,aÈ4Ÿøï׊îðmÙý�O1ÝÑùÓHªïë:ÄÌÜÈlRýE5Eß �ka&þ{¼ê÷zâk›:MÈ¢5Ïâóæ™,L^˜ eX¦uC}霤/’Qžšþõø«Áè@ÿ²ô>¡D±˜LZ4µ¤�ØQ¡°—ÚìRŽ2±‰ÂÏRa¢ÆØ°"ìy6œc �ÙåœzþÀ (Ï}ýÂ3£!WòEÄ®D( D†ÎTm™ÄµÌàŽë�àÇvrú`bŠ$c©†íŠg®+Z{ÿ‚ ¤d•K‘ÇÌØT¶\%ó³HS‘‡¥)Z²TrŠ kù¾Âئ<ØH+Φ²ã‹6ÀÞ.S,Ò’ÁÝÚɰÄ@ iº R¡ y¬ŸÁÉ[Õ–/<Ý•ì 2 @^‰œt=»_.Q 4rÂñÙù¤’bƒh–<û]‘ÚB ÕÇk,ñ <˜Î7b¡pü ¾�sBmçCÌÈ)av]“Ÿ4_)uEžŒÉ}n¨¯  ŒØ`R¼ÓÞRÁ¾�_±ŠÎÚfNoiâb³á€Tj3BKqª2êËèÎxt¦”a˜^µÐ+Y¤1åOÌO­ÒC9vd±øj3ÁͰ�J†ÓÓR¤�}Smi£.‡Um©Æ†ê¶��>áW,ÿ­-z ·˜—N$< áˆáŸ¾n½ÅÔù¬ùBB)K¾SúH剪.Ð<ò‚n†pmý"É­C‚é@ôû ¡!j(ëÖwVåÛöÏŠ�-b–ØÂ‚ Ö߯%@X�æoŸÑà½OE|¾§ —ÆÔºÏÒ�ý»I1ê´¬ilwÖ–÷+ HV­í?7_à†ïônC¸à�`¯ß}‰§ ù†]±î´õ¼âvÞÓ’d”17Wf¢UYb é¹o%}o@¼çe¦Ø-;*ͬéR.£¸ª² ¬£}¡�> \\]:_ikZ¹~MÒ+YŠ›Çæ’e•Gö�°‰=ôéoì~qßmeÞZÃýÔòšs2°P¿�ïgâp�4 P8�¹¶½¬9ãÎà�'Xb”!îÂ=Øe p!*° €�{ p£|·õ3‹Úc‘S?�¹¶9Þ¶±5¹Q�›Öîpàž6ˆÎ±{Üèq£{ÓX:WJÊ’º¨osßeßÞì÷CÒŧ\!|d,aà ƒÈ";h·µÊkâiA0ê¶@pä{¸ì1Ùg¾a#GâòäÀÁëV£c©ÓD¹:8:6¤4ëÀ‰’K@ vÅQ¦ÜékT;%Ú蹨Lz/RÄpÞØÉT1Øã÷F «g:;s‹ÛÑ…nØüõ¶*ûŒe÷ÝØöÀè'ãG!(ïšù»×UpñÓ…}°w‰i†3+ÑÅlPÆëˆïLƒÊŽïÀ»¿Ø¡óv­€/×6ÓÙ¦+}}ò>øx.•ÑÝmŽö7ûLë6·zmÓÍBe¥äwäß‚£=mïS¦M=æl 9¦wÌ_Çm½Ctï f5Ã-ÚÓŒ×G,ª9úä­jÄé 2à,±¯„DTà“Z5TEfÚ(Zl˜¾])(Ç�ºî ð¢:TÎs?%}ƒ§W ó§Û[*-®”TÍŽßÚ}¢Ò]üp™ÔMGF ¹W€Ê¿V5¤—Ý݉/4çжûc—êH‘} ŠC�¶54ú¦$u9ô¨ZÁÎì:vŸÃíŠU bQz'‹¬»ƒ¼á".¤�ƒjé=²Ô±JÛ–yu0�_µBp<Ôˆ�k¯Sçη÷#×Ú§Ý�º¯¬î6ë)äu»n¾(¨[³Ì탛­ÿãSÁS•UÐ>}g?€)Ư~ A‹èj¸ÃÝqÉŠÔXü»!gI§ÕO$žÃ?™XáO™À �“ýü€¨Pti;­i˜ëÏ¿¨úÓ¾–ª_W}YÕ:?þ:pBÿ™×˜38(«—ÿ?ö äáÿÃ'ýí)�7xœ…’ÏJQÆ1t3Ðh9-Š£BÄ,52¤DÅ IíÏ".ç839ÎÈ�1s×#wÝ®E‹v½CÏÐ"¢Gi*-&»»ËýÎù}ß9÷.÷.Þ^Ä®U�¨Øµu ÛÊèü0tÍgó[P·mÍDh6+OÏ¡âBö¬Z'ÕZ¥u\n7ZGä€!­bϰ xäZx§ 8.u]›D5œ¡I'»‚ È2´u„)Ô¢2Щ¥šÈ#Ò%í##?í‘Oñæ CwÄ,($â/ñäöü(rp.‰ëË_,_ôͽ7øZTÜÍ\_—V;óÀ*z»±'›xç¿q‚<Ó9‹ó‡Dfåßo‰VA‘?ÈŠ·}(• f©`÷àW)•ra_ó•ïèQµ¾xœÍkoâHò{~E­O³c"sYé>Ì\æ–'‡†�,†�F§“eì|cÜ^w;Lv”ÿ~Õcp#-Š»»ªºÞ]Õíî)\¼ÖN»'ÝÓÓ8…_AH ¦AÄIÒñpHŽz4~H‚劋·«ìL¯çïÎÿ·« â>¹„‘̯òÿ¯Ët>'oÕÚƒvCé—¯À�ü½ö0ðHĈiä#i¾"Ћ]ÿé™6üNÐÎ;ïÀ†ž2Z‰šÂÚ}€ˆrHA)(ù摘C�G×q¸‘G`ð•\GSé_4 :ç.‚» TtQ—k¦WœÇì}·»Ùl:®ä¶C“e7Tp¬;\Y#Û:CŽ5Æ, c��?Ò AiçàÆÈ‘çΑÏÐÝMÀ]&ç8o’€Ѳ Œ.øÆMˆ ãŒ'Á<å; ËøC±‹¨27£gÃÀ6à²gì¶ r7˜þ{<›Â]o2é�¦ˆñ®Æ£þ`:�ðíz£/ði0ê·� ºpò-N„Èf TI|©7›�T±Äbâ‹ÀCÑ¢eê. ,é=I"”b’¬&LÊ„_2a°¸ËåPE.±P÷ääoAä…©�æÏüweFÿ‰cü!&¬³úX ƒùÜe¤«ÿ×O¢<ŒW¦‚(NK£Œû>YTÆXŽá\¸Nœ™m9³‘=í]­ð&Lºs×ûJ"¿[¿„€�rtñÉ‘ŽOîQNhgQ]ô‰º‰V䋆Èp« +4º)£w¥g®ƒµ£ ápø~ø8Ó•THÎAq®BRCch%èJ1�|†¾Þd²&]–gNËcNÌ“ùŠw+"]tg�Œ#$1æçëÌ) ¡ö§!?œ<~Èêr÷å™8W¨¥k¸wÔ0*Ʀ€¯Ÿ‘êJ¿h<U1s‡A›yNEíž@vÖ›ýç¿p¡Ípißk4ûl#£ ÃÁå`t;›:å׃ç 3Ô'ç³…‰ ïŒÆ#«Õ.c^¦œÓ¨——#p9›NÇ#§7±zv•�ñŸ$ÇÉ—ëÁèÆš”é8¶…©jd Í–6O�6m/¡aø\u2‰}LŸj�Iì«Éx8Dmꧪ&¦ªôP§Q�~~}SE´üe�5ŠÕ¿©±ß8RìÓM´ͦ­×ß�Ž)½ G­Â’CÒh,NSo»~Y…}˾r.Çã¡y’…œa©˜3ÚùРq×Ï_ó´P\ˆÆ2Åm¡²%;:†Í$))J\Ç^?`ëK2}ˆÑz¯Æ©¯H;AÛá’øÓÁêd�°•¡˜c^‹ùbêi<Ý4ö·‡Þ1ÓMÝxJ¥ì¯&5wctnÅFóé&ÚI\¯ÅíN>k>¿?1íµËnæÓèADÏh,âC€n;.qãæ„Û�¶� þ$ …·¸KÂB•Áo3R¿ÆØµ®!.<<ù$÷¯§dÊ¥Ñ&@Ö gu­°hÊ€}(æ²(KeaàBV$�Ÿ*!0'Ã3ãúLÐÊò‹ZMSÕù!_tIrÕ¤ï%§ùévá&u4w覌¼‡Á2¢ A›Eg¹+h ±N.Â;øxQ5/÷±ì|(£âhqSó[�"[Þ¤QøÐÊ݃�çF0'  ]_-hÁ@gËý–ò•öàœÖ™,šÏTѼf�þ† ø÷n ì‚ ß·5 ìÕƒÀg¨‡,£ä8ûµwë媫øá_;ˆå­7Å’RœõáýQÈþÀ– zËÔR)ü¦½éÌvìÙÕ•eÛðÓ°¢&ÄoŽ~L—æ¥íÜõ&#,ÞwåÓ?ã5Іl¨·7qÞø-£–V­_ªÎ'kõiÊ}”O�@BqH‹šnÎhQáUeƒQçè2ÿ²4ÖE*&s�šÞÄU�f>®¸}¬‰%ÙÎè]LÅPŠÙá—sL²ùÖ¶Ï´ªaÉAƒš»ë e˜•Ÿ÷°ØõµpÍEhÞ(r ËÙ|vèî¬ý׈®Šl2¼Ò#áõ4ù*~ô¼å-/ÿ4k×�:÷Ð>Š E†E"•oŒÛÉàÛ/çÆ‘89Î]; ‡vƒ E–Xÿ=Œ('p†aì΃0àà­ò°è-‡ˆŸø¢E‘×W$£F¾Å $#vTÀ(ƒIˆªÇ° ÿjv/„+·{QŽe´ÔÞ rïVA~ \Ć_»ÑU"¥^kÅs’B&.;Í´w[“~ë J9÷áÖÀ�‡YY'{7±}Ø“Tõ$�5W\�§îœí(×8šö5Î óþÎÊ  ¾Qæ×8ÇR£ÿù¾*Q£zê‰ÍøåÛ�Æ ¯„¨:Rü±)÷Ôô)9¿röˆ½½ÐòÔV—ær‚ÈVœ“ë+z�]: €³=æ®E“`ê}º«ºx�¿Â1˜†^Îhu`°šÀ-€¿Uà÷ÙäýØvi˜» vf+7[Ú ƒe„ åßL”×9ÚTiuaûïìÊÿÔîªà×»„÷[{¬X±Å�ˆÞæ¡jÔú>A_ЇoاTIiRÊþ/jP0²ô9Û, 8L ã/øÔAŸ³é{âº3&Ž 8±›0b¢lò�éÍÎÓO/b‹8FX§æòYáŠéʽi>S¼uþ®nCÀ�‹íýŒÁcvmáL-}¶g[û®nšˆ¢Z&D~¼È ÙÖTEUƉE^öñÿ#îäW%ÒÌBZá?ò¸1ðxgßÁà£éC>u‘�tĤ8gÚ,ºvèü˜eÍ‚oã´ºÎÐs N~>䈯¢ån2¾ïFvvÝ}ñ…°¥©êUóŵ¸ÿ+îs/ i”Á¶×Ÿš²_)Þ:^¿(–¸ž±G#»1Éüçwk2¸þ⌬ߜñÄ™XÓÙddfVhË‹“6øÙÑäÁCZ*ââ±–üt2³¶jÏÉïšÍ'õ‰4®é—ý·ôÝB~¦{D‘km% �òid=ÚuohïÁ«=õ©%bývHÊÆ_躳Ρƾ¹Å&å5X¯ý6¥–íºò´9ßu{a)1¤QB¦ß*\ïX‘/> Í?¢}þíïÿåÞY¾¯xœ­Vßo"7~篑—ö”‡>„¶:Ž$= Q �NUµ2Þ�µn±·¶7tÊÿÞñ I›‡"¡ÕÚ3ßÌ7þf¼I~ù¿~ÐIZI§Ó‚|^Š¡TBZÔ½œ–ü*WåF‹UnÝÛpû§ü .>]üw¹(DYÂ-CƒN¿ûççUµX`�çgï¸ý¦ÔŠÂ�ÇC`2;@‰±Ç‚£4˜A%3‚¶9 dœq§  6BI¸è}‚SgÐŽ[í³¾ƒØ¨ ÖlRY¨ †0à‰â3ÇÒ‚�ÀÕº,“¡6÷q"JÏa|‹ja™3p%µÜ7fcÒ¹µ¥¹L’º®{ÌgÛSz•ÁÎ$ãÑðz2»>§Œ£Çƒ,ÐÐøw%4±]l€•”g ʳ`5( l¥‘ö¬r×ZX!W]0jik¦ÑÁdÂX-•ml›ÑÞ7 ’1 íÁ F³6|ÌF³®yÍ¿Næð8¸¿Læ£ëLïa8�\�æ£é„Þn`0ù·£ÉU�ÊEqð¹ÔŽ¥)\)1óu›!6RXª�’)‘‹¥àDM®*¶BX©'Ô’A‰z-Œ;Rãtá` ±–Y¿tÄËJZ'bIKHÓÇñïƒÛëûôn:šÌéù5M['´%$¾³KÎ’U†ðs! f0‰Ï^þë›ÄÏØ£-!Ëê`ÕØl¡Tá[TøŠ[¨ �zË4Ã'"ÐwÑ—!s^•qÿ>SÒá Ûð£u‚2K€$iظ¦…iªB¢`»ÆuõØ…[‹u7R­´€~ΨäR¬*í‹ë�'Ú:­hä6ˆÑªŠç%ËŒÇvÎ? mÑsôj lPzéî9Îu…‹¸²ª°;$÷sU‚£_ÀÈú�WÂäüêÜõ³Ý”$ g#õnipkFRï}‡5ÑQÙ%L”¤Aó¥²VÉ�FæuîMnÈ3Vü5ëj [=¤Ü8åÎ8 x°ÿÒ»€¬<·êܺ¾�•ø5òL­ ñšØ3®UQìq ]˜×*Ð0]¸ÎVÄt*×+UËÿæf<Ζ\ã­™Á„YRZMüôª‰m8K³(­cÛ^Ó0ÈÚûÂûsÐÓzþ²Ãè·^ú¡kn”¦‘™‘yÁ‚ö/á.r:ú€¤ t­Åž6»é°»l@÷·‰Ä¢yíÉ®¿âxì6ÚHFq†iéïÚ’òYM‰´´zo;öq\õ‰½„Á6`¤á„:y�Ý´G­•Óò�H§¹À}ú§û�~˜‘s9αë]ˆ¦±ð¯¨³GáìµnWH^jc¼"¶ã¬Yš-Ràž´ž”È‚dåôˆåžo º+=—‡çyy¹ßw^uM‹¸ý¡@wîÒ Å‘Ø ™¹¯¹¯¹3£^›|÷XÒî�ôÚíÒ&?¬\�ÏN}gC£®ÏØc'ï"w½Ið×…üAšN‹¼<}ùŠ¼Û¸ž†ä­Mc‘æöÿ‡uºXЮ³iéÃ`È7äM¬×ht!�Œ\‡ú1]’Ô_ŒdCI?´�ÙÒ!¦Qì>yÙ=%MìÐM�Ö9‚¸ R²µïˆ$$�)ÀpcÂX¢· âúÄ ¶¡çÚ¾CÉÎM6 �€ÒE? Á"±¡»M�w¬ÔŽÄNÑ›$ ã³^o·ÛumFm7ˆÖ=�÷‹{£áÅ`<¼ŠÅˆkߣqL"ú�Ô�€ÛűC È±Lô;DÄ^GÚ’)ÞEnâú뉃U²³#Š`–nœDî"M4�Iú€mµˆÌöI£?#ÃYƒ¼îφ³y?œÿ8¹ž“÷ýé´?ž32™’‹Éør8NÆðëŠôÇ?“·Ãñe‡Pà¡·a„™.Š’.™Üf”j$¬NRRÇ]¹°æ¯S{MÉ:¸¡‘‘�F[7Æ)��À%‚ñÜ­›Ø {Tâ õNN¾t}ÇK—0ý™¦nÊã?:¶ÝÍ÷Ê#Ï],ì˜öÄs#p'Å&?½í¹~˜&/è õ“N°¤±Þ)N–‹ ðJné™ë0Äwq/–ñ©ÂZ?¸IOügöÐêÉ%u<;2ú$«G³'3ÀMQÁíÜð±Ô'ubí¼­ý�F–l´òñ„ÀÏ7¹»`ZG#÷4n[øCDW$¬ÌÐJd¬>$Ú'NCD-çš 8×·=rãFI ÿ·4Ù` ÌhVSêÑ-LP…xjÝl“ SàÔ†ó“ Óp‹*š�šøÙ‘ä§ �³³œèzáXló‡a)‚Mxàv2Ìý$Ñp‘3ÜEºZ±¹P>‡!Ú¦fé"Œ­vøn‚¦€ ZƒºÃ¼ F–Dt¢14«ø»qÄ@{[~eÌd/‚í Œ‡"&Ž€±bÇqà¸6z%áöP�3dÎÆŽˆñÓv¶ ž.¶w6ºp>ÿ®Sö³BX zJãÀCåÅ;Ô�‰QVØ÷®¿ v Ò��ðÞ$J}æý˜�äR©³(VÔ‡PÚ”êÕʨXÄÚŠiR´™¶Q”©µãÈt!•(؂襑>¤ÿLEÁˆØQ BÐŒ�!Õ¼Ït*ÕÙý9wgïЇƒŠ�(QÓ1•\h�‘݇®‹8ô,ìo™œ ¬ù¯#ß Ï÷zönd]fÖl>Žß4O$a ¡ù#ÐÔF‡€ …´¥ì0:DÑæÂ�F£Õ9„e¤ï¯)cé§ Åâÿx0jB ŠRˆè&p—©¤á¦Î§ií\‚IÙ™€'‚Ê΋„—"í2t®–îΚš3ÚƒééÒíÔ&Qp×4„ €)¿jÄcŒ7©H1�"CIø‡rÑså“ÎGZ,¿ÀÎÚïZ\Ak”Ô™‡p6èµ!^abæ£urvXï6¤ÔÌ»�3aˆ ‚j£òa6F�šÿÄ´*õ~ò¸—zòD$ò™˜ «ø¦)D-*¹gü̉Lýÿ2…Ñþ&tð¥À¦4ÙØý•ª~@¤¹mö?Ö‚Ú¿úÆBÕχµÎ³4XúôE~–æ«™úW1¬â%[LCö¤¤é¹Ë2¥ÚåèiJ²«�.´gA´+&ôÒ\`]ÝÓê]:YZ0:̺˜¥cøfCª˜–�9ÇE1rPì$y@�܆A„9'î�Ë?¡äÅc} )öTEvLÁFÞUm~ñ½Â PP^ƒ1È_(’t¡Gó¹$_³©RÄ­–PVö)‘qo ¦¸šŠt¯À n™‹F¡Kt©&M$ªøy¾×¯‘ª°9Ú=\ J£ÕÀI4hš7nì‚Gx(7,go‰iÒ»Öp JeÄ;4(��:«%že*\H�R‚òúïa´—á<9éŵäÃ/Byd²3ì_hõ“]ëë¦Öȯ¶U*+ì¢@hf8þàšÓ@±9€&Ù3w •žàSÈ–ÆÌ&Gp1ÃBTŽ?Y°Ú[hèè±@�?ÐK7Í×3k0�N¦°’¿²�~¶SÀ5¯Æ²êNÈ«!€·ÛPæõ±²×“U3,›¬RÏc¥³ô HÊ]¹¬êÃësr7¨Š»yi­r£"–i¸j6°oÜ{7:3Ó*£ ÷ˆ‹ü“ð¯ãI.9ë/Ú_­FG›º’×0òˆ&¦#3y U2˜ÛåÚb+¡•«ÆÛ,«é�°CfÖðj:x#HXE”6ÃÞÍLÀ>Õ,JOÑTÈ“PSUqã>×ï>þåò�uÙŸ÷­ËátvßÌ;_º+ YýûÑOý·ƒ©5›\O/óq&éKêÅôÄLKåD)j¦qS&’4z1Ø•ì÷r[ÐFÖSÇO™Ò‡HʨÒ\­¿„lÌ]Uêx¶)’å�ü‰X3Ä�`SE©ë0Ó©è­ìÖ°Ä·«¯·0è eÜý–á˜!ÃZ�Z.ÄTÁóbj=Ü2˜ NÕnç'÷�¿je¤Êi8XO«ÄlÚ¿¨;í&™îY¨ p=Æd5©ŒŠ÷¼B�Us+ÅõŠLª…Z‘È=�@)ù“YˆuRëú¥>EŒLF2¤Mcw}5`ûwºÿ.~PÞªò¯†Š…C—´Œ¬²PŠ’ÄÈOÁW+¼ÑÈŽis?e$Ga¼WӉ׬Á%ÑU)²aùf\8™ukÿzó •†áUtWÈEW^Mè�EýAJMã«H5W"Ž�êáÜ‚å�µUY™jA§²ñz€8µg¥Øò>Ç‘qŒ”ê ç LorÑJÉJY'Ò’'ˆÇú~‰±:W;*‹¢’3Éäj#X‘xX…¿õÐêú“ÔÕùÉ”edïø*:(’Y̧ïÒ ¹öèóïBŸŽÚ´/ª§!YÖÈú·!Ÿ·?�èaI ÙÃe„ÍR§'PfvЉ�7f3—£VÏÙ=Ý‘™¤âž;)çV…U ®fÎkë„�õêI ó͸CâÚS-</ ±í ^B“LËHÛòÜe&5b³ê[5È�)?”ÕN’à¿S\­F3ÃstÙ0Æ|NÞ;Œ./)"<áŠ3Í¿pèü»ºÇ¦Ì†Ñ*TÔ$=½>`6²cU…påvBÀ*ײèÛ 76ï�«Ž­¿‡�•ë7OoW«¯_þáô[»CžÃèç0ü9ŒÎ¼‚b;Ihä+ÂT¡ÈgB8€ÐÎ}“N�g=ÒÈ¡ÍL>RÂc$Hæaû{GÔIlíiNÁtÈ òõKÒ†/=òêxÐQk0V®çåÃu€„TkÔØó½µ‚¨µ²u&?RH¯ Úåœú¹è§ëj2ž[³QþŽ'ÓŸú£ºcÞ†o~”ƒJ²g”±ã e_ŸvO3AtO+&,S+M° Wü£�Ú‚cíS{~£Šæ¢Bôñ�ŠÐÛD«X±&î‚FdSUaéO’ð”ýpòô!6�ý`Îε]ra{^Ìî��?Ë~ò²HhGö–(ÞNöä(ÇÇ«¿3ÿzf]€Šõ‡ãÁÔš\坯3ŸÌ�·ÙW¬ÕØ�ãGâ·€”W>jÕó~ã‰ÕÏÈì�7ù´x"QŽš‹Cðž;û.æw4~»£’Ÿ©zdáöõ|l�Ws\‰ñ=’Å~”k©ß�~š¿µ^_Ï瓱u1^¼5IîBZÁ`T5äæÚà§SW–ñu�r¬‡{E» å ²MM7&tûÀ/) ªò°ÏßÔ<(+´wmò’–^QÌ`å™H/D! j³¢\KÐR@ E­Ýß½D<‹îeš‹êì^ÛãV±q�ˆ~Õ ;¬_ÚéªI¯OºÞe{Ù{h1¹ªp¼§¸ Æ¥qR¥"ƺ®ÎJþ¼Æ1nó¦V úQëúøâ«~y¹þˆì,p>DlBWˆ®Ó¨¸ñtTyÛ\Ü6«úûþt<¿eI×õ,<#¹æÈzv C«KE ï!¹g7Z1Õ©%^ÔÉÉÁÍó~v÷›Mñ$ÅAÞó�Úº¨¹m±çý"Lq¶ó�p¬üÖP~äM9˜:Eê™uÅ…X¤H—Œ½ÃÌ'VOPW`”‚š£Ýº‰Å5Aå®}Û³üt» {cà¿ñ’‡B©róC%ôˆ„M%í¼T)‚ÛÚIÞ&/•(þà”ÅW »ZlÏ`¥¯À}õ,þŠ€m¶ˆâI¥ "lòlÙmäÁ}§2­•“ê¡úà²Ügq'(5ãиЂXã«IGÁ¥™ZeD­ì¥5ˆë:øµ×#óÉå¤téÿŒ¼¥4dÒ�2Ã3aZLB‚4 Ó„Ø7àñˆ½…†wãÆ©í¹¿ò»¡UJhv6ÇÅÑ’“yœýÐ* ÿ&îÝã› x ä•,Zsb˜PóØ.�ØT8;ž–‹²&±cÒülßãñí =QÇEBc÷ŒWXe"a€Ç\ÐhiA ›õ‘Þ‘<‚ÍoöÔp”Ç\7³¬ëñõlpiYOq 顾’«6®É£ý@ƒ1-2Ô´;D» %=êÑMko†_Ês`1¢ƒªXšî?îûYÏ`÷Ù3#ÞíÕŠêÜ3üŒ�¨:ØA§6ÌöŽ’e@¨ï)ÖºøÝöØ¡¾¹A—\á™#|&Á�JØd‘®™:¬Ü[â&]Ù&ÕŸÍÓyS½‘À‹Lñ‘FÛ:Ú ”7Ðþï{¹’Íýî.h^ 2Á¥œt 2Úš]sEïðßäê]BþŒ�Ã�äñ =ý÷AVœ9Æ d~ù?n5/¹ÿçÚÁáôò32„Š-µüæ÷ÙYñÆv—Ì@£Ù•'ö6)þ¬zWì±^ð[_ã[e´S� ‘ò%Wí§¸Ã¡ñØ)¾,A¦ r÷D9p/ö°V¶S�\ÓM Ó%…ã®Dh¢ÛKƒrÌþêóû…÷‰6ï@2×¾ J ˜>á¥q⌣úÊ„h¸×.€µ êâa×hP!Äß›ÞÈÄÇb³•4Q6ÆÊ;™>Š·!5J´à‹’ŒD’{ù$k>˜Í­‹þl ¼‰½wA§ˆ¿ŠÊLF±ï2X³Áœë¹ Jöm˜,+ãG¼ýãÑ}Øà–FŽ‹¯ßâŒCpŠÅñÃc'1;$ªLXÕ›ø_˜!Ò¯»øÃ+r^ .�²+°L¥3Ñø¨¼Q üÒ°iœ|ûål€\úâÛW·ß¾ê†þïy’3Cý›¦dfC»hSR} ¯‘-›Lo�`‹(ùœ .Xü�ª×~Y³<½ÉÚãé,×_73Æ¥s�ZôçÁtxõ³5üÉšL­é`~=+| Óëd·ŠŸ\ñV‹Ò…Qq¹½!oZ>}þÎŒþ=ñ§ê|kñÅ øy䨣\ž.ãÕ'(õ!N7±ÍS!³0˜Í§ƒ?©:e—î*—·©�0òà›+ªõÁ{ÐfÉï¹O¨+‚ì¨ 5‹ÈÂ!?—@ºÎƒ:›¹ ½˜ xœíisÛ¸õ»V�$’‡’’l§œ&³ŠÍ$šÈ’«Ãi¶Óa( ’8¦H– }ìnþ{.à!Q¶åÉl«™D"¼ ïÂàî1zûXtÜ=ê¡côËÒõ0òì;Y¡íc¯ãÀkÖâá]ä®Ö1}:•¨é´Ðë—¯ÿ†.Ö®ç†!úlc‚#Ô¼bß¿¬’ùwœuKCþŠ>Á Ð §ÉÀu°Oð%þ`ÄkŒz¡íÀ—h1Ð%Žˆøèuç%jÒ ÑÔh½¡ î‚mì;ä1J.AŒ+|ëà0F®�œ`z®í;ݸñšáP:ÆW#˜Ç6t·åKµ#²cAô:ŽCrÒíÞÜÜtlFm'ˆV]�÷#ÝAÿÔNÌ6P,FÌ|‚"üŸÄ�€Ûù²C È±çLú7(ˆ�½Š0´Å¥ø&rc×_ˆËøÆŽ0³pI¹ó$Ö&é¶Õ 2ÛG�Þõ' ô¾7éO äKúi4›¢/½ñ¸7œöÍ �ÑéhxÖŸöGCxú€zïèsxf â<ø6Œ(@¦KE‰LnŒ5–'‰„Øq—®¬ù«Ä^a´ ®qäG(ÄÑÆ%tJ ¸ `pd;1„ŒøiO@Dàæ�JK&§]±7�ÀÎJ$) pôók«"xÒÌfÌ"îo¸YþŽs0ˆ‹x�=®1]'(«•d y–‡-Ý5«¬öXþÊÐ3ÐÇ ;¶ëêe%8Þ'ÅÇ\ýƒ0ÖF©YÞ�ÙK=éÃXi$¼yø9M#/Ý(N `lp¼`YÆ–j2¦Kưl¬©þõ&®²hBoEÒÙQ Þî42ƒFœ}È|¡&©2Ûª¢]ú×-Ô+]2.„îT1�sîŒ1yæmDtÙÊù¸ç:@Lj?Ö‡¬ð¬ÕQý �¼uô{y(W‚8È_oVéâƒËÑ·ßÉ,=íšan¿c¹"@vÇ<÷5‡³Á€Î¡šF ÇIä#Ú&RŠ 9¦‘½M‘î—2íâÊÐzås¢ô½–åyË’¡ëÅ9�޳ˆ~4yÐß¹TÄû>¤’Ò&�†ÝäöÙº¯eÄ&Dõð”ú ªæATæs ^m³=˜¹Å]›O`ºìÈBc‹ÿMåì)]²©ÓÆåRX VH–®Ay¸ÉÓ(—Õð’Msà#EAÄ+NŠl>s5’Cp^°Zá…zçNf]¯ DƒN°l–Bj•L>÷/@®w/eò`îl[£)“ŒU+xsb+øÏ¬ª¡ÆvV¢sZÕi „ß3Qü¤vv}HQŸ)ÍjAz0ZªƒÙºò*®®¶øš<íø²ªE6ÃÛ)˺Ue2»ž>ä‘hð>ÛEÒÚ…YÉBZÚ A6CKùÐÑ^,Ònv„5yîðß)œŒ*V£"H®vëX«Tb/.q!‰Ë‹í( îë‚ÉM¾��X+`Y¼F’aÞ�ÃØ*Íb�ÌØ®ÛÅe–&¦<ñzì@äëHöc€�Õ'€VåS*I¾3Ek¿Ã× ¤Ã—¿u8Éíöc…~d^ é‚ š‡|ůñb”¦D�EtºxÝFv±VX�ðtœøœXœ›ï'Vøad Æ©È¦”| = yÆÅMó›gaCKuó5¡v"(K®¿LY‘ažñ $ùÔ’n¢e„1AI&¼ $p\&’“ ‰Lr)fŽn–æí®pÖ,nŠT«0O‚º�ôTS¥(;«$7È "ŽRF¬r YHÎ¥z?¥©^ÙÚˆ7Ex\c>¾™ö6j!ú^ð7 —ä­·$‹(^Ñ Ôƒ”÷<÷…Sá…K<ê~ðôøôPêJ`•Ìõ6ý+(A–‹ E�)Œ¶þ¾g6–¡¹_¶c1_Û5–XkùÊ¢PP­q Ë„)± ™NJí”>'%}¶°[q+וë¸ü’„zð’…ÌáâIrõ êN¾Vï<9‘é­ÜØÑ‚nË‹’Ârk¬ÈÅe›jG¬ÙÛkß•=¤ÓÑpÚëͱ5ú�ÙžÔ(g -–(ž¡{®[3ÝihÀ®Ÿlæà8`IdûÄcy;Ò!Ï® kÛKpµ_e… Q‚Y¸ ¢+R]–5Ç#«ýd;gÍ=7Ú´ªnûæ²%�68kÔT ëvl‚ѯô€Ï ÷fyòÉ ¬ËWâñ}ïôóÇñh6<;Ñm´ãËà|úÙú2ž\ôN͈ÌÚç�v^½©…o4�ŽÎkãb½ïƒg:º¨‹ºÞÃèÒïºXD÷RL ¼´/.‚j¿*íÿ½�wÒ;öè¡DÌ“m±Ûf"·¸Á1=”(½Ü$ é¦ A/†£¡ù‚¥ä/Ìžf“þ%<ë{àhžÄ"‹�á=™wÿQ@q²ÁôŒ ðÈùL“÷éèlT8\z’yr´L ý'œ@:r¥€¥«_Û×ÙØƒõÓƒNA´²}—:³cŸ>]:x˜*CÊó2p‚šxÕ⣠Y­Q0Î(8à)oáx®sEZ;—²E“Œò>³é\›Öq¨["‰ËÂ+ÔšóÂùIϦÁªtn³ñjr`¤ŸÍ¯ïG½ñ¬¸¦æ¸w:í_ö§_-ª”'y›º'¬T«3€4Ûê”hºœ1‰ž?×rÖ�ÞF®fp|6-¹Ê¥uèØbKϨS÷R'9k¿“ðŒ�/ë¬?¹¢,s<�­þùÅÀ<7!� G†‹æ­ã%Ä«ÓÕ$ñ…�òÊ\2Ï C?t¢Â°´=‚³–ïU‰;­ 2t4Â×HßÓéã(Êhh�™ç=c î¾Î¼=xÎöš¯ÆL™ááÙFYêµ)¤‚î¢SÇÇ*ÉÞaN`ý?1üM ŸÄS”±&\Æð²7èŸñÆœÇèsá+&æ+è?%Ö±†O±½!ö4×̇„J§’·ºž>èÇKxWzž•?�$Šè 8í^ ê)6.+�쬳fTdµV�2QÕ­(ÜJêPÀU@¾E<-ÝOJ�%j”5}(€Ø«&¼W šn—P�a®_Œ¼·NI&qëÉc¡Rô*œM<9Q�²˜÷TG}­„¥ì�ì.`I3‚/�-'ð—î*‰ðÖ½äŠÓ;‚Ûì@óáÖëŸrçšò÷<‚e¼�(’#å [ýrï­^Á1èí&yo1¿¾UŽæ*¯åÉÜ%ÏⶬÙp61Ï,+$øÑKCZ­[[÷u¶³»ªv̾r1ªè�vÆÊºG”RÛwÖ;#%)h¿ãï2G7çéä[L­õ¾ZSÉnåcx›‘­ã:;Z¹¾åáenoèУÐ��ÊzÃûbç¨H�èqjòæA›Ò¼IeËU¯õ[àc}�ÞÆ#€¸µÔEÛ§«cg��«Ô(™EŠb -û öš/Ù¡Ô€äfG-2°.MÚGyŽš4Ý3Ï>BBj~˜¢?Pú<îü4mµZè�?RE$b>kc¡U Oå[­–¤Ÿ8ÏÍÖðz¦;éÿjV$ºìTþ³º¥ÿQg+x}yû¬q1îßþüZX˜£DŒü‡T¶Ù-Ž–*~‘~jf&Šæ¥ÁŒ%umõ4=o(¯îuÓ¨�.ü³� ôõW~AÊo»$ã"Ü©Þò)¬‘K&³”f5ë¯]ᬠ¼¤„‰·Rñ„\½%Ê£·~=ô�Û!Éâ3-e32›÷¼{ô£æÜ‘ÅÄj»Üë´ª¬%>¡Âè‹ËUfFû(JsH]Ùû¦Ú�¦-…ã4‡ÔVÆxZ…É߯Ù~ùmè“ôÛ§;¤ÝãÞá�¢:%'ÄŽJ‚QÍ“1?Äá‰RÕ)ަߡ<üŒ*Ó›^›GltþïXì¥LjšuA7bÓ¿™AáSV[éŸùÖ©§��qõi¯ò�c™ø…£ôQ )Ï]Ò˺ъŽÒ¿Ø¡ìr¡†Å¨ÊÎôºêŽÛ…)¸}zjo› GÖ¹y>Í- >@ú‹Âá§ _ It…ruIv_zA¥îÝ!øâiÈâìHÈ™øZÊì;Xhü€?¼#®ªw¾)Dy)Nz®9±ØkÐ'öÊ»ÛÇl¿…o%˱´ÙŠË­A¶“ý;-rüά�:1 £ŒDô� lrjN¦ÖiobZs8ã4[ì¶²NͶ�ÍÜĸ‰9åóK“DCû;V� Oy·ÜÛË#óGŽ …¬:Ä–GÁ�ßIýfÝ•G9¶Ü{,}™Ýœ+Ü{ä?DÅ‚ö»ûÌnm±(+¬SNË¥9îøj ÍX`òcs:›’>ÂÈ]Dˆ;#âj_[ý~KºcTdáHÝÈ’º„ûÝ¥.%*·GÂ'f×-ê½ä\LorÛš²Nç:õu&=a¶ÔÿÖá=¼5‰ÿ�pj¿‰xœ½Z{sÚÆÿŸO±u'  n:s§c'¾Å6i™`ðÉt:BH l-´ºÒÊÀíø»ßsvWÒJ üHr™IŒöq¿=Ï­:yÿ­>¤Þª´êõ ©“ßæÌ£d³öš<ʇÛ�-–Ÿ.“RujäíÉÛ‘›%óX��6�hHªwòïo‹x6£MgYËoƒ-¿�ß9_›^ïR3é1‡úuIì»@C,)i¶ôLƒ|¢aĸOÞ6OHé©£Ú’Øò˜¬ì-ñ¹ qD�‹ˆÔ†nÂ|âðUà1Ûw(Y3±”|4•&Òø¢ið™°a¹MPwÂçæBb -ôRˆ :mµÖëuÓ–Ò6y¸hyj]Ôêu/;ýQç$Ö;n}�F éb‚¶³-±�ȱg §g¯ ‰½)Ì Ž¯C&˜¿h�ˆÏÅÚ)’qY$B6‹E°D>PÛ\�Ù>9j�HwtD.Ú£î¨�D&ÝñƒÛ1™´‡ÃvÜíŒÈ`H.ý«î¸;èÃÓÒî!»ý«¡ð¡› D @L†PRWâ6¢4'œ+‘¢€:lÎPÍ_Äö‚’¿§¡‘€†+á‘F  ‹d<¶bÂrhG¯fb�Ì@Âöða óN†ÔäóÄÞWJFW0$I!hÀwK�Ú¾·%.�ØÂtp}´ä±'˜5`΀5ÌÕA[2IÉ«KEǃ«ÁŽ�Ÿ’!=V7ŸýMAVÜ¥ò�× +¤òÑçk”+ŠitªÇDP í`IÚ7]h*(9(t¤ÖÌwùð AÐ�"MBíÐÛ6 àÚ¹R#Ò|$eéÀügÉð[Ä þ]£¶s‡‡ÁCP¹!ÑX/©O‚�ƒê‚QM­ðˆ!š5É;¢¢Iºâµ‹ˆÍ¨xTth•\xr+Ek&Ž™”=âÜ:]@`›&þ'ùW`ú¦g½wÐdÉÌŽT�ê€'04ŸŒ8¬O1 2I©·MD¡íGžòh0pðre8¹ˆdðlH;’Ç,4µG™ ”JOfJ&@†’÷ç2pI…8X·`²áÌF˶Ðæ®ëažcÜQô2ëQ†cDÇ0“{F×9ŸQ…ò<ƒ­FŸºØ4;¿5g—(‚ín“tÀ•5þÒ4Q¹C dD£µ±8€¡‚–‚R±6Ž­ ‘°~<ôÆÝ›d_˜ rÈ#XÚ÷àOd&£Ì™ÄwMõñè9ø8 TU”²XžF Si„£U©üÈ|Ç‹](«°ø[áÈôœAš¬Nz×í��¡õGûSÇúœOö¨aØ–~êz(áÙ“Ì a—ʪÈ óyr�²Ôâ‚™[‰u',Í1Í8¡ÚƒÊÊÀ0uÕ)Äw;%X]Ú m©ÆajR™î²µ¼„”þHBéèÙ–>]'ó-ä ö�—ÐSñU„Q-)¿AZH06†MM¼ÄÂeæá©O2ýùyÒ³®ÛŸ­öxpmu¯þ:{†E?<íøÑ¡¡¹ç j2À£–¶ZB=ù†'Ý�«äæ:´6ŽAûº‡¨ÚW‘NF¾þ uÔçwq€¥½´¬±€IŽ2+€ÂHû®TOŠò%¤ö:º¯°ÿvÜíuÇ_J èù0í_´‡¥ôü £›^{ôG)5}`ÿu§[º'á7ÜÀCÿ0‘ܪÔn7·7‡IeKÁJ©ƒ˜Âü ýÁ¸û¡{ÙÆ;�Ö•-Z©›u6± „~jÉÝæDU�=+«òÎgغù¨ÊÜêN>5Òh­òO.çj*øfRh·Ôµ訹Sý¹)濔ϫ¹ µšÊPAµÛë!ºšD ’ƒˆCŸà¸Z¥gŽÏ31€ÑKR{J©˜¤�^nH#QI²PÆ ÷Z.‹ÏÞf‘YC2£–®ê,¼�Ä®‰‡Uss2�šf[ç¶ÑrLvå­éà‰ŸYdèÕ‹‘ÕÃ9ú`CïìîÕèUÐ øOqle2¡e«²N] þ„ÕÂ�¾ ˜AcêÐoÔHŸR–ÌŸ‡¶ªsâ�â�x]"�©V�®º˜Ö ¯–[þñyb’XZ™0FížP ÃÒ\Î*ß>ÎÊbmŸ™ï�XŸÅж`Í­Zf¿»–zx÷>#K )�R?<1Jåæ ¿ggÅl30<Óæ!¥FÄyxRùÖ6¢ßË ¥5FxM._Ä  e¨¾X[Úx÷�¾°*ÞòÈ·'´ë«1c$]r¤ù%²Z7*{Äá=ø*Ç—×_êâÉd îÿ:¤�ô9^•«+©Ò„‡TR퀭ñ´È~Šîx1_®q«2ãܓڲÈR³ÎVT(�²ö=�¥ÐÀ`©­ªËIû›LddRÔ"©–°�ªžíΓw¤Ð�‘7ov¨n$)aIìxÀÉY>+3 Y¦¸P’åQ¿°÷Íf²JÜüIäþdPO¿/V™ófmü™£{^ʇJþÛƒÏeUó]¢yisû¸±wø'gú$5$y ÙÓ!ëØ˜¨á1í�:YŠG£x�6Ò`¾[Owž‚[…߇¤?êq¨úA'yP{i—¤¯!¾K í`q¥Ë£çßÂ壬y=c '·3•´È]$=í¢'ÿ±¬Ûþí¨seYÅ Çš1è .ýq»Û‡\6ø�U 9–ùý ²S5¤f¬‹Æ}É'?¢XƒÕÇsx±šC‹Jú 8)†¼@9A*´äTuW£‹:ÁâD®{R»dJþ*Ò²œ’Wîn“ôd}÷bC“ëe�­úÕƒòAsiÔÓ¾ËÏb QÙ@&€=Q#ÙÍ`&]^ÝN`†ÿçÄÁh_”¼šCi÷dä”Bâ©&\k ’~Í¡òBÍ�ë­–9ÊBÐNÕ—bŸû�Þ6Mäê)3ë�•�k\­A~J…1¬Â,¦óœ÷åãär £´'îÉòÌòä�Ïñùlgwæ#W�‹ÛßÁGðò×S÷ÂÜ'¯¢Suü*³ټ:ºv7¿¼Ý½žå0‡öM³tñ>keU~d/PÞL|{ªþƒf׆ä�L„·2%ÁFê¿Ç¶žŒIfs�LêãsõÕá.-€#áØg§R;Èw»UAʲ–rpN®FT¨b@ËeÇž êU%¾Þ‹õ�ÌŠ-€«ßØá/eB(!©ß”m¾NþÔŽºƒ¾Õ¿½&çïIõí y÷ŽüªÛ=8”Å|3‹çèÝú›òìlf¬ô5™5WL³Õì-*Î﹣ˈÕráÝè1`~މ>û¾Ã[óÍVd±%c€·+–�ÜÐ’âO/‹‚ýðžä¹R`–Ûjö(Õ6d(�0çîòÌ� �W!:–E€¼Éæ9æç }œØê¥nȃ¢°I:É+œÜ<ä©üH¡oø®†òCÞPJîE¾¼j áåWKÔk¦�$Õb~­¹"`À 9ñQO.í‘Þ/o-Q+ß[j‘Ïî‘Q'§¯t¾G;w¸xÖ+ò¯ë Ì7Á/é ÔçÿÒ”\â¾ݧ€ ‰Äg•�“…Zðü¾} ‹¯l_ã^µ3`¾h*y)cÐ{⫘R�!ïïæü2áSßxxÎ;Dýú2)Ÿ«ŸÿÝÏyþ4·)Wàwš6xœ•UQkÓP‘!ÙÖ9Öµk‹ó®u£íšÖ¢Cqtn…ù a »Nê%¤·m\LJrÓ1ǨO> ìIÐg÷ þÿ†{Wß|oÒ´Mšl̼%çœï|ç»ç»ù(þš?zÆ)â>ѱÑ$ÊW||)Qด¬JŠY#(9ˆ)ùf£·àÃhÞp é~‹…=EÇ®$Ü.æ›+\ÊTk¤Žž�qµ²Ž«•­µ²°îGn‰*QÞ�-ŸQ:cÛ� )AZ VU,PÔÕšBô<Ê8ƒê¦DÞS^Š»ÄÛœ¢±§�Í"FLÓ¨áÁi=PVª×£4˜eý³áÕ—¹~�²(íòЬî"ª!Ú$0;~Ê¡RÈ[¡Ð 8 EX(>R#¬¥?ÇcY…nt+²A‰JX³CÖÏ`¢ÈjkrÍ;0ï&>ÁñÔ#(_ß„§á+s^5²~‘À /m e¹_Ù€¨„­Ñ 5,‰Š¢Ii{^ë)æ�!¿"Z=ü=Í !e�N¨©«h£*Ë6Œ«àêôܪ÷“]įëÎhùðz:7‰n[Ç�ÚD7dMe' §á©Ô`áZ„Ÿê$é„m`:“„¿‘Í �üަ\‚N<ÇÑIx0‚/ÑâÎÇãWH›¨ÔÈ;+�뫾àCpÿm4�œìŒ³•ŽÞ.:/b�’À)8Á ]Èp�ÏGÏ¢ Ä‹ÞÙgc�ºNHz NзsÖÿ3¿:ÒßjBÉLìLn°š¼SñDshØ:=I‚í‰à£¨Åª òk~nü˜¸÷óç“r‹q”_ì�©×E‰ _*ý¯pš�tä:J[>A%Ï¢÷“ººk&m™4ã\tÝùÎϵ}Ó•À :3 nŠ»1,R,™º¡ééN0ГN©%R×ȇAêÚ×ù@•î«í]]�å¹3�‡ÂYsåþƒ¨�êvˆ—KÇ�à†»³é«Áô`wk±®Ö¬ÿ—kžß\ºlýoþëžçƒ„yxœ•UKoQNÄR(ô©5±æ†¦�GmŒ1Ôû²6RÚP¡6F'Sæ¶LœÎŒ3hc¢;7ÆxÆøÔ…]¹ñ¸rßèO°qm\xç3·@iï8÷œó�ó�ïŽÆ>EÞ07òg3|2’©c©�`¤O�aqE#àëþËxÒwU—9~â�Ͼ‹Íúë›t™sÃmK›÷OÔN`ˆ A ÖG”L‚¬Œ`ª£ € «~ȃj J• àM¹½z.ÃiÈòJÖ-B]ø£ÿÈk«ñUðŽ§®|Ð5€ÿ¸üÀqðP ÞÌA!pÛàà] :ÔR„ø0ðÒ–À‹®a 3ùÀ ±* d,ÉF+„&@Ò”‹¨ñpêyH'dùTd�%NâEÈ:9mÔߎü/jÙ„cF(NšP›’ô…C[¬GÏsˆÓ¿N1t¸ýpõ€‚•c5ví`d,ª�C0ìxþÍ�¶šÍí4Rs. €Þ!¬ 5¹¬!Ѭ†X¨ª²jƒ´Чëa1Êw=ÃÎ/­­ff6Ø…\n%wŸÍ®°Ë Ë+¹ Ú1t—D]¼í[ Y¤ï3û “ŒüÌwzF½ök’TöL’2ÿˆZêɸjˆ)pJ%-5)ÉÈÓAF†�­!³µï½ãµÆ/“r…SÉêrŽoScEy;<»Æ®Ïä²KÙEšd�ç¼$ìZwH$äSàáü"0 cJ ÌéD Ò60KS@U@%¹Œ€Ž5!¡PSBjÅèðëþc#|éO¹‰à¯ÿ©!þòxœí=ksÛ8’ßó+0¾GrÑr,MííÙ—ì8¶2£ZGžµìI¥®®4´Iüõ�õ?£¿÷úÂ@.YDd€¦GH‰Ç”nŒ5&!C)^à‘7ñF0µ`ºt§MÃ0#´ÀÑÜ‹É’Æ€à˜€ñ½¹—¸ }”›èèÕ«ó‚‘¿Ãò§œ:ÛSÿ§ïÝݹ1>â¿[³wÊË8ß…¡Ÿ{èIîП=º+˜Çø¸&q8 #\Ú`…I8ÊŽ&-�½ã‰`ôéòzx;èoûƒ›³÷—]µ‹%«°ükH˜8Œ½$Œ2€µVá2Y,“êCß…�SÚ0úggª7Àt‡tEŽiS²~“ü¤”õK`!î½äˆÿ&Ë(_2Ò¥="â]à‘ïFœ96wDÞ¡@žlìúñ2š¸# ÓBoºŒp£Ù"Ʉɤ–À-�öDàÁðÑŸ»÷8Òû¦‡I³ñ ÁlÇå() ¨Ú+èÑ'3Gþ=ÃD–‚Ü+� L!ÆÁ˜þ�Ç™I<„Þ8?Ù¼u�2`AÑÀÎÅBVò–T›€D `ëGtxM61LêO:�@€ñÂ0‰‹!•áÆÛQK^IØ‹a¯ñG˜(�½ Å�"‰83ŠQîì1`žþðG:ÜK�ưƒ¢T,þàGŠË(ö¦�ë²ýQ‚èù}•ÿ˜é‹ÓÆ…«mŽÏA*Ãåœñ!Œ@s�¡Eº…OÐ%Œ 1¤‘3¤ÿyZ ðŠ ¼J�\ìe ‹§UÌ\ÌÄ6 \ɸF†Œ* °›HËZš!» o Àn¢C#¸L™ÁD·ü¦f X0ŒÊ÷Éý�´Uyž‚¦u®'pUW15ÄQÔže€ö‚ÿÁ#bÕÌq2 Ç'`XrQK‰A±ç"4®RFƒ�Å æ_žVŒÍÍë["V•ÑäceŠ—Ÿ°±f¡?&–RvfqfEF¢‡²,é³J9Ä�–‹{$Hh#…–Aµ´™AÚžè[WäIî�ÜÑ=l#ß§£2žÎ.œ:¤­X½ƒÌCm¯23ç%åóß=ýá‰Y¢�œŒý ËýpÇ>A9»‹–RrŸ�ï0J¼1&2%ž…KL&ïý8DSœ ÆÈÅò1•»RLv}<ÇAB– �? ž¥šÄœLHEŸM1#}Œ&DV:ØXfeF¢.K™)+I™Î�ãpä¹D|eQ`Ì `fD¶úJë=ðÇ¡ï÷ÂZÑPF5©0é# Ô§¦Þ�ÔbSÐ ào€[¸;Ȥ=⿺ãV’8VÈø“BPh€#v_„“e0W8¿ MƸܘ©9lP÷˜S*­‚Äd=Á`þFÛ‘ON„¬¶@Aª22DÆ*‹‰—=b>@ÞŸ�{êã¡0(E#ˆnb …x0v—<×Àj$N®�#e?ãàŒ€u\íØØ`N¹ýb6_r�¨]¦YcNÞ¾JM*gc#Æ¡Ò$)]<±bYj Ù";³Ia&éó Â_”*ACáT>ë1+ˆ™»c››�½0êá‚�À-v•“3Ã,Ç•3á—‹ñ&C¨Od;›�Í$ÜÂÈbhªêJȪs£Ù :Pÿ4‹‰õÌ'?¨qgVÒo�YØ€çŒ.°ÞÃדiÅm YÌ b,/¦PÃlÇ0ûµp«ñh÷Ë"Œ¤ºf”Ç@Ñá¶~@C<ï ÷L•9¹ š;k¾ú3Ïrhúoh‡Þ‚:�‚Þ�£Ždòsì€MõONyÍ&3Ǽ jôo// eU¨Mn”"ò’5U_¾“ˆ¹ˆŸÞ¶"úˆÞ¦(—µä„“cÀñ/ÀÌàu]*1üqÑÜsRH­BÛ¸ÖB,hK#ÛçÖèë+ň"Éb`Ö¤ÛzÈÌlÉ%ûµhtø?€©·„aìÊ…beËJk›Oñ ¦Á \8‹¬c1 ޥɷ_#,øÛ>TZ2P•Q¤� 9°h–ê� êä×ÍHŽQ厎P?Lð ü_8“\èSDZb‰[ È$Â8»}¾ ]r‚ÌD ¦Åšëj”�è^�ð+å©M²&'ˆ‰¥˜€# Faáx4®J£¢ÊaáFî©z_y,Ôþ«”Ûvé5Ûk?�ÑçWý›³^¿{=¼ú E‚6†’“Oq^Ã+Dò¨+Æ'´,öÇ�JP5iË41{¾wuGÝ/o¯;æF¬Mq«’æz¢¶H—gÔ4ððr¸ŒH`0[GQ54å[@N˜ï©ëêO—ËÞà·Ë³ÏLõûWÃ�Ý�WןõvÒ ˜'Ú:þ¸pPÞ¨\T;z+Ê_š(ŸÎ®û½þ/N]œlѪ‹SyšE"‘íõ?\¦—¡Lej äñ# »þ¸Øs´ñ¹ôDv?•¤­›>Aç>v*g— 5Ü--Þº ]Ãmoû·ƒîÅpø/¹“Á̴ȉö7ûXlOåQq�ÔØH e�27ioDJ–͈ÔÃQY ¥b˜«½Ä¢·‹6íOÝ®Ðþ±ï_â÷›�ªÔô)-×Ðä–ƒw7T<˜FwmHרT,4“Ez“âÕ�NUµ&0r¡4¹òÙ7Ÿä¦Ó2$ÅMb•™§=) ÓV4bñëŠF——0ÖÙ² /ðîúÑÙ§ošªAgΰ(ú_·1¶ú(cm-Ü¡•%fú›ª>Šˆ–Oجï°Â�%lŒhgJF*0f­w‹lA@¦V0¦:³« ×ülä8_jTþ¥"ƒ¯2ÑÈ5C*Ðx[*ftÊ o²ûÎä4'o DZÙ-Žm`21xíþ¦}­˜¬Â< –A$ŠgÊå=å¢sR8]d w)W' XÍrõ4S-è]°=7 cô[è ©¥ Óš&-ÿ]Të”âNT0ãq£Ä9ß|뛨Ԋ— èÉ!íŠ:´ÒWÙ­¤2ŽçcEY#rÁaµEûô·)V˜ÕŽă7ÒVòA¦Ýð|‘¬TÎá•fͦæÀSe.{ʆ>…Ìm�,„@¤‡¥ÌæÃcÖSºeí’´£i§VîDU€š…neÐIÁ¶uu$K—µ5ãZä'Ûêõ?»ì] ?vo~½ºÈwPJ*Ñx<é�ùŒWÙü-`2XåÕô•ªåkAP‰‘L1N†^ìÝùØ(õ�î pÿ°’iø%rƒÝãÕ]Hªê'áh‹¢Â‰ÅÉá$\ã´(�Ô€¾ŽeU_«2© šP¾©=6ÚG¹ µÆ¢[&ïãITôídWÔPöQ2÷fho¢é01 ²r´Â–Ë™a ŸgÊ?skwM*÷yÕ&©¾€ÓÅÊ£9=!rä{°ö- ®Q¹ÏvªI�iÛ"õ“4ò] 4%)Øà#�—1ò²¡º \BL-&¤©âÍc–[-R«„¬ äù ƒµ§!º~M’Rç�Iù³$bgä»ËŸ OäÀî¸R)œ'L|GìÎÀêÖ®Yˆ•ò ‡�¡D¤<‡À$Ä™¹„s8>?Xdt4¹®jÍJä7Õ™ÖlCc©XHB U'8äl<öˆ0 l¬[Ì!°#qlK?|TOÜ]Ã)köHÙT%þŸ]‚PåÑ‚à¤R^·Sß,f'Ê\ § É7Õdäºí3sû7@õíw5Þ­ËM5ÖÌÖ—®µÊÒ:Ý4ÿlGüºyhÒ0³Ý” Þ„N‡ïr‘Q�`k$† ’éöTrÃr?ëø§ïÜD,tÒšq–‡ýNÐG7ÒãÌŽãgüØ�[³ÊêWôW&‘•Û7ÓòA½-øƒ×�uÿ„ú«œ6ëy±OÈY¶':ODpZ—Û&+í¼ a+ TV®L>©…ƒ÷B@­_ƒ°«âtž„’5|‰«Œ VŸ‡Q–e�£1ÕkÇÖ AÔ+u� 3ÞªQiN|«‘yñÏÓT‹ñÀÎbφw`þš¢¤<УÂÔâ"jŠJ6Ú]PšUI°/»‹H\úÀÕíœÞZE}—lô) ²ÜÒ^E1iÃé§Íªà8ÒŽØL%¾ð¿”»Æ’Rä¦,�04úäÊã·�fqÀ)[^�ñ©ô0˜¥±Z·§`OhHoØÙ ôjžlèä.ü‚à?±Ù*â\Âk“giSò°®JžÖÔ¿jH릔Z‚¡F%5å_¼lÊaÀÖ‡þZíÌX@•º úøà7½u­N÷eµÁiF¬Gx>àlÄÞ¤ !ü ©/´ú9K.wÎXh\\‚bJF€0áAý…¿Ža‘<’Y XšB"7Úˆ0û7œ®xþy‰Ý™ý²¤Tu‡Må$Ó²*S](šKRŒ%Ÿ‚óÌ5ŸßòÑà¢ZšÊ¥<@™p —gQgàá+!ËÆCl®ÚOÃÆEaœüûVÿ±�ðsŽ¡nµšµ­‹BÙéÐì4Ed6jÛNí¯ª8•/ÝPu©Õn‡ÉB°;†jÀßî„GÁ\”¥Û@c­¼"^-K+ñ./Ô´­eÝ`éÑüuÊmiŸN†iDz¹\iSoJ¬³bc|Â( ÷í’kUé…¹û(§I£ö—4—NlIP�‰z+›€Ãx+.ÏV µ4Ë™þx2Áôz­¡£ÆNäf<Ðþ›5 <÷ì]HèmMw¬JNÒ¹=¥Ãóíù7ÊвzÑ�Sà&û2mélŽYáìE˜x “& "É“'+6®eZøk]ÂM(«ÔÇ^E.Ïð˜ü­²VåªmK°D{g™�9‘y~—ÉLyz•A˜¯R7(îºBsŸ©oÐò±ž[vH¹Þ/Ý—âÒ€‘²^®¨ÔÎÚ]êh­ÒÆ’D’D°É'YÛÑOÈkß½Èùà|†I]ûczw&ÉWS‹›’Ã0:tï@æÑ7¯S»ê5oåˆ�D˜\êkIR¢È…+k;¯ÈÕä¤n^+˜goé'\ôZ©ã|�¨e�I@ŒVÏÑúÏ�1qý;µž —Ëõí–?Ö™ÒQ¹i�;¢_3!M‰å@ø™áÃ"Ã_ŒáÈ'F`·áqšŒ¡ÕÀ²|²Òˆ-£ˆpLÎxW2™lcÕ$(U*ÌRM yÚ M¡½v”Hè%èÇ“ åW÷bµø0�âßÊcƒ›Ü—ÛE%ièµöPÝP27þ׎!¢ý}ÍØ+fµ.á6ð”Ïop%¯KPo$Å7ò‹œ"¢Ï€†ô÷"w¡ì2ŠÜxV«Ç|é'ž¨25w=Óë�‡sÒÓêšÝ~�D»…ÑŒTæ›$¥è(_Ñ “ãÔŒ0jóµ/R(Ïí(×ÐÊb]ˆ¤H¹�ܘßÔÔ‡¿Åb’çÙ¯OÐ BÄËø¿þv#Û¼RÁèð�1ÜsrL‰¾:†æ”û´Öô‰¹±ÊxZõ…è ¢â¦;¸žŸ ºÃA·2ã²Ñ¤wKë³%1†¢ÉÂ;&v(¬A÷†-¡†Õ4Ú3]¦’ÒJÐýšÉœ×_b)¥kì ~q½igåo§wãGSb4ú.]krϸ›Aév{â1Ù–ÞÍ 6Ò'‹�Ú`=¬tÁcƒ�ÄC½ „üâ<÷‘ìñņ‘(QÖV¿0‹t\²:óD”ºã²ù±ýf^|s÷g!¿ ̓�¦åÄ„)Õçn2šÓQ#ûÜ�î•4[j•d)¯Z@§…­TÃè´Þ2¶wµŒFMQkYÍk±ÞºYÄrYÏé-6Üð�[u¿²„nÏ‚­nUÀñ⚦— ߤW-–y¢YÞîå‹!<‹LÜÏñN"Ͳ³•æî£œ†D¸�îê}\ƤÐsîz�ú!SëtÎ‹Çøâ1¾xŒ/ã‹ÇÈ^<Æ�ñÅc\×c<§öý‹ÓòÿÃié‡Üai¡^>=HÊEù%‡Û`Éa›Ÿ°kV˜EÊý+¯à9’àÅS±ðTøf'Å6‹4o·øu;�¿"•Æ�rm6¹€°5{*•ed/ wXŽ×rV €ÚÛÔÙ¾ûtÜ´hÔ¶iÔÙÜ È€d|ó B�va»µ)ØŽÎ@/~Ý‹_÷â×UòÃ÷ä×­�Á±;Î+ ÏîØÆµ;6ûvÇÖ~ÝqÞ±«ž|Û~òíõ&ß¶™|Û<ù¶õäÛëL¾c?ùÎz“ïØL¾cž|Çzò�b—~ÀG¥Œƒe]_Æ‘åæBn]ý´™¬o9ãÛ„R …YÌQJ—Ô f�¤Eó•;§d_Ó$ŠÈ’&‹ Å!MAÁ„Á"ÈÜŒ•èÂŽú{{ß‘®|þBT¯ZJù�A”e·Kšö®^(¥a0�º)í‹ÿí/�¢43_N¬4óÝd^*›ÆqX*lÊeq©00Ëà’^¶Š=_/»qo�¹þ¨pöÀ‹N›OgADÉû³±s98—£ÉÅÉ˳�Ú2LúŒQ}øË‰WÙre j«á„.hZmÅÆ‹#�ƒ8+ã  b¯×§™×ׯ²xëz( ¶2'ÈèBáÅÑ,˜ëeéjºLbDÖYÄQ�ʼnñžq� PÍ*yE½ÐM„ Þˬ¢]%g* I‘øxåeĹ î'š89 œŒü¹GàAïƒÈ�oX,‚ÊÙ'ç†B½âÙeË,9ÞË[_¨½q°)i†Ð):Öž}VÈ J€g €0¸ ÓðŸ\¥9½¦Q–:ÙÑQ¼¤‘ã]�Òƒ�� ‚oB'”PÄÃZ«mò÷Œ¦•¦™ã…qJ›v­5Rú^Û¹`úöÝ UlÜJ3äÄ® ç­™�/]ïÓADŸpZb–�+Ž"DLˆy±*Hû¢ˆ‰Ñݱ¡.—Ì9vç‚vvÉÍNÙ7.›ö½…cçl §g.Ú´0‚ÀBm&E�(?¤eL ËÀkÒûåBMúˆ ªv2ò™«’ó'ðyŒÔC/Ëx¹ ÝlKu}‹ÐÓì;:£ ­P|V¤pÆaî¿5p¯n#w³ùt5›áä“«8ô¡f1rÈÉz $YE8Ûç�NSÇ¿�€‰=vŠ`äëmÌŽ:”Ä·MÕ^TWäýŽ”­q>.NÒ›¢â‘ë8ðm¦[H�M­ÛU(íË¿�æ.«ÅÀïûnæbø·u«ió½ºmØoÉ~ZŽ·²Ctßlà7ð¿fI¼pÜ$qoÛe]ÌÕ¯+}òºøŽÿ‘¿¬P½}£D­¯Åž«"@ÚA8Néÿ;Rp¡·žpÓŒ€�‚d` 0gô›#%78›ÑãTcgä¬ÓËÂN »ÚÖ Z *Ù‡ޤi½îMUË£õ·£Sê8ÃÕkTXp“@ ¦6v7x;X6ĉ?½Ø§Ê8(˃ð,ˆ{ Ec5L�×õšUù"ʽæ~�bpÄgUK¡aeAHSøEV¼èõzâ8'ã7λñptñÚqÚϺä‡Îq¾„q¼ -_ #?ð€ æÄ†±ë3O‰Å§D”„úð™Åaß ÷ç&ó™‰Õ"c�J�…gLb°�#W¸ÏIkÈÿ|‡Z€a‹;ápÑ�2Î]8æ—®R†I¯ha`tt¡.²`½· Ú9€÷ñ· ñK˜“ à�Q½SóHÑ|�™eèÞr.ü÷)P•k,xª4B(êÊB¾M{äí àB‹ÑåÙ«ËbŠèzÁ+ÙsªQXîœ;µ¶å=º-EE”I ;;f’2Z-¦`µ`u”[�´‹!MÌ%²#)§àZr³I­yK“Ã\Ÿ—q‚ [Ðì*ö· ˆ õ @[Ôd¿Êˆ´×§²µ9Và žšfQzÛª *•maJ;{ZÌs�Kn£a¬”:̺ÇSÔN¯J3��‰×šŸ¢›5sN¼@ZÓqùñÖ–þ¢ŽSyã1T@�‚Àû>Øø)Hvr«@ÃÉ´i]÷N‡/ƒi3õñ³p®Ëál•DBëò& ;ZÛÚôÇçäiGÄø8Cçí—g0Ÿ�»¤5¢”åyJX3BCfݘ a†•gÛl ŒñÑ�‚»³—“wgÎ䦪7ä‘w1šÑGS!ÃÍlDtÉa§SOɆù¯‡¿“…0‹.±é5¨W¸š,s)†®1èç¹0‰x)O»Ì„ų¶JýH—ǘ!©¾?xQ=à`h*ë¼0Ñ×~ i2š+µC ¬“Ŭ¡­¨b™KÒ3Ös¬ {.(«ú¶ |#ù|´ÑÂ9h'ª[QÉðNåSå�ÊGñAu™¿/ñŠa_eAˆV6böU®7Ò`¹aAó“Rت]5®/Ô€JAÐ[kì¼h³AäF¼3¢´°ÍªìQñ&di-;v•,6Lr¥EªÓP0ƒÓÇía%�rÕž€]d׫|�ð· ô[Hˆ¶[a²P¡®±”Ôîž4‘€í5 9}Z„v-}öÝ™ èÓHuÐ�X¨‘Y¨H}%þ]›$q—À¹vís:Ží} �>¬ø©',²R��åV®Ø´fcƾ{øõ ‹Uëæz·H®rƒð¨d”yM1àÃ�L&ƒñ…ɾÆt�Ž«¤ Î *,DmCYS›Dk¨´›½~Ÿ\F wÙei=lc;ûGÊ“°| «Ö[ŠQˆÂN¢ÈÅɧtéz,üVü°Ls�ʼB­×¢ X�¯¤õ£aËa/�hk-u§¦þi‚¢`}�3v~�šöN§~##¬t÷}Ø ¡4 7yuëDXÁŒ:ç äòÕHk…_¾5å5ŽÐñ¶Ú�,uPí^(„WòÔj*¸31K(-ù+;°ÊFúŒ%`$¯±}¶Í(ªüì�Žê°þsoÌùf‰³Öè¤Íã‹�mÂ; bäôgæõ¤ÑÛiì&>™ÅÞ*Ý`0·Î•øf³~Rä£)–­ˆÕCPë´Ç¥ �¼Xº BƒyÓ¤Ë.(‡_¼Få9l„ò~õØ®´Öj&=H•Á¬žö;rî©Ý©Ø6Í�d®l*Mª"p—©7__†×:,¶¨n§µ©Cž<ÉûÏýïj¿¾FFJò\á£WKcÓ®ªÂ�voõŽP°¹€]ÞjDÙÏ`h– ð&É*¶ñddâû³·¿8o£Kçíù«�3¾ùùâôlxú LßHÁ"öKÈçì6W3 ='¼Ï÷ÃÑ«ó÷λñù;X•~Pz>¶ô«h~ tÁÿóÄ�ê\6°´Ú’ v*¡Èõ¦¦¢ŸJf(k+9H›2çôì|‚9òê2Í*\hS €u¡’¢–œîv5]óò„šÍ:‘Í´^~‹é.ˆ>¥(³šÕ´-åe”°Åãºè¥+órxŽÍß53W•~Ÿ´Ÿvß®/¶Ñ±ã.}#Wî5%/ž“gr¿==â"ÏÞâŪŸÊŸ‘ÄÌT(ÝõÛú…¨‚ÂÓPŽ¢cÛágG‹†i¹˜‡ÊЕ%K3OÅÍÚÊ–ÀSÑCzdÞ•ÈÀPÊàr YØ—%FàÆG…GWRÛrË"³PO"n9Ø1P3Á�'|8ð¤#Ua¹hO–È)7 oÉl•ðÃ�b°zä�Èå姘_QØIf¤õL–n™²-]ºåsÿ½r1\æé' ›Qh,Œv�dÉvL~’¢þÿÖzœþÖjYcÞ5ÛNšdutÿ²,ÀŠKr1ÌP½Å²ìO²Žµ.Õ¤+‹¤–Ño’´É¸fC}»„øÒð[ýÜfL}f!i�HáS+Æ{î%¯åûÖì¯Ôh0äø £aäôÃCR3w\FŸ¢ø&*ÍUººñxUÎ6¬ztWavT`˜P÷“1•qæk_N¿8©¨ëø¢£¥ï‹nwçTË Cór™¼®µáò(#oÃÌr/³óZ8ã-×LàA¶~SO ~ÓØ7wŠVLc¼¯¥›]åf’4Ih‡×Ð;¼jk¸°ÝË|!�ÔϯÝ}Fk7ÜÝ”Í�ŠÅá“rØD¨·xÁƉEåøhåôp ,“Æ(SÉWÀÿõ—<±*±Ä\jÙ%O6rµáp™6T­*x¥n7+8hòLOåmV£>$×ÀØ�„\*æîÊxÀF±É—GÉuÓ±Èc¿EÈž� ³bŒÉ]IP+¸/‰®wjœÝUä]Ñ”‡íÄή¶¦3m²´Å—)k&÷¦j¦ãIËÄõ>ñCØr7¡è�­»"~"3ÀäÓâB9Ñq̨wë…týD Ýÿ6æ�oò ‚üX¤z˜^?E¿4² ×_> ~,·=‡¦À³�HÓz°yCZ…5GÒŒºÊޤüÓS峬lÜðcogÜÇÃç]ñ[’yzøìŸ]2]üðÌy{ò?ëX`�Ú!ó´ˆ&Iœh; [Ÿ>RöøE²ªrÄÂÆDó" Ú£µwPäAsý$qúëïÀ¼?I«? ¢~zÞCëÀƒU�áù*wb?ÄjCîûìÃß»x7­ôyt`†³‰Eïì^’µ�–zS}þŽ^Ÿk“Ië”áè«Çì/aYÕz7ú ,­ßÙzú7É•ßZð÷�Çþ+ÏM:*Ú«ve ÷3¨ NÃ2ðM ´v ÷åÀÕŽÿþ{»‚Ù®ñàivæÝâBä4�…e!ÆêÁ YI#³ŠüÍ.+)Úi4Å(2ÛüZ«åë ƒ�‹YÕ–€{ìÍ‘ûQYâ5‡Iy)“Ù�úªÛÓèªÝιX2y…ÆÉëV™Ÿ—£R›I³õm= ™–JÍÆ´|zÑ¢"õ� ¬Ê¹²Bešªª²th¨Ùµ'BÖOxš4ŠVIªV¤&:gCtÍI‘FÓ£ºÔ4Çyà îþ;ï-%~s¸çUR[ä”Õ:¡[g—)P»ö ;vGOq$ yÙÊìítô¹�ËO(^E Üßão6ê_ë¯ídCÅ«tZ£"zlÞqrÈæGDI5Dø¼xçípd}�� x£K»êŒr}ÈYD4º„~8÷Èc|gŽ£m/‡C&šó9³‚²EôícZêÔšèûµÅ×HœKâÖ¸m˜¤€Ù€CF‡Ü‰*'­ ‡B/q¤+fmØæ1T>OÖ{5“oePU¸]€õëË‹¦¹IJ­±ÜÞ¾=7 ×a�µÌÁ‹�FóìÊ’·ÒÆó~ù$»F¶m‚{/‚Ì{SZV'|ªîáÙ$²-Ÿ Á~}ÓÀwE'kDÓÖÂ~³�|L‡¶CV•35ÛH Ås¡«—·¸Ð-|šè׃ÒþPä?îöÊUmV™ �¶®ðÑf/ýR�;ÛšóQiÓÝ<¹¦o´W©vݾûÆëâÚ8ÚÁÁqyÙÜð¦Lå|ß!&©×v¤³"‡“;íf�*´á}žœ…»Û ã!ÖTÍ�³'*ù}jRƒø¬�Rƹh™­œBiŠ–�Òç«°�–nêäïÁWĨŠå�02–#Çã_ƒñðõg4øoç|쌗ãQ[âÊS©``RÛµhÚ'5¬ ¥ü>È,TdÖ_SR5m)¹sœó ±j'eÜJÙn�-w[w!uÅH'°Èáo¯á¿¬Æ‘ÞÑî³®(°¬¡Ÿ]ü%w&XÚ¯öíÆ\Y|CAXOmíûù·Éÿ:’Kw¯“S $]µºøtq^[þ–•¹‹°æâ;¡ZÛp ‡c‰NB!ß°eëœöYW½ÓÞç 3�Z­5Ú(”h߃Vx¦—›6§Ž„myZÂDG–ÙÕ[e•ˆ)cÔµ½­êmì%j:ÂJ6[}i…A¿BJרkß3ñ ï·'¿ ÆÎäür|:p^ Ç{ß±Ui½Å�¸GsõB€ØÖàQ šD~0³áV�ÚaV<~�´Ëà:�§Bùã>|¦‰¤”€È—» ­>ͼ¾�âÖÄý�Ľ*W…s3å<ýá6ü6bÅöà?«4WôF«ÍÜû]Ì-_’G%Ÿ¦P¥¼¥ªKò>ÞáÅà­3ᵯ¯¥±±£á“ÍÕPC[XãPæçalÞ`�kÔ5\YEŒŠ«évG1ûoòÄæ•¼ÈJ{Q‘húðQüúiä£]ÍYØó¶ ÂbàÿÞdé^³Æ xœÍ]sÛ6òÝ¿ÕÍ%²G–šÞÌ=$—LÛmgcwuZí]þA~æ| #ÝÞ^fƒÜú. cæ‘$ô‡\1rQþd-#òo&bŸ‡ä‡ñ÷dˆƒ¬ipúQlyBÖtKB.I3ÀáÇD1ľº,’ĉË×QàÓÐedãË•'Ã2F¿e8ø\R§y'|¡*3¢WRFñÛÉd³ÙŒ©¢vÌÅr¤pñäöæòz:»>г�aÀâ˜öŸÄÀí|Kh¹tttC¸ t)´IŽo„/ýp9"1_È  Ñx~,…?OdE`9}À¶"£!\ÌÈÍl@>^Ìnf#DòåæáÓÝãùrq1}¸¹ž‘»{ry7½ºy¸¹›Â¯ŸÈÅô7òùfz5" Äã°¯‘@€LEÉ<%·c<%)Ž˜ë/|X — ]2²ä`[!pD"&Ö~Œ*��@ÑþÚ—TªG ¾p ÉÉÉßüÐ ÔŸÙéj =üWàÏç4f“ìïxõÁЄDzÞ&_'~%òœ=³Pž»Ücq(– PÏ<¶ðCF¾ÜÞ;�³kçq:{¸øx{­o1™S÷‰…^ 6ÈmßœgŸm".$rb‡róVˆ… .JŽNìÒ€9ÏoÚÀ=*©ãòP tõØ3¨¡ LÉ®O$V¶FÂ_S±ub0ÅZ/àLÅŒ¶ƒÍ¥l !.šŠÖ µ¼ÞÞ¢úJD§ÎÈD„›RÀ²ñüƒF ©ýj‚rÀâ{¬� €ßÁ®êp5et©+ÁÃZ{+“ l§$±AÌ6Àªºj"zWÀ6dõÇI¦*'ö—¸zû¡/‡¯ô¡‹™Äa!Š^:ƒfX»õ„ˆ°Úugß�z|“Ùz÷!³n™úкb°°À¢.ëdN&äWÁ"ˆ~1²� tÀˆBbˆ®ä¸¡ø *Y�ez×d®»CÙU º®ç±n3ü8s®ïïïîGdðSõLc 4½¿ÐF ,¦Üë˱Fõ¼€&’ Ü-\îImľb©ÓtHù¤‡Ö…€*õiü…²©ÖQ6&rŽ.–F¦�E¨©�´¡[@ŸßääÚ…6"o:È­�šÎRì@çÁÅ “Ò¥Z‘&¿ �Èfå»+ÌÁñQÄ!’>  Qb¼­¢Q’&I�ECŒ“kŽo¾…l�aõB*Uùa ðL¼Žó(ݨQ——&®~v7î©Äõt–�×Y?:E{)¢”ùJ“3²4ZY‚ºDÔï®s¿Ö¿;‡•Ží•Zã³úü ó[cÈ΢1õ÷èt0*úw%ðPòT©íÙBIMοzË< _ ?˜eWÆïdp§˜›AÅÆª¬40ÈrqÀh±NLVb*Œ`=gNþ¬Ç`�Ñ\ÁÒ0 3¨ûc˜èÉU¸ê±Иmág‘‰p!.v·&ÀŽ&mñ1V—{Î%�”{ KHsšÐZ*]>9êA»Æ÷Óæù儨ÆS¦éè‘!Å…4‡(¹|¥I@%ñÐHáùs-ªF%#±®r2Ê^ý*^§†\5­RU1”€¿vª-uýV�Ô7´™ö‹ÝW_ø{úã«"M UÉ6#(Õnq aýɦZ2X]“Êó¦ò ÅøL L¯¥YäàÇâ./²Z8ªVb»óRí§ÕfLm‘’J’—Ù®…­Îz©y•£xÜÈcvXŸŽ¯ë‚aQ…@Äh˜ZAn@|dÍC_rƒ`|nÖZN¥©¯€êx{ É4r‹ Z˜ÇDaξ!nàGsN…w.X€ùY±R¯˜Ð@efI¹VQÄþ=�‹2±ÿžcÄ]M )p÷³;Cß‘yäÙ§ê™`¸ç ¦ÊH,°Æcò‰á¾sÈsta¶Ë‹Í˜ëŒ†ç@JZ‡‘øšðDÜ@4fÚÆ\cé0·4#° ß9/h!ð¸¥3ón¡Q’•jH—^u ¿XÊIí,õL[ •=h­bÓ”¼ rg ª«›©ègÊvúžôZÃmAÜù‡t]7-‹¸Ö°–·íUîˆôb¢aã˜iúE'¦ÝLÑ=–bX&‚Ú2[>ï»ÆV0öZ`cµX…£yÌåéÉëÜZ’fî7¥Ž¬pýöûì”·‰»^Ñ2IßÎâߣéYk5s| =5ˆÍ`ãzKïP»Šµ_´Ý÷¨–î»uû.cZ·ÅšÛëò]Cn×n¢gT¥c|ŽÏºxµþPŽm®Ld4@úZ Û‘Âvª;X8?´È‘kU4–THçë†nX’ô¡+Sئ±àlìNØÆy†I¨/ŸF^S~-†fNMãd îBê¿w‚ºonjú¨3 ¬acNÉÖ]¨<á²dR}VQU+tdð‰ËK…j U5ÇC4YÙ“ó¨-ÒÞ«ÀØÓAµ8®We*EîD<ö•ß&‘‡uÉ;�åÊèe:Uu¥ë|ýŸaÝ?Œ:£ÕþÊ}©Ž�£j›Æ3R_ìºÙó²Ï»“?FQ«´�Ûvì/?몦¼i1ê®,ÉVy¸95äT6¶�{,§¡Eùä9QáTß¶�Ö,Ll i ÐTI½û;ãC B(Y°M~L:Û½‰ñ˜¤º@ Θlü ® ñ øËQAèŽçÕòsp ~uh�FxŽ-”ÁvT P¥?ubdM°naek…L¼u"ØÛüÉ™5ü±Ch{A-hŠsêŠ�P—IBN²HÂ4ÍmU¨y–¬M†&UjÍz¬,†Æ ÉD�eÅoYØMÄéXÌÔUB–ÎÔ¢+=`3‘’w1“Q~�ièÛÚ;äôZãõö±Ì,íÈ:z0Ú!UªÀ˜Äß@b!º™v¦³%÷5¥¸&*ë(ÌD6S÷î†Û©F²£ö±Ó\ZF±˜KÑ©s­â/ŒZén't/ÁÛÏ'k-UŽø�ÈÉzºÊÔËÉZ¶�;}F'«#±8Ys³»“Yör[¶F÷Ò;[|«º÷Üs6¶ûS^7õÔKçU?ê~¤ ï7€e‡¿Šºq¦Å¨û†Ú–…==Ürri?ÞwjˆW n�fw²=µq«2Ñ¢w4“S=~x” ì[N\-·|Ê\< ñúP·B{ TºŠ#§¦Y*m4I«ìf¦H¿qÕ E�ž¬¤¾ß(�5\¡ì’ÒêŒ5®LƒR$,ßy̯!±uÇËV£<‘=‚ Š‹”y5¥qáë5Y¡Ò§e”ÝÚ“U$šÕš¥ýÀèájwî¼ ï¶ýû÷ä–»4 ÃôÚêé¾7H�rw/WR•|…A¶$�tI i ë-¹WoÂxíñMø/�@î�Ó:ÁúĸðŠov�ºÛmÑ�3çònúpq3½¾wî~*M¦‚¿�eDº­ŒéªX� *¿S:r²µšÒLJ©sóó§4ŽzX£Ré¿zU�üåö—‡ÏÎÇLJ‡»©su÷ejлí¬]éÀX¹¹»ºk¼å-¹ÄrI¢qû]ʽBãÇšD5ÔhÂð¿–*Øi3¨ÙpñGT�ó‰œôžfc”<2Á\+UeGsœÒØr«Qš�ÔBk=6*)Å#Ù�Ò¼¢û— ùEÛŒ•™pïÙÐ&2e„ÌÁÜø i5¨U-¨Û5÷Ø -½J]êÙ¿\O�_î®®Óiáòöæò³�ˆ¸[•Œ4Š‹kGôE&½Q˜ç\°¶0u(+Ö˜ 3¯°/*/~K†ã–c¾qäOÓß®jD±º×žç¯Ÿ0»3% Ľ²€ê‹F•ý¬úÔ‹�{ñ#ªŸ†ÙcæÕC£lÄ1²Ñ0@9<Ž’ ØË§è uõ�nlJÌmრ~ÌŒ(G:†ãN€ø¾°‹(ü| OÏc‘óuún5p/t-¼s�>Uœÿ'嫤ò—ÛETÐuÅVµÇ¹+èsû¯Ü¥ò\J´ÄMmÀ™Ç*ÃIß^vÌ×øÙ]ÛþfÊî!rÖÔ ©{yåôÑ¢÷˜'ÂeÕGLø48ÚßÃ&³sÃ/µÈ¦5"°åcTKóÜs{Ý÷�P®ÙÚ�xw0_‹œº›±Ef4g|«Vèáy…áØÝóMq¨úÿ—µ…±ãxœí]msÛ8’þî_�ÑUI%Ë™ìÕ}°7©Ql'ãZ�œ²�Íními(’X¡H._ìxwò߯$ÞEʲ�½Y~Hdlt7ÐO7Ðx8Dovu¡ááÞáp¸‡†è§Ea”—ó4K|œç³u‡E’�}xJ øIzŸ…ËUAþ:á ¾?@¯_½þôqFaš¢?y8Çê¡ÿÿ´,çs<öWù5xåèC’,¡Ö‹‹VÉEèã8Ç*ãh+Œ&©çÃìÉýgy˜Äèõøê“=ö¨78&$î“­½{'*s 4ÂQáðW§ cä'ë4 ½ØÇè.,V´FeLhü•ÑHæ…Å=DdGÉB,ˆ¼‚1½*Š4?:<¼»»{”Ûq’-£ª\~xq~r6½>;ŽÙŸâTŒ2ü�2Ì@Úù=òRàÈ÷æÀgäÝ¡$CÞ2Ãð¬HÇwYX„ñr„òdQÜy&d‚0/²p^’Â8 ¶XTæÅ¨7¹Fç×=ônr}~="D>Ÿßü|ùé}ž\]M¦7çg×èò �\NOÏoÎ/§ð×{4™þýé|z:BÔõà¯iF$6C¢JP½]c,±°H*–òûá"ôA´xYzKŒ–É-Îb�¥8[‡9iÒ ™(\‡…WÐ[š\¤¢Ã½½ÿ c?*h~CŸ]õ„ 㢸Oq>^½îFá|îåø�ý/?ÌÃeìEʽ"˜'‰~È÷î¼{�38€ÞBøIF© ,@çKX²ÿ ¿‚<ô5*´^éè.šá[³(IÒcínž”™��÷À”Ð)ö#/cê{4l@×И÷ýF׈ëšdÍ÷g3½Ifúׂ‹�¼Â œab}й Y*"ÒQr¤œItD¯¡to–ÙqMùŒªB¬? ]�ñ@zÛõù‡“Ÿ/N­Õ°WI5ÐüUHªÚêê.À¾pŒ³#ÉÇá-†.Œª¾ƒîVðîÜ…q�܃ô3 Ú ÕGŒbWõÎŒ½P?>Þ¾î5@L—ª«ò»¨¹Œ»ÖÍߨEí†ê’û.Õׯõ× üRõ&è[M7páÄçù,ˆÈkÐååK,ÞÈó™V™‹ÕAÚ»� p *í!cœ>£}ôÛqe¾�Ô&[]jEX2Õ³¯‰I�ýÿ”árZø¯"׿4'�Äz‘;�.>&€Õ9G…qFJ¨YTÂP¸/é{âû¡Eý,L�OÊ^B‡’²¨k&�ùª ÎÀ(ƒÙ"°à>8Ç4‰‚8 Eâî#bËä!@*»Ð¨ªYy$£_+I3ð§Y;Q¡àNE+v‹Jj6ŠZ³qâEÑÜó¿!t#;¦;z *b j =·)5ó9èS†»�ô“lYRƒÉà!Õ//ÕtÓÛ$ ÌZ‚Sʯðä–»nš(g ÔlÇ�ýè¨2 ØÃh <½�½5„dórÞ­’¨jCH±‹ ó¦Ÿ..P¸ auŽ ©ÊûˆhUò.ËÛšo¡š´swh‘%ë*r7€€]#B+*þSoɆƒÏMý•×Ù]õÌ‹µ¬�{½ÝÕ_ûÑ–ÔŽow,4¾´á�;§+¼$#”{ÿÈû|ñ¥îædX ;DÒP–Iã²È d£¯::R<¦ÉÉ*î nËîŠë¼•× ã#°ÈI”W^ŒP"¡í—2E_ð½¨CQFÁnØ=Ù`\Rq¿Í…2#›\¯¢ÅÊ+(ûjüÀWã�«?Ô±ÄP¿)Köy…é˜R¨�tÅ §IF°ÀÓBW2 3ÃkU:˜ 6�ÉÀ ÆG¾›oûà TúƈD‰CFöÁŒ>Žsƨ±3ÀlªßBù© ô†>qû®Ñ{‡:ÿ Æûu¸°F¨„x ¬½ü˨bwÈÝÚÖÕ€×ïVMÛzøß‹ �ÊLýÕÜŽ,h ±Jó“†þ–%¢·|§È_y.‚xe¡FѽOÚAv&ÍöIq2Z*×}Ò-ˆÊN¶o\ ù¯F.[�éXseïÏP1w¬ÏPuíPTw+Ôo’°€1b5¥f@8¬ �ó¡x=Á×¹yÞ°Ç,Ûëm‰›ÁYn«z¦ê?t“¶¿H=¹¿NûzèAË�þ_Ë[«zD!�¢”$©Õ êyȳ¯Ì�¯Á¿'Á–s‘lìê"]�vèêÒJ«ŠÑùZÚoª_D {ÿêfè ĈË%‹’¿ª‚\?ޝÿ‰“EßEl0¨B9¡õéP 4'�@8U”YLÃ6{ �Þ*Ñ0°#´±(µ»Só¾9j[–u+7÷{î³¾šë¿»ž�]]]^�Pï½F$©¢ ð"¡é ×p·…@Ûȵ¡+pÛ¬yQj9r㛩ýÔXZîažFÞýl‰ áyÓ’M�¥$xñ†{‹hÁk›6±³4÷žLY¶™ýJg‚X^̪©åFen-4ý„eš탴‘©²3Ûʳ•EÍÔcìµã×xÝ—ÚWž¡|75í›ìMÍ>tF¦ÁÚè¡Ü7³ mø×çñ·‘ ¦Â› 6ŠÞH”%I¡šB�©Õ ”T5™Ú7VQËXÅ¡2>•¡dTä7º†Ü²­>®„<Ön!£–7ÚVJNhÀQ„üËÀE¨øxïÛî£[8¦"`·CIxs«-ÖoiGº9·&¦‚ÊHiÍýZ:%ÍÖ[9ÙøåÇ^'·¸ßŽÎ±Ùå8œ—æõ¬Ì»ã)6\­þª…M= ìÀ4o,€ŒâñÁð輢à²2ÞjлaŽ�[¦k)%8ô,¼(ÇMdöƒL†sYÑW^ eBsY–dMÑqð‹Þ´ ÍÈ,~™2ÜkÚà—i¢”_D OyÇíš'Z)³-â@êµ# ÑcÑ»€ ÉZZÅ‘n(ýBîÜ`•RýʨÁzºN¦{Ín-Ë:×Ó:ó+Ξ‰©[+åÖ™Èî”T^wZ6°; =›g˜§Ö¿Žé��!cA—Mƒ×Öö¯ 0›[Zæ«Q‰`ˆ©Km¡¾ö�¼­u<±2ô\ðŸÖ• *b‡×õ¥ –÷,=œ §]Yx75¥«6ÄŒm7-­ã7Ô,r7=Ý Ä~jÏ{;(k6,ÚP°>hI¬>È„::ÊÚlöiúéúìt6Û"3ÐuFž¡Š–äV6ÓôÛ»YU;Ñ~ó¼šèä `°È’X‰`ÚX)K¼ÊØ¿åä?°ñîzvr9½™œOÏ®f—ïe†G.ª#T£ÈN®¯Ï®núR…"*IpûÆ º Yo”€ó pÚMQTØ–¬7U pÓQØ‘ô/E‡RöÆQŽ-�x#Æïäú¶…u\é¦AýýïO…ßšžÕ®×êƒËNŽòÑbb->5/M©jìn»P�ƒ¢ËGHj|D= ¨õ«Äà`ÛÔÝ£$íØF¥K�Eß›–CÑuclò‡.*–¥ó�M©—ykå¡ð@ ù{LÅÊÊ©ªözÉdW£ÅjÛÊ÷¶àèqùVùJNÕ”®lp;#(IH+³—eõ‘$Ô"È…„˜eR‚N txˆb²‹¬ôËéV2ˆÑÂx,ä,œá[M=jS^X}½!ú·¯ 7f´ø*¨ªå­ ¬Ž’|¾˜�ýùlz3»:›œNÞ]œuJµ+Í6p0ØF[ÖŽ?Ž*„FÚµ*€ôfU<Ïx€AñiJäêÖ0�~ÉÄmŽÊyyžø!]Ü ¦C[%WX\«¸çwb›�fq¹žó�J†ÉÙ&òP&*RŠ�оTÕ¾T×Àœs?¹™XõXóÐÍjãŒÐ¼,¼Â(BY“�˜G¨§kÂÞJô"Eý4 ŽÐ‹ÞÇ«óà—É_z†•niÔÒd5âÚû þ@ÖÔÓwRHy¹FôÓ³wŸ>€è7µ²‘$˜ng,ágÀÛ ~Že96´ˆ½í�ùa™˜!þüˤ(ð:¥L‚Ì«<  "Úg;hÐ/’ #?JòzÃF˜Rkê2õÊ^›ð´ßs%!¸ßfA°�;#oÁÝž¥ Cƒ9Y²�‘¤‚ò#±KÉï(Ý«=“›Ò�nó’qÅ”ap¾neT‹Ñ:ñh sZä\ÛÓ²3ï ³DA6Ëa‰Q¶’ÃFË%‡=FÒ„¦^Uky¼˜âgZMF÷y~Þ†Å=D1\È[Gdw9ŠþžÓƒ 9’[6”±?+”pc�åøH¹&hôA²0ÀIsëçÉôç�„šãyê誸±P}=ë.\l3lR´ꎕ1é«j¸ù¸ÛGakó³Ð ÓÂèE~„~!̼úúâëÑ™ ÆÇ:€€2Gb·Ѧ•6—gA¬Ü6oÚŠHsV}6¢~ž\Mϧna?Å0ÈÀ>™n®¶%UQ_–Ù)oÃÈc¡ys?††¥Ÿ«“ Åóøü¢'È÷ÜÞÆ“X:Åsüª/a­™ãTƒ-vâ6QY #¶õeHÝ"¬õ¯Hœ’Z¡546,ÛE>dé§nX:&IÙüqðz{!è‡AæÆ¤Ç)k^¬¬ÚWÐJW뼙ܠ_´:º¬èݸ’W,¼9»éÊ‚“뛈[O‰!ì°º8%Lbr�T¡cÒÊäHü’žÍØÀ&YÇŠƒç„–|€œ�+.à­�ç§£jù?d­ÇÖ÷êãÖF¤šæÜ%ŽTÓ¤ÀäT±Š=ÆwåÝ’l4ò2öf^v�ª~@F¿láf}2§ŒmâA;Ô* ÷äǬl�…Vl‘*s’Ù3•vë™ÎêбøA;ôçâq&ÒFmYbë•JvF­k]è/Ÿm"câ\#r+Ÿ2"s)lö ãgB2ص›O8ÞÁ¶šS|+eØ1jM²ãé rJ5`1²á+U-ЉPjtÞÀSµü¥W­ñÝOß#°<ø(£gÆ×&àv°b8ІuSV^(P�å×ÒPÜ@"Ôñø°ëR ¿aß+Ù¨îû´Z¬ØÞSÛ/’wì1èô¢uOYË%îR'€J+œlû ªÍ¢O†œÕî‹Ýc'ßÕño‰ž;8 î™ñÓ}ŒÂô÷ˆ Î}Z_ýÞQ”íÊz2¥#{@‰fÐ �”…ø¶úzŠ£›Q¥ï–û¼J &cg>�%7¥ù‰f Mð“tF5ôæÇ–S4f³ ´œ°V›q\×RWþ=#ò.NÈ|fHÞpâÎî19Àó�¿LVŽìTä|0Š›üf Ïù¢}"¹ñοý†:oÛíäü·Ù\Ì©™6'Á……Öå6–µ�Å^;ŽÃTsh? SßüFaJ>]81É)‹ëØ$¥R ¨Y3KEžnCl·q· õ;˜(ŠêÜC«„¾Mt7'zušç0�Þ K™ÌÕ¶-øÿtÄPÆt*�iÞnÔé>·¦ë»Ï¯Pƒ¦ÚôguÅŸ}Ù‰Šl]Ò‰Šél>LÝÕH7>Í(�$k‚iäC†BØ’ñO†Ñïj©ª¯]µüfFËÏe´¸L_Ô°ƒâÓ[Íq0�'~û`M³PáS\IÐ|䑚ò”Mx¶kÊ.Ÿ#q}‰äù[cÛ'òõ”8 2úÜÃØïpQçÿ¶/D°šxœµXß�Ú8~ç¯ÑQRõ¤{¸ÞžJéöŠº«e÷ª>'À"Ø9Û)E§ûßo&? �À.U‹´bÛão¾™ùÆÆëÂõ�ú@×kyÝn ºðv.c›‰Ñ!Z믵’N›þ’F³ ¡N¶F.–ŽŸ†åtÂ+xýêõ¯p·”±Lø$Т�Î*û~»Hƒûáòª¾Œ–üj½ ]oo‡Å&·2De1‚TEdÃ-‰é«éÁßh¬Ô ^÷_A‡'´‹¡öÕ6±Õ)¬Å”v�Z$ÒBæ~ 1q „z�ÄR¨a#Ý2Û§°Òg_ :p‚¦ `ßAÏ«A¸ôÒ¹Äþæy›Í¦/2´}m^œÏ³Þíhx3žÞ¼$ÄÅŠGÅ`ðŸTò6Ø‚HQ(‹ hba�ÆœfÄ#�T‹X=wa�ÍDÒ:#ƒÔÕ+ñ‘ÛÕ D™PÐLa4mûÁt4í±‘Ï£‡�“Çø<¸¿ŒF7S˜ÜÃp2~?zMÆôôã/ði4~ß$ºhü–ö€`J¦£Œ·)b Â\ç�l‚¡œË�\S‹T,ú+EA‚f--‡ÔÀˆÍÄr-�pÙ«#¿x#¯õBÎi`¾?}|ww?ÞL§þ_“ñèarïôýÖ • OO *ŒÓá÷X�°èßýå•Aë¢@ë˜_r­Àmˆÿ2û’AîÝÜU”ÕÃXÝ6AÆJ±HCþ&^‹ÿ¸Ø|çßœ ªÛ/‰ÉŸ»{>»yórìM…°¶våÇ”\ýe»úšxZIçß<È�Êã&qjù¯…ßÅÚÃ6üÛz�*’sÏ«Í)ei(â8áªäXMYî8ƒ”pyfZ•#Ö\þ&Nø‰3•×ÇÞŒSîaå9Ô¶ªT~Õ2‚N·�­=?,�ûîªÓúd«ºUL½ìý¹€t±æë¤r9Ôý##%l$oÃäm¤Šôð+*ÇY’$¨HŽøÇ0™oW¼|Å‚ŸÎ­s«r7šºZ¥Ï ±kO”v� BF‚Vxš½7èRÊò;M�aÓ'ì°†ŽooY ÑÒŒŒ«s:Ð=§a†ºS§'GÇœìpV}�$z{ÊÓƒ\(7*£šÅæ  (·Þ9°½öu+¦+ð¦Ng©÷(’0ë¸K@H‰dëšW0üÐ ùK�}¦û€ÑBiêÏ= …âõá�ÐSÛÛ(:…,eÒ?ˆ6D¤MÃ²Š¼·�sŽ›T]HNžÄ�­IO-áë Þ k«°Ë£Œef©þgõ•3ærVÙoVú9ˆ­¦cË‚¤Ÿ,1ÿèØXY°œB‚úˆŒcè,@g½ƒ}¸cetY”Vˆ £c‡TÄ;‹lŠ,íp¬—.¤ µeÑdŠUˆ-uÀì$ÃyASb©V�&$†Jaȧ&é„‚·;ìÕ GªT$ÿiá,u.«ÕÆ…ÅŒ5Kô¹ ©zrJQ€Ísèx£SçG[¤óý@RW°óñiãùB¶—è3_TûϨŒ“½£±}7E¸wiW:jl§Ràr ñ¿ÜÀQ ]nâ8Åv�(2‹ƒp˜j…ºÇ…-š­Á9‰ßΖg7—Yc/Ÿõa³êm‹ÍíBFªÁ‚jC÷•DÓY“ä€þÕ©¡¹;¡šóUëR¨yžîƒÜ²¾+›/?äÄ«\Ã9©³ƒ¢˜‘Óz� Q­ÏÏí’Ý#Žjªa[<6pÒégxÑp}ø/»,ì/'¯Å{¯{ò˜ÿþ××pC½ƒšOÓïüÜÂöþ—ź§ xœåksÛ6ò»êNSÉ‘,ǹëÜ8µ[ÙVMË#Éq;� M‘�„ E²$eÅ×Ë¿]>dI¶“Þ?Äûö…Å2­=rüXÙkí´öövÈùuÂÐ(f�O÷H ìÊ®Ýú+q,Èܾ#~��E œÌXL8Oô³CÄ0Ÿ8Á<ô˜í;”,Y2ãx$”}„ñ»„Œ†Ûy'ÁDHìD=K’0>jµ–Ëå¾Í©Ý¢iËãâV¯{Ö¹vš@±œqå{4ŽIDÿ\°¸ß;Š{ tzö’±§…¾$@Š—K˜?m�8˜$K;¢Æ…ý‰Øx‘ –Òlë`ÉlŸì¶‡¤;Ü%§íawØ@ ×ÝÑÛþÕˆ\·ƒöŨÛ’þ€œõ/λ£nÿ~½&í‹ßÉ»îÅyƒPX.ÀC?‡rd2\JêòuRj�0 IqH6a°æOö”’ipK#8"!�æ,Æ-��@ÁxlÎ;áM¾Qkg§Õjqð—íÑ[ë}û·ý�ï]:a>%ÖeØýÍ:³†ý«ÁY‡¼ØÙùžùŽ·pAV2¹žíjí?;6‹‚ýÙ‰ÖÄü$¹ il¶zl<¶cÚ’Ë;C�Áìžr°âÄ�WhdA¡ çÚîâV yKûÖØm‚æÁ7� â$âju嘅'\΀AMl‡Æ(ÚK/  P­ãuo`] ;ÖÕÅpÔ>íut,^ÔZ$ÌkQwZ ú2È-øi�“ �ø0ÜÎI´¶CNàOØ·'kܘm ¬Ü'–´ä_¾Ÿ-4‰äœ:žIùy�ID›H† Š4U~”ÂwûA»NB¬¥7·?ÑÈRÒe%ä¯8§» œDÁl‰$˜„¶O=ˆä“ÅÛ`²þÄ PÑójG=åk 4Ù ‰gÁ2Vä!þc0Y° ~’C ¶ÂÄ ÚtضPüOh{¸úŠå× qnE³kO6…‰®QˆMÍÁM¶NÔGQtoH̦¾í¡%¼ÉÿQ˜åz/=>™Ã”O¢ñj-:ÀR>6!)H�’rR~�èDnª Eoaßb+9:Z2ß –ÖœXØû‘ãï[�{Ñ.üM§3ÖfÙXÞ£#Ø”9µœx  ZgÉ�Y%¨¯g”{-C�Ñ?:‹(Þ‘RCj‚�ºB‹š”>rJ¶ºX¼É‰“;�ÉÎ[>L ~µó¨�Ñå9a‰E�—;«*¨œ½ìè_ƒ�#œÞCÃÿƒ“ ‚’T¨)©õ&{NDÁZYSM³+Ë úCìC‘�ìÇà ¹ ˜[¶nd/-ð3�Å]· …¿"âe[ì�µ[“F©›‘*p ‚&`Èq ÿ›£q`_B¯x�I·@‹D‹75‡ ˜í$à“DÀ a[wG‚ P)Ø÷Æ$HßžÓÚ=8ë™d+bK¥ûÏÅ­`ÿ6@¦qO¼(&�æ%s“YösFñ`¡á«Ú0.,bÁeÔªìÌ^ú–áç°÷\;±×…ÎÔ;| ¤º}ü ø /ôõð¥ÎçAÓZ~ºæ!¤P¥qz–KcǪ4@¼û��äX†Ž§ÃËžuÞžYgWÃQÿ½à ŸÝ×ÌóvX„ñj³ÖÀ³¯'ÿUtûâR'pÁÌðaW½žþo½‘£å¼{6Ò)£´>%0Xþ[J ·p¸ ¬íÁ›Ó—‡Þýœœ¡Í[¹ššÀLK˜Kãý €tØ�ãðE§W«sÿŒ.~¤¢0˜ Op„="¯Ùgˆ\6‡*ž:¢r8­Â)ž}'ÃgüêNWn µJBöa­0`qKÍÿ88h˜=Â(Bסê²}g!Ø1?…uÎßt¬Óþä‹ü'kõ/õŸ½Îë‘þ{Ð}óv¤å‹ |æe.‡Ë4ÆÎ˜*ø¿�'å|CGƯî% õ?" D}î|ƒ³ ‚Æ-�…òHHš�õ ÀÒP®Ø(¢/3úS0íܵ]¸µóךa%¬ºL1ÎvlÏ œL%_€ ¢“Z ”z]„ɬæIF6ÀÕÏŽec9ÍÇÕ2ÂgRûN—%泤ö,?=;Eg¶ ø<[­zõºÅòÕ­K¢à®fà–¬ãÑdùܰЯ/…P,õÝ­È/£ÔGâH¨±Æ‘úXÌ@Uk9[,éWR¦·^Ü™e(ÝÑ÷” ¹�’À*&%�Š¿l"fUl†1„íºj¾‚¾bµöÅ»�w�ë‘”.¶˜ƒ98„èS'QqŽ%2Q™f5OЉ~ÌnT²P�YÉæld«ù úU~em ™Í8H§?|ŠiŽä—f7Ö¢\Ÿ¹Ñ…ìPMgƒ·£^6OŒ�¿š—òüÔ:¼3·‘¡MYIO÷2SÈymÀN:7ÝiÖ D¯v¾<~ÐÁÏ=Õf÷þ8@‹Ò=õÈoCÁŠU­YqG7€ö(°ÊÕlKXÕç¡ó6Xw¾›ø‘æIhcêSsh¸«¦S6ü]DçÁ--º¼2ÕXËíeHÖv}8E÷ôrâ�ªYúz‹È¦t´ÖÈ%�D”�/鉢@Ljâ@Sßö\ñ$' y‘? cçÏaÃY°ð\2¦Ä |J–3$°„ÄVyq¡Q-u�–€ÌØsÓ4Š‚H(Ï·¾�‘ú³Ž �ck:ù MÖ ‘^}è6Ó\+ë²Yê^`Mí‡ÊŠKTMþ.¹Á;}�¥RБ*H¦¯]C�4UÊŸ{׸Ë®™2Ém�ü…VÆt fôš1ªIâSêõ9pZíyÜñ”*/òIn©79Vê[¥ê2Nþ{Þ>æRva„Fì–Æ‚$žÏ×wý™BÐ<Á¾t?[-r$$a¢ÖÌÎVárMŒD”Ðàþa¿¨FYR>R’—–ë©Ô¦°tR™òl¢sMGˆ9ŽO +§^Ùu;A2!ç×ÌhD3j3è^Ö(澘rXÆ_�”¯ya`z†JïÁ˯ª} i‘Còœ¼8xµÒWTd¦C)?>†¾WfZ‡v�• UKv¡d5êÙ´Òµ*Y¬lFZ3P!­Æš—@‡#hDo³E�ªáL–ë¤vf¿È< I·Û�㜟�ÉKm’AK òüy†.¢[liŶ®ÞÚ5¶wå—î,_\ ùÍ&!êÍÂÒí‘ßÔY{§°z¥;iêæ7ÙÉ”„ÿÛ�|¾z'Ÿ:@㩎Xœ^êÛDef�“>—NÕT�‘Ö(�´ñdµ,Né3*ÿĤ} ŒXNóù]]øó³óÅÑÎdîžxñ©LF—‚$¼H‹ƒE^ߎ¦ãÁ£G–£qxÀ ‚H,çÒ†š”7O°?7ÓÃL(‡ä+]dNÈzÏÚÝAßzÝ¿YÃ^þ½èÞ·{Ù).¿è#¯;XÚ`�ö{ç䨤CÀ0HæiÑÄ8a½8h(ï¨ñ6ÃmC ½§ºN+¤CXO˜=AiÃJÌ?w™ƒcœa5 ,¢ê9Aù‰®�z2äÒL…8)8k Ô}2Bµdˆ3-ÕêÚÝ€ªŒ~Î#¿—AhcÊó³?º ˜�¥€p:�¶ˆ6üó¡=qøÁ1HÁhk+4vƒJÇEŽ*!"Ê[šù €?ÿùÓÇ4‚oqe7v`œ–- K<ãëˆ1,ç�ºläò àTÕ|Ýù tªÁ.`f×w'5¤L•nà�:—ÝÙý!Æâ*Ç tã$nà÷Z´òšH4³ŒÇZ5XšÔ£?¾­Üú3Àoo&W–®=<ûV¢S(Îý†Â³Í7�ñ©ªrþ„ßÀÁ™ «ÒÁûÎ6_ÁáÞýèÀ3§¾Òxœ­W[oÛ6~÷¯8p–±U´À’5­›¸«Q7)b·E1 -Q6aZÔH*®1ô¿ï;¤Û‰³ ÃŽÏõ;7*éЫÿë¡NÒJ:�uèM®´$/Ürª•ó½ˆ�žšrcÕ|áùí¢y¡£ô˜^<ñ3}Z(­Ê’>餥£eø}3¯f3ÙKÇûbyI¿3‡±Ñè¢62R©,œÌ¨*2èð IýR¤ø©ONè‹´N™‚^ôžÓ3´ë£öñ«Ø˜ŠVbC…ñT9D²PŽBLò{*KOª Ô¬J­D‘JZ+¿vj-=Öñ­Öaf^€]ÇN&ße$ák§Þ—î4IÖëuOo{ÆÎù\2^ ®Æƒ.<®%>Z:GVþQ)‹hg% ¯.OH.Ø‘ßKËÀMÅPÊ,à6–rÏ…ÜD—\)S•«¡óJÌ%ÍÍ�´"¢RÚ•rœR3V£ÕJyáéQ\l(i=S9ršN'ýñ‡éh8žLßO§­g ªB>¢C Hu•IúÅùL¨ëóšV³™p2)cÉŸ·¸è�±€:£ S±?§4AcsÑÞjÙcoü¦”ì ЮROÓµ^‰¥´Óû.šz:@; Vúµ¦€–hø½ˆþV}dÙÑÝÎvâm{côRù¤þí-Ú1º±ßÀh]Ôþ>,NŒ›`¸6øØudÿlž€”Òš1È1B~§h? 7ë‡ßζ¢¦ð\äèÕò8ß‘ÅÛVv"¿{ô¦p¡ÔàŸ Q ]’ýòÅVÃöñ�žé³Ö�YK*-uåø¯”)µ/Úö™,2•Ãr²ÇÓÂûäúòúÑÀ;¥�3 Ø ¸”›™AYõ‚ »Œ”.Nùue`ÉEýÑä„û�>Ý ÆãÁ%·ó¤ÿ–dÁ3‘òñx�€A  Ö8©ÜïšRôEÉ5ÛãÃnÐóH%XYîïÈ6^¨ÜwŸfž‰tÉœG]º¨¬C´Ì}ç{Ú™}¹Ú@ÒÈGS—F Lo¬l¬Ä⼃÷øGpÏqä4“93EÍh’:²ZÃ*ÌìÔ+€_^éÓh�2ŠVj‰^Ï"QBFÈTÞ©,ޝØ�Ó1“`( »Fbeª8]#Õ>ª#^«"3ë(´6vé°‚`íwÖøýQŠÂ�ÒÐ#c2ƒIZ1á÷z½(?·b6ãqÙTO�—ÀÂèŒ�L·‰ð¡htü_ËñÕÆ1ÇVÆ›²kò®óÈÝ©Wç¨Si¬4^�¦$‰}ˆ±¿AQwÏ�,r¢üOˆm]Dòk^"×_7£þ7zÍJˆŽ´ZJ½Á–ˆû¹žIÇ5^°:‰½¥¸ç—°Gíhˆë¥Ý¬{Î úÀŠ:Š[o˜ ®×j.=V /ùí ¼_^q׫ûk¬kmR<�±nb_Z9ç9„9®æ…Ðõ™EòͽTÜu„õÔ‡!q»�±±Ó³©¼“…¿ JwN±Ú÷ŽMÎÚXóíÃmpÛøþ¦V¬ê€¦¥·»Ä0l#-�­ôFßä@„œ±«Ï£ß)¤µ[êcå¸-¯4)`Bõ°6~ 1?°J2t¼5›¸Ö-ÎÎcbÒuÔìŠýeØÙÆw8BÆévYç\#›²¸Œ¾¹ý<#Á(£àBˆó‰À"Çì·Çê¤ugTö4,û1îÁ²§ªvš=N­*}½3™šÐãá¢ÌËf�6.†)_g„�{¾^s¨Ö›O^Z"L|üÛï‡wæ�°!·›óɽYÓ“ÎÃÛZüÁ× Pô¼¶_#ÿ囇õý«ÜVZ²äxœÍmsÚ8ú{~…š›é–@®3wšm§”Ж)���\vïfÇklš›³DIn'ÿýɲ^ü†„îò¡YzÞßåvšèÝK}P³sÒi6OP}˜‘#ºK­Û>,‰U?^=&d¾`üW/û�¿�Þœ¿ù'úyAB²Z¡¯¦8Aνøûa¾žNqÛ_4ìcpäèsÏÕpØ“H†ÄÇÅZGÀ` Œº+χ?òI ý '”ÄzÓ>Gßp*�6.8ˆÇx�–Þ#Šb†ÖøXŠGøÁÇ+†H„üx¹ ‰ùm[<J›ÃøUˆ§Ìƒíâ¼£xfnD“D/[Ñ·�Îf³i{‚ÚvœÌ;aº�v†ƒ^4éŸÅòÄmbJQ‚ÿ»& p;}DÞ (ò½)Ðz'È›'ž±˜S¼I#Ѽ…h°Í×Þ£yü 'p„V8YÊUJ�À€ƒ É’0�‰¥_Qçääo$òÃuêÏ uqj¬þ’éÔ£¸#ÿ¶ï�‡”žÖ`¯½¶ñ�âà ìÈ=óãD2�{>'“ãί¹„á¥ý #íp×E—ؽDrú,×律& 4ÌÍÔ«Y…ø `áQo��ß &±ör7áһlj+åç2ôÇ ‚‡ôsL"Æå ѧ Ö‡Ïd÷òŒ€ÈO™‹Èú4ÅâŠ%UàézÊ÷pÊ_‚a¸,v7Ôõ?õ{›8¹§àz˜ºXŠPaü‡�&ßâ0qU¹ìíÛ ŒbPÿIÚ)uƒ0OÊcÍèÇ©�ì£çߟ…$ºç J¥Lìé² ²)—,IY ´*r2I-úšrÍÃØÀHž{¦¡L¹,&b”K‚rSrýø+øv†MšÎ&tà �üHÖ1µéb'þ$Ž™�]©s òg ô W¸¦P° á‡+ï‚Íÿð^,Û\ÊÍÒ2/ªÁÞFË}¯£ú ?­Ã�ú�¢Z gjûnГ…Ô#˜ò�uÄ@kƒ\Gå@¯xdP =Æ Áò¼!œA¹}拵Ԙ1[8ðØ֣_ìTŸ.Ò�<à�|d`�+ ©'³cqû!ú��¥QŠû¤nJÂ�]f H£™aý=({b^]Í’x Ib™‰¶,ļ¥RS‹vH¹KÓ „'.×=“’)2ƒdÁ×<4åâ]z±^¨Ó|èxEÒ|Ž“ZqFíÝ#¼•á 0àˆk`”;ÍxöT�Åà)�ÂPl}‹IPLóZ^°j�8q!<ð°ÍÏ;å6ÕLm‰‹¸%vØÍuÀW îÞŽ1%�j„8â…®&åX! GIbrªÞ̾åQóöAšÏEÏÂh£”ÖàT¸UŽš'X¬K*•%¿œ��TÙþÍàÁÔY±þPIãOËÄz*ÑBKY)ÕÊ9h ‘æÏî’gyŸ{Qܹ2J3`\œ<½|Ñ#úͪH°½<1Š˜LÛ¡y…ï/)ñÁn³ÙL¶3hvraî)+P‡1[ÈIÛÝÆ¡eêQ Tq‹#̎ʨ–2HߢÛU¿©häÕL“"Ï÷ã$ ÑN¡T/$šzÑ·²`7VCk!¥Ž=zÎê@ ö8VGÉó�–PEdn@Õ£ãdu¸oÏvÒ®nß÷Þø…>Í¿aüBï íÝÑío(Û;;ÃTŠ!Õ6­%>jXIåç“ã^&Q– ·ÄyJÑWZT,¾Å©·/�×_äݱ¿’‘åÓ_£NY¦{•ŠzGMéûQ þ3Rö?´�äÿãq&¾¤xœÝYmoÚHþί˜rRÏ R¤»Í¥*MÝ*…HsÑéd-ö«¯Ï» *ÿýf×ï`MÒª:TÕe=;/Ï<³;CÛM8ª4Ûµv³Yƒ&¼ž1�”Úrð»^rx° Ù|!Õ·‹ô N:§�ßáÓ‚y,à¡‚†`Üèçëy4�¢–E£¼ ·üï9Ÿ£�~ÿ"1Ògõu!ò]Ô!ºqð‘¼1á ã>tZ§`(�zòªÞ8S*6<‚%Ù€Ï%D‚¢&@‡C× $0¾ •Å ¨Ø�ÏŠ‚@dâôBÊ@¼l·W«U‹ho[<œ·½XN´û½ k0¶NÐãdÇ¥ïQ! ¤ÿF,Äh§ zä�)úé‘ðÈ<¤øNråñ*d’ùsŸÉ ©Rã2!C6�d °Ô? »(€�êÝ1ôÆuxÓ÷ƦRrÕ›ü9¼œÀUw4ê&=k Ã\ o{“Þp€ßÞAwp zƒ·&P„ íÐuªÐM¦ ¤®ÆmLiÉ…�]uØŒ9š?�ȜœßÒÐLj  á’ •R�ºJ�Ç–L©—vâR†ÚµÚ/Ìw¼ÈÅôk–.ê…¥?„t]:k-^Ö<6U’íä©^”H*$*h·¡wu} AHæKòn( jXXfð–: ÇUf5¡âsà–3ì•·”7¶òʦ]R_Ús*m—á?50F ’O,šJIh¦ÿ dhfRÌÇ7ë‹›ªÅu§JR/bM¥ÁIÖèàñ��/,”ñ`Iå‚#Ë5óS¾((~ bCWD„Aä…h©í tâ"·ð¸]*LªðÄ7p_uˆ­2¸¸~jwÖ:ࡪ¨Øõ2!Nžê£N9÷  󙌹SXÄÜêg–òCœjÔb̲-ˆ–QTÖøŠÈhÒÌÀx¿ÁÔ¨s“†±ÏÓ½'¯t*ó÷�ž�2 }˜OÐXQ.Íñª°ã-å<–=¦kI}7¯‘ýöZ%e9áŸï¡L㬠�ĸe‚!a�ÝmFj¼a‚ #ª (…gpÙïós(â›pr+GʆòZ‡·¥Ø,í�=¼‹�$`*»gµ»§ç—>® nÎ0¿F5µRâ”§„wÎ"m拾K¬;UQ‘ëá'Ù8–Ž?$cU9øöbKC1è"ÏóÀ«6ì0?OxH—Ø”¼¿ºÍ’Üaãg{nTû€�oŒcw”ÓJAQ*Ñ">߈ qÝL”H�‹ö¬4w|$«i`?€·A$� ìSõÍØîŽÇÖhr˜ Û‡|‰ÒzÌ–³¼láG$�ÿÓì;pšìïúÜÁöÖˆ»ÕÆC›ÅïÒ&ªþû=•¢Økç­îKQ�l0©†ÏT&�³•1ã§ž[vÛYjb‘E8ãNº½�5²‡ïŒªtš¥í&¼¾w½óoàt±âø)èëéY¥à&ÜÜ#ãÔH; 1Ül Þ!,H�í»é¾¬«ÏA Ì$Z3 ÆL|5!?ãªÍÊê' ˜à-ýˆ ¼bWó¿žVâfs*l½‚œÑÏlnjÛà`PbgMw91ÝŠõ‘­‹¿ÿÉѯð„zfµnn¹w1·‘«k<±/ºcË[ƒ ’¶o4ôLZ¶¬Ú; Ûvq5f¼Ö2¶&q†Ô˜`Æ¿Ÿ éjo“ùÿÉ�k�#=S ¢sQ ~ùÁàIèò•ŸŸ0Gd¥ªÎÕ£8IÌÈ -ž=3Џãß�¤dSd¾X£Þ»k{2º´ŒÔ€¹3Oë;!™W~n)°>ç%�é)ß)­#'ÓŠ¡CïO-_õ?N>”Œ_õGo†Uû§ùŸ£JÙžòuÕ�›ø�žyh6•—ÀvBJ$5ªaXŸíáÈY“ËÑ xÜ­¢ÎÆÞ$Šò�£ÚÁ°^œ–öìy’>Ç�'¯òüÁˆÆ¶ŸÓv>aÓ«~´ÍnÞž_À”n¸ª¼›“ÐZð1ƒ@ýê-|…waª¥l½•$¬sLÂŽA¾£"ל>,Í�Ý”•ºýµÊÏ¡rb²íS"¨~Žæ–K€Wj.®˜ŠhžR”¥·; ´›“ ¿=á´#¨à¥âq¦4T>È�Q+äåQR©Ã#|f¾X–T,<ôMé¯7ðÁ§3H«’ˆøûˆÒº¦µ‘‘Zù´”«3ƒ¯á¾)qÖëÁçbó²JÏ¡ ºÛ•çË8�~¦�kA@íµ. pâyNZLÌe‒ÓʽŠÜª`Y 4öµm´²$þœ¹c.v˜«ê¦ ¬hâÇá¸3v£rÿñþ~¸…”ÊI ªQi¾*ß `¸�á€ç`9›Aù‡` (:¡’Ïupí=_c,�æT*Ú\äåS±ö]˜‡Óì³ú? ÁÃÜ<À$í.)PºÔ�–CàùFXNˆÂ‚ë<›–žš¥#¦.D{z '‘0È4BZ,“åëk/!ù”ºèT˜ÝLp hÑ­sÌ'Eáâ{âãG¾+ƒzi¥C)w½½=I›ýbÏ_^¨›ƒ«‹%}py‘Üâ‘ %·F’­j‘óË0u�^T¥D•)ŒšÎm¨ŠK‘„žÌzæ>‡MeYa{A<©Ê1VØhÀOkâO›Ê5ö™Ð±eßàì,™}ªÄÝUÍvBhÅ6„«m‹ —É–вUqÄBnBVÑ ©\Ûs{¬ö¸¼¨Óvqðª'ÃZë½gc«\zÝʹ�çEZN½Õù¸’Ï•êžZ{¥áØ4u³ ŠÁ�,ã-I)  /ÙëÑ/V FAl8ïú£N¯ßŒ>E¦ïp3`ÐúŠt–[‡ç÷‚z로M{'Õ„°|¹=s§'¢y‰‚«ÜZì90!·TÿˆôG=r�Ëm²%Tœ@“êq2S¬‘®›–üËI“õxüŠ'ŽÜÜ ‰Üd*u÷Ä8›„Ù?`"H¤¥jû=P¬?á­"NŽ-Ç$¥V*"ÉäbÍlbðb8På&Ofv˜ùÞ2erèjXY¤´NÉ£|¨ï (ßk#V[jÜ€rfåU{ëî“5¾OÇ9„ìÉ×ZZy+t¹�@ß |â2Ö‘N9&<ʤƒÅ!¼5aK3ËMÖPá3ú”^&­fÊpfpz‚Prwœon’é*ˆ…¾�¬kˆÄ8ì­qK'¾¸öög¾“öê�µ¹¶- vøoÛÆ{û]ô^BÃWò¦ííUÊb ;hF ³£âé†MÅ"7DΑÅHö{ëÛxV¾–ì]Kþ®%ƒ×»¶6DÅRPÛ¶à�&(Ù™‘]zD’®¨ÃÑmj(»h`AÕë- Gv­¼†'®£rÕqÙWöf‹ÒA>¥§�ÂA ¢š�&'|.¨Ø9ûßžý…CMNĉóh£ü!ÞW–Gá®JZoÒ"åœ#„Â,\cø,ëõù.ÞÀ:do"ºÎAqø±ƒÂ v@gJ¡°}Ýoa»÷èÊÒ�¼àü<ç]BTÇ]z«š�ÕÌ"ê¾2§½Uá3Ø„cRK±šUr—¦óU8Æÿóßð”õ`¤¾ 1_Y)RàÙ2g€£îp4¾ë »ãa·?ÂÞì¾\Qg¯IìR"“žõHÐÜ)HÃî(P’¬©­è $a?åF Cµ_¶–ѨÏ/¤B²gÎû÷ãzô¦›>o6‰¦p´øÜÜÎfMu铿*üL¬èn”2dqADªf<,`�DÂÍÇ!t¾Š†˜zRaÖÞo�X°‹/ØUbUL^åTX°8eI%Øn[ÇÍÛ3O«@W2G,e™ çr{ Ý-õm&ƒ :éz¥ŠgA‰?õ6nÊb‹£T¦fä+�Ö,Ùû'†uû¯…2Ó›ˆ‘.C÷l5›{ç„%a»y°¼6ëõ¨µ*²ö6™çb½ù™«·Ñà±[N;m¸X}ÉPx†¶ŒhœÊ®£xß}ÿÕt²+ÊvU2¶fcLHED¬A]?²V ¶fZÍ·7:ÑqõX«‰®ªÿšç!:²êAvÚí—±t\eÑL0dôËzw6HÁ°Vï¦aÑÍqž z3½ßnJ%…‚;å$)4ÆÊy²8ÉŽÚ·£vdG­ËR«Ý~S'ZR+2¥¼�"]æÖïf)tâEo³Aaä×ÒgÕ¾2�ü #­Çt¸¾xœåmoÛ¸ù{~ÅÓ ËÙ©b§Nq’¥h.uwF]g‹ÝvÝ0²DÛDeÉ“èØ¾6ÿ})Q"%Jqœ¤]1ápŽ(òáóþF¶}ç�õÀa{¯}x¸‡ðzB}ãpÝrñUŒ¸ábÑéŒñ·Kù · �ãίð·õébï“_Äïëér<&-wÖÔ—á’økNq›~ÿ2ݤO]Äăeà! 6#p±p\üI¿Xð‘D1 è´Ž¡Á'ì§Ÿö›gÄ&\ÂÜÙ@2XÆaÐ5dí’€Î>u—Àв™Ø'…Òâ0>§0Â1spºœv'êDpXŠôŒ±E|Ún¯V«–#°m…Ñ´í'óâv¿wÙ »GˆqºâCà“8†ˆügI#¤v¼g�¹Îñô�„8Óˆà7rŒWe4˜Z‡¶r"ÂÁx4f/™Æ0‰’­N@–9ì_ ¡7܇ß.†½¡Å�|ê�~¿ú0‚O×׃Q¯;„«k¸¼¼é�zW|{ ƒÏð®7xcAvá>d½ˆ8ˆ&å¬$žàÛ� …I˜ /ˆK'ÔEÒ‚éÒ™˜†7$ �"X�hNc.Òô8ŸÎ)s˜*ÑÅ7jïíý‰®¿ôPü\IgûÊÈ_bæÑ€µf¯´±wãcÊRŸŽÇNLÚé/“tÀKŸDúpD\†Tøb6š š¼!®ïD)Æ2¿½˜îÂMH=°Wþœ}±‘@›ødNfûê7k$ä ƒCùç‚Eh)�ql{¾�:`¡Gø47"#ö܉¦4häàñÿÑVQHÙ1a¶úaÔØ|Ê{x¾ø aYb†€s¸ôRè’esî¹ËÐeÁG±¥ãܰYˆFà é^GdE^9LO¨÷É ñ[ZJ< ?f…¥7s¾\•ŽÀ9|d¶Ñà{¥Ø¬½ÛŒþîzFÜ.ŒwT›”~8z¬‡‡¡¯p‡”%²6é‹UøF ¦€³”·dbÂW4Æ¥+Ù+õŠmÐ÷Š¡*{ _ÚÃa÷zÔ|è÷áÙ9äšÊ¿KD�ñªB7¿B+y®…Ûd�@ãY2;3ó„ЃÜÑ«x‰®)ÿÜl¦‚æODØ2 `âø1I Þ&Ö #áS;� +‹®RdÍHà52¸%ÍiHb-8(h·¥mi‘ËÌÔä»Np]Cço¹:1Ü3ÜИ¢éÕmÝÒØbêI÷*Ráx^ƈJÁXšUÏÖÈ4·×ŽTkC‹;×ð³Ý†w„, YØÌhà‰hŠà~‰!]·îb¿í°p‘ëéÂàÖjÔ£/•Z5$éAÐz”·„”‘œæ³½ÛÇ÷…"xÄi ›©·âªSðSwj]k�ùœúY,©Sþ9"sLŸ2ËÐôK3¹¯I˜Ð̨ó1ªfS¯ ?–/wêé–l)9 É£ÉcÐ!ïö{Ø€ê&Ql™T$«©ÝlçùK*f� ḛ̈�–0›kó{ólì¸_ž˜e•ÞÚÀ@á§6 ÜÈ¿�xWü3j\‰ì2u�+ ¤•®GwßCØ<û‰û]Ærÿš"'²T!ì*Q6«…'!²Êqg&,™„周¦êÎ'58¿›êG¯ÄŒL½°<ì‡.Å�¤¨mîZk>I•™6ûÎ&^7ó¤TrB¶Úxk'm™ YÄ8…Ëé ߇43ÎY¼}È(>�ÐaLEÞÈBÌ|Ec‰LØ �D ²~Ârá9ŒˆÞ�\‹-æ«YrÒ ÅÄÄ™«Ž`/Óß{6š_Ò•ÕkàË«Áè¢7è^ÛWos­R@Xêb jÍ×è·pÇTÙ¨¸9†¬ÜPÞ¦-�†î!“ô/`)7í5Â<>+ŽnŒ£+걜 ¶%†K“Qè¼³\5[L�é¼I•êŽ�)\¶9oY6êšZP›Øgª2#Ž—ûñ(0žI¶š>ž+/G¯²fæÄ׬J¥æhs;�ØÈ+ ,OÍG¯Rî4Klzþ\K^9ï3#Ë8Xf^*3+Ýa�…ÿ§‚yf– 'Œ¼èÍgL88 ýÝÂáã,d cá¼X­H S +Å?Eÿ½¡¹“ƒØ�€v�‚;Hà©Hâ¶k 6u[H%)l€Ë6M…„xE™;ƒ†êgd½¯²ÙubŸúïGïìß®þaÿ~uÝû'÷�ýS­˜ãž&ÓÏ#Áœ3m‚⑲yÏÎÕÍW gNEé3³È6y^ãÆä3ŽˆóEáH�Ò�ÝëQï²HçF§³„Œâb2«‘/8ßj~”©ÜìD¥G&ÎÒg:Uhë~8m`ì{{�¢µ`ÿFuOíÁŸ½Ö¾el)z[¡™q¥fr}VÝàÑ‘‚{» ]'ò7@Ö”� ”YC@ˆG<‹[1?3#¿Ä„À½† Û-ÍÒEwäÜìcàÛ78†Wç%Tš%^">½Iv´x —âÈ÷]¥ˆY�@ÉŽ0ÑÒ3^%/®Z¤¢6šÃÔœÊ�Ð\6ÆÜ£Úa–±©r@š˜Ë»X™�fmJ°²C¿¤*Á$Bï.g Le•PÞ³iUÚ›¥;Õ;šø[ËuF ըעƧàqœ)).“Ýß?$Ïštµâ~DaíDUÎóûvÿŠé†e ¥R•�W, 7&‰Jn?A]ÌÏ7+ƪD1di®VZ»ð^Ô>© ç™;�kÕH®Õ8ƒ¿hâο&ps¾[ü¿B0�*ŸP>Ù“Ð¥ÖïÒâ* ¸âD¢ˆ¾Ô› c‘¤ÉÜ !;‹Q;,ɪTµ5?± FO¤^ôj!~á„w/¤žåWLeî£Ü Ð4NÍŸï—5›4Ö ªÛåaYÒFZb>{Òv‰Iu}©” ¦ˆ�ŽÎ$_¥¶a>”š/{ÀíÃýQœK¦ÇŒ±-Fwñ»(ÝæÃI‘nÏ0Yúƒ‰Ï…7$ÂñÊeBŸFø.r׸t§B®â_q©¢HÙpü¯g×*¾Â Ìü2"1ÛÓ©†[K™W¢MÎ/}0®“¤VÉa¹ר;Ù—î=ìF½A·ßhŠÛ:‘\7HÄÁ¤§#€ »£Äb¸¯°Äu&ÜÞÈÉÝ¡èZ.ù=9qÊ+VùòJuC4Á"m}¦·Wd²ßIHL5/êדɱxäå•Gw>Ý5‰\Êu9³\ˆhƈyá*È=ÎÊnh¢áÿÏ4™aÕ{ûÙ]è6äBµu–ßv©zx¨±ŒÕ'/–3Ö6¥ß/ìÜý{>C@‹c­ÚÞ]1‚©ýh¾º©ÏìÔZ|{²2Î-±~«/74¬Œ4nç~êDYÖ¶òÐØ2q¾-μвísšØÜ™$@²î“vC~Ë»-™É½8®œ’u^œÕ!ßy ä;äÅå‰}§ûÎØwj±?y ìOjXR‰üËãÊ)ò/ϲj2r ]Ù4v"˜’¢Æâ,|Ÿ‡{Þ(iTµ|jžèmQišFw…N#wV<«Î²+Q‹ ¯QÙH5x„jl:ÿS؜ܛ»bƒÒí ÏtŠÊßP�¡A:ü5 d×µuçö/;ÚZoÇÄâ²Í–Ë:»íÖÙm·“Ýv;)ï&¹þíÑ‘�- ‚p+ñ,\úÌœ"EÒriÝÛ“•nÞy°+é]´ýúr7y =ÙQr;ˆ ó0Ý.Ø©ñâ‘Ù¼‹U? VßûbƒMž4Áû%/(Åù²¨§‹1Ápëd'§×y€×ÙA &�Âw‰=;Òý4”B¹A�d±BÎU“�¼©šlL)~h= KèŠj ¶§ð€Z@ž—ý4•À6¥Àñ÷¯TôëJ�ãÊ9y-pü#òé�RßRb¨& Oà—L~üû§��ÎëožÆ?­SåÿÊ+ðø]6]܇ýÃÀÿÎvÒ±‚'xœí=ksÜ8rßý+°NÅ–”ÑÈ3ºKRrì¬ì•wU«•I>ßU*5Ë!1–9ä„äHš»Ó/’ ç!9'ÕÝZ"°ôÝ�ƒ=ôn]?hïàÅÁÞÞ ´‡~œ„F~ç^ã´ï“‡ô¹ŸÌ—ix3Íá¯�â´ãï¢á›á¿¢ÏÓ0 çsô«‡3œ¢�oôßoã1îûÓ]µérˆ~N’ò±³³�ü#g¡�ã hdŒ|ŠÑñÜóÉ?üMý §Y˜Äh؃v ÁKþêåî[b™,ÐÌ[¢8ÉÑ"ÃdŒ0CtNøÞÇó…1™Ýl…^ìctæSú>JÆø #Ã" ÁÜQ2‘"/ç@Oó|žÜÝÝõ= m?Io"Ö.;8;ýxr~u²O æ=¾ÄÎ2”âÿ]„)™íx‰¼9�È÷ÆÎÈ»CIм›“wyߥaÆ7=”%“üÎK1 „Yž†ãE®,˜€�L[n@–Ì‹ÑËã+tzõ}8¾:½êÁ _O¯¹ør�¾_^Ÿ_Ÿž\¡‹Kôñâü§ÓëÓ‹sò×'t|þôëéùO=„Ér‘ïàûy 3 `†°”8 ëv…±Â$a es쇓Ð'S‹oÞ F7É-Nc2#4Çé,Ì`K3`ÃDá,̽œ>2æ:xñâŸÂØ�ÙþU§/¥çÿ…ñâþ Œç‹|ßâ8ß÷“gýé{©ÑÌ˧ê“,Â87žEáX}–'Iô-Ìy©oî¼%™g°_¼ ð„À‡¾ž]޾\�Œ¾œ_]8;‘»DéA¾œd!¿�¾áå8ñÒ@Vi“‘ÅÀ´¬Ð¤nðûoc‚ð³$>(~ƒŽÒÒE¢ËwLý„ýÈKù¬ÄT^d°‘>ºMÂ�î¢YþmTlØGxF6fD1ÙšÙ�´+Ú«ü®Ÿb/ÇR[öiÖ\4"_¿Â§i ãC9¡U´§þ­ÂV¿7˜¬BH~¥„à AT´á‰þ(OæÚÊtµgã$Ï“™ å˜`u5”ó„t${>pÒl4KE\¡e-ĬïˆR&4Wþîár¹;,dœ,çíXÏåï.«s:ï „Hrwöq†Pæó6ˆ%zߤÞxäƒÈ‹šas\0ªÑ8Z¤m†­_ÈbX:ÛKY @~‘—Ò|Þ ¬l9sŠp\èGz�×ìöÇ‹™xK(?Å> ?*~c­dó‡¤ë, ˆ%ª�ëÖDQ„ÖŽŒW?à”ˆ²�4ÖfòëbùGÀ´IÔ°&þ[¹#" ˆ˜úi4â,<ðrÏå8:ZGØ»ÅëT`ô§0Í^„f8Ÿ&Dý¢*™Pd x^g([ÕA™eGè„ÁÕ™Çç!³öí ÖßÜ-ò½C£ö ©D^¹‹06;*PêFP[òîvN_7Œ½‡6c­.ð–:4„:Á@Úi]%þç2‚Ôœ¤ðºº!”†zgÆŒ\zÓ–zwÂ>œ:l¦Ïø@]'Ö¢÷âá­PÿNîçI 6£�Ž* UÿÈ€ûëú�)‡ÖgÆa¾£?$l®üèû#¯W£ã««“Ëë�ó/ggè‡wHmö–¶Rû’å3Çßý¬Õ¦&hç•Ð)H¯”AößSŽ!šìîrz‡Ÿç‹4F/Ê0àA¨)ò QãFÊ(œs¨Ç÷„é;Åèµpôä×Ò¨|!ØÈ`ÊÀÜc"œ ö:Ê›؋¿Ó§�õÎÝ«ìkeôŧË~­Ÿ_m"/Èb?lIG^žƒ �år5ÊÖÚi’äºT–Ð\ÂI�PTtßÕpφvê>ªÖ#�êíh0µQ´j²o‹¹Rb~÷®-è2ê £=ܱ1‡*Šuá/ mI©öc�²+'--t¢�0AìjVÀ§›Õ®ÝPvÐw‚¾88@ŸˆnV¼ËC´€9öê‰p÷}ňì`"ÆŠ¹Ç-ųä“&8ÅD Èà!(€_Ï.Ý.8ØWÑÏÜk ÊJ§¡Jöν”RRFj˜$”s}'L�ößCi¤ÆDâÄ•=š†ÔŸÓ�$h‚0ÓeM#fU �'ª€¤h^/�Û©Û¢O/„mDH­h׋2J�öß× _CO+‡÷ÞAh0À Æû¡�d'S4OÈ>æ¡EK”á�bž�GœšcÓ0 ú–)Ú¼šÑ«r°ù"›Ž&i"ÖÕ�‡±�r†EzÏŠÍ‘?¦hRâ™ËH_S¹WÏÎïL†vëEa¢•[üz·G@MÂa“ùJøY0Þ‘¼$O—Õ~rˆ¬sAÿ»�yVö¨ÛP�¬Ýe¹£¡ýØó¿Õkc&æ·Æþ²�¶ÆN¹›LùÁz¡±¯Ç*s±É”ö­ìÛ͘F¯€¹Õ~3`•ñ¢ä¨1±C€7,·n¤…l~¨ÄáÚ¡-pÎ#� á�“[ )à§aäžÑ¡ P’9þt¹/SX7¯42$C‹µJ¿ÂËçÁ è­Q½"ÒeNªòhïÛŠˆÝP'ßöº»A¢*´ ”)ÉË,DÐú³&Y¸ÎV&ËуHqxæ´A®(ÖBÅ× ßç$ñ™#ú’·¤.¼º HaóÔ%°š¤€éòJ1ÉH|#êiGæVÍ,ô °ÙØCƒª§zŸ·Én½0+…£«*¼ëgºÂÂ:ŽRìKäåÔd"€þ':Oò)µ·$¥à _ÈǪX `J>É37–aom™Õmذë¢Ä•QÞ2œâ¸ä¦Ÿ<Àñh˜£I˜f9ùs‘£yÊŽ=ò†÷ƒm!vdÌ@„MÑV‚Twà€Ø“}‚ÆKÓî°nêv£W¯T£½�ðªÖfÔ•ãi8UÍ—(6囨®(ÉÕêG³hí œ|$d=�¬ÁLy�÷‘ÞÇ•æ`ãÒê‘PÑÌ7&ðá‡ôv· È¥–ÉÑ?½ÝžJ!kûöí6p;ƒ³¹:Ï=Å(‰£%'iÑ3c„=õn1ò�ŠÁ" úÍPî¿¿�å¶hŽu˜ŒŠ’Bfñ‘ç!‘Âl£åþG…œÁÚÍ´l2´Ä÷¯§8î�»{1§J[ù]çªô'h-¶j3æ:1�fÛÔÅ.}0U0gŠ—ø~GÑa.‹•y·ä×�ÂtTÃwÅ|Z3}àNc¥ÀØ qÙ�@Û 2ÝçKxÔ%ÞÏÓðæ¦PË ©fÁÒ6zL¯#†Ì~DsÔ¯œ^³©²]‚hr­Ëj¡ºŽ!¸4hC(€g¢›º?/ÈŽºÈð‘jYô •ÀB_7mÅ¥Ùݵ¦RaÔxr¬žQÑ@ °4œ™îÀžM 5ÕMÝ¢âÚ¥fcŒ} œ¡ó‹k¦*­´3µ,®íL�h��ñ»NØ5s=µ© ·m³"“5:q ÁM´ –££"&˜YŸ–%}›¯%¡IZl`q*s­ÔuPß·[+nîis3ãžÄü×;K|/B;,Ö·k¤ôFb¤!‹á£E ò$¥î’S<1¤á;¿—9u¿CPÍï,£î÷~�•›ÊÙã+_') GuIJwÊ�•Æê½&£±OWwh'¨Õ'<ÖM± ?ŽàYÚ§³9ûÕ·Dj2¥Fš‘¢/ìÑ‘™URØÞtÄ�ô}FÇ!˜Ÿ$Äjg‘ŽDõœOi¼£™Óƒœ]tFÄZ¦a•Dµˆ`8®aì‹–�ä `¡”Òàd1Ž"Ì�„ëx3yÕ¥§&]Š^œŽ?sw¡ù!tüùÁäYñÌU袛‰ºøñâüúøôüärtñ©�ÊQ¢1PY½�±¨•Ñ×�í(+Ù:îHJ@ÅØ�ÁÔÖžÂ�òaFEØ«Œ%^fÀÕ:f�‡^\¨`&Òˆá–GwÞ2CÄÒ'7!=bä@V>«3àA&G1ÒHEòišåŸ¤æÞj £�:n‰Œ™gk0�Ò«C¤~?衹H´üƒ¶)¾­wb¬Â�íHåv®eB˜ œŽ±tcvL®lûm³Vª¤E‹˜Þ0€,&[$°ÔŒ¢f¸Â¿}qÄ·çu†´lD&yŽÐ%›AùƉÿ‹„wYI?#ÏfI–£y’…Cýæ-ÑSjèK½yn¼Ü]'s·ÎE½ÔùžÝm½Ë„û²÷ú¬þëMªØ#x,‘püdàÓóëÃáè·ã?÷`åo‹¦¬îLñêô¼Ç7D~öveö,BVfÏ1¾oð1¯Êžé™zÆf!1vÙ©YÖ¡XÃûAÒZÂ?U¬°Q ²ÖôŠwEûË€ÀÐ2.ùÿý�ü;¬\BumøŒ ÿ�A^-‡Ò'8¢ $»¼õØlп�OK þðvT¸/y³¥ÜL`4<þóýSQþt�e¼-û[whžePpß¿cŸ! àó¶ø2ùµ †¨Is64´gÓ mНÓö†óF0€Ý’ öÔtlsΰ[ðDŸ™ †±+1ÄŸ›Í%^"3$ÞlEre6#™æëÙsÛ�„•~<­™*_§˜T¢ÚcÁR¨_VÖIÁGÈ?Î$k �¼y5[A4BI“„ÊÐ¥„P¹›ë¹¢~.¤„„8}»�‡Öù=«€¨a¶2Lý{•Ôþû{´Ï£¢×ÒÚkIzÉkGÙ$CsûÙ¨ŒŠmç§ys%èTfúýˆr6E ×‚T wŒ2>õTÐeõª¦ #Žs¶° QW˜¤´pø.±Hê©VMÚºS¼™ø�€‘‹Ÿ.Œ‚‹Gè¤J6MQ€âEI ǵIJ€ä¥-b¨Gô�îfb¼øyPþjtí¸Dµ©u u”€VQéAæ#�ÂÈœ“^Ì…{<[]+¨&Î6ú�hç¥AŽ/æ%ƒá„ŽR„JÁÙ%-ì(m:éJzá5MW‚Ýa'¦˜7EwDObþ’ �.´ÁZq�¤Ñy3�еº¹¢S+Âå•?�D¸¥èžD}œÅ¡ŸÁyæEá_EaW"Þã$Þ?;ùtÍÇÈÊ€à×ç#ú†pXýûïÙ%­2» sJ£1TÞT4;/ƒj¿]ÿ:úðåúúâ|ôÓÅ×ó#eN¬8ÐŒð*«�Ìâ¸`õâe�NméCŽ‹�=‚Êx6æ%Qo³‹•ŽaÌâGÂ&÷½oÀãÃf2Z 2è³Ú ·Q™^fíÜf&xÖ,Ù¶péú¥»FªŽSì}“*M¿|nBRÅêL±�Ã[ÌŽQÅ luEºŠémå*>Û áÚ®ñdzÓ�¿ÕòŠ/ÎNôv|;¾Â9�¢ÓWØô„èÛ“åÐn‡VŠ&óÞÕ‡‚.ƒWÚ19^¶Ý¢Ë)c=�5mOЇžx‹(?¢ÛóeÚCA¿ÎÑ·8¹CÞBr@¦õûêFã5Jnvˆºðéøúø¬‡^~‰¹Î#Ô N蟃—V +d™¬çê²^¬¯=ä«•¹b©.ÖJ¡h/H,{ôxÊ4­X)Ž»~¡»Æ²ÓÄ/aNæ 7Úµ”]ÕQ¡®.\£Z—ÐuU¯·SùÑŽ�6¨N—+×€ÿõÊtmÙhIwT×3—¥°y2º®e«Í÷DN¤´ "ðŠþÅüýdz¹Lê<ñ§šÒ›Ž†»•ÿ~tÒª1&i)�ÍÍeÀš$‹ñk”ScRÈfðÍ Ù”y–‘›A\óʦ8vïTK»?5l�>–Ðú2슋L1 &3{E^'=Bõî¼e¿ ïê’j0O]¾¶) íCó7„nLca1ÄÅZ2Íå}†8S¢ªˆû¼¬ õ,�ªÃ¶Kþ?!œe¾ )Nñfð³Y÷¶-~+4” gÔܲ°$ÍS/Î"ZÖÀ×k ªvAÑG½þáqmˆ[lIG´å[#­¿rãÆ†÷r[Å8ÏÏ¡u. ¤žF¥ _o³èN±’1T›•A[—bl6IC�çVr&ˆâÔ¬71xú†;Yx¥åÖ=F (|‹!h_ZÐØµ—ÿѲ-=Ø¡sOg®]5GyÛ^¬À©½W5œô½åܘBHw�3¤NˆóÂn½i½ƒªéR�üMƒã2ãÚ\Ñ Þ’®†=R¼ÌþûŠ›sÿjÉË‚J^ö4²A=©]‰ê¢aù¤ª¥AfW£‰9VIr÷ò©ÚC¥ ÑC»Q×Ú0\oOS:*[sz°uâ¯Ô¾*AˆnÚm·öGŒìLÁ Ð��U²¶6(@ïj4¨G‚}ñV¡Š"ìcÐWb"à¯O®®G�¯NFW'ç×DÒŸíìÒ+zUú€ri¹iñWLW C]�ð«© Ô‹@W4&àÔP×î, ý“{œú!P>½“t—fQÑVÔÒê¡[œÂLYÇŠ‡È•~ ±XéWÐlE±P:¹<ýô—Ñõå—“1ˆéLÐî÷Ö½o!K¾Ÿc?/k2ÉÕeƘÎ9¤Y eécùãç'ÿUª>% –HÊ[_Mp’ë#«É¾õZž®€;Îaï7Mcí³Ø�ï¬DFO” Ø ðGqF!á#f>òS¼Õp²AóÚRJŸ¡ê«æ9°�èïCé÷ÃÒ�]4+ÜËÊ@ªÅm/a:�¼XÅ—º7Ô†;\m¸C¡’pägU£2¸¹"öñ:DûïÑþ3è[wM¾”³\ýzU̽8صn«�Nä¡Ì‘ênƒmàpm7àáÚ¸ì¦æ}²ÝÍc#¸òÍymâ&JP\¢B‚!E2gó=bÈp¸FX‘½q-çëÖM\•¢\WðpS{°³Wî´·;O�IyÓBH+ÇóèLýjC Ì5õ7É•¤Y’¾ðC|V6ïó7=WÕÏbº{�ªÏîdŸ™,¥k-ìyná„Úð•¥`·¨VM°³>â,ù˜í%…ÞÑr»‹9g]­Åd:Š=½£}2·^¸hᙪ)bÃ.g¼t^w§ÁœçÝŠ‰—ó6bW,ÛÔg¼º‹´°|(çEL-È]~H0ßkvo!� Éùóþû"™꘢� cýA¾Qo·_›`<ƒZ[ °1‰ ›/ˆ{‹á@¢Pî¼å4"²}?úSÂ3ãê"ÿŸù��Lp*Fã©z‘—•UüùíôWàn[VË[¦ÌZªAê€$ûï£0þ¶Ú6‚ÄÊÊ*€5’BX!m=æf · ˆú½ëó”TÓ$ÃQ9[IãœåúÑ4i‰ó~-Ó|ÓÂ"mdzo\‰7 jÐ9D¼�ÃQq#!äÓJsÒäZš¸:xm¸Z›‰�PI .ó?‰ÒË“$‚ËÌ4^§dŽD|¸EÂkœÇcÞSâká „gIŠË#¦½ÃÔc¸AN$À„zmãÒj$Ñ�&7�U�!§ï$}d8–ž¾yáŸ=®…*™W䈪i¼”ü.åål'VRT.z…ü=.è·w“¸‰œÎ§ÇðX¾ìjmT<‡aíÝì)×ý©qbYÖÔÉåUÿÁ6nKkGç�­•-öË4·èö ã §�þè•å™/Îôæ‘ç“wÄ@©wþ4ÄïÔÄj¯ÁË3º¸]ž\¹<7PÖ¯$R :^½œìÛa�Òàä[b[D\€Þ9WI$é!Ë÷!òxM*D ¤ºÌ* lPvüÂzî|ÂT 6B;%À]ÖJs©(ÑŒ!„Ñê²ozˆÖ‹…óp†G³ ûäïCu55yY׊�Uá¶ÐŤìÐ4º¸έ±Z ÖíÔÊÄ _ý’-C Äê¡ rª ¿ÂyÝC7p³ XNžÿ­­[zü*ÐZ×XÝКiÁ¾LòîþÂ5`øJJ[¡Ñ.Í‘DõJKN½S9oo²[ÓM»/~K&Cÿ7Ð5©¬[Ì!IÕVo�,¤’†up a5lá\kÙ»Ü×Âà ©ŒäçœÌá"Œktg‹T½\�Þ&Kz°–b8©~4/@,iWH' AÚ¹rGKz;·Icuìnk”ÔAºP’£¬o‡ž+Z"ë"Ë·ìþ"{F(Ý›·b›€:³ˆUwfÅOAs§+Ô¥­ruÕ€qH%hÇ#Æ@댒*€l—4„ê­¨–ãØ3éµt%Û]�‡u�¦˜ßJ4èáU‡š¶¥XAC-®À°FÇ ÞÀÿÝÖq×5âJM+<ÏÃþßIYyýâ™({&T†}g*§½¬ŒòÓñÙÕIsð�Îô9µU£ù+^o]RÖ(�󼂛Dœ¥êeÄd ¡…Y1=*ZÏ®i•³Ã»5rg£«ÙZ=°ñ‹'¹ªpw›iÊìB�›&aAu'b.뙺"S&ì2,nRÁvïÔ!Ä.G†�À†Á# ƒƒbR½yO‰Xp¯ùÌ+ó½.9+ÝÁë³�—l­û:—¶.»ê—`¯¬ÌâË秃ÕkÀ±�Qèc7ùBÆMœ¤8hZZ¦ý±yå9¾“øeáÏ¡!ÊÉâfZîýp{Ÿû>v¿nºrx1E ¶¢¬âšWï{¡ǵû…Öù£¤°Ñe«&¶ïuåŽ' ÎD>1=)½óQ`b†îè=c¨xâ¦O�¢%Ê0½„ƒ¬{·Xr'WI§êöX¿Fiùj½D5ïÖzO›èñ†öN´­ r(#ÇLq©@¢”¬!ç{qÀo¨oH°+Â^†¥K¬�¼â–ö9Ñ+éÔzpXa¸×�{‹aæ»Í&i¹1}kAÆu‡'ÕŸòq�"¶k‚ŒV>9j ô°ÐV24ü}RMx«ˆ2¼çu«ÿàe¡O«öpÔG“(¹;bù#Põz"Ë�;»v&¯�JÕ®ØY_½µ¯Ñhßi™^:ž7 çÂ&ÚÌ� ”®¹]®5î»s„ÍJ‹Ì†¨]X�v?S¦Tˆ] t#"ÔS° Á¡’Puƒw=bGdDÙ`sŠDòvgÌséèrªÙä¡Üµém‘†ÄV…\T=7�ùÄa«¡ç-`Ì·MÂ4ËÈî[åºÂíŽBÌõ3cü·AbM gðÕÚܼ^×ÛfÄX‰:ò�3­TÞ Ç,•Š“–¦RJŠ:G ªÒWE½N Œ3/îC�ý1¤¼mêHKxŠŽô³£¢@®U{¬;îi̳MµÏ sm4ZñÒv6ô¦ºMy4ô¦»Þ©a�Ï‹ñ*Ø`ÂßçX+ð”"¶ÛÖ6,E;2,Ãŧ£%WìqãÉ•ýÀÌìÞõ7v °ÃlÓ;jììúmºcÇpmÃ*Ø1ÔÎ; þJþ›0Y­)j�×øš‚ôš†Ò¼fg¯Û�Šý‘Ÿƒüq3Ç íNÃ5â²üø€KšAzf– ‡}» Þ!¶q'•Tv2³�Ž¡<l?šåî>šq“ÔùöœuhÀ@J=eCczÃÔ*¢‘q‚¢$¾!XF*-q ËÄ[«e*xa×�«9öæuåÖrøÝàw¬ð72:Z¦ºŸ¸ƒ~m¢þ&MéW]=Ò'O NZ¦^Ñqât€U¦­Pø×C#ªÊòÜë�ÌG¯Óð°ß‹¡Ô÷~2Ù÷ˆ å½Ä(lmµHôÜò´ùߘlG'x{.Û¼5lö D ;J¥"ì§´)å~òFÀ`)W>4÷ð€Ã'"¿× s±Ãö"vh£pz)ˆ3R°Û)Ú"Å3o)˜.Ü^¡�‘â u©T½¸9+Ü`}S1â^ •[´ñL;ûÆž¸*ÝN-~t_•z•#Mg¬ Vï|Õo¥}idá `‚|~’ÆMx^k ÌU²p6}Õì°Ê‹2õ2�”Zèö€Ëµ¨+Ö¸1'�*Ã�¥�#"÷‹•¥GÌA˜yãK*J5}èK;l±´ÃvK[éÿ\yoô@††tZ„¤9îGõïßHRÕžÔ•´Ü"ÂWFW®¸+mÑzƒ´Xêð¢J²/S̲QY RM^CôD]ÒŠÜ­ªIs°“EQjüí„”ŽŠâtͱ’~è,„ÒÝDUöA- )DÁÂyA¼ ¥i^Íí+_Æ]nÞ[M/¥ã¡õ®j—*•×?r-®þØßîÄ}®xÁ{J¯Ÿ+^<™Š„Ô?sÊàyÿƒ£*–xª‡Mý£…à�9Õ_sw+JHX�SߨöÀWפͮæÑ6~X±ñßGˆ'²÷¶ÍX14Ëm?ŸNÆò&³v×à[l½Ë�ïµÊ,ÚËŠ÷QWú¥ˆÎèQ"z&È\pÏEȵ/žøüI±ú,^‘ßè;û6®æ°ª‰/OæÚå$Aé|vk_D§óE[‹ßÎï° ¾Û2ø]—¡a|± ¾² uÚ”a¯ÊÙ«Æ;´ô¾Î7§%ÑœîUK.heík.HPeköU7΢ȢôW£_mÒKcWC·y³ÁÓÊ™ÝØÍw}Q¬ú7½‚*±xÍ}膵éã·ÿŒßþ+óö_™k_ŠèoR2[Zî™Ëy )÷ûLS¨¼”,Z–© úB3û›ÑM 4t�%T רIÖ<„Îo]†èÐWõŒF×–«."׫Î8Š7ó¶°ê±™[!ØÁVv°V‚ý-Rꄹ‚OòËgÈ» RÕ‰š¤Óî`¿ éct¤ÓÝÛÍt8:Ò„é1Þ6º¶ƒîƒ­ û Ý?±0�%pk%IôÐ-NÃɲ»YP€©9ß�ñ,ž¤`n‰R†[¡”áZ)…ãååõd}tš£©ÇÏîz(Kê$DÓù�Ã�ܳ¤Ø°¤è‚ÿÃíàÿp+ø?¬Æÿ닟.v¾y8Ãé�7‹ñ˜´˜î ² ¶u®Ô™…7©£}{á¦c«}CÍšóc^1Ö½ÛRîÆï°%b�^T„>z\P$“f°—dË*g òâ%$ÿÞ¸Ü_ûLµÏTÛ�j‚^á˜^¡'2ȹ_Ç4`G3Ï&ý³æÖ„h‡[¢�íH®ÃµJ.Isóû¢!¾³“ã?�@4 i囹UûUŸyþvy~|?ܾnß+ð½»±±RRÅ“¨HÈËY*'ÊhŒó;Œc$®,+"ø+"å6ŠÖ gÆÄ#ò›vŽŒ‡Å»¡rŽl‰³ÛÀÉjÇ3«ïó@Vú€"·™„�†²‰Rd�b8 ÿk¿$®Õ ]×ܱޡÀóf\ëz{1efÕù-ƒ¾Z/_’Ëy“¼F©qµgÅñÛL ,NßÞlèðÍ,ä1s Í´×8Œƒ.N:µ‹ØŒaŸšä™Rn¥GoÖ÷ Ññ¯)î¼fy$E$Sõn&)É’“äF$ŽU"‡�©Ð­^¤�(TØ-2—S¡Cü› n»E®·u4�¤šîØrùìp}$uHø”ðPi …1ßÒ>=Ìì¡“�DWå‘&›~k¬�Ó¶×´¿ëf]C7Öe ÕzöwåCC±¿àûæˆoò Å«_#³:RöÓ#Ñáã�¨#ê¸ná5„( w4,‰£%ù !/uHÏ�2�R?Ê:kŠTG"Tý°ÐIþðÿN'9|$®‘°;D¿¸Øô­îŒîtet…¶]÷™ÖY†§¼�äOP wpg†zcF†’4 vØíôÌ�Û­u,Ð]¯70}ß•ïàÙöþG²½O ¾„^To}ÎxðýZÙ]Lä¶RP3™1‘>ILn+'-cu"ÿ³9ühæ°HT5©cÓVò:¼7.*÷F¬q³¸­ª/™É—^˜aJ=¿±¾´T†i=Iºô(é)Uíê÷°£-ÒÅlÚÒv±–[+ó²ù ¶±²¼;oYzá'³ù"§¡J08\{±UFu�Ê04R·Íè�˜ïBˆN|àÿ— UgsèÝ;tB ( TxÎ}ò´õ2ÿ´• èõÎ2xœ­W]L[eRÊ8�ü:þ_+-ž•‚²Äñ?Àd¬àfldËéiû•žpè�sN d#„%îb‰ÉØëÂ…^¸DMLLܦɌɲq©Æ¨WÆdš]y¡YœÑgÜüÎç´@Ë…½é9ï÷~ïßóþ�›Ol¿¾~í‚-’ŠÇoÇÞ¡^yÀ¢?ÑLQ‡¸d”OÅ0ê‘d‘KÎÀ…]%ÆylÖ³Bw›É×<_"çä2BííèÄÔ¹3h^dgæØchãy°U½dró\$ÂJ¸]ÿWtš‡)™ãá”í8‹UëàGܯ´Ãt«ž�\… ½Z{i.Á#{ |R´¿Q³‘I°É�™E^d¤(NbF£Ã%êUx@µh÷~£êŠpR&dÊA࣪õö¢aVfÑzE÷:²Ã{¥Å…mh ÂŽªMB«„Òœ(§XÍa9!Ä$xÛáÑäm:^6Ìí6̬…Éý­†™n/½Ó¬›È%9Ù»ÈÏɳºqŒŒÚt3;|�ú#3/‹¾n ‘Ÿ…‚z‘å*\*ÓUÜrVÃý²Ãº{e[ÔbyÔçÓåÇ ¢ n•—ÝØKUñÊOãyîªiQãÂðœ$“艴ÉÁXô’ÚFøÁ)�Ò­‹¿vqKõ…º¨ÊââÈ;~&Dî^džéSÓƒp»µjB?J%y!:ëÝ™Ñ"å·;‰š¢òzGmC6SF’Ünm‚&WÉX.&�šb8‡ê ©w]ƒ6#¼¥™á…Š;Õ”8ÏŒ×�MÂ@—cU—)aÙ«êÙ=´H¤^SQ™ž£¶¬PYÈŽÏv,*-‘ˆå”˜„ÖéÚ³–½`‘ÆÓYv¡‹{ÃÐBP5X¤>‹4¸ÿüëX3ì:Õ»e\>¤M,r¹?¿�ß—íã˜Çs$,3°Õé{¨% dæŽÔý„lðoït‹Â–”°ÚF¬,³ÑŽ!Y@aóX1Vä‡aíäaø»¯ûQf#¾Ú®4Ó$p³]ÏA<Ø,‚ñ�Êé|qÒ‹'›ŽzJ_ BtžŒ�ÍÎj`ê‹<;t™L¶¼3wjćZr3”K.¸7T‘o=jðì­½ÀÍ^Æ'¨O?Se#oðÏ-î‡×F]�z:ÝTf×Ý¡Ò3+ÊÏVÁ;£5…äªúüÕh]EÀluZΑRR˜rÔyçbr>=âÎÁ”ÀÜLB&¾ž½¡J–IZ¬Ìê–"N"…�æb8F#W$'0šdNH¢;?�É¥ENNpIå„BmÈÙðåp><ÙÁð¾>ï�–}Š4“y‹Å8Rt)ù‰„v*"<²Î¼†à›ù‚?­dž§&¶Ç“Ñ)¨§W÷‰Ái¥-¨—– >xN×L)ï=9„ê1ߣìe«ìVå=·l*X?6ý'‹!¶@¯f§¬9%üSÏ“D!=T�ñŽ…�fùö£a¬5)K!®²+’®1HŒ N"73ƒEÒ®¼ œŒb•+Êòv>ÂFg}ðx¡?ÿú‚oÓ/n°*«ê 2½½Û”K�ÛFÙ]JA¼Χçëµp7ÅŒ-GмÀ)ÛRwIõŠ1Ù›CXÖ^£B2Îͤ×£)QD¿’ši�‹¡¬u̲Ï>H7ìâg›é§1|t˜Ì½Ä×½±^XàT¦ ¦Ø#Û±:à2rĸ®1‘NºBQímJ—9C¶\4ï®N£µ}v-ý`míÜ»|T«š§—W Úy3�d’L&­híþxˆ™ M0Cƒ¡&42>qb|$èõÑÔ ér$T’Œ"£ P —³V&�NÀ2…F p¥´Jc º­1={ã‡öƒœ`Óœ�•ôq<«)¸á»?˜îU_ÿ«˜Ÿìe[{ÏÝZ8õu½S§«ú$Øè  \pmÖ֤޻V�‘¬‹n Ój‡$±V0j>=‡ÓäÚEä_"]4ò/“ÿŽZÑz6ù&K‘È$EEVJ�šŠ "I5ëªhi±~Xû¥O©�µ¦‚ƒ“D�‡¨ðùôI’wÍ$Ú ÚW!%ù,kõ¨oSuàû·\ð䛯}[[ÑMä4ŠXYyu�i‹"˜»Qýa™j–3Û,ò �½E¼¢cð³ƒ[~x³/XŒŽ‰Â¼×jH»~`næ8#·GÈ "Ù ø£ä£ñ(ùb´ýÓºlƒ°ºxœíksÚHò»E¯¯ÊInë>�K*ØÆ»TXœãWêêJ%ÄS’VûRþï×3š3z`ðs“  ŒzúÝ=Ý=¸yïë‡Í½æááÂÇõLÆßrqE,ºAxÑù‚ño'ê ÔÜ:¼}ýöðyA=†ðÉ!1‰ v)Þ?ΓɄXî¢nnÃ-‡ß‚`Ž”z½I¤G]âÇd ‰?ElA :.¾É' øB¢˜>¼µ^C�ìËGûõwÅM�ÀÒ¹?`�ÄqЄ@äÚ%!êƒ,C�:¾K`EÙBБX,Žã«ÄL˜ƒàpÙ!˜é€à0Éô‚±0n5›«ÕÊr·VÍ›^ 7{Ý“NØ9BŽåޱï‘8†ˆü™Ð¥�Ü€"G®3A>=gAÎ<"øŒœãUDõç ˆƒ[9áh¦4fE; Sü¡Ø:ªÌña¿=„îpŽÛÃî°Á‘\tG¿Ÿ�GpÑ ÚýQ·3„óœœ÷O»£îy¿�A»ÿ>uû§ ¨.¤C®ÃˆK€lR®J2zb°0 R–â�¸tF]ÍŸ'ΜÀ<¸"‘�AH¢%�¹IcdpÊÑxtI™ÃÄRA.N¨¹·÷7ê»^2EóK?]ìk‹ÿô¨Ÿ\7©&ìˆ\Ÿ¹Á”ÄÖ⃴tØÂ\áêòçbmJfÔ'pÑØãaÇ÷‡£öq¯£¯¼¨ÉnB´0~²c䎈�œåYq£Æò|v=IfÈ2@³ Ý‹¯c#g¾tZpIH¨A ¸l륄QÏ\ñèdâĤ)ß…*š<5À)q='’ª|PjØ‹¹E\¸ èì•·d—vªyÛEǽ$Óš±ÈàP~ Y„‘©öO‚ÀËí'Y¢…ì0 >#‘\®í¾R@�H3`5„‹¢10^I`ã»ÉG‰ t?�dlþ7åÝ"q9ÛÈB;Gp¨>­9(§s$­­IyĹ""eÐâ¹äÆæ+ä£H/A<?H„—à _hÄǃ%a‹˜Hj*¤ô_Å q‚ÐMãØâ(¤ ºY•ͯ–ܪ9¯ÁExß„Z,Ó…p}Okìݾۚó*%ƒEƒ5%žäºb‚Û£sÏÙ)÷Œ\i8z¬G(Â×àŸú”mò²½Tþã¡Ý;ƒQ­?îõà—÷`x"Ñv¡–r8ëßÀJõ©)—«Œï¤3¨ý¢àg3´¬àê`�ðèƒð3ù¸^—VY§ Š9ŸÖ œñWDXù0s¼˜¤«·â¯N#ÀÚÆN ™îiú1¹ÆpžÖ2Ô•|Z®FaƒF¤.U‘Râg„�îé—e‰ÄŽéÜw¼�éZ"Æ–‘‹Ì,LFy |wÆ•*9DÖ½C³«b“2ˆôLëè6·�‘%éÇÌyš³U‡iåYšb<Ô¾iA])BÀT¡O\Ñb5—$Ö£2êÑ™C¢¥ÑŠÍ*6uå+¦žÄâÈ-&‘»S¢æâX¨ç½¼¶…?cekDf€>ÑÓqTå.é.Þ�©ÍKo™ó¹j�¼ž=I|/p/k[ìWêxåÀ胜3ý0¨â6Âä<„Ý*åüV@ç6BXÀÇ­>5Ÿ)|b¶KWY­¬8„à É+á+,«B4³'–S[Ø0Wäà¦�nv‹E0vÉeû+ˆ}XÑ)[ ælÏúUNKnÑõí^çlT7ëöôøXQæ.Êöó) Šps\ôþ}²�Ç£Ñyß>=¿è·6Õ(oJFÁTa†$"Î¥d«Hpüù.rZûôNzÝ“Oå$µ’ýnÌÓ4t[ù‡Ú�òÔݧÏi€(Hc1ûq�‘\�ôfAŽå“pŠ ƒ+'ŽGÿ+òLUêxèlQK9¹Ù߃¢É T§�½ÂÅ“YEôOc•�ǰ/j•ÒÆë¹­2$LÚ" £ Œ(×=#×¥¥“Ó]æÓZ ¾å€G©†ú1�88Ø”Ëf€¢ÊúõªìˆØöU\¾îtòw•ÇY17ö)C{Åì×C%D£íbx2bOñsÜÔ&±-–ÑÈâ½xG’.sÍÙR€{m²ƒ„qû︙w/[„«�ð{zø‰«Aš%ãa\¸P(ùSÄkf ìIüïÿdwßàMöMõí7Ê” · m‡¡µÁXܯ´VºO=,ÝÏ—Û%ºG ‹inÔŽì“ö°c;ýæ»^­.î;L ¡çç§—j=M—ϰ3Jムuÿ‹ TiÖ0 âK‚ÇÆ‚ˆ«ìW²dÛ^€"c!ÔŽFÐÕàíѨ;j�ÏÝñûÁù> ‡Gýq·3‚ÁŽý“î¸;èã¯S8ê�ÝþI 8Š ñð›EL ™>‰’{Jn#Îs$L"M’Xpןø.²N—lÊa]ñ8DŽ`Áã¹/H¥ ôLàÏ}ɤ*ñEˆöwvþâ‡n°ôPýÊJg5kèï~(åí‚‹öìµ5øLð}ó·z™2?%¤wEAi·Ð˜EÊEtC”dn’ñX¸OÎ 'Ü XlØû.g%o…JŠ'¶)‘ÎK_jÏ¥%¨ÿ¥+Á¹æòÒ¡aGÂ×Àm>CK % ABÂ!,Xȵ•Öèmj ÷Ù±DÅé™—)¸ú¶²®üX.ñïœËY„6¬ìÚШö<ð&惀|Žt8²€8¿šË}xöøË�Œ™Hø$U²*?DÛ›+!§V¸šµE¶ñ”Æ,ÇV X¬À§"‰®™—ºß$ŽæÜJyÔPËd;’·A‚)¡ U³ÒˆVµ[à®fð¯-¨ Ô_ ~Œ mx7 %·`†²&”£§(� jMdDwndÌtb¥ÕR_= }H/s]Œ$)ô áxù`%tñrç\X¢ŸÿòÂÉ›·Ö…VŽ@„ÿ;¯W˜rÓ(YÆ-5‹�0xr–ýœqJT˜T ¶«È÷r˜+ æY/˜24“¯ˆ¤‘�‰/Š}œV6SàÁš©ç<·©YT < MydÖS/Z›¡µÁ šåÁõx•ö—Úìêm†‘Çú@}K PRm.½<ð’¬p5G·�„_&Ù÷×L*übt YDšë”�O�ÿëßðÊÄÜ·£³žsÒ;'Ýã±f„>µ�,ÆÈYk:19+|¹…U ­Kÿ¦ôJ�SÏ)Ô�Ví¨ƒ)¾ßéÕÊ‘ÈG?mŠÕ&‘(Åh–m«Ñ±¹ì�8ž²Ý¶ÝG×ûëö”a1N¶ŠÆ|©òÞ�Ú• æýÜPG¦Ô¹YD1ÕVšÀfs“Éaï±>0ïóÖ/7æX/h˳U‘Œš¥±ÔµôÎU§©í�Ünçëš„r¢é”{%ÉÈ­?GËGˆ&õ,Ð5t6ñ'PïŸ÷z$÷t+h¹ŒC  ½,™Ú{]$‘U2TܧYxe1a2'QðS½ýЗ™Óg7’dÂVnÁªŸòÓZOÑÒÖŽÝhk-6‰ŽVŠHV•ÄtWQdQè¾ò…�NVÏØ4³õ æ:Z5lù¬MDÉ'Ÿ‚*Ø-¥žÇṨF�f­"­2÷�ÔbžB#¯¿9�áp0lAí”ùTih×,êA¯2¿‘<ôê+X‚ÝÊÀÝ(:E¡èF÷ÈÛŽÁRE~b@™‘ç‘Z`KÒ³éÙÉa^Š=CQÍQ[O÷3ä&“NH0’MV`Ùùø1ZÕ'Š^Qñ™PšÕ=XD;œ¹³"#X_¯*�Z`YQêÛ‰† �N‹–bT .:f6ó9ó˪{ˆúî¥Âò† zXåê\l°§¸Ã¢ÒlšÄœÛ^ùLDÙv~Í—×ë*ûmSôÊÕºž×«³ïå¬NâÍèªJ‚°›nÝ{m’˜¡â/¾Âx’™5–•^®M;�Y}ôÈÖÔÉÅš�õ¹(SyˆÛÒÄ6;WÅÜüg ˜0_=I€¡8J÷†Nçä]ÇÎìò®\õYè΢¾}KUœnîuNÇ[í.G,R·•cœ æ^Þ³´['+ eV¾XŠÙfD¢ ‹9çRGI«³ˆéOE‚od¬‰Ê�ÉçfLdtçüð Ï$ÂÇõ½U1’”ÖNtœúºFÁà­™­Jík h ãÉ GcØ&la,N3)´$‹äm(Mî¨&ûÒku8+Êü­hÎPÊ5÷và.i¹ô"—P×�žÆC/?¤åb®N‡œn—’‹´¥þL¨¶¢£.`�ˆè rîKxîK.XÑñØÒÆ´5¦:ÓÖoÝšNö¡¨‹±Gì‰;Îyÿ|Ô9qœb{¼0c:å›z=ºŽýñQ·ß:ƒÓ̵3¼¹Í-È×Ü÷½¶¡¶Þ]Vf¬ìM¤ávï{”?£õðóÅpÅ ú6z�IÖ®µ2öÊ'÷ƒ•ÇmuK4•ôåêȃâöÔŠr/f×Ö¥Óg®í¸ó†1,eä-ðy»x=ÖN“âCïOl[�5ÖUÊ”Cº<á±eÂæçg`õ"ÓËÒôjñ‘ŵ/ÝÔ3ÆKU:÷¦<ñ:—a›+ñ+ƇÝwïÍ„å Ÿ{Çœ·ƒ_�OX.u��z&„ä7ã¡£ æÛÁx<ø¸èûÁ°ûO2¹¬Ç'lÈÊpqz„Ë0\d²Á˜q]4ˆcø¹v6ìÞüò"_[f×Zw´QB³{€h8ýH"ÙôÅK¯WײñƒìíÔ‘]Úäb¤Êg\òX¨r+e¶ÓþÇÓ]n&Fš´Ab²{ò‘RÃsÊ¥ãùøU=\¡eÅæç¶�ƧðˆJn÷^›ÓôÚ33á~¹ �ǽcrT‰ £ñÇ£_ëtÑUû?â³*ü.»>)¥fe9ìšJ.±¹ó�ß,¸+¹—¯@ìÚFYÐÁÍÆÂn³%”­à.wMI×ð?¬|ëbuÆ$ ¢Â-MãF\ ý¾PYSú¡ˤxßì¬.å ïÅÖ¾çÊ¿åÊ‹�RÈÝF¨v TÏàÑÞëÒ ájô¹·½+e%!¥¥›§'ÕQzAŒŠªvª€]0WÁº"WêbH2Õ§ôÜË»?@I+�ü¶6VôÚ³£SÚ ¶×'Å|þâ}e:mBž‡>&Üôï·MÒ~ïHÍû,[àj˜dGÉSÆ&Œ©åBE„(½}KvÑlá¨žŽ ëYâWxŽ1-�šÞ"– ‚»äE¡3îŒÆÎñѨS|T˜§�Ü"w�Œê�¬ Œ:æ:C¿~TOúk­t›§œ�îC�»>‰Ó%O¢N­—¯û)¨Ü¬*t*#‘Šž>*ÜC¡tÊÒE;-ú Õ¢^bZœ«nú“÷lÔ ]ïPåW‚»Hir{o<'Q٧ΰ{úÅéü#;÷&üg”çkÁoÕôçS¡Ý”«¸»}Ïïƒ&W4<¶ô©ù$\lrUáO¯­F]5¶>¢K±¨‡e¹'Cû©xš–ÉúO41ÿ‡ìÁq’œï¿Ä‰N²ðxœí=ksã6’ßý+�¹ÊDöÊòX›Ë]yn¼qf4‰+Žœµä¸RWW -Bk(RKR~\vþûuãA<¾dÉ™ì�+[$Ðh4ú…F£ut@Þlë‡íì‘òí,)Iâ8ëMá3{4�W�I0_døé­ü@:Ó}ÒÕÿ†ü¼Â`µ"?z4¥ é|`¿¿�¯ooioºØ7»A—¯É÷q<‡q..ÞŠA.‚)�Rê“uäŒlAÉÙÊ›Â/ñ¦K~¡IÄé÷^‘6x!^½Ø� ã5Yz�$Š3²N)ÀR¦C¦t•‘ "Óx¹ /šRrd 6Ž€ÒC¿ ñmæAs�àÜI<Ó/H/²l•žÝßß÷<†m/NæG!o—]œ¿ GƒCÀXô¸ŽBš¦$¡ÿX Ìöö‘x+ÀhêÝž¡wOâ„xó„»,FŒï“ ¢y—¤ñ,»÷Š`ü Í’àv�“øÁ´õ@2/"/ÎFä|ô‚|w6:uÈÍùø‡Ëë1¹9»º:ŽÏ#ryEÞ^ß��Ï/‡ðé=9þJ~<¾ë ä‚qèÃ*Áš’’úŒn#J f1G)]Ñi0 ¦0µh¾öæ”Ìã;šD0#²¢É2HqIS@ÐG0a° 2/c� óêí›îý[Mõ«Ï˜tñB{ô_ap{ë¥ôHüî-Nµ—iæÃsóÙ½÷˜ù‡À¯€Öá*‰³x‡¬‘OgADÉÍÅÕäz4˜\Gã³ï.zï09ÊW°Òð×$^g«ufÁw´˜„°jeÃ&̱G*ÌŠ8hd˜Æò*M�êq1dôGbø,ñ¦ìæ �f°D!5g4E „‘ó›_¯É*ñæKï„| t¥· ìžkxd>¹"?¾·žÅɇ$‡uÞž]O3‚ø@oc/ññ� ½£Qöztè<òŽNC/\ò$�‡J�Œ€á¨qä)BCº„{ØD 4¹—Ù‡ ¾ždä÷=?Øy,ú|•’|NÈqè§$Ÿco~ºN° ƒŒ 8Ô¼'€–?ù³×ùX—À¡¨ 2‘» ÉÖ^H–4[Ä›©�[ b‡ÚZø(aÁÜIÌlr·Ìò±bPË“t Ò¨¿VÃ\–¢:º¥¨X™ÖbÂ'�këv?í‡òΰv9¸;Í»ó‚л5o!ˈ͖>Í‘å5p7 ÊôR¾x ü€Ê 4 I§û9ÜÛ8‰ó‡7Ì'Í~¾¼ø –7gŠÑ"¾�P©fñ °—M¿M@Nu.99«=Éek²Ê’žèàZ–LÃèÇÍß@GmÒo9`‰Ó ùUiZ¹Äu廽’G0„‘Í '5è�amÜ °*¸`:ggÚ=f­ø~›‚-àƒäí�~Å>Uk‘Nü0€Æ¬‚9ô×úŒù’zÓ,¸£ª‘5/mHc^Ø[{Yœ(Ü„™­‚¢>9á\® …¹~ÊþR¤ôîÞ-o¤ 4kFx3"Õ²9SgóIXµÉ‘牄5Œï™3üÊ…VgÏ^ŽLž¤k�ŽK¶�i…g6Y=`à¥8ñaüUDÐh @®^ÕDÅëÉ2FÅÎ妻¤w¯÷>¾æªZ®SÊÊSÞ…0½šÂÖ¦*楔ö9hÇ)(ý”»ˆâ}€n™ç?2N¦ÌùðÈ,ñ–MVœµs,ñw°B‡a}PòQÔq–°‡Œ”8ñ¢)¹‹ß0C)8°ÓÅ$‹[wŠ�s`Ý!90„<é²ñh´^R0»TuO;–(£„#L.ëjXì`í× áƒ·‘Ä�ö„Z×0}oÌAªSÁlà gqÔqªâM÷òQÀ&§’³xÏœMŒÏÆ,k‘ð‚´) š4êÝù¸\"‹Ïâ“»\¬Ó}6ŽQñ¹µNF�éƒõqéÑN™$È¿l¶ð½Ìs0ÞÁ¤ #r�:M�ÙCIeg(¾¶,n $8~ã‘ô'ª]ýø‚ÀLm=‰¢¥\ŽýIJASe�ᦢ\ >×ZO�Y–_\þ´ÜÄæ¾8¸�ÌQ&°IÓik…Ýaˆê:Âô¶]"oÈa™z¦R‚çõš«köD‘¯é‡MD/SÞKú™�º¹)>KæŒ>A†6!NЖzñ`ÿ ‚ ¾ÿÇ…)ÛHå+7ñš²ÏWtFÁO)M„Ây ©ŸƒÓŒ™`4œð sMì‡Ìœ–S]—€ÇW&po=Ìm5ß Vq‚‘ÎMnRÅ•nëš¾�ö©Dã±@WCìO§Þ7ýB·[¸¿÷{…sÌÆó9õ'S/„�Oç¸KÒài<ëèö÷ù3Ò^_\ ±%ˆ}pÚ²u|Á›ÉW‡§N”`Ð*Võ.ºÛ¬«Ã=שÈgë€ßÍ_›¾þÔÚ$ÕçÙ¨£°1´sMq_¤µhÒ`åd­fcÓÿãž9âoˆÁHúØè ;DA6‘;LEUùóRáŸ÷é[cžf ¥ûúÐÍgèœ%Ÿ)û6ä)m2©Ž õ!åðãPñÞiFΉIÏuakº¦û¶L¹ãF¹dÈg<¥¯Áxªm‰ÍÜ/ˆ¨3r‘㢞Úª ˆHUï>ð³E×ñbAñø¢K^=Ìf¯à§ÿªR¢œmE¨TgÅTžïKU“ºk�Ÿ£'»×MAŸ÷Ç0/âA›Üsœ¤Á<òB §"Úê�•® ñÊPŠ[ýUla[m56CÈmsÒ ”� ˆònù¾@¼¶ã:âaCüÔ´�ð•–ÞjÕ| Ñiµë¶ŽÀu–`èõ ³Ô-œ×µðÇö æü;DÏí—˜^ ãu?HmvoÏ'’Ôf|é ;ç„¥.nŽ/ÄMŠ.†Q7Æ­••Ò7 Å0^QåÔ«�m¨ [‡–ÁxíÐýeÖ£àÊØd˜›»my%¹›x[%�‚J ®¹9�×ÂÄfàéæg’ç:Ô9(<¬D¡" Še'³ ƒ†�¢®¶úñÃƧGþi  þÚ‡>hÃÉ2¥S=@+G�òO�ùä—ôV³SAßWŒÕ{€Æ]Ò{„ß�ð;>+1óžÀè?€ý;›½¥$¢ÏGQ_tQ“° Kà]ÆL©ª%ž‘õÊ÷0²ªƒÒÓ„J¼-Qu?x ç<®�x�;Hy>œ'b­ä/lR]j5¥ëDˆ/K¦[ót-–sG”ÜÓ0亯WÃ*¶…h¸:eí¹Åp±¿OghrÅààu‡ñ¼óÝhrsv5<~_½¸ÂT’/W'ä:âDŸ0Å»ÿ/Í„ÂÂÏ/ ýs%MlÍÒ-*¸ânæ…)-ÄоHîL7Qîµè²Å^Ë’µ�ÆÎëîYý(ûàÓmâ6?ò4}F7*Úò_åYë3ì\1ä³Í“A-�Š30kWã<¦e÷œÓŒ�Ñ1û›q¡-„¦xÙÝ= ¨¦Á#ÓìDzVωPâ)UgÚ1ü·z® ™Úð¯Öébr F¥ñŽŸ�-N3gI¼Ô–¿@1ìëSÀ.ÔXÞèÙÕÄó—Ž¥*øEÆè–Ç ì€i1IKWxuÉ/JéÚ)-LÑÙÁùZýY•~añ–�Ê»:Œk#óm]tQ|Ƨ¶k)¨]mI%©Æ®Ó––ðºÜÈ'ÂK >Å}ü±MÇ'E]É]Z<ž ª‹o�Ø2ÚTŽ gªxDê¢ãÎפ{ êùú mEhì¼%àaE:Éñ›…ÏjVï9ï“ÐK›ãï¢Hjõ¹yÂÕ˜-¢nŸ a�ŸÖ)†.`Ó4ëŽéæš”Èmœ ¥zÕ•Àpã„�¾¿b¡–1£™sá:&§ošbòÏ*N¥äÕ §Öé 5 ]®2yb*~�²hØáíïËŒ Ãïs[+Í4§šé´Ëcd£gàC¥�"úМ[ù0�ªÔr6vÛ7÷Ñ…cÄâÁ´ãð”MÔRÊ•*´D�\£�¯Ë�“qVà¥Ènî>+‹­zÄët·löipε=—I]Ü–Ëähÿ¹Lúm6 Fç`¶Ž¦û�ÊÌY3kÖ‘H+Öá�ºÝP‚ƒvIž†û q'L�hºqr§¼ëb©fÅ/�é|^Œ0Š�¢ò;&¼ ÞÉ‘'#_®ÊÈ|ý2XM̰º3žî•6$æ@×9úÖÅ�° _t_Ü:¥()&.4ÎÌj»÷jÄQȉ„¸mÅ»*×Ù±˜À9oFÞ ð rÄ“Œ¶#I_{R"çxª(ú±ò�¿( _Q °¡ÎõF F�ÐNé\]]^�ì^@k² ¡%±]¼þˆ~3¿ÿÈé‚MÚ s—4—l›è Ö:JdF»fƒŠ@\$€?ŸèXú¢ö Cå³åî'é+=—õ™å†½í]^M~¢ÀL[3–-äб©|jð®ÖŽVÚJž,µ³øŽF¼b¶sët¨’c«gáoë&VƒètñêŽóæŽÞ§pÇqG’§öÎþÿFÓü=×è¸M(EŠõ*b€©&…‡\…›ÙSÊ_öÂp£Ð­ã&œ¹…æÎ5lÐÀ�y!éðËjû›^PÚÉÕ$Q€hÄöU”Õ›øÍÐÝ¿‘µsÃÐ4fn¨B}Ÿ][¯COý´7×®£ ¶§¿ ý§/9Ñ,¶A;ƒ¹úô°bUÈÕ�X¼$žJè-7[ÎÇ o…øŸŠŠ¶*�ΜK—•ž¥´ˆ9í")+Œ€þÅãxžÜSˆbœ�w<8ÍU�Y~¤A!ƒÁ•гŽM£W–+ÄçÅerçùvi�þY—�<ºl´2[«b!;æ ±ãQ±&k-HÞ,£Ù"*QÉÒÆÿ ;ÞÍR 9=_®d}­à‘qÑýäÄÌ¿ì‘X�̘:@˜ìÌ ä[œ®*â”{±ày…!.ý=Æð=]0Ŭê8 ±d ÂÊDµ&ÕÝr 4¿^{j—î�}´í!„.¦pŽˆH ìíåžðTh¤îb5Xõ·—ÃñÙùpp5¹|¯ψ@èÝ»¤d¡g8B0ÊØ–§žÞЯ`Aï=™$΋ðDò.~G‡"ˆ Ã£’ïpÁUnia»ì¾–Ú«Ê 6á(äÂlw–ÜÎGU0©2ûÐ…MEœÈhW†h~†ý4ça¶Hâõ|ÑÓ÷"æó‰h Ì�Ìu«`ñÉiˆòTÏ ]¡¦²�¾xžš@ŸUGÕa¦ ;ÈP‘¼;RĪ©©GøÏ¢JÌ"5¹2‘�?M…⮊µm…òÜE½>«”&*ŵ*.Bäµ´”ÚöLŽmTL5ÊNsBÆqLn=T /#5M(e—Œ¤Üâ=©\øÄø”¤X”ÀÃÛ�i⥠êÿm¿G†1Yb�\&9¬Nê uDÊ®^õzϫظŽJIi¹Ñ“Yot L4Çû‹ìºÎÚ(Õ‰ÐxÓ”_=½§ZýsØ5Ë{cÆ Y’XTmÂj²°�FXeaU½œöXƒsGUwSÉoc-c@¶ÔL]ÅK16©z…IJçü@Ën†ÕÛø¡ã®ÅË<14žõ`Fuv™qQÑ &ób§�yÉïéçÇE$ª^>î02•żbpYÉY�Úe­Ió¬«0Ž� ¢Pk-§ž*ºV¬´›V{²¤›jqxªIj1\Ö¾¢�Zf-íJBz;³Roe5½H¦£D�‚º3{לŚ�L®‡×£Á»É¤)·i=”Ý€uBZ|શS·¦b©TI£0êŽVJšs†Ê ¹F“ µbÚÛ.ÅZXÛ:¶Y‚†³ d—8—À´œ�â&Å^%¥ ¤¾§îËu‚G—x÷Q—UÇËçÎËïî�²»"‰kÔ&•é*:ï®v‰81¶Âv�ݼj­¸Èïeä7uŒð[Ϲ[ÜJ±^óÜŠCT ƉTy\U›»qÿXFDWÑnêhžHŽ–ZUñð´:ß#oæÈ<ÙÙÂ�h–ª’ºl•Ña—Ë,¾FÃ<ýÚYÕäQ>uÍK’išÜi*9į’Cè‰6×Q�±OøŠ‘h£—òFˆÂcÌÍ:Ìš=ê³ß«BpöX;ïlØÃ:¥hÙ‹Ýêu÷a =Æê#SV1Å.Ÿ-{á[«rvþ<ýïÿÉS·~G×õ…I–]±ÈÇ®ÖC‘D¶VOÌ–&)dk«b·³’Án�Ïdk°¼ãÁhµ*äð2TûÌ^ƒÿ9#Q|hV22û¬²¶Ê2Üc׌ߔ(vjÿ^ÌGw¾+op\âI7û®òâìÿK>ÔF¼Î(2ŸY*O‹¶:4b Ñ¥%W8À�¯®ŽJwyʱÀùðT$—bï@:ì1fо§®ë1vÂè«Åç¼R/±8[çÏ<�ÆjÒ£å‡8i¡’D+¦m€q›ÊPU ^é“/gûþÙ¾ÿT¶ï·eûþÆöïÏ.Fò}¿5ߘ·ZHúUBÒ„ïí¨,.R­z¶ �Û£|S�ãdœþ­/Ý|úélîщ®¡äÉóŸV?Á¬†Iõ}ä0âÍÙWø¾b°SuSùnËLnŸW  ýMJý}z‹µmZi”j­¾…U*½_ÑÞ làZ×M‹ƒlïâ”YyûT¯’(ÕûrÛî«íyùîæÏ·›³²Fûv…±�o²5¶ò3ïÕ3�Ϻf¼xÂÖÙ ¶jÇK»Çá©ý½TŸ»ZBù©E8›a!å Vlæ4²*Ξ•]y%ÿc%-6sΊxçÙÜÓ)]e©ü&TÃì+š¤7ó M‚Ù£øÆNy©Éǯµç ¾AÊraYr1q^00¾^¢ô…qµ>ÿ†‡¯ûÚÕy^UýMÝ—,h=ôï€øú¯ýcþêãü[‡ZÕ–êeÙô`ýL÷ÈþÚŒ‡X8ê7'FÙWGl8ûŸ?9ý%&ŠÚ‹rÞ2(�w0¾¬Ó¤ƒ^§{¬îЭW‚ÙÄ—!d<ÉøqÓr�à&}-ÛÈoµøC¹Â¤nÝ—Y|Ú‹g+ØV…Xªu¬/äîV­jþW<¿áŸÌkB2§=ò>ˆ@*açD§X( v&òÛkÀ»÷)ûfº$A$§ǰôTþóYës¹QÅ´Kñ ,á9×XßûUÿë.Öðù?¾ùOø˜‚-G%rLŒ#~„§Ðáß¾(rvËöKè#yë,.Ë<·û’Ο èçÓ–ÅHzÕF•ooJ� , ¸¬v8_Q .uÚ�¶]ò58ðù×ò‚\<ù�ßK`Ávd=.Yó¥·cuÔÃq5û; f¿5ûuÔì×P³ÿ4jö›PSFÝû;Üþ{T÷ÆÞPO‰,«X^‚ÝrßrÇ&rSÛé8—‡}ÿ°{� \”»æÛ¢ÓY䥚au)+ö¸nØã÷¶.6\ÞÊA»?ÔW~ó† "“•qäÞtãÜXtnÿB$§í‚ °2xœ½Ûñ…±n’Þí¬ä=”Þ(D|ˆ|»?ÓŸôw kÇNËø£™^×±ve##“ÿ…�û#5¡TYÚ� ÔL-¨‚NŒ‡hœ™©J¦íÑ.hŠŽ¹…®¶özfЈû]ï€ï66m±ÝPV3^ÑP‚ð�MçñãèEM-#}C66 ŒѺ¡o(99Ç (Mᔜ,âÔ\ºk|,úpLg&'TUç»Ó?†l Íq®ðäî5øæ´Çò8�%à+óÉ-ì]_’ %_F›rÜ ©º¶*h]2°{ÎÓ°M]ÂV²¾– ²ºfIŠ&Ì Ÿ]ÌÌ-.ž_XbìÍúÜú¦j~-³ž·ŒäÌnb„䳃�ŒòH¶cð½O�Z139UUL‹áÆQ‰æTú=o辈ʦߖbÌCƒ¦ý‹ó‘ÃŽDÉj:ííGÏ&[ÀÌ�{ñòÒÜó—Ï/d^¼�°Qâ SÈGm Ù>Œ¾DtïÜò1O²Gø§ù¶̃_0ðî|ÿV°¼ˆPåh“9S™o¸ [M¹�ï46J–%e×2–n+œZOÂÞæž‘‡8¦`H¢øW­m+c²ì0êZ¶é!&3Sõ`U7 Á›VÝ~ïÏtä®õBÁìb索&*K‡8W•ø0QðÉ ]}ÕÏ ¹ØúZ[„33^ÅÉ{wg†OÚ$Ð$àÍ»ýÃ5Ï#¡N/ölMyíú2×Ϋ2»NTÓà7꟩Bƒ?O¬Lª°Búk˜‘ ½2~…|ˆí@�“GÛÉ`ÊS+E?äˆÞ é–VÝ·¥øš8÷’5R=å;æÝ^øÏ-F,ÅTð>µq­ë¦B2³ÏŒŒb;]ª �-ª,P`0¾à?ÀA¦êÚŽ*\ߢæ°ÓÉsèÃqñ [‘ÓqíªéÊb®Ê$þYkжŠ,Ï¥kq$rSbÈ ÷g—5ùD?|zé†-ZáLO®Q·ÐŠ®«ˆ[�‘0c—Ež –¡½ŠÈ‹oàpX&hLİUÃ×ö?Ç‘@®Ñ70MÀ“Ƥh’ªäˆ›°ÅgwC„xäxƪÀ¼òOî:ÊÀ·z»á;õ-§…=½˜k¦7ŒÌCûðQ¯ÓKZN.í%á<�”\ã¡ßËtWñK´ƒ‹Î‚TÆîhCF+²¬¡.­shSÁÅ4Î0‹ÔÚ•,¶�t:�Šë„h¤XÕ«lŒ¤§šJI;‚�ƒßo�ÑÒ O`O±ð2üNwïåÃÄx÷/+U&e¾_æ™2>G}¤—AåZrEZæÒüü,»¦ÞòØv)…ÊÞŒ‚4GÖ-¥ ¼*Ñò:«JES~�“4D›BhM6°ÏÂäfuÝÈ)V¦‰rJN‹s�–ö%röJ(óɈŒ—käŒD×¥€q¢ÔòÌ®ÏãuÒyƒ½Ü_‹¶Ò¾Ò߇¡/E#¬™ÀÊ8ÑÆ{Z—1ÞŠ3;$)Õ[žëÙr\&(/©¦lc >XÑL…özœ—¹üâAœØ%ÖdBÌ�àƒÍS¨B S £hzd&F»ãy=[4E¼©Ê�HŠÓÀWŠCÁRtBü“³�/ìÜ|¬Ù„ÝáÇ‘þ#–Q”ýɦ�\Úîj¯à>}9ÒFñ5pf¤f–\“b}ÿkio™VU¬Q”€‹‹Þ]´·éƒ¾–í©¶öíånøÞ�.8»<(¬i«#⇯%¹9Ä ¿=?£Û-[œ-{s&‰½8øð:òö1†|·ˆ3Ú g’ÙË�Ï-vD‚·La»è…w¾ê 2š·OvW?‰}Õmˆ(3ìÊ/<>Û¹à�«e\´KîÚ–…Ûè"¶pêÜ*uñXõî� ëõD¶Ã„’›’/™F¤cc8~¯)+Š+¬ÚóòdDdá!‡æ0]Ë;vîS´®JîUÃ4¹À\\¤\Ô¨J‚ô8vÚ,®Ñ1@V`ni‰K§Uh ’r|l<�¬*½GýǪBˆ'ÔÁŸú¥?ˆ£y;ç&ijÊ–²ÉDšF#Ç[¨SÍoFNrã½òþ>¸: ÓK¹=lˆv¼|Šì¿.7æ ”× ¤âø�“iÞéCô¥�$›€äì;2¥°®RtA×sÞ[DƒØ:6Ù;¦±Š^&øÏ3übÛ²ÐÙ2Gø¬C…bÚågN`—õj†®ð 7¯ÏqF_aˆ,CÒL‡ì½¬Ü+¢À{¸jš[ªz7êÂîòšTØ|æ©~ÂS-È9CÚdW’6Y´‘YS€9]“«jÏN_j«í<¾Ù´E�Æí^Be%U]‘²wx«Õ›!âÜlÿÍQ8šÌád"§dig­I²d,K}SC›«Så’œ--­:>ájyØÞä öRîÞi¬¼æ�Æ)ÖÄ`5nó7%á­»‘-KiĄ̊újç4æ–æ.¥Pôš&—Ö±ð1rJ/>ò:­8âX|¶!¬žcëÃÑòýûüvù‚tÇÑH —"r¾¨Rã+jJ3áÕ$ zÔ> gêëá_î´†òxEÆ–&õä'Ý[pvn}"E#tIeÓèvxª×m�î°X`_míX4.ûÉ@Ð n°AÙ *®[™å<Ã6lµKÓZ¶à[êÜzRUügû±-ïÚ’âGæ¹/DÕ,Á^ä¿Ø«4 ? Ä¯ã¼ tÔËnÑ*ìÅÿZCPmªåþ <šZ† Ú†Ø�ð(–i%A,¾ð`N­`×ñ þ£³ó”P¸±»ÜN26ìõ†ìÈûÛýmbyEÅý÷Û1X�ôÀþ ~úzw¹€è‰Aÿöax¼ÝؘB1 øIOW;¯"àdÈ>€™£G\±©ä¬µý¸&Bª]²}Ÿk?i Dn*Èß:Ý/L³…xž}˜!Exoõ>×ûÛƒðQt‘$‚ý)QƒàªI`IÆê85#k’éj�fA<“÷§µ°ï�"Žötµ~"�ïfÎÝK¶ÿûõ>²E´b‹Š*¼·Ýÿ•·Z„<÷å³,&ÑÜGÞ(á4êä8øäØ :<ðWú¨m¡›àQH.¤ðßðþÂÔ Ô³‹Ö÷·‡Ûò8¦$De�Ë™­9Ž4^á=âÏ¢stÌD{6EoÉ0M+Å|^6hX¥Tí)Êy®<ãºyý`_ÕÐ'ô�ñŒ�V˜c#vÍ? s»f½ýu§ÌJîXÐvc§í;Ö_9áJÈëGã>…TDåùÏ#*Ãôƒ\¥NÒ’¬ì*r!€n¤MLÒ!‹­:)¬*W®rN Ä“›oøñëý‡+¼Â(ÙòY‘�„4DÌIø®•΄nÛ*R F_Þ7¿µ4>é Qn)P#ŽÑKm;�i`i üäjŸxªB¢ ˆÓ™Zô¸Û¦cèÔl@ $öC‚%tа%U MùW�M¢S[”wÆRfXº„Ц‰áMùàdœÅò¸]Ù9Ž*/$É^ÑâÄ.Í ¡šBêfûfHŠŽý — ™‚Ò¯¶v’.� âÐLýÁWåÖñ8É öBÒ“�¾#IG:ð¦ôtCûK‘˜øÊh!¡óøÈ®Þ²º‘3i‰�‹ >]î: eñ¹ˆ*émÒï»[EêˆP:…‘Ð~æúFÍ*)‹ ³Z'ÀCDÀ[á€ðá'¡»3(¸Â­›·p}ÞülÜ›UíE;WÃ'írÿ‹5 èð¢ b€Ÿ<§›8“µ=`¯( ~‘¥ÿpMþÚER<Ü=W%½\´â¶½Ue«²êصvÙï€ß¸³ÚLrMúZ Nz]äϘðD˜L¸W‚ «#ao¸ÄP“ûéÌ$ =è‡+j9Ètµ×®È�+ ‹sC±Êd´�‘‰?FàÏÔ§·œöé"g%S6oÜtª¨{̨C?.ù)˜3€î§¸ Îø’ 9²:ë].^‰ŠKÅq/R¯ˆøo&‘)Dú˜ÿæóâ…<,ôÀFar‹ÈÒ@ß‚zzÍá}Ç:x×CÅ":q Ïx2Uqh[q� §íŸcÀ>íXÄÖò4³à¤¦]¤ENI6i’Ã&ùcëÚ ïÙt²çM‰\,®º‡À«il/]påÍF\-4Àw[ùß76ºF—®�O0±U>†£DðVvÚŸëŠÊ%KÖrΠ]O‰u<\µzã–$Áq\Zxï�dw‘!Ê߯î†?Ù õV]FÂó-ì÷—HY€‚Œ3EÅF›sÂÃI,)UÕ7MŸ˜ªœéÏMbÝÁÃhç¹ãÇÑ¢lY$hIö³ç€ä¡�–›ŠªÒ#¦™%¸�ç»Ú,þ&EÓYÁ€éœ¤Ó­Ò¥Þu„àÞ¤_"éxøÆ[¢«£^]>"š†¯ß®ª¾9Ù…«?ºó–„óÛÌv|\¸?\”.þFÅY(šbŠ!VÞÙ?’Áõ›Ez½T¼Sî+O^jyEiy,ÉaŒ0Óðgxü r8¤a‹¸âý¦ :—¬~øéV¼{¯ù·øºÁ)ê|/÷‚„Åo ˆtuŸ]ÔZe?úº ›¶H‰ l¡RAJª©ã`Ì'G*Ág°u"ÐÌg‘c³³KÓ1Á Ù 8D ^ñãþ<ä˜ÿ¹äxN•%Ã/F‹Ñ�Ék A|{;7h§s�R=ƒ¶'õHZr'üÛ|K$ŠÍø|6öÈ’ã‘=ñž8bñ‘Íž]³'«Šü?8å¬�ó%ÙÈ*¦œb¦M’¹Fµ1)2èSƒï"™¾]BeÚí¬²,†!be®¿ºñ*ñ/�ã콑m¡6wÕžš'¬ìjß°gßúI²1±„/XvŸê³œË&>@Žå±ÇÒ]ß_??¼�×>0©OMîYE>SxÙô8ßexj·øV§w�¯âmi•—d¤Ð ŒàéÝk.TBT]ÓÓ^›ØÑ/`< ì°Âãmz®‡‚ε ¢q93ážd¡Câ=œõ Ϙ^€ýoî»]ؤÆ&ÆíRKìW‘‰I{"ÐLŸždsǃ:é“®;ýåÊczÈK`Ú·HÃzV<©ÕN­¥¯§hO1Åz})»=7Skç!¬6º0ÆVÆì¥1»—ÉU8F/äÍcÄÈÉgdjÒFl�´Í³=8�BËyE/!òßYŸÓóIÀ8¡aX¶4-”òËlǾZ(-dÈ1ÀÐô$¡Zá£í“lÔ¾±-òƒícðQt© Gƒ—x™B¼=|ºÜùör|-Úpö­8uWV5œ5z³w¿Ç½oú�ŽÊ¤ˆ#åìý”ý”’}vîäî݇¿Zé…¿|- o¶ð+ž¡tàÛ$£“»Zb? ŠÑ¿�K¢þ%ö] Š9?ò…}›�‡.Ì]Z¿x‘œuöC3×”/(†i!¾�¢‰o$Òè,® Í~ï˜ví>§ãÀZfØýLkt`Q™!z'Òãö�Š'é'ñGÅSè>ܽwÊ~ìÙïo÷Õ‰%›%^‘þúþ«É¦q[!äËÕq®‘4mÏœ\Ö7Q�4i y)¶°m“Ÿ¸ÅéžqšÄÙ.qžñÔÁŒ¦h>–^�¹¿/ãƒÖHr¼4195M˜ùµ˜ÈFe#ItAŽQ½ÃD§gWÎÝ7_킯ox8lžà’o-1ÙùÊ …½É×qØ«cZ,Ù é¬ øÑëÔkÔ§ùë¼ýö1ä@“Á@ehßT%Èó®nmÌi4Ãs,‹TBŠ–FK¶.^!Šq”À@S4zlg󙯿 {� ¢÷Jwò³I÷ùê'ÈÏ)®¦“ÜÕqVã»ÈäQ›I‡G?‹'v8!{ðp«®Ž³w�ÅÆ®m];²\Íkòñ øÚX÷7ðÞk£ Ô_‘�#6=8üsŸ ïh¡HÔûL!jw•°ŽÀЃ÷àêSGê™kú³ö†}䵯™ðLúKø®TM›çjÃ]Q±='âçýÐ.' |:ÚÂ/x(êÚ -!÷N#€�gœkr»€bˆ>¨àÿ�Þ\§¢AëÒøÑÞ^良xÇŒ‹Ðû=\y™@öÝM¼¿=L"u�5tUîÀâŨ÷Ò JmTì´GwâXè?»L/_TÌ‘ë‘Ý(¦£+à.CÎà ^#7¬’&‰á`àNüõ/'¬+ðå#!µ%E*òŸd5ôÕ‰ à¬;˜V¿bŽñöpé;}~Wäx‡F÷u5Œü!ÏE†}úc}Îó¾óXÿä�£®«w+�E�ÚOÀ™úºÿ9TüïQÝxœkZÇ´LzB,kA~AiÁÆÄÉL" Zú\\Ê™yÉ9¥)© J`‰ÉlÌzìE™yézv“?±„Nž.<òj-n°l|f^f‰—”çä–dÇCDK´ šýmõ Ìø‚’"[ ešÕµÖ\“ˆËËÁÕèÚ—¤Å'çç•$fæ¥ij*TO²“e«³ERa¢¨;Ý1i@7j@œóËV}ò3�É|Rbf¸mÔ�£†aÝäâ“�w‰N^�À2ÙA‚S› I…’¢ÒTMk.°I™i ~¡>> ж “'‹s#é™l`'ÍR4ù «Ð]¢bèöB7™O\\I45'575¯ÄZÒSË5ÙÎ9uòît$c‹°&®j ·!«…š¯�Ðô_bÈJx8MÞ()6ù4+×äú.‰É^â\Z$h5ˆ™ Î"‰K‹æä¹b’( ¸Éyrº‚XƒÆoЀ¦‚ɱò¢ ÿc�dA=|ÞÅKBØ ,š&?tUÇ ¦B„]u$ä1¡¦ÓÉÍ’L!²‰))�xÃ߈4 JkþÞbÙp5i¥E%@s1Ò†Sp¼cp°kPˆ†"¶¤�©3ž!‘ò2(ÏaÉ,zR’ „Œ�üP)lòaWqwT…„’vQjIiQ¶Xåa=7ŽÉÆ�B¶¶ ®y) ùigè%+L4ŸìÀÊy¿ÐĶáxœ­WmoÛ6þî_qó€@vù%À>4MQ7Q:#®³Evƒ`Y¦-¢²¨‰Tœ ÈßEÙ’í!m~�Lòx÷ÜsÇ;¹Ó†³·Ðî4:ívÚðiÁc ¹rBšº÷,QÇóŒã<áŠ�T>WAñ„9(¥Ï…"}Ìø2R4;/'`…-èwû¿Á�yšÂUÀ$ËÀú®ßŸ–ùlÆœ0jÕ�á‘ø"ÄÁŒFçÆÈˆ‡,‘ly2G*b0H+wløÆ2ÉE}§  4ÍV³uJ*EŽÐ! rÉP— }f!KðB±Jc$!ƒ5W‘¶c´�»pgtˆ™ P<òÄ¢*�2 #¥Rù¾ÓY¯×N Ñ:"[vâBNvFÃswì¹ÇˆØœ˜&1“2öOÎ3ôvöAŠˆÂ`†8ã` "ƒ`™1ÜS‚¯3®x²´AŠ…Z#5s.UÆg¹ªVâC·«HY�@sàÁÐkÂç�7ôlRr;œü~=�Àíàæf0ž ]®oàüz|1œ ¯Ç8»„Áø®†ã Ò…vØCš‘“•l®yó«AXˆ’LYÈÄ|6 $똷}l`öãm÷!±±b*sùº¦ Âã·¤ð^ð9¬ã•úî£G>ÝD«8¶k ÚôJUfë� RÕý,H$'ÞHt;“…8O(ùq%ˆ}}Å[�'½a”¬�3¨ÌNërE�8«k;m<¿=+3!⊇ŒªÕK´�ŸZ°˜jbÛùœ6cbZ?JDg±Tð¢G>T)«Uz÷®.Ñ‚§Í&_`=;tžw‡ú££­N»Ç´÷tLÿ¨¡¡9Íîu„ó*:b<�à—}g"¼ž1ËvÕÑ0ÚÊ[epÀÄ nñ¹6ÛK±]¥Jø»¤’}¦ò,AÛåBoñ4Û‹ –›4Ev¦˜¾ ˜T¯¬•j@˜°¾é\#�&SgÒ×3Ê'zîS+† Î!Äò$I�9_¤dyŠvñ¨_IÑrYþõ7’Sâ z64µÁ¦]±ÏÅøìù×›øçÏõ=küØY­Æ3"©”Œ î˜ÃE4U*ñÜIqÿ(wl]}ÑèAˆ5OM2lZÂ¥÷k»áÿ=v ­ëäÄžGê� 3Å:é&#ejùþt<õÜ ß?\ê—ßðÕ¶,ÒÖn•ËÕ‹`R¥XxÞâßæ!(Ý¥:âìÅò?ÊK‘ÛB²Î^·kCßÞó¸Œémöº(ybƒ¾°fãvôurå#(‚íÜÓ=|tò·.Ñ%E’¨p6­ì…[` q5jø,b cqŒ�ú¬¼µ;âº?á/{�K$ÆÔ˜2i¿¹7ÃË;ßýÓ*íÛD2}9�ШïtÀ*Èo�ŒD£ø¹ºDÞ åe|Á ?bhÍê·œCö&7S·bq·�èu¤� o[?€»_Çý²Y£º=È*¹Ý7nãW#}9ÏþÀ¯ºZé<èäå`äý¼—u»=mwcs¼¥Ëk" Ì9šo© ?ù_„÷~Šð“ ߥ`Ãø¦Ó¸˜-ø÷Aÿëz}¯¡»õ/á<¹»˜xœÅYmo9þί˜ËIw€ôRétj.QiB[T �‹úicv½`uYomowê¿™]oö…%MÓrgEìñ3/Ï‹ÓkÃÙ�Ðî5zívÚðÒ‡¥·ˆý®‹3ɤ+£­Ë•¡_Ùhº-8yvò;|X‰@D¼c\sÍOÉçËe¼Xð®»j•·á–çðFÊ%r�.,“‘py¨¹qè!†YqèGÌÅ»Ò�¿¸ÒB†pÒ}M"8²KG­S‚ØÊÖl ¡4kŽBC¢¿syd@„àÊuº6¬>¥K-†\†ä Hw�~‘˜±B¯Œ‰ô‹^o³ÙtY"mWªe/Héto4¼Œgƒc”Øî¸ ®5(þ9 µ]l�E(‘Ë(gÀ6 °¥â¸f$I¼QˆpÙ-}³aŠŒ'´Qb›’Á2ùPí"šŒ…pÔŸÁpv¯ú³á¬C ×ÃùÛÉÕ®ûÓi<f0™ÂÅd|9œ'cüõúã�ðn8¾ìGs!~)ÒÅdJî%v›q^Á—©H:â®ð…‹ª…˘-ѹä-W!jWk¡éH5 èL ÖÂ0“LíèEŒz�ÆÏ"tƒØÃã·~º:*Lþ驵ãËX¹nwu^˜ÄbÁ4ïÙÏdÑã¾9\�¦ÎÕlà\�góþ«Ñ °k¨ž W>sñ@ñ§ƒ,}®ÊàDf¶Ñ.)á?Ä@o!eP†ÃIdº3‡¢Wç’æðc@€KîLY~W@ ˆ3< žyÿF¡§¢;-ñs%\ VM¢&ÝIQ(v üÓ„0§��’Ò”èiÙÒæƒläS§�sMÌ‚Z ˆl¡�Ô%åѶó‘AÈ/È`Í>quO�l4¹�»³íÊŒ¯äÚÉlîQ¥�'¦­œÃ­^• Ý$Çã#·�G´€ä>µ€ ¾¡ã1ÃhƒÃ\ô`ýHøNB£>?A+ø[êt.‘¿Ý¾‡Ý%nãå_3“¯hñ7§yrW�—¥ßkzO”�ŒM3D¾æaPÀeA°`î'�'딿ÂÍÞMâPV>o’Ùå™ÈX'7œYïïÚóĉN;5P·ö¨ö쬥µ85FÛƒRCÙøršÅ‘Á]$åŽ57+é=1–Ø8Ç?j¤ç²süù½wLjµs?ãP‹eˆêl„gV�òÜŠSYÒj¤Ç¶òÐ�¦ ärÉ=‡|IºÍß:‰oK¿YÝÖ¢’‡ð¡9¾�Ȩ9P «hÉÆ¸¢o…¤‚Óø%ßw|žSur‚šóÍW *Ó°ÚZ¦Eè’žU“6S˜|û~åŠH-{j,[Šy{t´|h” F_R ìü ö?ÞmȺ?.OÉÈ ã þ¶Yãž•ð”ØÅz[Ñ‚udV½Óª‡Ëf݆”²fÏ4µÝ RÌ»…ËøÄTù¸‹ø˜´\1Znë4õ>{¹L(IJ¤_lœªJýhãXvÁî­Ÿ[`ÿ�T°[™æèŒ#‰!šil=5 $ØVuš˜@'¥éK…µçŽÃPš¿)ëxÓÍzÚËäuÅ;R{}�IÁOí«¯ÞÇX,°%¤F�ù ÊɆiÀþéVÈX[HOŽºÀ¯ % >�æîÕ²ŽÑ‡(×b¯…Ò†êP%¯uÕ^�wì¹N;rúîÕf÷•üqºï˜ê3\À~©ZÛ¿š9ýÙl0�7Ÿ‘®ùÚ]G¹“ç£,Åñy9æãᬙ›àë+ÀV+ˤÖð(&6Ðóþp<˜:“ו+ÖÙ9‚”²Ý"�½.»Žœ(ps_¿Àæ1IÆé•ªöXÖÁæÉ›JºII}-‚€.a$ èÓ’ÞV"%½ØE§À¾p1)£ã5WÚv“„åJ¥¸ŽdèÑk@Á“Vì–fSºH+–]T·7щ¹âÈ@·¼È�BšþîFëÀù¨\üô˜J*™*{öäåÓÞBo§ òçÍÆÿê”3n Ž’XNí ¤­Ê×N?[HúÖÊ…ŽÃXÛW+KUìe3ªIl¢ØS˘ZÈNP³pªãäA(ÙYDÊ;ߜߒæUœ_µÝYÄÊ»åoÅJw¦Ñý€¯Žs5¾š .ç€ÿA5Xö]ÀãóŒÌî*œõ\Nß;¯'Ó÷ý¹ÓŸ¾yõŽÓ$÷ >Çâ–<õ�‹þp:¢x~ÒMa Ç|¶�s$îx �ÅèÁ²�e¥Ì”åº��êÚ>æÎ0è/+¼Å¨ÝÁtÊcu Î{Ÿ} Þö-µÇXbÑ�é�ïÉn^à’Œ˜Š²u<ñ±”TùÉ«zºÆxœÅmoÚHú;¿âYN»5È…luºɵ*¥¤‡‘] Û«N'k°Åx¼ã!$·Ê¿g^l� $i›h-$ðÌ<ïïC¿ oŸë�n¿Õïv[Ð…÷+–P`[²¦½ôZij;Á֩ކŠxQÞœ½ùüºa Ë2ø…М ð®õ÷ûõn¹D,›N AþŸ8_#¡Édh‰LXDӜưKcÄ!7‰ðËîøð;9ã)¼é��§´íV»s¡PÜñlɤ\Â.§ˆƒå å¡·Í$°"¾ÍF҈žÉ�¦c±ôŽ/_J‚Ç (Ù�¯Üƒ@¤ez#e–Ÿ÷ûûý¾G4·=.ÖýÄœËû“ñp4 F¯‘c ±Hšç è;&PÚå� 9ŠÈùLȸ²÷$Wï“,]û�ó•ÜAš˜åR°åNÖVð‡b»Pe$…ö €qІƒ`ø Éçñü_W‹9|Ìfƒé|< àjëéÇñ||5Å·KL¿À/ãéG(ª éÐÛL( �M¦TIc­·€Ò +nXÊ3±‹P´t½C¯‚5¿¡"E‰ £bËreÒŒš„m™$R/È¥õ[­¿±4Jv1šß¸é¦í¬ý3"LðÞæ�³”°å’ä´o¿ë›¹Œ—œ'‹xX­9Ô–»ÕŠ E®Z[¯nq×ú}þ²€L�õ–œÃ5¥Y à Ã>Ò(!ÂÊõ]aªâT-œÒ(Aí¡Åw‘„pŸlåu¨×C ¶6/N¿Ê!ß¡ö‘©¦:&ÀNIç.”¨\˜Aã¯i쟎ä”{!–MÑ$ZQðtÎw"*x¥ªýC1Š1¯B h$IºNjpgúãøŸÙ[±$©ŽÕ¶”E®imÓÙ-,vöiv-»3×I^¤™T·ãmf:¿ü؆¾—8?/®¬¸‰%7×Xßykóø4‹Udx5�ÆÓÑ,¼º¬œ×Aã×øµë³žy±ÇË åá©lv8äö’ò;îM³ë^s)„E’)ÈcŽÔË(¿þ./µº0§ª½!9¶´Í‹¤Jí6ï�Ê�ü?ÿ-/‘þ„Ÿ±¹¨ÓnûÇ8‚{Êh�ù(˜‡ÃA0 ƒÑtަ˜x}ËTgB…©„pÙR£ FscD¬Ô·—ËÈÁ ÖíÅÞ³{þè–Šˆ)•FRý�Æ §~ª�wá#W[n¯¤1ÅD=ú{bT�ä<¹f²¯—T¾èaÔ.“B¡¿�fãË/átô›«j‚®Kƒùtùï‹ËpôÛb0 Â_§Ÿ*ÆJT.«¶ˆ7ë≡±×¨tªS’ç{€­S#k=î}§?t#T°ðä1BÇÁ�Êþ“÷­IByÝÿ“̳¼ãxœí\msÛ6þî_�¸ÓVReI–í$g_œ>Ä",»Ï.vPÚ ôê©>¨Ñ>h7¨�~ž#Ï^á¨åÀ þÎ ÂUD¦3ÊžÞ¨Tsê¨Ûé>GïgÄ#aˆ~³qŒ#T»çž.ÆcÜrfus 9A¿Á&º¾~#'¹&öc종ï :Ãè*´ø#[šèOÅ$ðQ·ÕA5ÖáP6Ö/‰U°@s{…ü€¢EŒ�‰_~ppHñ‘ÌC�ؾƒÑ’ПGRi1%�`Lmèn#¶vLôŽÈ¦’é¥a|Þn/—˖͹mÑ´í‰~qûºÿ¦7öŽ€c9âÎ÷p£ÿµ ¬v¼Bv9ö˜Ë}‰‚ÙÓC ÇˈPâO›(&tiG˜‘qIL#2^PC`Š?X¶ÞDfûèðjˆúÃCôújØ6‘ýѯ7w#ôáêööj0ê÷†èæ½¹¼í�ú7xz‡®ÑoýÁÛ& .˜?„[°I˜(±Ëå6ÄØ`a–â;dBXš?]ØSŒ¦Ágù°"âhNb¦Òt�Ì µ)•[›¨}pðñoá‚úLg‡Ú»zd<¶cÜ–[³K­1¦.¼7ß-íðæb�±#'ˆpq‡%´ Ôn£þ‡�w(ŒìéÜ>G÷‡ß¹xB|Œ>\ßZwÞu7Ž®^_÷tB^Ô¦«p߬`AÃÍLUÐÂUE?mìNqÌÛ˜¸&ùé5q9�Ï@-D–¾–ÓÐÈvî³m¡ícÏ|Eq Ì–‰!í†gŽ\Ñ} VÂ_ƒÃ†Þbdz#©ñG90æÁÐÀƒ™¹Ú‡5Á~A« mó…GIF&Ö·Ø 0’…C‘µôæôÞâC,о øpz )â… cƒY'10‘, VŸ˜ M[.š”€œMSšhŒ½ÀŸÆ`÷ò‰à4ò�ôeH5ê#¦Æ˜SÕš3Õ-(62/9ů™¦œ‚GaSòûršRq*ÒšT{û”= ƒ˜0í« Dwæøät�¤Íe*è3ãHû`A2•Ã{†\Xû¯�çÆ°%¸æ£èçŒD°Ç±mÑós×cÍÉ$ãØr=ü§&mñá#b!™˜¹)}ˆ[¸ÞÏy– -æªeÈóŠª�š¼§ÙX¸ì ìlëf²]W§_(û†ñœÌd5Ä—Íf•ë{ú‰�™ES;DÖ²ˆ�Èih®sW‘.B÷13èoÒ~Ì ¥Q°zú‰¥·í=„AÄB‚9¦³ÀÝÑãJo‹ŽžêÚ¸¡?Vê[n�†lÄ+¾ôƒ/9l& „vô yÁtŠ]˱=/pjÇg‘¿q0©CêuáYÈÕw××L˜ ‘:„stùˆµ€¬UÇgÙÍ�ø„Ö~HÆ]föˆz]:É,Ë é”I?cnöâ«à@›&ïÝaÝ…»�.jÁD�P››6—oua~Õ}¤HêRÞR É¢ÁI?=F¹aé¥ ^ ~ lž½ªÖK™>9$RIѱ9ؼZWÃaïvTK7*<éª ¸-ìá9Û•�d‘¡1©D¾6�¼Çù”(X“ g]�|½V¡nU"Ï’à^V2Ï€cE$ /¢z nn¶ù(I¤xQ~Q'<ÅT0PK·xe²›Å ežŠQ¯j›�½Ðk¥Æ’÷ìZÍ*Á*`ǨÁ²!mô¿4Ñá5Š¿‘à˜—$ T4û}x˜NŸe¶YÌ�³‰íÅØ°Ò¬}1Í+ì&£+ÐÙÌx¡EÁÐU¶SœWiy™�©:NÍÅ„HK°aà~B¦‹çÈQR\]ümü¿W›[ù§4ÎY˜¶ìÁÌ’y¬Á͈™B­$É\ÊürhY›.Të3Ä1K69×Þíax~ 4p²Ü29ö"f•Iˆ° å=ãÑe’x‹ <ˆP­*Ã-¤!òêÖ ÛnZÌàmà3=XÉ6¾ÒŽ.}`ª8šÊ[2-is™´×R’ •òññÒ2e¤=]”�“¹à%êŸ&¬J½ XëP3œˆf?”ªÚ2ÌéË`û]5Ö�.?“˜ÀX]Œìc.73Gºä¯…Âg¥ob-–f•­ªàu*ŠÖ’¸t¶¦Ï ³³m+5Ã>æRTåÉd¢Z&kf}XÓ¾ª«]ü[ø¶È¤J¸óöTPƒ4k�ÊqhÞÕhgØ6k•_U=â:€´ÕDY¤¾kUb/õVC¼åIŒ>¥žàÓ�±P+bžB/‰ŠH�•4eŽ#"*—û¨ZÖÝànØ{kYfm˨w<ÒÛ•F: bò!ÃÚ¨$…[Éž¯LÔÌ­+6ä=˜Sþ•ëŠ2ºP7 ´òtµZ÷Q�-Ðk\ˆ5óÌEWá"žYcHŽráQ~cÎG)›yÞÑצÖ[p¨úI''{@5ê GÖ›«aÏö£þ w]«óû&‹,b¡(Ç ¼…LNfØ ¤°‚pS^ƒ™K&�ÉŸÜšG�°[H? “f_…t¤!‹Í…Ýý‘Á­ªNÄ©)o†Šì†b¹$a­�CŠ/I¨ZTì$A¯Ä÷gï¶ÿî£5èýaÝÜZ·½ÑÝí ¦¦�ai4åð­w²Š�D2–Ÿd+‹o$¹™›wÊHmÍ“~`ÂG·Ûè‰âÊ�Cþ©’N)ÀŸ$J4o/nÔÚK{Õ*‰K‚cf®HÍàëq§{ÚD²>Ï/ž¿„Ç"QÌZÑW=¢à\kk‚ãzYp(/–ìݘÿq§‰ºmÍl•F‚=Ñ„ LƒÝß›¬ÐÔ Ævj`A­\"£5‚2ÃãÄS±O ¶=vçÖJEÜ$o‰œuL!Lì{œ¦~“ðØ€‡Ö*1òCx¼ "$áâ¡£Û»^ºë%C ˜ì9›�¾$/÷G®Žº¦‡÷Rrç Tâ¯â\Ú]T›ÙÞDEË\~utÄ$%8Íz«ˆóÞÚJO_t›…Ö {õ£Ë)ƒ·®Á@7Ëš,ûoݳ-y;yñ�My[i‚ë=°ÛÛ„z+¬I‹›³Õu´rýÔK¨"·,Š�!‹WÝÄ{¼ ø9´-œˆt +LKÝD×tÏO;º—8}ÙѼ»€¿‰Ÿèê†N)»F¸±³6›¼ÿ¾ùMlâ­5ðnµ�¿,´ï“çÕöÝ]kßÝÝì»[nßï®®‡�6ðnÆÀ»º�‚å9÷¶a_‰¸$­ªÍ™ôý`É‹ö­]ü<Øøi‡;úÎþ¼YåbS�Œè.`Ý>•Ž�£Y ‚¹ŒÓmÝÙY·Sê2ºw†Šý™ÁXY9wÇ/·uh�rgÛ-wh·8¹’]f›Zn,î°s:ìdDŽÜ6g 6'�N¡*Ïö©Êç�UåÉ>Uyºƒ*ór–åäòøLûˆ0±ýç1‹Ž‚øc¬ê°J"‹}È¥A”ßÿŠn²îÈn·4 M�ŽÊ£u-ýQÝÍ4cOµ‘ Êm™ÿ-ùyм af}yýÿó½Mó½’ÈGüyò$IXûN‰’L~À#%žÐ(Ô$‹àÓ[1]k¼À Ë`g<†35�ðc#ÌIÛ6».~vÜ&ÕÛítÿ�%áY6ÌËZìar.RX~€…'ôh°Ÿá©ð–¶í³ŸŸz‹˜€7ý;ðñÚ¨6ˆ×ç­ùV-ÕZ“©-65‹äõVÛwf“2ûÙ`ïí/=ëº÷n$ŠÛ�K� ™+f¢›¿—#oügÓ¤&¯íÁhÓ,X7Óª-¸<<(I\+C³³­rÍuäx!#)/X)Àl0þ¬xxr‡*É!ðU›?/UF˜ý¾—Ê£5ä¨Ï0ˆ/Õ‰^]‚½Q)bbŸƒ,Ãÿ<ÚŒLÀã�Ù©óoÄcÖÿ@l¡¬ƒÿÒ>ð\\ôŸôytó^|}3Ýü~‘ÈYMçq)a‚÷­ñÜ5ñÌïzWØN·Ä_mš'mŠ÷ÓÍãáGYOAøº!Ü»»Â�ïæã+ð~ކöóÃyãW«ì¼ÞwD”< ¢9Ľ:è[˜:Ù#¦N }d¤N¶„ÔÉÓCêä)!u²3¤N ©ÜxR þוö¶™¡»Õ ÝfxD1s£(,wý¿<½, €“ 1†n›ƒVùO˜‡²ï²|¢þo–]Ï›Y^ù_¶¬«º˜xœí=ýsÛ6²¿ç¯@ýfRÉ'É’¯½yϹdêØJ£ÇîYv{�N‡GS°Å Eê‘”_ÇÿûÛÅ�à—l)¾^9™È"€Åb¿°X, ½]òú©²»÷bow÷Ù%ß]û% ®|g¯¼hyû7ó¿É/¤ãuÉþpÿ/䇹øË%ùà҄Ƥó‰}~w³ºº¢oÞ5›A“oÈ÷Qtýœœ‰NN|�† �‘U8霒åëÁ‡(é‘iœøQHöCÒÁ ;¢h§û AÜG+²pïI¥d•P€á'„ ‡~öè2%~H¼h± |7ô(¹óÓ9ëG@ ŒŸŒè*u¡ºKpì$ºÖ+7HÏÓt™ìíÝÝÝ \†í Šoö^/Ù;™�O§ã>`,Z\†MÓÿ[ù1ŒöꞸKÀÈs¯ÏÀ½#QLÜ›˜BY!Æw±ŸúáM�$ÑuzçÆÁÌü$�ý«UjLâÃÖ+ÉÜ�ìNÉdºCÞN'Óùirñþìò‚ütx~~xz1OÉÙ99:;=ž\LÎNáÛ;rxú3ù09=î ä‚~èçeŒ#4}$%�1ºM)5P¸Ž8JÉ’zþµïÁЛ•{CÉMtKãFD–4^ø ²4g&ð~ê¦ìUa\ØÑÞ‹ÿå‡^°šû™”Îw´W ü«+7¡{âs0c)´“4_®>ïùár•öé- Ó¾ÍhbVJÒ™¦…wÖ|÷ùÓÈÙ" ÷²¿j+ô?Ñûä~ÁzԆȱ£½a8š¯V©0:ì¡M ÇÔ ÜXPñQ6�™C¨TFr,ñZy)qî‚EúÉÁ×NJ~{AàÁFG gª3´s Ž‚Ð€Bµ”µÆj¼%AKãÁw¯2P‡á¬'ÀDŸ{d3”ì’ø)’å FŸó±¥8Mïƒâ€°ÔŠ¥D MYÂZì²g™Æ Ö9½¦1EsÚ*áV1‡–äÄôšc¸«¾3¨ Eÿ&tƒ º)ñ@{¯(‰]?ᣀ·ÖÞ$#ÿªðýÑ�Ó•@ót�‰afHñà ƒI� =èT-hJC¤{®OÁTçv‘f}F`ç9_µRmp' }اâ`�Üún,BDfAº˜u|•8³5¸ÀeK�õÈä­b`OÜ÷ç0ù8 ³ž{hÇN/ONˆ óFheÇ#ëeWƒ¤U0ÄbrÍgøç’du…ÕzüÕ2’HqYºˆžB¨Iÿ¼MY×G|Äd4³ 9ü½ÐÁ³ŠìÁ¿^½xxÅõþâ~Iû‰{M™Ê�)–6]‰ø×8“Ið¢U˜Ò¸Ì:袯ìÄ¡ÇÄÑEp;SóÉÚ0”Ø{À3a(*x0±ð‰(SÉõvÄ(³è¶@JâT3ºEç��Ù°`YG�Ó>Á Ð#·‘?3º 89†`‹Ó;9]@�¾w¹² HôX fw5Ã7à& àWQ˜øÓ”ËCäqÀ ˜gÀQÀÖ„] ͽ´30c§qtß1m—ÖÀÊñº°2ŠfµPßiì€/–Fa=Èž6‰ �µä–+ß›!ÎÅUäÆ3\Œ¦(€kâp§Šù=e@D©37ÖCWƒdñZ+öŸ÷fˆÀß]óÀ}Ã�3éD±¤Bè#Áyjæpi*A¹ �àÜ”È?«d�k´”„Ì íŠÉºû*s·ÜÔ}üR,s·pÖJVàƒ—$_K· fÈâÌ-ì€.1æ¬l(!¯…­èÒoëˆSw HSÒ@”Šº¦”41+õ²‰hü9�]‚ˆ؟©¥(¾ò)�#š°…ßܽ£OfÀ]t•fôÚ]i{BéfÅ –}Ø+”áL/ö—ˆâ>£°´@–-a?BëЦ9n|_Äô*YÓslSüòk†ÞÛé'ÎñxzäOŽ.¸¦à³3û».H¼§®dê ŸˆýŸ5Í{"ªßn¯ºÏ�n|ã‡õ½.X½ì3×3ï�—5êömÃD^ßí«—}¶îv:†Eóéø¤Ó¬Fk0þ¼Œbîc¢ž®¹Ö€ôŸêA€ÆRmWûæÅÜŠN‰;c.Rº/~Ëû§P…}B)ÈaÝÜ€©öÜ ˆ¼Îèïÿ‹F×:ô´Ûå8Ýæ}È%ˆ.8oé*™[Ϋɢþè,·~²Uæ­cƒŽ\?ì˜ã Dí+µ ôC?U"†ÏKÕ®K�²ŸN>^|pÞžýÝùq|~19:<镵Í�ì¿á²Öí UÎQYÎD�^eµ {°Lñ|×É�ÖìVÇQW¡ü²Â0 4̾Ðá»õ¦¨N}?LOmtÇ%d5áq‰jÖöötäUp­ ÞLèj 5ë䆤yY2y7%{5ôk7H2ÊßNœæZmDDK:ÞÜ A¿³þkÚ` (ëxAÄzjÔHðBTä—µ`‘ôô¦‘yi°[9eå@è¢j =&;’Î{{äã æuéH À"2Öœ—Å;‰îÅB¡C]o®DHë‘ÇQ²’ò…£ª£‰z> w œ)È‹¦¡ÌhõºÌ2—è˜ny ;eíu=»Ž)ÕÕrì/,†ŠJW# BHhÝ b©:öøªê9<7ˆ¦§Ü-¼l�¯ rnc¶¢Î…2´‰Ó^UØ·Ö�2*GÁLswJÔåËyIjºU„É9FÕÎŽ€ÃÈß ÊÜ�ÙÖÒ™Q,U§w©ô�{­ -— œÉŸuÎõÃ[7ðgh]E\¶¤r®KÛ¨±Þœ ãÛ­Ì�HLœòKԱǙ†5ÀbŒsCÕÀûo„‚¿h*†eåJÑ£±ÃíI`•sTià¾d¾â¢(h M_´låeµhŠ »›Í¼ Ý?ëé³ûì"FìÊeÅ:KÖHÈÖdœ¥« ¹õ±î¢àº*(±b\åb�LâMiàuÄ. ³Š�výŒÈ»ÕóÛ‘­¤Ñ¾ßT=',&}¶"îlÆ÷XZKEq×n×44blгËU2/ÍûÔ„µ¼ˆs_ÇÑBuÕ1û1æ*œŸqLÒª–vg2òc1TÕÞ‘i5�s¶ÐäfEcm“³ Æf¤7/ 1]D·tMyh!¦½¨˜ÄŒf›»ÚÏ,k�_¬eMñT’2ò´Â¥´…wó䪲¥™FumQj#NŠ0¶t’¢M¯ö$ª¹Y ž ¥“�= b+ö€G´X§™šµZnÈ~ôïÑ.Œ)«üòecìs¨±Øs{š“;Žä¤7Oœ4?¨ÄÁ÷ÍTFOÖ&o17�*S}öÊ- ©ñ#Ü¥×UÂ'o^׎%·wT›'`„Tx.᜺3ÅÓ»9¦wü~¿KŒfe¹à¨ß¹ñ¬£*›k÷<ØÌ$ÒŒ6›�¥²¼ÆBBl¢é“<¨ÉšÚMæ{PjÛòƒ·ÅÍ‘iÁ·’98ŠI<Èò¹zå9&¡YS™UµQ7âu…÷i„E�* ÑÏ‚c’ä{{äûñw%· [¦ª™EøqííÔ9:;½8œœŽÏ�³wÓfðKwÒ´³YØÀâ6À„štݪhfeÐ5›)Z¾¥ý7™PŠ!?ŽØl=• }تÐ|ÚB̲-Ey+;I·ÍöÃӲʖ�]ý“zlHü•9qŽsyz9;ŽHÎY…˜ú¦RtnÝ`¢§þVmyBcö²Æ°’ÝÝÄ%«.:›ÔY‡ˆ·ƒ¹CJŒx ¼âS @G�£[(Ê’zTùƒ*.p»ÏI5KÒæ„•¹:°7e³š©2Põ+éjnMeÄXÙ.ëcǪ;ÅÀ±¦;¯e€ÐuÖ�î‡?È<§“fyÒá™jÝu³�6’çIJàÁÁ0˸+ÆQÈ9‹$$lç� ªÈ“›À’AÆÁŠ<ìÊ2ÎkÝ´kG,=^iR'ŸšQbØÚÌY9kö /(¹Û\¨‡û<4|ãƒ+Ó�â>ÒoxêKé—@±ýŸ(ý£Iì8|í™Èwi£Å=vÉv¾c¥-‰2`‹ê °0–¤ãH:<Ø�àðÁžŠ£DÝìš+ûy.ׯ”oîFÛ=�BŽL>GÒH‘޼uE‡WßåÓ…uW_ ðL.Æ�ñéáÛ“ññAy�÷“ïߟ`hGÖÒC D·¼yÇ“©Ö‰fŒ жž­°ÛSÍ”nj—An¼Z®Ñk};O›¬µÍ¤ä°cËÉ<£Èâeè§$œq-£Ø3×o-BˆYØVc +ªð…;ºØël[ªaý,Dº·þ ‹r?¢)È#.ÓK ð ‘à;Ó„Är5oåñƒÏ†¼Ë÷‰v;ÎodÔ#;RáU1V’‡žÖNRJV—ßÍZ–AÊ–¢Ú¶‚@ D ¤ùb<½pާãü5&ÕC¨å[® Êt,nÖáwé°+Wwz%”–{‡ŠùùH…ãü†7ÝÕ¨§@H•]Ã*‡_ˆÒ;þ•]Վȃ¤È€#ðZseüø1orçÏR´û²«ÁoªªÁRGXùœ2i�^‡=hIÿE‡µy \ÍZ�†Ã,r%. zr#zÁ¬ »6T:<ÏEÉtvß„²¬-,H]0[+ùV/‹lDî 3)íÇñùäÝÏÎéøoÎÙ¹s>¾¸‚‚Wáéõ lPÞ%p°´Üõn%ØHœP°ü3�ÑRôÈ*ì›ö!€:§>¸SÅ‚nŽü£gBዯKû'äð~#ï·çð-åüe3ŸÑ\Ðïò6 ´˜œe[ÿkqtøŸÂÑ&=4â¦9_µà'PÎ�ipO¢Ušø3*xÓþÁ?ñâ(²…ÛšÌü¶L=ßžLÿàæ“qó-¦.6°¾XÙ§´²’ú<½›D!÷uñg+ßóÓྦྷ&8VŒ¹xâ&¶Ù*Æ{Y½€ºájYÈ+(É�.óÀdpÂVak©obA>n*¯œÅ>¿NXÊ[nP x.Ž¿†þîïÃŒ4¤\¤5߿ʷ� Ù½–v¯€¾ˆà—’e(ôßw³êÖN«ð9* m ê)àOèªËn›yìZm©WS]�Ø•78#-ãhéÞÖƒÆjž½ÉçÈ�ž�ÔïKµi<R…ž%õݸ~$oVr¹x-ŒS)Ö Z[Ì%eIH︱b4…ÚŠÊì7‚ä”_"Oû_Fžö5yÚã÷K_"ÆO†dk>ïçù\£A,NN7�hNÀ­³ÂÐÁ/ µÞÛ°gŸxN=i49×u1ªëb£³» -®Œ)”°`0~ý¥}?ÛF)Üœ¯¸ÂÜ/hõþŒO…óVœ ÿ‘C«‡b¤ÕmÒO1ó÷T£ 4CÅö]X ø€ÈÍž\|jÊúÖ›5}@Ÿ¶´œ Lã0ò™rVBŠÌ?X„YˆøÀqÿå/jûŸ_F¿òˆ¡èH$¾ë�ý_öë«|SYE¬ÌðÈT\ćòá+øø+ùöùÓŸ|=­‚£éÿ*÷֊ضV£×®•Ü5²——?†¬BzÿMÆ©Ü=øY½V«šäˇ£+PYÁ3I „(7RWLÅoqÅ«ŒëÈÝüÑÕþ¯ëÅ·÷·±ôW`ÞŸT.»åPáµQc&ÎLlü ™ÁwIä?ÿ*~£R(ÏüyÈq…½í².$Øø*ü sÀF.;WÏñëÞà#<%¾)§‰p—rÅô€�$n­ðÇ ÍÁ�žÓàI»Ñ�Š£¿\r)à'íCÞ,¿/—Ûä¶/—úÕRq½§¼ÿï:`‹¢�Ãñÿ®Kö£µ[àé…h{L=$Ülö¤_�QƒœÓþ †æ¯#o•ðÃL-Cºÿ½5»þ¬&LD· Îfíöº1ɱ˜?žŒÊÇyÁ�œÜ8u�+ nœó·^fµ«}¤Í�‹£¿ÁF=R‡z¶<Îl ;2Å©uÎW)Ù kµ–mD¶êEÎ-º«Àº@¬ô^¤úÜá Jq mv@N#¹4²jßè{ú‘^þ°�q~ÁM“²°  wˆG>‰ªeŠãâ+¸àÀäÙÅPüÂý�0¬°A'‡£®íÈ,Ôßt fX‘«4®»ôm·ìZ�PëÉaI …æ–²¿†•é_CYã ‡š†�2!Açn’�ü^ŒŠR‹I•þ¨DjG—Ú²ýÝD;ò è¨É²·$ߣJù•ÔPhn+»±R¾GÅMbµCk‹ª(­æ†öT^F‚FàßÑ  דå˜à êçqgqÃ{\ÂÜlÔ7`}onˆ¥˜ç7†øqšÅÈ–�÷:ãGü·„‚RxµÂãTfòtlßå~æÁ©aYŽuMh!›]Ç›#0*“ÍÓèÎP½ÑpÀ¦žò–�Bþâ†V:“ØF ¾­¨§D·@¼Ñ!ÞÖ¥¶í µÇû8í>æFà1e®òŸ(]*Ÿ•`“ÄCŒ~Ç*ÿh(£á™mvù§~yú[.Æåþ0DFV¸—ÃÒ@ }ïLdê‚3äò[p $�ºò<ã%ÿIò_G‹a»ØQ&ÙîÄ€ÿ*±¼Ìð9òû±q£aW;Œ‹ÛÑ5(zkŸÇÅèÏÿ7ý¯¿·xœí=ksã6’ßý+_Å‘½²,;s“YÏM6[3QÅ#ÏJræR·W,Z‚$ÖP¤Ž¤üØÄÿýºñ  (Yòxvã*ÛˆG£ÑèÍ£òfS?äàhçèà`‡�ŸÆžOÉŒ ÇKè¬1„BV> ç÷‘7™&øíL~!µá>9iž¼$§žïÍçä—Æ4"µÏìÿO“Åõ5m §ûz3hò‚¼à vqq&¹ð†4ˆéˆ,‚ô‘L)iÍÝ!üOêäWÅ^�“F“԰®x´»ÿ»¸dæÞ“ LÈ"¦Ð‡6'z7¤ó„x†³¹ï¹Á�’[/™²qD/ ìã7ÑGx�¸PÝ%8wŽÕŠÄMÐÓ$™Ç§GG··· —AۣɑÏëÅG�³v·ß>ˆE‹«À§qL"ú /‚Ù^ßw Ýk€ÓwoIwQx–„ñmä%^0©“8'·nD±›‘'‘w½H4„Iø`Új@™�ÝVŸtú»äm«ßé×±“O�ÁÏ—Wò©Õ뵺ƒN»O.{äì²{Þt.»ðíiu#¿tºçuB]0½›G8ÓCTÒÃ[ŸR „qÈAŠçtè�½!L-˜,Ü %“ð†F̈Ìi4ób\Òa7¾7ó7aE…yá@G;;ÿáC1‚åÏHuº«”ÿ×Ðõ¢°1ýQ)ò‚$¹ŸÓX/õ½ëk7¦Gâ¿ù!L2Nò�‚ÅÝ‘ÌÉ!½¡Ar8 GùÎãd]`™2fW-YŒÇ4ÒË&ã;(†2BŽŽHçÓoWd¹“™{J>S:Wj2ôÆó‘èÝ …±è寋’…ëÃÐÉ4„݆;07¢¬|3KÒaCàbVyüz'¼�A�hh«A~Šasä§ÏÈ&Î�—ŒƒòOJÿ^ðyî †6üÀPwMý0˜Äð°AÎ`Ë_SÒ½º¸0 ¢!‘�V8O"eœ ðš À73, _Ô»½ðÞ³I zTuqX=9õÓÓpNg8Î\FŽ$èéÖw$Š3Zà©ÍÒJÓ±o§8,•ì~Ÿ€ÇápWQÔw(û› U2’w\wŸº7´Ú8š¤ÃÌBÜå 2AðNp¦³%ä ¸Ö Ãó~l(¹;)�bȯ*,'RO6ÛëØùØ•ƒíTªùX¢ô> w Î ˆ.ž†·A*]tNƒÕaÙ#bü9H �?À0R¶³®j †Òí´Ÿv �fê\m¼ÓYÐEDû"ÓС K7(à…ÈgF€mà—EÜK^–�-8Ù3}Æœ‚Þå£îµÊ¸J£ÇŒ ŠÈŠ–- £¦Ã~šR¦‹dãįy…¡o&QSé/•]fšÊóf6F°ì“ÒQ�°•K…?C½û‚laý±lNÿÙw Ï},¶8žd(Õ{—�²X2† ‚T�4rokÆy'BL°/Ð]½tp�*PÔÅð7¡7*Ó„£wlëÃþ/§+†\6ZÖU‘z #Š`ðç›ÎpÎ1 d'°î„Ë”h‘Xo½y“Q¸@ëæNûv¿¿Œ¥z%ÅX3I¨¨Ì”¹ƒ´²§Ô±%×,°²ö½Šdç ã'Qx¿enæA;!ŠT¢fÃòS6;ÖëÁÈMÜ*ðçFcjÄ“�Ƶ†G Wi ~qúƒß.ÚÎÙåÅeÏ9ßk�wöºÞ`îF.ëøû$rGº°Ø×qâƒæÝx|Ü£œ¾ú”xCIvGGdpy~Y8§?%ýi¸ðGìýš¦§Ôߘö’td¿a ÇV‰?Owt ‘ÿ¡Ý½r:ƒö§Ým½½hŸï[únÉ\'„KŽuø#sàk»Ln?鯋=¾ÀÒE�—á*£ìMp­Êë�‡�‹Cæ–°±Á¥®ÊTîIÁAgK‡0 ‹¼ ´Œ<ñº²�hq­Ò™¾ƒ×—ÎÞT9Åd÷7o ò/;·,J&ëðšÎZä%'”Š4+�ÊR†8óEjY—º…ØÒâ[Ë�qø£ºäõõ‰DoZ%pbƒdõuX¡HXÅYÒI½0{¨¯t\•Lóæ…ÔÆ^Û5Y fÊdU6�³UìhZ]‹ÑÀ r†šb‚aˆùhU0ü¦³ ªÝŠ7bÄD.ÕQ]N+—O`¤ËL#íW�V]Bù<–ø)mtô`­êËBìÓhz9)^Ð[Gñ‹‰3Õ8‰F‹y-çRS�MÕvé©éØõcº\óÊÚUðãYžÝÕ¿¢1n?Ú6·.ãoÂÓ¸Êr3ºíJ´çô èMZYךmõs‘øö®U&Xf¾*V–i–f?;!ð‚ØiTiX<è®î¼ÿù~JWXÅ%�ë„ò rIîÂ|"Ž&d*ŸƒÆ·^2œ vCD`fvˆm§ö ¾OU £ÊÁkç£Ù°ŒÄ"ê~dbVnÙˆŽÝ…Ÿœfíc„oímßùÔêu;Ý÷u²Û™aÄîgñaYl"ÂD¾�“Ú?v¿�ÿ±»_ç÷LÈ·£Ýß(ç*6Ñú7ë“S²»d¹D[ä ùkˆÂÂá— Ìèk5=ï舼_¸Ñcùìú:Y-XÌrœ#ïGnBðB8µ"îÖVA‚ÒÄŠ ã³Ëî Õé¶{Îå»ZÖ¦^\�ºèr›Ö°rëǦe¬h h‡«²�%ç«2¨ó"º>©ñÀâýuC;7Ô)r‹�Ó—cvÓ…›®ð¶ü8$‹9žÚ¨u•›£_æÎ¢”!ÅK´N¦ð+UXjºc¸Xd¦6ÁS'ê=>l7ÝÜŠè*Ž3ÚÜ8RVéƒ8%üAR¶ ~™ênK4ƒî“³TšV©4R*ézC;¥­Í 94£R]4•6R¹>Œ=ÝT‰a`KeU»&ûi›ê¨dh}šÄœs18Ny²,îüœë»9M‚îpFx9Þ¿o¤~ȧ¸þ\rêÅô<<ïâZ°´ÙmJy#ﻯº,�òwªlõ ‹ îõ4Vá®Nîë4„(ï½WFñ ñ%þ[O蜇1» Tó„²²ìÝ—¶yð‘%EJ[ñÛ}{÷^T°îÈ_Þ¨ó¹Çï‰m„X… ÄbDÅRÎÇŒGC ôÄVÁ\ÝÖ®ma¢-¡Zlvß®’D`£i¶d‚w¢•j�E_�mÅJÅþ,5Q5ËxËÐ.x• Ö�ê-‚[.#ó°–X÷ïZƒð†Ý«€æŽóýIMi%�àS1�—¢+§ÙöR…Ÿà®ÉrùL¼]_&äWþ´i(嬮ö](—²¹PZ uL\ÇÄPEaijÊ}]=ìIA�RáÙä$øY�Œ±+'âÊ«RÂo@mr´«HîŒ>Û½Þe¯°/vß¹Òjpßî~ìu/_ìÖë$ý¼_ôU‡ ÷Ä;Èg;ÜñÏ4æøPà3î\=iOe˜IQQ:Ö·sÃÄ­¬#§¨›!-E†áœ¯Ëî.=¹ÎÎâ”v–:¯ÕÁ8É‹¼žïãR°ÿ¼‹=eM±˜²�îû¨*/¾*µŠ7n3àK•¢+÷Ì€äï7gˆ5Àc¾×«9èM I‰d„Ë0ŒáBp>ªÖýŒ„�&‰t8åájYªŸtÎYxm¾SzØE茑((@i7õÕ»T«ÛU³ÒŠs Šl¼„™ –f㥵�RéûB@³…Ù)¼à—urBþ¢‚7ÙÙ}uå°9÷¸®SIöU9W8Ÿ¼Œ“1-͚ΧbÛžòpžz q1™v ÓPÖ’ÈsA‡=�x‚5/qRc–LNsHgcÑdÙ�òù~g™žÿRe �/¾Os¬…‘Ük”ó;å—R<¢ªPØösu¿Ÿµ:½Kç¢Óm;g­�ÎÛ«ÁÀÜŽmŒÚÒ�®5ž…7 ï„j+ÕðÌFˆÃE4¤N4¹vÕÊÍFSÿóBëžÁUè~Ê6áú,€È?먩Á1Ž›ýi6^U˜  ¬°¹yÕ²ÍnjèHÿóÃR¨—ÁbÆõý ðFÀjÂH¼%‹ÿlJ‡Ÿc3ü8�%¼bg¯<ÍðÐ÷†Ÿë˜|š`ÞÁw˜!•8Ö`­õ»úøo¶xÝ”-ßç;Me^1n5tMÒyÂòïç{Ú;íÌæ#±)£:ËAxz*4É9.oÉÔ¼©,›Ï‚;”DKªš--ÊÏL\Å|û2W¶ÈúÍp‘& >%iö»¸ŽZMš¼�<¥ŽsÕ½ê·Ï'Ÿgsk§ gZ¼å¹ÌNo` ¶{zöƒ*ö¤Jˆ¨¥Ç’ó“'pÚZɈåàÈ‘Q;Å™¨¨³«¸ì�+”¿Ÿ´« L ·á"ÂÓÿžˆÄ+93M]G¥X’J‰AöØÄ¶Ïƒ< iN–“§UÞå|YËDÞ WÍakúåº?þȈøuÆ1?J,qòØÒYv”mé—Ÿn¿Dìµ;úä»”½Ó µÝg¯`H粭ݰzâå籊)„þd×*ù¥´WõÍ)W"¦Ž�ºed·”�6”Qû)h§Jf%ëÕpŒ6ÀòšG«ßo÷µÜÕðþ¥P©�ñiîìêü÷K’-L÷*M\û˜Dè,®TÍàÎRÛŠ£.©-_Ç<ãm‚GN±)+?+á£U« –Rµz�_µ3 faWm’¹�å²™îð¬àA¨Vq!q°l…OM¹ßÓ‡±’UýwÌgº«£S"D&Áµç ìÑCÎë$ÄWžŒ½É‚]/À!¦÷(öðMp®çC…´³È»ó(›Ñ"`/‚sÑ7Y¹¸«–½«H@۬ˤÅL@,àã3ë,ëH1¬ÕIWÖJ ôzʚʚJ‘^·°˜²Eá�l¬eÐV¿�Ï"­/,»¡Yäuâç–¬«~[$xç™­³×â0%d!Þt°qéÔ¾£ÑÐC’0sæ.L¨�ðÍMÜw8¦·Ì}Çagr¨*_ȇ?d!kâLÅ”ŒIäLŽÕQrl$„QŠt2ǃS ñ!tv�k±ÌB ì×v¯óî7gлj;—=§×\õº59½:ɇN–ݳHá2_XÉñöí¿+£ª—*ö”P%y™ÂÜ^9WŒ]ÀèA0&üª—I”{ rÞ+�¶§Šååùym¾¾a pѤY¯UgÑð»˜Ô·dRYo<г K̺©°˜jOfÊ�=•ú°räS p5'W)ùoIÎx Í‚,É1“2uak\$æ÷»ÓTð¯šÏš»T!áÒ}»«ð$ºÓ˜ˆÂ‰p–�bª[š›ÇûwÿýöêP÷Uë¢ï|ì¾702Aù†þg/9Rö´po΃I wU—”nLe¨µ@-&Ýœr È2£JyöXjî¶ÿn$fÎçŠ9c�7+DššŒ® öÛ3#a áWBʵ÷™“´„wiUBqÀ ví­¬=æÊ/ÊÈ2ùKÊH= ¬1ÁjŠL“ƒ§”ÅZ*ç3K˜L%�n¹Š¼Î êŽF\—˼ß+èûÅFŠßɘ:¿xˬ:«€æ"\üë¿è/1JßD'#<ê¹Wi1�Ò[ð}êø~&×—w€$k]Ýä°ÝÙµYÂ(ÕÌXy\‹¥£óJSu«Ð’¨9c.¶X Œ@ s7Ž1=Eì¡‹ß ¡²öÊ’%bŠ‚¥)(b_�B¼ ¯“`^ã{µŠoFÿ�¼#b¡™é(#vwwD[/2?Û^’Â�û±€ÍëaB":s½ N_§b9w<#wôŒïfÒ¸co½‚­‹ï†]—<ÄÉeoÖÂà¿âÒ}Rbëðåt¾�oiÑÞîͶ©ý^LxˆÒ*›EÄç?ájr�g‹óRÕxKÜíczFŒqá"Á:Ím9¾«je;o_칿.Ýsï@/þ"›î+ˆÚ‰ –^_Ûâ ¤QÓ2!f¹­¡…™=ckäWàxíÆ—Iñ˜aÂȵ "îr¨�×UÏäþÅ�xåæˆ‘8XRjV3=4OHâªÈ¼ÕZUåef-Ÿ§­NÇ<üQÉ^¶—ø%Ž—R­Ë4]ëÿSÓ_OÓ—Nf wôRÐö7r”¸¾Tíó n-lŽNyî�[Dq‘ó‰ŠÀÁ0ýô¬t%Úyc Rärx}žŽ`fãzÐÐc™1”÷š»¬'‰Š4-¼Ú-�§dÿªÂhô÷šU“žUxV–'8?¬šWÜN†Šzš2“s¼z„ÏÓä¸Z€�¡Z ÔÉÆ�:©ÔÉzá ëµ<Ú‡ÞøÅe ¤½x/WS’˜xueV>HT:™Øß9YæfM£aOLݦJšj£HËfŽ�VŸ·ÔlÞÒ‰œ’()d`DŒŽn› 9!¢©ºI÷W©Úœl@Ã�­qô^þN‡’ãöc»ÿd“ܨK1aÊev¢‚Û;i�‹X‘ÓL«@A¯ˆÝµN±2ß¿ÜþÊlg¶¥ÙúÊ|0¬ �¸‹�_%U-c¾¿ÕMĥ봅YEû4>^²L.¹Æ0I‘iÂ;nd9~�«söÑûèøûíï#³ÈÑb¸Gÿ¯Ã&—®r›¬Etÿ«a•%×'¿Ð Û8—XáÔ¦æ§ù=nú gŒXüõÏñŸr�·ˆ<‰5y¡ª·mù8€)=ÈÙÐ1ȺË#|[³µ]‡ÍçÔ÷®iø÷ïOÉyÈ|€#š¸Ãiæa™!E^c8uÑ{²˜Vb5÷Ø–¢ß¼Áœeè¶J׳1\'§ zÆþ”KNl¼õxœí[moÛ8þž_ÁÍ�ì*vãÜí’m±iê^� œÛØÙ¢X‚,ѶYÒIrÜ\Ñÿ~Ã7‰”HIv“ûtÆn‹Ã™áÌð™áˆöÑ›çú þðhØï¡>úu„ÅÛ<ÙæNžºÞNŒÐA/NžÒ`µÎÉ·kñY^�^�~FÿZa�$è7g8EÖýùëj»Xà�·î¦ý3ŽW õææ¹‘_áÂeߎ2ì£mäë|�ÑUâzðƒ�ØèœfA¡Ñà5²Á1:î]OñmÜ'Å9Úfx¢ëÅ_=œä(ˆ�o’0p#£]�¯©Îe@x|æ<âEˆIP¼” ‘›s¥×yždÃán·¸TÛAœ®†!£Ë†7“ëñt6>�ùŒû(ÄY†Rüïm�ÂjOÈM@#Ï]€ž¡»CqŠÜUŠa,�‰Æ»4ȃhe£,^æ;7Å„�dy,¶¹b0¡,[&“¹:¾š¡Éì½»šMf6aòi2ÿx{?GŸ®îóÉx†nïÐõíôýd>¹�·èjúý6™¾·s�ü5IÉ @Í€˜ûÔn3Œ–1S)K°,–­¶î £UüˆÓV„œn‚Œ¸4#qAØ„Á&ÈÝœ>ª­‹ý%ˆ¼pëƒû+a¼>– ƒÅÂÍð�ÿ¬ßJƒYî/â8¬= ¢¼ö ¨Ïvî¬Æ?…à…¥œzqʸûxD}º¹sîgcç~:›_½»Ë3Ãt˜?%ð›ÃÔ¯ðÖP8¡ ¡Í‰Q–u’Qrœé1BÃ!š|ú|�’Ô]mÜ ô€q"Ñmó ¤&ƒ@Ùz9Ú…NAsyPЃÞc/tSî‹‚‚=hnÅb#±u!á8Bĵpvá&pTÏ:9úv„àCÍËùÌ.œ�� <1åEH‹UU¬ˆP¿öÌIòô²pVÀ„‰áíÅ~ÄQžÜADkäQ+Òùü£ŠbÓ "0·�{í†áVÂä®1ݵ|¥°£]߇5ZÇ^ŠÁ’þq¯­5ÿʨ�óKò'Ëè²A2q·S8³Ñ‚˜º4~Š7€�Y'-¶‰_Ñ‚?i×¢4è &ð!ÄÓø©³8½,_<’ ‘y#Í\Ø5ñcà3þ5…=§ÀŒY(ñ>Ò~ú‚1‹µBæUò%‚…óuoWëyâëÅ…ûF±_Š^d<Ê!qÀêTÑ£Ò¿_²=yUlE„A‚1< ¥�ó‘i£eo´õ*2ujV’®|A¥;Þ³Ó0ˆˆ H(Èpat9eØßTctªðã›!£4¡ V|ŒC?£Ê¥8ߦztÃ-…¹Fë{°KäÄÈþ܇Í"<œ®¶WEë–…­î‡Ø‘ ­°‰fßÃnºR¢ä0'É>Ø~nÂ�œÍr´Ñž‘¢ÂcV6¤˜"$94X†xíó@%Ì¡î䜡pheìmÆÔƒ”š·°¶%Jªvÿ?©2VéK=–)]õÅo¥x&XÀV)™ÔN{D’N$=TEm}.ˆ!ã¯Iœ’vƒóuìXˆð"�>ׇ04v¿!OXÍ•‰¾0QÌ#%ûGrû䆔Ý>¹1áö޾5!ƒ è ãÕ û”Sì1’Ïœ‡‚ÿàxiéÙôz R‚%²¦÷77$˜$Æ=�×dŒQJ£§oµžmJGe¾ì(˜¦&ÿuá ­”g*WáÂR- ºÒÖ@(W:uÒ Z½KrÔ’O€³;s½šýTS°ÇS�1€zËξ,¦Ô¼þýHŠEr$rr#ìå.:Y°ŠÜ°´ÂI{¤œ¾U ¶anãÉ Í’ Ï|`j8XÏv»ôxr7(À÷ç‡h š�QÒƒT¸¢!âY5J2�0Gü?u~i›jB¾>õRï¦úþ¯í�%ŒU¶ÓKùJŸùìu.—?'²$·Ä§æ:–ߨ¿z„{,#[ò=[G&÷´¤ýYlK›¼b4¼ñ6(åsö0ØgÏœajñnØ'ÊÉæä¤�ª›ÖqD¨‘fÞ춑θeÌS´z_î³ÏõœR|¼Pd_Ç›ÄM!Ûti`«6¤¡ý?l¢ñ gCÏúøJ,ùø½I7«!òPÙ|!ŸÞ²ä¬¼ ò/Ð$Ç)}¡÷àå–/1Ú êe“{u”ŒTĘÙͯó¸/÷lñ‘.�új¨cÛ�üÿFµ�±¨(g ï“W „Ê0Ón$0ðÕl6¾›;ÓÛ9A£ÒÄ Í%òÙ«ÁÔe‚h2q¿šYÒiž‡švq°ë­ë”†6�z[3¥{{\7û\Û©q8Dsr—eÀFãK‚:áI¼!‰b¨|�QJî‹”¯nÕì/ªàmˆ‚—Ñ�tÅGZéÄQñz—ß à¯Øé«àŒ·BÉMò[^úÕ‡ÓÖV”¶ �VÁ¦�Ü&ÁF„Ô„'ÕÃ’¶5-ç öûå>àÒ‡DÿÚ—°iŸlYÖ"û´X|b¸"§oõAk·tW~ªtWtE`S^ *Ü:©©"£oíWOE-äu°jªM‹SÕN­*?T!fØdé®Í„&• NW¬­?¿/]PM)�ë¥W�=rMÒí“Úÿš“j9J{ÁîªÖìjÖëܾ¡ ­¾xÁ׳Z!J—å� ¾�$9qOõdhÏèön‚¨[«—ÇßnMî›¶áŽFƒÍ£ô)!Q7Fc4*Â9v}¼ïi‡nŠÂD:MÇ›º£÷­VÔÒL‘"™KõIÕMGŠ~ð}äh…Ö\šäýà—V@ŽaB~ÒmJ~&âEŸê†Î î¢�tʯ88Sj2ê�Ý‚"ûóKqÐøFú©ôšé±Ý0}·EÍ=ÏæÎõÕlìÌÆÓ9ÄÒ�Õ£×rTU2LaVÆYHR~³ñÜâ§‘-¶«—�UcKFú@šâÄaÒ%Uå^\Öá†*U�N—îB‘«¸ÒÍ%î¦ê§p[y j–Ǥ�R^€Ì:\R㔞‘.UÆ[rû«µ!£¹+BjðBû ÊAA�òHš™í/WsS¤«`yê’ë\º –ffåeJ)ŒÄKWw¿RÞÕý¦eveC†›CúÛpži Þ>ýQ)ûù6æGç‚¢@Uj ô :/Ž+…ˆIäW¯¾^•ðšÌ"‹é÷LBEµ¤˜ÿžú‡Üxlu@ƒ=¸—Ðþò©}F¯P0üc|7ùðÙÿ^fhÉb_lÝ ð�%¾'÷ŠÑ):ûÒò[b&o�W¯tÎ)Þ~üˆoÄÖ—�ã8÷ÓûÙø½ãüßO­~R°³pÔów}X!Ä��KWƒ)jª­r' Ý'Ôç¿ÇK1"N“=­¦ãß�Û;çn<¿¿›Z…ñiÓÂFÏC»º:wˆ¾1S¬.d//Y ‡h šð®š -ÈpøûÓ·øŒþ5^<À  æCƒ]àùtözôW Ö˜þußô÷Ÿÿ_3ˆ!LFI{ØÀhãñ™qø\f!A*­Ž¨n�Äg½D£.DçQÕ‚®ï[ú‹¸p|‡Ê÷5üwØäQ1y/PaÿROÿ“f½øõ»¼ZÍÉÝÔo©6Z.“Ï U£õzg­ré4Cɵ�f¨à�½c˜Ð\RŽlfýA¥æh›÷ZÌ“ó¡ð{C.(9ðºçÌp°–®Äå4.{F­ÌÌG™�Š{ü¹²[š v~ ¡GM††<Àîî:xH�ÓËÃ�]ȶéö·fݪLÙŸ·5 G‘gB¤’ß{M�ûâòè>^2°¯j^Þ²5[PJÖ‚\MŠEgeL’Õ²ö·ë{¶YHññ_·¹«ºèxœíksÛ6ò»â›q$Ÿ$?šWíÆSÇVRO;gÉÍennXŠ„$Ž)‚GB±ÝÖÿývñ >$Ú‰ÜÌÍi¦•H,ûÆbÎÖ&yý­>dskmkss�l’ŸÇAHIìF4ìyðB¼óX|›“)ǧ#ý@Z^›ìnï¾ §AÄ1yïÒ”&¤u%¾žÌG#Úó¦m{LyFÞ16�…NO�Ô"§�G£”údù€ƒO)9Œ]¾ÔH‡üJ“4`Ùím“¬«¡õö>¢¸es2soIÄ8™§p)üÐ�ÆœñØ,7ò(¹øT¬£°ôÇg…ƒ�¸ à.AÞ ›€ÄåŠè)çqº·µu}}Ýsµ=–L¶B —n�žõÏý.P¬f\F!MS’Ðÿ̃¸Ý7Š� 9¿’O‡‡gÓþ€œ_�£ó³ã“áÉù<½%‡gŸÉû“³ã¡ .X‡ÞÄ rd(Jê ¹ (µH3IRS/°Mæî„’ ûB“8"1MfAŠ*M�@Ñ„Á,à.¯J|áB[kk "/œû ~i¦ÓuãÝOa0¹)ÝRß½é�1˜rÞß%@Œýî:L¶æ<·¨?¡)Ž«ršrX”�­-ròéó%‰w2s÷È¥ñ88&9¦^è&Š“¯rL…°³µ9£|Êü"Õ»ßêƒGŒ…ä:œñ+GhÄ ¢€·Ö|Ì·œlÊ1O:bÔáp &fi€2�xãfµ×þór<ä5iYk´ÿ¸Û0Á˜´žÈ!X�™&’°�lv÷ �ƒæí6‘+iÃÌV6¯½Ÿ%”Ï“ˆŒÝ0¥òí�ø¿±†Á�[äiíäÆ,žÇ« ·¸²ëû éŒFJ‘øY ¸N%P�Üžœ¤ð*ªäÊê�“Rî| Ò‚á"ÆmL“9m+I*ñÕþÚÝ··ö/,ðË®3tm²¨ß¦üôbˆò Œ\ˆ¨¤ê|8¡3ªe…5VÚCWm8B‹Lv¿Ù´¢c®D•¦Ò¾Ì¸cÇ"zÃiä?,ŽIl›ùƒa e8‰Qþ‚„!xa„ˆ³ËÓSòD(ÄÝL'`WrÒàwjŠ�§g‚‘…H´ŠM�²©\¡Bt0ZúPæ½ÌÕ¹ ¬É¶ç•p2J? ! s"æÓœ?Ïã„Í$ �"‡PûkÃ.ÚšKÙ¯c[.¶r×)èIð&—n•Å Pà1ZËá›�9êððä¬᜿mås:¶ŸvºÕ�Ò>€{WèÞÒ¤V¥vÀ-|ä$�'Ɇ˜ Û|7w£�/2ˆ=ò3hN`e<"1Òýð…±ˆâP�F0+¤.&º—†B 8/ œéý‹a+SJN †ž²1vr߫ƈ�i…6™IÐÔÂd©n Ñ£Š�G6#‡Íy d“ü$|�[ß P·UpIŸ¦º’CDçà Ý4íü›‡qÍŽlMÕ Evª¤ú{JBðb©×à”Óˆê:ÍÎYy5‹¬BÃk±.�°:-ËÜ,½óÀ]8­«‹4+úU-d<©Ú.›Làȉõ9æåå²�Ž]ë2±èŠ¾Ù¥±ñ:5ô“Ò•� {"lÓø³S¬ÂU\k0&jg,Qºã�÷�î*üÖlîâ§š´ÞÂÆ¡qéa¡›X=o«·\'� |�í¹«.nWh`¹ÉUµ¼e«¿’q%¨qBiY½w2Ø�Ìb)ÙÔ ýVÐÚÛÓáHßB+•oîïã{Ŷef<ÂNªi`Š–‰5q%Í·(˜@‹Ô•þq¹b¯äjîQ’�²Í})ç«ÕËXÛ_Á6'ºÆ«¥�zàì,mÊ*}·{ {1B-*ǹ„€IðªáW\T÷ýÌ$1Š`,ýp”:â ˆR|[9L>¡¢ˆÿà™N–Öà†9„gâ¹)MKY€ž…£¹å)Mê�ô_ÿÎÒ€?pƒ[Ϙ^ï¤@î:\™b=¡<²lfÎk=ŽFcöCçèpÐwý³!ói«-ò[Ø+.4QÔké Í ?”n€Wâ:êÖ,ÐS#¸ö ² ­QqÏ`£,ÜÄgײȖ²ý=�¦)KÒV˜Õf\)YUmjv3EdiŽ»ä´|ÿl·Ô5Ýyad»™“Öw2•ÁYmŸ¢‘V7ûŒuìôïÙvyH'|?TŒÉo·b$Ëêv²,ýÿiºF¶£�íÈv~|n"û±—‘Ícç¾èðŽC¤/,L\ ¬ t%sìî],Cöë<þ5»íVûŸðç’:t½†O ˆµoüóÝb3�"–e]C�—ð»ç! è÷Ï‘>µ‰�lÜâ—:kËÆ1cÀgâÔs¡ÝlA�-8S‡õW ÅÞÛÀÊڃ녰 â p™ÈÎ&ë¨ ¶·Z;Ôrm[.9�¥Ép|8¼ihár ˜ìàÍå…`…‚ée®ƒ³““ívÛ´8·MÏ_œ8Ѻà¤ß;ïFÝcäX@L\‡*ù?ꣴÓ=Xkäȶ¦È§cmÁóÁZøçB�q¼õiHÝ…�7·–Oš BŸN7¡¢°˜?[^€*³\8쌠7:„÷�Qod0$W½ñÇËÉ®:Ãag0îuGp9„óËÁEoÜ»à¯Ð|�O½Á…Õ…tÈní3 �MÊTIf\o#Bæ^ÄR°&6�SEskA`áÝßE‰`Mü ˜IdpÆÐ8tEC+äC9¹¡“ƒƒP×v634êªËCiüµC§S+ 'â³¹|+MálêyNn«c[k�LÏŽÑ•‘ãcÛó#L32§.�«þМŒºæd0wÞ÷»2¤ãŸ„û5:~3d>‚dÍó€’D¶ç2¿#>“(¦îzªC›�:\ì– à‚ØŽå Í=(°l#4ano¡¨í9hÎTÛlº×ÆÁÜ:«ð›™Ì™!|?|8– šùBw!Y7ä l:£”>Šg^%¸.1yP×rà†úá?W$\z$xb‚ùZ6J …ÐÇš'på Ã�Bì¶O0º¥µ5mHÊÄ ½„X§dë³ß*o,ÕË+ʲY}Ì•ÇXUv¢1Ž6²[®üVxÒë~APï)ð‚T•³�Ýif`Ÿص³+ÚUxZbýÁ�ÿ®¹d#þ–æ43+´î@[ëÓ¢W,yx›T¬Ï%eAÊ�d‡ìÍX÷Á&ä"!”�8µÕ¢Ð‹qÞˆ²×vÁ�Û¢;RO3�e ¹Å‰>�Ê0é!:5thÔOm‘]»»µç³¶3²Ä=»aW8~¬‡!Ì÷*�ìP¤àZ¨¼„§qbC­§¼|Ö¾K)C¡‘þÀ@e:ÞbAf¦m9ŽgG¤ØsŠ->ý/ñ济z=*_tµÁ¤ßgzV°Öñg¸ñ]`³ÑZeþø-‰K=IfE$.‘»�,+Ú�Ë[S  ¡ÑD0>Qcˆº4¬eH*]W½.bHG7NwªÔ¯’õ9ñèDÔ·zL+jÀ“Dj™R¶�tYY†¨ËúdÍ;ªÑuY:ORfÔU$یڔ¦’—�BØòÖ¤L†²:Û[h_¡‰íÎãG0/&E¾q[èÅ!šDÑ/orQ’kdU§”;¬„nV×y$Q+«¸ë-蓲/g}·÷Y÷)ö›rÛ+Ž/Å.½%Z#°9v|¹ üY†gɈ¥ÌZ¥Ìk”'õØ;ž7ËÉø¬*ceÿ1J—§ò¬*‰Ýïg4ª.w°¨¾gŠŽ0Þ([Ý{›hîxVsŽë¯_¿¦a= ÌÅ|‡[XÓòS¶7CÏäËÕ€Ö€#�¤õ5ý~*}oKß_~•ˆÆK4eYë‚ñ§4nnßTP+§lQ`J3Ò“ä� -ãÜ÷V� vìB˜÷#³3u‡ãš4yü›„;ìêúþLÓn"¹óËÁ¸Ót‡æå‡ÔåÍl‘º|_Vé±)í{ØÕB-ÚKÕï»x’æ_Þ$ÎBÏ?ƒž{ã}#êñEÀ'›IŠzØMõMÁ£Z©j÷ü#î’z«uÄ?’ÖœÏfÔvv–ßGÞÓ7'ç|> ®µ'‘×`…!»‚á'×ùC©ëÓ»µå[+Y±Òhî@:îþ_:c{ßʤ/íVT[Æ+Î3ž–“›ç,X”PkÁë7 ˜9~»ƒ£#ù×kýîìèHé­AE²W�ìóH’ÍÎÏ.‡BØsd\£ÊwÃH-éL¥]G€ª’¦´`Û‚èºB]ÀoA›ð‡µ‡)á¹FA©�ˆî;s E�Õ³\À<“¿Ë[ψùz,’jåíãEû’E‘�õXZÈ�ôCAÃí àâ“�§ ��<é<,^ˆ4ÿØùÔ²¿±€),K"$®¨ã ߸æH4µ]\‡_�¨Ç’‘7sK‹¿‚2%ÄÅäL Â^Щ- {Yˆ­Š¹©g¢P¾ó’†ã+¯—Üû=Óœ &£î…if/ÝÁËr:7¯RõÓóâ»ÞrîyŸöãÉ’}âÈ[ôØ–ÙA]˜{ö&8Ã~!rÑ™[â�”n9o¿Š•L¯[YÅAbüüP‰óV‹pÕÿcüÉüó²7£‡œO†£Ë¡yÑýЙôÇué$`âÒƨö¼Ž$NäkjÜ&›«8Dp Á‡Ñ�ùgþr[¹HUXµm*€âög"c¦Ð²—Ê1›ÕÝ''“Á__“ åïì^ðPåýÐÐI?ŒBˆHZ=\4§B«òÆpêh �)cÜ�ÍóΨkŽºƒ1æŽ~­Î¯„UÁ™C…ùÜ!¦¢ìÃQ�ºãȯY“lH/ú!'%j/>tú£û0r�‚Ê޳IJ§ôŸö}6ZÌqÿû‰Gµ¶ýxœÝ[msÛ6þî_�êfRɧHNr“ÌÄm¦²M'š(r*ÉqÓ›DAÆÉ ËnÏÿý/$Á7I¶¬¤wü`‘°Ø}v±»XÂíCôóS]è°}Ð><<@‡è—)õ b„Ó?ȳ–/Õ{/Œî�Í…|:�PÝk —G/_£ÏsêÓ(B1á„¡úµúýe¶�IË›7²Ã`È+ô> g0Y¯wj&éQ�œLÐ2˜ 1'¨a~LK}!ŒÓ0@/[G¨.;ÔLS­q,IÜ…K´Àw(Zr4(GJ&rë‘H  /\D>Å�GÐŠŠ¹šÇPiI_ �p,0tÇHʎ©Ýaa˜ž ñ·íöjµjaÅm+d³¶¯ûñv¯{êô‡ÎsàØŒ¸ |Â9€üï%e íøá8òðøôñ … á#Ð&BÉñŠQAƒYñp*V˜IfB¹`t¼Àbþ@l»@†Të QwXC'�awØ”D®º£—#tÕ :ýQ×¢‹:½èŸuGÝ‹><�£Nÿ+úØíŸ5¸`r1)°I%”d¢p’aaj–xD<:¥ˆÌ–xFÐ,¼!,‰PDØ‚r©R N$Ÿ.¨ÀB½*È%'jü�ž¿œ€úSS�׬÷?y˜²°5g½òéxŒ9i›ßòF‡‹lh;aè_Sч·å “bÐÂËÛ¾]P'V}&dJ‚®z÷r踗ýá¨sÒs¬Á+Ÿµi ›bL Ýñr:%,;‡ì¶Ôo“ÉÌÌ/�œI[@ "å®!Ôn£îÕ×K1<[à·èš�èüøtF<3£œ�ü�t8hz&ñÊT€ùKMÊv0Ý¥'�»òâÚMÑèÏ—¾ó†8‹N@rœlÓc@5Ð;{q9B¶$TN—Œ‘@€/˜€30Ìè ndßeÀé,€µT¸ÔÈã”-qç“5„<O #`^>®ª7Ìbô{×3‚*RcîΦ·`IÍû,‰™ D|²�g8´Ìó$-TOpèý¬'i•;Qö‰Ú1v!­bŽEùáÒÅxè&¤“¢‘™ÉÜ ¬ ÞÕu{üV ÃøÈAÜ1´Æ°´‹´ÀÃ3¼ª—ò”Ò Õ|¸}4³¶©L1Ç’æÀx þ­9‰=x÷Œ%ñ_(Kì£ó¤ �äV@Œ�éAznä‰ñÄkG£�Ò¢f}³�Ê.š¼G??Ô26/6™QSžäþŒp�ÑH—’Ó ñ‰Œ†*¡¨ñ'˜Õ ‚îcâ‡+T»¢Á$\ÕÏšÙ1�|IÛs+”£ÿù¯„דáçž{æ OÝÓËáèâ“6yÕΩïך4 IÖ:…7e6囿ÉxÝ5ž¤!®îÖ¿ìõì¿�fŽ“Ënôú'ˆ\­[ò2W�Óß7›éŸ�?ˆ%�sË9Ær€«Ì9û�Ð|±yÖÓ�„=dZO�ÈO•y[2ñÐ�$®ïôê clrí9·QÈd¨WË##·Y{èùS]’`‰Ê¿òÀo b»­•Z ª¯¾{œÃjèe‘ñ™± Ìf40É"wÙW°°î�·ò§ÐÕg32q=ìû¡—šÆ 0èNë�†&N§¨.�Ôe†jÅ’jié¾™öçïa��|N!Éþ�æJ4 ¢þ,?>N˜RÌ_W½O£�îÉÅoA÷÷‹þ¨Ó«îý¬c£aüS�qÎÊ}œô/poé#vÀ*0LRÌ+¥lé;0¹õƒ¼Z?:Í2VPh`sÔ39è¡4S±­\ �Z¶JÛ�³÷ŽÛsÎGè?éóÉÅÜø&+Éq°7´¥áÉ$AdÊÆ-@χ‘2†\}­LÐç’Ê�ÀßìÜœß î1lö†v^¨ ¼³™öNpºï?<ÊØ³<üÀŸ“)E_1˜é Û›§Ój›TߦØÇÙDg?öÀB~ç˜eÂs?óç6þÙé‹Ú‹�D– ß…Ë8ë’{�x²êíN©44¸Á>�`&�™E¹d§»õûNJ+Cû��“£òxŽ"FŒÊÃ$²Oûs¡v»hŸ¿ýóWy6 Î(I&ôMúeÆ>Öq˜Ù§-¦ˆjü�Üô·Ð’:©½W/z‚„˜Eßëdá&óÖ [w{Š„Èúj@Åö¿ÜâøL/=Qåêiäæz2c=³@�m [°xê”ÃãoVܸ/ÛîeÔjÖ)é'$ÖA£’Õø_T y’k‡ÓYf»d�‘Ý + ü¡)e ê5X¨ú5�ÊU;‚g€�^8`�’­nÙñ®¸‘[çþ”IkÙùåWô"Wè>þ áÈ=í �ü7ñ,#ÒÑWuܤ“@Ej茴»ÔïÓƒ‡ÀÉ1Ì ˜'÷ŒŸ ›†lÁµ©ÈÃ#œá atJ‰v˜Æ“s.ñy¸OÉåáMâ 2Iw¥Ûj7³ÿ´¾Á'ŸyTOÓ`Ü“:ó¡-v­M¾§£–ö_pÿRÖVn£ðâ Úþ‹{ÅI‚Uì*Òí‘ÒðgÐ=ÿêö�_Ý‹�;pF—ƒ~=–>>Ä’ßÓ–D[aŽÉÉçuŸÁ Åä-Ê·…™L&³ë\å…öÂl:qÙu²ÂGÕ±ÝF¿j'×Ò÷*£>w­2U�wzCÇRQöçïLW®æüØ��ÆËÖ"A¸œÍ�°—�ö²g'Õ‘tìûH‘ू�—N oÊFuå9WexõªQ.˜ó«%•\�k*™L£ŒÃï¡™ÜÄÕŠÙ(üëâè[K¡CH*©GÕ‘ÂD¡Rq¶ïm×§½[}�lؾïM‰ë{»¾ŒA—ÑsÆ`¼á˜¼ÙÍöb±;XZnèw4´ÂÔ·Û |³ÞBÿb.çåÓ+ðÄU”*Â(A¦m¾u,?à¿x]þ÷ ¡O†þÑð³LÕ‰•ÒN¶”bÒÚ„BÃÚu9 Âpjÿ×Ücv_2ÿ/‚èI½®xœÕksÚHò»Å„«s…Áq¶²Wö%lã,^ëË]]i…`ÊBÒjFÆlâÿ¾=I3z€qìÛ[ÊÐ<úÝ=Ý=J«ŽÞ=×Õ[­zýÕч9q1 1%¿ã©Zvˆí¦3bÒñƒMHKÆŸ.âTujèäøä-º^—údcŠCT½ßÑtŠ›Î²fnƒ-oÐGß_Æ^ïB!é{ÏPäÍ[bÔl¾ÔLý‚CJ|�4�Q•/¨¨©JíŒƒØøZÙäù E B‘` ß;8`ˆxÈñW�KlÏÁhMØRàQPšÆß2–ÛˆóŽü¹¾ÙL½d, §­Öz½nÚ‚Ú¦.Z®\G[½îE§?êÅjÇÄs1¥ éß"·Ó ² È±§@§k¯‘"{b˜c>§xF¼EQÎÖ fF( É4b†Àbú€m}ˆÌöP¥=BÝQ�·GÝQƒ¹éŽLÆè¦=¶ûãng„Ct1è_vÇÝAž®P»ÿ}êö/ƒ¸¾BÎ�I¸(ñLÈm„±AÂÜ—$Ñ;dN`Í[Dö£…‡C8BW„r•R pÆÁ¸dE˜ÍÄPŽ/ލupð7â9n4õgìuYÑ&ÿéØ$ô›Ë÷ÚñÛ˜š£.™NmŠ[ê;;éE÷-â;ÂwØcGŽ?ËB l›Í±µ½–gGàÀ/ì %䞣›ÞКŒ:Ö¤?·Ï{}§B†Ã¹í€ Á£5�æsfÀ2Á�XáD!õ·®ð#Lì^a¹68RÁˆ·…g Å=WÏ<χ¦ž˜êŠ6¶˜ßÃ0Œ!Ôj¡îÍ— B{±²OÑ-Æ�¶RÈÜÜ„Ü<ȧ Rdæ0Ô=ð…Í�œUsd퇷<\,„¨ Q]bǵCe­ß…yF#0|ÌC 8+vñ Œ-<’AÄ9äËÁµ#‡!kí®Ø­ez�ÅÐ×2")=EçR|?Ÿ–{¥Š`�ö¡|“š9K@ xÏvÑ Yß+Ì–>)¸�4Ás�Á§F­»K�úÙ’Hµé³ƒ5Ø’ä ¥ïÎxèàÈÔ„ÈöÙüì \„ J%¦Ô�ëñ+�´¦±»§ˆ‚û TrS”K,",ÇÃ5ÉÃ8¸uq7 >ˆ«p¾%§¾ï¢Â�Ú # ÞÌ_óce½$Î2cY绾· M4æ‡8·I±D�QæZÀ3Œ§®Æ YvxÌl6ÓP&ßc¡ïÒxÁï͉i“ò#‚ÏÙÁðÄŧÊ�Of%~ÛDk€¿©X!Cõø' I„‚*„ºjà‹ šdÌ÷voˆÀ+e‰ëñ�–8TøbãY’cp9 íµ%¬­Zâ÷ṏÁSÞ2ë,bX=J¦Ô’šƒS¿ê#�’…6ø”ð¨—^“[êŒ&8Mr)Û@Ȩ‹¯ºº2ZÎ{ë™3]cÏ•ÿBæ"T#‚·LVKã˜|Hí� ®<3Ž•ØL¢w*7•ÂÀ£ÌUʤišÙÎÍæò†ðyVuî?äÉ  ÛO<¯”ÑÑs}8À2k.—6[-ŠBZìi˜�DÄ�ÚÁWm×cœÄíú‹ÄlÇv]ß‘Xùç5dã°ØŸ—¸b­&£™£jÒëq 8'Œ±(ô_"7œ�¬öhÔŽå¶WïPÊ”‚™‡sô>]„ôå„D`­Š¨š`b’ %Fþ€µ7½ÏãOÖõ Ûw†ÖÅd8 ­ËÎU{ÒKLJ*8Wª¥kÊ8{EêØ¹üرÎãñàói"Ýí‡�Q÷ßkt–¬Ÿ’Û³RÀè[:Òë\�÷Etó$LÃîÇŸöFÕ)Dy¶¹,Q ³ ÆÒêL“‰âB®Æéò~¿ÇT*Ü !eÔ•ÔÖ>ÀÇ·¬ 4¹2v[ÉñÔÃA1±çAó ^Ñó!)GUY›ÕžZÖ¼HAà ÍKæËâq�ÅÖæÄSªH/ýq»Û‡Tsp•š¸Þ˜(Ù@åjHã}3À°VÑæÄ}ñ¼¨8?=5‹âfr–þé žÿ3•&gT–У÷ªýåèù¸/Ѝš†Œò/¿Ÿß1dj?^�O jê[—ƒ›þ©v®”Sà_C—Ê.iÎ[S¼ ^Ü��Riä‚féÞªU«¾œí‹w¯Õ«²Ç~þQea‰$'×�•£vl?}¾,MKV• ¼ôQ¥n¯/�zšéϫιìÏ󖄺"€’,n26ãûíÀíU�À’þ´æ«rpQnoQG¾ªVyÅÌ´Þ�ÇånÃÛžg=@£úÒ³5±zxA£¹‰=EvöB‚á{°Zœ±Ž4á×ãÌ_%€öœÔú6ÉsŒ@yI©3‡ßÐØÈÃkwƒDãÑæm�_sU˯òBü¯ÕÙ//ýr™rJ²â']‘ša¶ä:z¿Äü5“‚l¶€2úP!ñ×>„•¦˜ÒÉyè¯tb2€è˜ÿí¨�óå¢F» ð$ô¹¾äU�ŠßJ";(ÉË �´¥~,†¢Ås£æSQ¾嫓 zKDÌ)þ»–­E�¿’nZÒ.2²0Ä#á)±½J/‹RÈqú™òyfEÙª+©'&aˆ¿1ðWùªžÐoí8DiG`>âÔ)¾m<ó8Íiîº)ÞÅg­Ò+ÿxýÏ“§¯ü–¢Âç+�” ôЈ/Æ�ÑØºh�xß¹?†„²W­‰#3}+ïé)^˜—y©€7ꌥ²ù™ßȾ¢”ìâ@]ý=wÄ2¦éÝú/í;âGá)úQ–¹ëÊ9[|µ|á%I¶ë3ܬ¡�koP]ýˆ¯h’™øB«vf¨å—ΰ{õÅêw~¶ÄõÀx2ìWc4 ²Hƒ™ol/óÈàj IJŒ`Î+ÂòHö¢±oµH}� £¦ðrøùúøä‡jÊhÏ?¾ý9.ÖÃx8é¤ÚK4’© Š¯l’»#G| ”½>3ì×r4Æ9Nº§˜²�Wÿ:Ÿ\Y�Ÿ'íÞȺîÜFfyv[Ò˜Í&½ ¤ ƒ|ÿ–°VŽgYy6oQ)¦Ø0i�‚Ûäo^PÍ¢®U‰´â‡öùMÉ «Ú_4©øË<¶Ãß÷æ/íñÞRS³à¸­·òyž4çVøNšÙ=·'°°�~¶-=ß[¯á¡ ¡úU>]lf3”²¬¥Ù�å®[àBÃaO/2ôDÔ…Mࢠ¾¤Ð]z¦·4Œ<@§Ù†ª ¨è©JíBˆxdX‘G‡MDQ†�´‡>8tÍÁ Àa«µï‘À¡°õøR®£¥4…ŒÏZ›q‚ä„íÀæû„@¸VzÉù:zßjm·Û&‘Ú6Y¸hùŠ.j =cdïPcÍ1 |EÒÿl¼­�=Y£F™¡ž>Ù �,BŠsœ �·¡Ç½`Ñ€ˆÍù–„Tˆq½ˆ‡ÞlÃS‹õC³÷ Ðe$€Jׄ�Y�Ë®90BÈÝÀºO-¸ëN&Ý‘50LO 7õÖ`<ÂoWÐ}†ƒQ¿Ý…ëЇu(,@5=áJêJ¿™”¦T˜3¥R´¦Ž7÷4-XlȂ‚ÝÓ0@‹`MÕ‰�F¨ +ÄøÞÊã„Ë¡Œ]b¡ÖÉÉ_½Àñ7.…úÞlF"ÚÒÏæò—œIÔ1â驈».�§Ç8cþW�·ôq 33 ú„“ïÏ4©¸ƒVq˜Ekßvi䨶þеå’öŠ„ /�3ÿú7ü ßN_—æ§¡Ý7Ìž=Œ¬ŸÎ«rT¼*wžË—•ðpƒÉ�áÞ8±D-K n Б0y´k�ÉÝÉõåYgOr�ù,oËWf Ó@|�Œaµvòt!\[‡O".àR‡¹ ÂîÐ[sÄÍ\þ¸$FS8/¨p`Ï…4جb?Úûެ¶bÍõ¦1š~¬VF,\�¼~´>ØWã‘e߃ëË�'»Ã}Ë%óÝ\†Ëñ°H~hryð¥Î…¡ïÝt'—Ó«½]‡&Êâ ˜#�~O¸”â¦õyh(õ¯º=ãk릣ép˜‰›4|rÒ«¥ +Ç'σųnn”/£Q& Lï¿å®ˆ� ~?ýNhêB€EÅþ¸¦/Æ&2Ú‚±™æx8è'8Sáê�‡ã‰-g²¸¼¹žtû4#—)™Í2Þ–2Þ3öKûÅŒÝrÎn!ëëä6ôü8†H},LHeŒ…qÌGžŒrC"D¿'xM½ B\@]‚¤7AuÄ|ÏU›ùKý%™l) ØkÏÔ™}¡)yX�…7ŠÍw›L”(X„ÄõhPTgž±9æ~�ÙW![•X½“¨ Ÿ#}ü~Dõ¶ØK„s¦þŽð§h‡Œ‡5 ES¹¢|ÉÜèu­‘l‡Pà»·z �ñB–* 95VÏhZt©Š«r_u܉ÔNTl#<:8K¨êQq»ÍÂ2÷[~¾ èu“qÁE™ Ñ`”HÓŠÛZ²ñùû ³ÈöÙ¢zib±·Dµ­Lƒ%6Û>†p¯Ç‚¿‰ÎFÛs‘p#[÷r<±ªzìI¾?kÉÓÛw†íy*>2©U&ªàÉde³ß©#VןÖïxïî‹õd0†Av;†º|" î ýÅN?×SÚɧ"Û�K8*½«;õµë½9TE‹&’0æ®Å¡˜?¢˜£1á_´YÒE‚ºšª(1ºÎ”UÁ4eld-W�8;bªw¿ˆ2•“%™º·ƒ­û6f§4ýßwz®IHVMIXËäcnOõ‡jœ*-Ï(½Œ‰Ë¿ýá¿?Nñþ§¸{œâÝOsr¨zIÕ0&“ñ$]5â³�ªY~qh¤ÞÄÈÓIZvR‘–ÍfžK ,ÙVÝÜ-‡% i³¢¥¦%>Å-É4ð8X4â¯lFöZÝýÉý>åiŽâS%­�c¸—˧ڨ�ãDz[À/›Vi�”�ŠšúvVš¨†rU%MÌöªÉD´×�~Ó beϦJ£Üfxjäó¢=¥¼¢ÍxŠ»KÛ2LËîuM£è†D1Gô°‹‡ÑˆXŒiX*i´F’0Wéåá·mLâÈDò¥L“ëσè+�¥û å gIB¨#Ö¡ZùVÉ?)Ê£)R${ÏE¡8‹ á— ³³ÎûöCû´ÝiŸµÏ¿T ™,vÀÒiŸ·ÿÑv¾TŠ8žâÎé™Äc¯EÊkátg±ˆû&'¤„Ó˜G6Pò¾×·ßÁ¢éecÀÜ“ÁÕg{düfãÞ<1¬édT�ý¬îÀ�ê½òØ­ÉÔØmÖ {2RÔ§îõ¤z)¹7×ò•4~ÛÓ¬¤"ª=¸¹·ý– ÚXóT³¦pâqrtÔ äpVËFu„t^Í:8 çÂ7y΃í9ÚÔiŸ¦a›�ª¦j§ ºƒçŸ�°`7¯˜vr Ë—,ŽU�œ7˜$ïžêC�ŽÈ!ùy┤‹dâz´ëÁ#öÔ‹âUâOg~;“_HËk“ŸÞüô7òyæ~“_]šÒ„´¾²ž.F#€2këÝ Ë¿�_¢h C]^ž‰A.}�†)“E8ÙŒ’ÓØõàñ¦C~£IêG!ù©û†´°ÁKñêeû-‚XE 2wW$Œ2²H)ÀðSÂfDï=gĉÍãÀwC�’¥ŸÍØ8Jaü!`D£Ì…æ.Á¹“h¢6$n&�žeYœž/—ˮ˰íFÉô8àíÒãË‹³^Ð;ŒE�›0 iJú? ?�ÙŽVÄ�#Ïž�»$QBÜiBá]!ÆËÄÏüpÚ!i4É–nBÌØO³Ä-2�`?˜¶ÚHæ†äåé€\ ^’§ƒ‹A�|¹þýêfH¾œ^_Ÿö‡½¹º&gWýó‹áÅU¾}$§ý?ȯýó¡@.‡ÞÇ ÎÐô‘”tÌè6 TCaq”Ò˜zþÄ÷`játáN)™Fw4 aF$¦ÉÜOqIS@pŒ`îgnÆ•æ…ü“zÁb Ë/uöRyúo�?¹)=ÿvgï•—i6†çú³¥»ìÆGÀ³€ÚQœDYäE�½Ñ"óù›1�ø!%_.¯�›AϹ醧.{j— 9ÎV1ðüå çE©ŸE‰Xkõ•®F‘›ŒëÚ¤@ ZÛ€ºYÝûûñÔIg4àA:OÊóPèL:§a†t.zQˆ"Býñtr?ZLà!ÇÇäâË7$NÜéÜ=!_)�•–~/ ˜œ¸ê“Œ¦Y50Ð[ É9õ7ló(=ˆŠ� €)ʼKnAŠ^FU9·Ý‚(„ž§‹d‹ñy_ý¬‹D{g̳¯²‹“‘o> úøžÂ(D\¯ˆf'ä4$’Ö »p@â)*>)’oÞæðó“ládN³YJ„)–| [t!# Ø£rÌh8F�ô«†½›CG Åml|]Œ?@æ“`TòÁsûCù̉³äíAÞ}hÚ¨z%hhÞF-HÏ Wg‚Ð`¥�6ˆòÜ2 &§äôóX$àePÉqà®�Ю\rÔ{Ä  ý뎎ċÔ:Oèd`”yæÏô©^‚€7I®‰oÇ JI´º%©? �A€'nËŽÞ#²·åá�@B£/vsÄy[…"gÑt;Ú£¥?Ë«ó~M]ìßÓ ˜¹ª £~< Ëa°ÞZ™Qæl<Í(˜2™2uéHBÚå}Ê4LÖ€¬’Ÿ8$…’ÕcÏÝø)F0ûnêÚGü9ã¤k#Nj';9‰#X=PDuïhƒ¡%­ßº97Â`¡õÙÞÑF†/3Ê<.æÆJ}º^æß�…7ägnJ¤×øz‹‚µG`˜ì¬�Ãx{ðC¦èsy¼½aºüÐÏZŠI(LÚ¡¢ù:U*ÙÔÅà©[Sõè–ª*Ú¦›‹�ï"lÎ\9¿U7^» ’­¿\U¡ÿZ¦Õ>Ì—]Ť¤Ú™�8¬€í%`-©Ò²e±ÔúPåe),ˆÅzÔÓHÂ�R˜§2Ͻ)¨f:1eQlŽ ÙlÇ‚çÈÑS}`I¦ d<Ñ2ÍQ•ï[öÆ0%¢%BÚßÖ 3Ð8ˆ¦SK=7"¯õ—A5ML¶Ýæ!�?!­þÍå%R[õiIB³E|'lú¨ó ¦SßòWF]šLÝ•–mµññÁ'QÑøè½J˜º%m$ h‰˜‡½1ƒgT0ĺØäÏÆ¥p­Äfðõ]03ÓöÙÕ†–bim!i™*ðF�Ç;ãuÅý¶*õFÓÄ¥­™‘»ÞÏb  ÞÔ_:d“ñŒ{Mê$sAzñŽä=ÚJoòNgZkS…¢Œ¡X Gæ(÷CËh™'‘š”�y/•vÚœò¨–Šæ‚“}$iU©Tµc‰�(ÑÐÈ.ò]+S«~Í…ø)¸Óhìb `¶å¤cä Cðü&Ô¢;Ö*µ¯E²Ó°¹¹z6¤TÄ�ÞÃ7<ã´iûp1wêû¼*wšGcâÓ$Uˆ�Ÿ‡üÛ„¸ nò•J†,‡Ší²Ì)ˆ‘´ÓïÞÙª£’¹påEó M6R5fÊ´„É«gxM—vc7aáŠÜ�51ãRZ¼N.ç¨åÚ½šçש/g�ò×Siîí ¥±Ÿ^í1ue¸Â©¢õ6°~öìKYÃÊ„ÔË´ ‡oàlœi¯Í`Àk¡˜&‹Ð@<7R6“ïlJ»ì€¾U°ÃÚ€|&ÖY”9Í{ô^ìÒ! ¾+O?ó}¯‘¾+ö]¯#Ŧë8_Fÿ€îg™¤ö¶i…�$D�ÝM¾€/šªÛÓ]YpÕ�«´WžÔ’]„¢ Xð•.<�¦)/™ÚǾžà�çt0è]m¶J(u#f-§ßÔ˜SEÚ5æD‹4›ETŠšž¸à8TZ{å sµÍaVOSÅìºñ+^*Û�(Ý< iÒrá�Šžõä�*ÊÖ¥Ýw;5º~:Æfû¦óáÝÛ‚;ët˜Œ#ÔŒƒ¦Z­Q‹Ý—¬×•c–±'¥‘6¥9#^•&æQf½O§mK”)\Me½k{èkf¤wÕðn‚8ôÛÖ†ù«Æ[w6ÁœõTpߎÕ´,Z �8Mõ¶³C°ØwL¢É«�?ÿÅ´ªÃ«ó«Riú ùD“©(·˜ÈÆ&ÙÈxnj–÷QóÃDÿ0Ñß¹‰6DB˜éRï:]hï—kä?…­o‚ýI ÿæAõDžÉ Ø&"®žÄ÷åÜ„GþµÈÃl\ jÙó)SI/¬,%`gŒýÔd޵\±¦à¾­Ñg*¨Ø�]×¥ÔxÍüi» 5§ Çca颦ӿéZodËÖú°uïA-›2�¨óçš»Œ»QäbsTR[±>+;9’Ò%gnp%1ÆÃxUz`£’îfÕàyŸ]õ‡§ýÞµsõ±X#µ¸ÕÒ!6j7)¢Øa քॊ9 ={ĉϞèêY!ƒ™ðÔÔ©\g­¦‰ÏŠWâð�yúJ±òü‚Ãô°œÄºJ˜ÃŽ& ðÌ�rÔïÿOõþޙЈ$­ÛæƒõvÉ [km“^fÝØÃªº¬BÙ›Ý;ëqÚ�o0o‡°VNV|¿5n¼lß~I¹Ðë‘û8–Ä2 Ôjo×v·º7ëÂÁ‚FÖr¤*Ì÷¢CÙ¹e©«Šâh£°˜ïb��kŽñ¦‘•oT&»„gó(Í;–ÎN¯}rWdD7t•Þ⌎ڙ £¸Yçü8�ÒùŸ•ºÛzŠÞسúÑëmú3aÚ«¢.)\9ƒv1íM9i!æÕÎgho–(…jÉú:µŠz7…<*­ž¥Þm/Ç^’ßH(̓d¶œ±ÏÔBÂ~æ^K+JT¥MÜŒi�©°ˆEÉØÑÓBxÙÌÅs–Þ±kP°¥@ôÌ<ºãi»Ê­âg9T÷Œ2bÚM=»©û{š¹iíø˜üB9­ÁíŒ XY‚²8ªŠ×»�%%žwüß‹”u•�°Åïߺï�U»8Œm ãf·«¿»¹â  C°*î…у!”6.ö*O¿C^ᯂ•–Ù¯Ê×+„aóˆÑ’}õIŠ™0q’õ�GÈŒ“ MdÔŸq´ÀSE žý+¦Yá™—Q×3A¿‚›Q!ïf²òÑû{òϸ 5-VØBõŒÄ´Œ«¶^Ñ®‘S{õJ[R¼ßepÖë÷œþÕyÏùpóñcïY½Üõè=^³Óò³›¦ Bqd‰!š,~î=b\! 3•+pƒo•ïêA×cÇÈbÊ®1IEU»^éVWÞvÉßQ“‚”1ùº£‰?YqõÈdí9KƒÕ‘1jYã  ÅÅÅýFŠß<¹k(þâà?Õ)³tSv0n1ç·.5S×;OÜéT¦2¤M’hŽ[ßE�ìÒÇÑRÂÂ{·P¨\Еá‘ÙŠ{8pSÁzÀ cæ\æ€À¿ŠEúrùiø+h‘áðªïÜ|æ Ú%}JÇè¨àµM,·2ñïÏ®ìv:æ ÁB­:\pðÒ7I™¸‹Ñ—3ôb0.æ—å„tI~ÿr 3�1¿*ŒƒC4åàdÑT* ì!%’œ`[C†Ã땆.Ž9ôG ^JQ1pEö£>ÿm+$Wa0’DQ&´ªXDÏ-ã"Ð×la‚úK7a·8}¾îZç0ÍëÞeïÿ¸}¾çW_úÈ™ö“ü#Çß$Se'•L4\`ÏPG\\ÿ¿N¡V"0Z ï6ùwÃÊ9Ÿ¯.úCP4¢Õ`x:ì9’&'•-$­ÔÝ)æsQ4«êÓµúAßz³Ì ¢PgžRo]Cõ´n>*ROËýìiÃÍ0ßìÐ0ç±ïÀzW_wQcÇ miÅ‹û:8o®³ãœ?¶½·ã‡aß•a?¨S-ú"7P*ÕKhU-uͣėÉÈfÆ4ÈÜ š²mJ mÖ'……öhÞ´SÛ8¡`~GaŒ„z8‡¼£ž÷Ýyub“œ‘~(6¦ïŠ[¼rsÍ@^Ó)f¿“T߸��Je~·ÿ¹f‘scuf¿(§:eª3¡°×k³}_õó÷º[«A«Wž4YzæÞíeí¥OªBOÈE±çâÞ§1jË¥ÏÓ-ÒyAH, î(þňrï‡GOÇ!�¾âñ¹8em®-,Ø"*(ñe”ë%{¾f)0|î*&UÄóá­²³oä™ó*/*ó*êþæVÙt51_[[§Õîy›MntyØ£óæ™¨ÅåwKÊü%>¯4“”JÛRD/ÆðoÙÑ•}㲞“ÀŒ.Bã;G<=ìæÌØE&Å-¼Õœé�E<ÆÀ… t|Í å[]ìšó�ô Q>ŸLÊ÷”Ê^›R]M¶«‹_÷«êN4­Öt$öOUL󣌆Ô+V~ÐT¶Ø´€FåOË´´MO[ጵ榼ù©¯ž�¶baÍ'†E-½Æý~~’À"Eë˜[ðÒ£ÊÑrÕ‘¬ø�|ø– ]˵¢Ö� !ßv'½Öî[Î�m•n@mz�`ÕÖ -ßÄ×pS¸Q½­.¬íõ›Õª1Ú¯²�UlV[‘Ú¥·3�KƤ¢Bs2'd�áoZ1·¾sGDñ%¸Ë ‘[“f×;`ŽFùÎÓ/•nÁ“ÞÌ^uG¦QO�ß;ªj®Šâ—‘ò;F+ؘcˆ!*Î}–�ð›�H}Ü-†~xç>®²ÃöQ—ª,,î» ýŒU=âæÄ�bê¥þQ “yóQê°Çx²ÿ�K?@À+çì=X~wˆOž‹A€y¹¾ì…o�òOÖÉWéþW~¿þ7ò—y©ãû²c›yè(= \ekåd‹h F`Ø �³ÓAÏôúCpl/[mvQ¼Ž0&P2SªÄ î3@ƒÞ�s¦Î:ù¯E•S?„ðÔ:‹­BJ¼ Ûô‘~î´)4 ]Ä—¡ÄuØEqr�Ñè·ÞõÅÇ?œ~ï?ZrÞN;oÖ.ì´Ú :åR–÷.®j¡ër\—þjkÇÒä›}xÅß�•±0D¬ 粊K5@Ž“(v§ÜÖDÉb:3Vy�ÌŠ¶d\Ô¿Ad�ÆÂ6qãŽÒ¦+w ŠÃ†©Ãâ„ö6=ÝQ¤\#NZúj—†4o¯ÐkС¸¡¢Acy¡E…ðT‰Œ jcÑ‘ÃñzEµ9D®~·‹ySügHˆýàš3Šî«6ÚŠä”WWô¿ýõÍÛÊF¹ÿ뿾1ÉM絋¯Ql+*føi)aÕ!êÝ¡½¯ËÓRÞö?FòË…)Òº‚xœ­UmOÛ0þÞ_q*ÒÔVmÂ�6íº-¢kQÓ‚ø¹É5±HìÌvªiÿ}ç4@;Ƥ2ü%±ïîyîÎwg·G¯µ ã6ÜN§8YðÁ 6NHûê(”ùJñ81vwv¿�V؆ƒýƒ÷p™ð”ç9\0Ô¨ u[}Oâb>G'LÚÛfdò¾JÏpxV“ yˆBc…ˆÃ$ýœ…ô©%]¸B¥¹pàìCË*4kQ³ýÙB¬d[�� MA$\CÞ…˜àB™å)g"D(¹I*žű75†œFê lì ›ŠÀLítbL®?¹nY–«¼u¤ŠÝt­§Ý¡w6ùƒy\[ÌDŠZƒÂWí|,'�B6'?SV‚TÀb…$3Òz\*n¸ˆ» å”L¡…‰¸6ŠÏ ³•°{ÿ(ìMJÐìûàùM8íûžßµ ×ÞôÛx6…ëþdÒM½�ã œ�GçÞÔ�h÷ú£¸ðFç]@Jñà]®lä&·©Ä¨Ê›�¸åÂB®]Ò9†|ÁC MÄ‹b¹D%("ÈQe\Û+Õä`daRžqÃLuô$.Kä6{\„iÑõWUš47ŽK¶"¦¨GõG4½P*t’ã¿(†§•$Â×ÃI0óÁläOû§ÃÁ¦Iª\³Êé>é/�…É ó(iX@—E‘OeËuåVÖâ)õõ3 îr©ì¥ehé—õx•'ì½Ö²€KÉ#(ÓÌÜ6ñÁc2.¸iQÁ¡�Çcèl¨äFµ?@ËžV‰ 4Ö¶o¶5{Ç•\Û¾ÞÕ`�¯³{5˜øTÅÁhöŽ� õö#‡v�¤Ô!Ï`YóˉwÕŸj¤~é™üFXJãNÛ?€#ÔõT½q!P¬ã̽5D1‡EŠ›˜ÑävÈÊ' �Ï“�z‘O`IùLÒÑXÚÇ�#s—{ öñÄ\×LÏ8OÒŸ:�årÙö$·í˜M;¡Z—vnú×½Á°w‚kˆû($i ŒüoAîv¼/AŽ|oŒ|†ÞbÞ”œã±àxÉ(§Ñ´i<áK��& )gt¼à–À2þpÛæ™A½;„þ°ï»Ãþ°%�|î�þy{?‚ÏÝ»»î`Ôï áö®oú£þíŸ>Bwð~í>´€ ¸�Y%LìÙ¤B”$�rb±0‰KiB|:¡>n-š.¼)�iüHX„;‚„°9M…JSd0hB:§Üãr¨´/A¨S«ý…F~¸PýÒJgucè¾GYÜž]C4â|��Ô éx쥤£¿«'q)·§„X£©3™NVãÅYèt ÿùË=$̛νŸà��ÄX™0±Cú(Ø©ã)_ë½t„_Ãâ‡Ó’ø&¿®¥B > ã ŸÃ2d.ò:AÑ6Ýe8箣ë3âq¢§œàÇGÚ|—/•¼BS~¹ gè}šÆcL0±z!�F."â$âŽ1Á¡)¿x¶i�{ßÔä¦ïã4 Ý€¤¾[Þ€ÿÏá¾Êݾ~ºq?ô†×î}0:?S2Ÿú�þAê-@A`Ú"œÀUÙÿó³FkÖk%•½�çPZ”®"c?�½ÛMô=ùƒ„ŸiÀgûÓ w)€ZöÃi‰àõýptû/ƒàG†»e‡Q:ÔÿsPµ*Ó•ÄUË÷77æÿÃF³AïÆiÔžrãê­’˜‰p8'|/ô*i\ˆðäµ>á8ŽCS(4¢\‰°ÚcZ‡¹gMxŽ&o¹bã+´Ð¥BtŽNÀy£Ö {ôH˜bñm†ñä*]`4ßÌ7Ú« ›˜ œc8~¶„¾`L¼0%jô©D\…¦e5ü7 $$séW„»�4¥˜›‹ ½ ‚eêšRQj^ä 9+Û¥Xô…�ÄZe±~ó[›ÀŽà_äL˜…ޕЉð;áU6ÚWU‚2fÔÀØó¦,Æz ã0w„"ýLìù`ÀâÄ)-ªÔ…º.jO¯ïÕÆ.çÜÎ@d…q;Ø×ÇK˜šùoÿK«b¬·ÅOTöF‘øløµTã=¯ñ¡²Qèfj1ÕjajWÂ.T¦šµ26�¢Y‘MqK¢åX’‹ò°�>góùÌ" cÿÁÙÁE…p·®F æJArÏqr•à"âFPªôÛÍ4#s¬ØËakßÐuXøª„�J+/·dPd»SLPG±¶b"Ö/#瓳¥dMïxð¸O ã ÄÓ~…L`–ø»"¿Ìk¶9—Ë …P Œ=Âåñc~ÆÿöXÉlµÛW¹ýáÁt¾ñ“+ —eÄÛ�N…ìi—)4zÄÕB÷®öîÐ[Ç ëÀ•y]ŽHc:v¬r‰²rw&Â�vlFï‚ݵefBµ: ñó3¬xmØ“+yž�?ÿ¬Ðë¾(fDÜn`ZGÏ(O'|.§’Ê3‚2*²“[{íïûX^1õιÛÞlÃéìgÁÔŒa[»™“›p¨×\±Õ6±ªœK÷ËÏâ³o޶m¥Š‘‹ÚÞk­*ÅpÅ�°Oãìäz#ü7ítßÓ\ ðå1žL¹ÒùÎáÿ;ùœ¸ïE†Žãs—¦÷hJ¶÷¨0ól$®„;nEü<ñªAźmg�ÞõÜ­Ä3üo1ý]ZfŸC™ù<^�ø»,Àm' øSvÄ2§š±eg�˦ ¸®­ÒùUu7²¼øØ�*‹o-†¨qŠÉr꼺Ÿ»wƒþà—Ôo•ä=0ü5€•ø§Ðé±ú§»þâü¬²öÏ™oÁ†›V53Å Rv!á}IœRÑE°ÍԤ͇ã`}Ó¨”ÛɆ»tàô@À¬>Bȼ>9Ú­‹D*‚àóç]—l÷„¶zÖØŽ}wdoaÂ⹄QÍ/E‡Ê�b¡š¨Ø šèõí`ÔízwîíGg³°e…䨙#�K +Úš¹7ÁÇÁ R2ž1'Üľ‚£ÚR�—^ù岿ÓlÂ5Ãd�‚gžgd;zS3·ÀKA´¨“@4‚Ï��¿·¢#6ô´ KšP:8 NU÷3#¸YáÑ>?üê.™=°ÿõ‚Êwj7¢Õ+ìCýPì¨ßšiP?[ï2¢9¦-G°í7 †]–R2JÚ{«óÞ¯«‰v–“ÓlÁ[C,bnSíáæ-]ÙŠ3á p«M§;†Ôа8‹×`ûLfoñ(±@¸�ÃHÃÇJX*ûüºÚͲS;¯(n)[WâÕ%ì8w•­c{YùZÕÌË+™V1UŒîK¿ ‚9¼zÙÖt+W-•Ë‹ª•+•<§ÜG”†mþ /dدdHÛeSS]Y£F䈰fñ�d/H4a„Ïè¿)fþ%ŽìE%fÔô—|<5^vø ?¢¦sª¢Ao±OYOÝõ†#÷º;ìÛê6e¡5+©d£H3Ã2ì�”BÕÛòý$]Í­~-äÕƒNoE˜O…‘¹E"Þ<ÂÍ{,ˆ—QÛì¾Ê)w߸´ø ;2’mv!.Zïªov~–wÜ3yýÖ»ëüâŽîî{NFϪ×6½ð,yd׺½o<)Çd÷yÛÏV¹ÅV´UOKø¼ùÜé@7Àb+‹æBÀx(¢Š2§üo8#c¾|s,G8ÁgFêIöªBÅ´JïŽÞê$+ó誣æ%œ½ÛºD‡ÈKøûùEQ�æMa&Ý …æ! ظ¿U ÖU×2S3Žá#½]oU×FßïZ°ÚcÙ�gÈG¦ƒbÇSiÈf=TŠx«Q(Þq<îò @¾º xœímsÚ8ú{~…š›éBŽ�4íew›m¯„�]f)éÙ^ïæÆcŒM�ÍÙ&$»›ÿ~�^,K– & iv§þÀ’=ozÞ$å`½y¨íììíí =ônL|Œ’øxèFuÞ±×^8¿‰ÈdšÐ_ÍôªxUttxtŒ>L‰Oæsô³‹c¡Êgöùn²qÝ›Võa0ä%ú1 '0W§Ó“tˆ‡ƒ�Ð"ŒdŠQcîzð!ZjèWÅ$ ÐQýUh‡]Ñ´[=¡ nš¹7(´ˆ��)‰# _{xž  /œÍ}âFK’LÙ<J�Âø$`„ÃÄ…î.¢´£p¬vDn"�ž&É<~}p°\.ë.öF“Ÿ÷‹:íf«ÛoíÆbÄeàã8Fþß‚D@íð¹sÀÈs‡€§ï.Q!wahKBŠñ2" &5‡ãdéF˜‚‘8‰Èp‘h Kñ²ÕÀ27@»�>j÷wÑi£ßî×(��íÁO—ô±Ñë5ºƒv«�.z¨yÑ=kÚ]øuŽÝOèçv÷¬†0° æÁ×óˆRhÊJˆ§‹…Í…¨th;hãÂK�³ôgÉg'Cý¾ƒàa£ €O¿F§À :Œ¶ñ!Àè­?1A[N$� >Ó…#1€Y¾‰ùW ÑräMÝ(í±ŽÎ<‰Nv$Ì£ûx†ƒÄN^†§ÎkŽò^î¥þ= @Ì¿0.ºx¼•Ü›‰é;/×ôÃxXí®Òé†î@¥÷y… Á5´œâV··ˆÁXKøÃØ™Œ¯AÛ í¥}Ó²ÅS ýE•›AtÕ&ÈØAÛ@lK2s_(¼E“IÆÒxØÀ ãaLèzIAy6vƒ0±‡³{.€ƒMT�+W(–÷£Iö¦@s ø8ÅÌh|¤.Çãœöo¨ó™ûî pÓËî�» ˆ2 }û„²¯"ÀQ«;# Û oNß&h/ý Ð €YŠF-àÊ¥|ãŠÕâ(†`Ö6¦)‰(�¬±È] Ÿ¶%jújdºW ­­q$u•nâÞ?èf®’#0„5BuÃsìX°¶ÿü½.ò´ÿ¡ãœµúM§yÙ\¼çÓg÷\˜Øsâû»5”A´¼‚Àš4à„Ç~MBÒ_‹±)6^8¢¡{Ù騫µŠ�Þ�§/�,(ðuÒ ý0*…§‰Xãa9€šýå¡�‹Á®Sî/Ês+u0:³´·É+�ßY•¢¥2ÅöÎdÔe»;8~¥ ò¦ [9MYßìóèh=üSüö™‡Ú�D:Žû¥šþã…1éY»9P¦|ïF”#iÆúÊO D5ockuÝÔçaP’—cè)þZ'¥-ö)û-È㺭N¥ÊÜuv¿’(Y¸>š�Û!©d‰&èdT4ÞH5%XN#ÝXä"„g6˜[/Ý™]ͨC3<¼–æ«.Ü ¼Xã(%ægxì.üDñá¯QÃ÷E°£x.5 äçqxN½a¸}ì¼üì ÚƒNë´Ñs>ô.>´zƒONráöù'ôÇÊ^ÍÎE¿%ýDëzF4ëæü¾cZ%R*´ÿPh:Ç|˜a¾TOº$Á(\²®üÛݼèÎï%¼3h†N&`¦<×÷C/[:/`½@ºŽ+yÕ*LÉU¨q¥PaVQ„“E0ËË»ªÍûo%Ž0»Û™eæ5ÞLpªd,ªŠ`“¢õ,Ë_!mK*Ïs¤9l¶ÔóW¼Ó‹9?]ôÚÿ¾è�âÞyø)Qûo¹­ªVÅŠ´H$ U5žÈÞ/o±¦«˜™–Q&¼"zëüè­´áHü`j–ñŠYIl‘‘–fKaåš„¦k[¥G6Ø[â#Uw4’ÔŽ#àÐzvæ=E_­²–(;g-“µ¢Mµ">‚Õ´ð ÌqR€ Ú9#†ºŸ�¾¬æ°D¢C@}֊΂üŸDva»ðldÙ¥—¯ñÜOt ÚÝäÆ)%´<Î�$1Z‡Ú–À ’RiåÝ'Vc” 3•¥Ïú(GðBðA…r²sûða«`ˆhu�Æ R+žm®T ž…W™ÍÓ\e9÷{_±®†cSX œ«ä­ËYUî5,,e Ÿ‹Öì.Œ´�*â¥Ý±mÀÎ2qÁV¸¹¹__ ÈÎJ  "NZƒ¯ iVñUFfå;¢–ÑEˆš]sˆ®ÆÓ²Ÿ± ¢¶áE˜ZúÚxš)Ù´¹0ÁH½Í8Â8ï+·àX­ÖâÙX¥¦°Z;´€¿� ÷ßò½x«ôlÑgaų5åk˜ Q`YéY€“ðÆ® €§}§ÑïCº_‘sKDt÷ZP�6f{\¦³i×1ýî)=Õ½lšçŒPiÄÐs3UóãrRd”±ýñ‡\7@ôÁZækÊX¬&MJfU@ÓµGŠžla™�Y SbAu)ivB­Š½QG’mEuŒ¹ÕV¡´T‹$£b[T”ÙÎ")b±Ü},Ïa¶Þä¸bæÊ.”·Yÿ¬UF¨Û¢ÄDäb¡À„j wòÌϰ³Îƽ{©É̈`ù6¡,¯šê‘ /‰´Ëe>–MAýQW¯i–^ Ê™šìMñ‚P‚Ä7(ë/Äš ¬üÎõæéÙc³ß¾ç±™ZÜ­”šÒŒî„žë£ ß.©Þu‹b+›êV‹œ!¡Û”ËÈ�ÇôDW¸ˆÐ^Õ¥ÒÞëÀEÙ- ˆüš´âÞî¶zÎÅyf~`5L–¹•’ÝT¶¶ ¯âp“4Aò ‰væ0HÉŽktQ1c2YDü(0 gnÅVãb>«£wzýZ? UcÝ(³§Xíé)7Áä=Y|˜bônîFîL[ýÊkiv Õê±ß�´=‘täÚÄÁ,6êàõÂpƶs]íPÚ? Â’¡€SËýØ¥„ð|‡ý`„ž?—š__>_½—ªÅé«× $6|öMÌW0«÷uÈ&õGFRSWØv»Â�e\?™³v ±3ôt þe.ÏÔªçpmg{A­³.Æ~?¥cHͲd-³™I»DæåßùœtëmæHL*Åe7uR muEou ÏVYâËoŠñùè9±ŠœZ¯—h'òª*Í)2Î6 Y‹�" [½öîò)¬ßÞA<+kÁ_Tšê‘ÁrÂüuè[‹ë²/ô£ý�^å·álW}Ë]sho)ŽcÛu¿""îu¾b»Q)Ä(?.Üh¡¥»ˆñkÔ “)�V 3…õzÝà¤.…ˆÞZƒÊ\­Þš¼›ñ¶žÇk‡³Ó ŸYËT*gÒgm�jEo‹Â¯ên*ÛªÞzµ¾'·Õ+ºÉºQ‰Yó¨•/šgZ—fÄ´ztEb2ô�£$9v—Þ½äÇfU�Ñ®Î?m›‚ˆJ¡MCL&Ùʦk†¬U•ÍÕ…>‡æ+»¨ìâÊDVRlÑm¼�ŸS?.H† 7ÈSóÛå�Œc�ô#õÊ\âùª� ©Z-Üê�QÔþ2J·ŠO\�C5_T×`“ÓÄÔñž…Á7 ­LpBým„÷¥"ÒPAF[ì»7á"-3ÓmÚ±ÂSZ%2C‚+×'´œçÌÝHœãÖ&3X"*ˆñ/DBôw� É¢D­Þ¨£�+Rïaê:+m²×4H¢Ÿsã�í¼ÚG°Øm€éö‡ãظ��Ž¢­–k ²-Vn{ýN½ïêÓË#&趦ŒÈ�M{+EžÛôºˆ3hõN³ÑoåoŒè³} Ãu‹^g�ú-qÑ…_n‘·bjÅݺ�¢LydüÌþÍCÊ2Ñg[eÕBÛ-P.Ed'iiOÑ ’jvÙëDãø¯­^ûü“Ómýâ\ôœ^kpÙëVÒIÒKbK1hU€Îÿ‚² º¨{½AGG5Tç/Þ’f1G`Ûn훹Æv‡ýFÈ2McïBOnó"µjýi%(pg8®±r'™�ª^áwä‰O’›IK‘f/ìhëÑ媲¢fÚL_¢Ü·/ž1gö ¦,>™hÌÉ]Å}',8SJÒ ñ™8šLÒ#¾\~ÃQÈ;ðW`þ¹ë“W¥spÞèô[Šäuí¿Ãíê“l°{Ãñ:ëÔÁ)±üV|¼�‰GèU7Vn÷}NmêO‘È6œæ¾ë±m.sþAïR�¾ðÌ›žÆ~÷}ÕNNÜF¬Ì�Ý”“¹áÅŒ´Œmý¢Œ=yo¶ÄKrß}o.ÕÞÇÁ]dP°0Kß•:öø«ã¶­Îm'tô9AhH5N ®{wÚ�¢éÙÿe)�`½ûxœíkoÛºõ{~›©�9vš^ìCrS4I�^£ž“úѬ�–i›ˆ,i'½·ÿ}çP¤DI”íqƒEèq껌¬¸˜K: Kq|U8‚± N ÊN‚© H¨PLÏ…ããVkµZ5©ä¶D³–—ÀÅ­nç¢Ý´€c5cä{,ŽIÄþ½äH;~ 4Ž\:>=º"ADè,b0&äxqÁýYƒÄÁT¬hÄÍ„Ç"â`§œÂ4 ¶ *£>Ù=�Î`—œŸ :ƒ"¹é » ÉÍY¿ÖvÚrÕ'W½�a窿.ÉYï+ùÔé}hê:ì>ŒP`“£*ÙDêmÀXŽ…i�°‡ÌåSî‚hþlIgŒÌ‚;ù  Y´à1š4'ˆÆã .¨�¯Jr!¡ÖÎÎ_¸ïzË ˜¿è°ó]côW��Ç4f-õÙœ¿Ë úËû÷Ã¥8`wÌn0aq(˜,ßMØ”ûŒÜtûÎhÐvF½Áðì¼Û6€W^ø‹¦ÔÓÃO`k:e‘œ�ÂLËÓ a4ðnî�–+{7›Þ(¼#¤Õ"�›¯#Ft¶ Ç䖱Ѐ”Òå'‡*˜ß¡¤æûX0×£‘2ͳr&2+3Œ+𦢤EpÞ¥+ˆ³òâÖ)ØÙä÷�D´7Öâø˜œgp8™œÎ1ž'©‘“ÕÍœIG—+0…!E]PÅ€ªMwC²ª§´ÆAàë“N;ÙI)]PÏS÷V†ÉjÎ|I3#åBB¸…ÈÒØï>±c¯íKXgä±è¨^K¤^q¬@ê}õ-Q=õ:W�1à á÷øfaž˜SA\EœÅI&Õ*)è7�‘=ÍŒä%ˆ:‰è #ß�Ö f€—Ö¢Éq&ÐÀC�O$‰�¢V©Sm%²ùÖc�2�*Ë¢4c^Osô$'ƒÊ‹´5y™2I]Íy"ñ±·Œ¢í(*Сï è“±›ø_Ñ$ �XÎ�´)¢àA9ž~ ž§¿&®·•ò÷Z!b÷Õ·2‚e8��RìÆµª4±_|•ÃTÖɾ1…·–„uì$ù‘©o€£!!„HÄsb#ËÊpÆmÓäx|,2â9Îe&ûò##´1$@&�¦© ÏoÓ4ݾ‡à”M—Jqšn‰2ïë˜ÜñH,©G�?°.ö2*�KE'çýãn�>RåS0JNU–o*ƒ\°!}ÖÊw�˜K'àu�︊eEÌÎqŽSåÇNÕˆæMÕ¾ƒˉ"ÿ4³)S‘ƒ—zae<Ù’¨©e¥ªP•Ö¥†­–Û†ÀNbƒó�s6´ûÃZoÔí’W§¤TË yn« T¤U³cO8à ^0›A†u¡ìn¢'|Þ@SÏ¿±`Z•Àꊟ’„‹Ó²g¡\PßÄ2ò Â$3,Pïò­)Ƚfb¦BbêsÍ Ô95ë5Êð*PÜ碶g�n6hõº ¯5~ J�=Ýë¹%%}7l©S ÃÌ1Él´™¿¦þ!û÷ä‡ÂÖ¨FcdºœOéáG³±†VBcÇÐ�ü—O$2¬·Ù£ãIGºÑ¯L¢ tü¥çñéÃ&�mjïRk<»½}{r{§¦M›óX­òmb+ág1V™¹„«—äM›¾š%‹¯—Õ¥>ÉS¾|e?gǽ½[ ÐT©Î/ßjm(RÏ®±/Eem­}&‘õ5·Òx›4@Ns´­6=Ù’„]ü* †3l `�äÆÐÃV5Oë>¢T÷âk;£=«†6´S{6¡7Í1£áÄì™eøþ�ª]Ú9³×gðôŽY ò¬%�VT´n ZRKvLêOÝÆø!¸ôEíîÈ…¤ôrò*¢¡Üö6¶¼+&Í´U}ÖÆgºXyÔ†´W½áY§×î;W—Y¢2P7ª�fueK›®ÝÔX¿¶þÑ€¶ûMnIÄÙ~woãcrM!Šc¹¡‡'¨À™ÞßK6%ªm¶Åó [Êè + E,k±-vnìë˜Ü>ײËHæøØ8nxëSˆ ™?ÁãÕ[öô�Óë,‰+ŒÙ7æeÊLiçD–@ŽÜ-i�WÊUÍ%8ôÛ#Ѐx oÌK“”`÷‘™¸¯Ò+!¶©:gÊ9;—ÚVZq—@ê†,Ól~o¿tޝûõ‘Ï¡_‚ÙϸÀ£úõÜa>`TY÷•?±|ã§âSÓ»5—bÿV—Ò‘I’ŽO"´‡‰«%[MÅÛhÀÍFAÔ¹ý‹—�¡4²ÒÃ�aÄ¿ÉnÌè�7˜-׳x˜ˆ…�j@�cœf›™RK_ÚýÎåW§×þì\õ�~{8ê÷jšHCF6ôǹˆÂ‰æ!;°Hp}bjÙ߇Ÿœ›NïÃÕ�sÝ¿ºn÷‡_�‹îÕ/Óåpàíµ„C�Ç‚ù ‚x%pŸ¹"iÃä’÷�‘úÁ;µú€eE€[z{^þÆÒÍæ ›�ÑKÚ(P\5*“ „p¸›®(eŠÒnÍ65æZ£AkúôËä¢hܱ d(:¡cîqñÐÌ�YÉlZ2רJW{[mR˜¼ÚWâ•j×Û*’k(1„{¬–[…þ€'ßé­¶|Obit’O�âØÔgäè(«ïMKù�Ãû©z Ðr¹Q�ìÐ=¤‡&dV äMRbO,'>�SíÍ!´GGõâ–QMõ#Hú—Ã_€x #ãi¸Çî·‡øÇîíÃþ¨�…Têí… «8,3ýZɢȾÖö¤íê%þt�¾T�£ž?^þã|té´?�κçº÷q�Õc1˜Þ{ÈÉ®€&ý–‹–TDzˆ\\Ù5C¶køº¼F -öb‰e”Ár æf^FÄ5uñö=®Ð!o÷ÌÀ“ùÎ"ÀÚ”$Xp‰»Ãˆ Í{tJ(ðÍùE»æ“LœÆlŽf>”÷€rÝ�ï<ò¯œ^âÌ%®$ƒ(¾•̹`Nß�{N·}9iÄCˆ!Ÿ”³óÑpxÕsP5fø½ˆŒª¼çdÔ µþ?â{Zí¦ë©`ÓãèúOý­�] ÏíÏF‰ÍkâMµØ„ Ü<&ã<¤òZ3õüo)~@Ü9õg ÷ß’^³¹ÉHÝÎŧ?íô,;½±Ùé;Ð]Ù1̃¡qzüIíœ<õúïV¡–ÏÌy?Ñ57#=ƒS=vz S7VÍm"/ØÿsÜãÖиìú˜Q%5¾ïxœÝksÛ6ò{~ê›q$Ÿ,9n¯í9M®~ȉ¦:)•亙›¢ ‰cšä‘ e7Í¿]>dɱÓÜÉ�JÄc±Ø]ì.v—éì‘W�õ!{�g�½½gd�ü8÷|F¸Ç}6¥±#~´]è�nÝÅÞbÉñé4{ ·I¿%ï–žïEù‰²„Ťq%¾\¤Ó)@Y6íi0åkò& °b¿ªé{. 6#i0|ÉÈqD]øR=-ò ‹/ Èaû€4pÀŽêÚi¾DwaJ®é BNÒ¶³ô"6Æn]qâÄ ¯#ߣ�ËÈÊãK±Ž‚ÒFïŒpÊ) §÷N¹9�P®�^r%G�ÎjµjS�m;Œ_ŽK:ýÞiw0îîÆjÆEà³$!1ûOêŰÛé¡`äÒ)àéÓ cB1ƒ>"Æ«Øã^°h‘$œó�‚™y �½iÊ-‚eøÁ¶Í@2��ã1é�wÈÉñ¸7n!�ËÞäíðbB.�G£ãÁ¤×“ሜg½Io8€§srOäï6ŽƒC›ºœ8+ÿš_9¶|;œ|xFà#`¥pz»$9"'’8»å\ɘc|œ¤z^jPïB!J¨n±•Ì•�#óð„“)óÃ`؆…¥ä k©=ÕqXH¯4˜³˜® ¹EVK€ªpÓ4¿†ªˆ�Ku6ëä�r�õKLý4Ž7[A --ðö‘ jrÉMèÍjX¤hå€âàqx×0¨”uq²—ýÀ`¹Ôiú÷@�$§/ÎÃà~à-1ÂéåZ*p¦#ô¶žŠ½õ’M‘1(oN—€ØT¶+Ì,úKTÆê‚n€“53q(�½B‹ÆHlTÃ( ‡œë¸1ƒ!ªQ.6M©ìpõKCNƒÄ[`€£0ñP‰šWÞŒ/UlùëCÂÙ-wÀ„óÜ%ÕXøª±µv-T#Ù_õ”N£œ~º ©uZ÷–ƒiF¯L©�D+&PÈ{Ðo7^ÌSê“kÆ—!°}¥îCM Í$ææ¨Fò “¼RÚ°­Î4lt&%ñÚöYºw²=¼%uc8¸%Ô‰dH÷6 ctÓ$uhhCÈþc}`ÝQªl—§£Q0 †!h>û°åšúábÚØ¥¾ºòÌáç¸ÃÞï,œ7ª¡5›Ò$xsÒ\ôûHè2ü&øá<�‚Cä„ò ý×ù¡’aÃ%¾²l®x¼±[É4ÀͦàZšdF¤ñ—zfi+41ÃS:ËÉw/rmù æ·j'²©è¡*¯6ôñETØ‚µÔÛ\ÜrÕ¦‡‘¤¾ïÍïªYZã‘(mªÚõÐ� ƒû�·�„É™s¸LVËÐðCXÓJ’ÃÎÀ/{ �6ͬ¦xÑÞZ£2b�.Øcl1L±í<®Vv2vŽÇãîhÒ¨Ø ê@5VyªSq6‡£—ؽd"ÊÓ+�ËîÍT­_3?#)ùáiE›õ;«ŸûWɇÍ`Tëw©ÖM ²³£ÆVøu5‡—dÀ*<>q¢*$0*S˜äºw%ea¸y9²-búk5(W+‰û0®8 !œÍÛß?êˆûÇz¥lh�æ³ d�"]¯×N]«o S6§~Â2ãúØb‹¤–¦�ml6Ÿ@꘧¢êšV¡å­ ˜éEÄ)ûÌ~Ãã_)•Ö)ž`ç¥;úº[˶ûÏöªØ·¥Ÿ™í4B?—Ÿ4ä±ùлѓ܊ðÚ{ÆCp{ÔXÐC.H`FO‡ƒÉqoÐ9Ãó\囡—�-RÏ ËíÜæbò"+žŒ‘c6¯ Ù×ó¶öDÿôxÚÆRm¤O&§ß=Ÿë[@t½ Šìîê¥/ûÿœüäœ\L&Ã�s6¼TOÄ\Bù^«cëΔ-¼ ‹\‡7,ßœ1\v/@S꙲f^Ì›- Î=ƒK^šÃÀg$õÞ¼ýL4R4aA*l 0J6kl±¯–ØÇ»{j㪒Êoi0óYBÔ1#"L&‘‘±‡¾�áyæ“4ž’%�±�ý²! DS;ËDG4¦×æ90ZëcÇÙlcß-d1Æ+s¤ÈŠ&âp§×˜*˜,)ÇL*r)fUçÄãÏ„„Ÿ$LcWäZ)IŒÍ´0—)f…±`Eú‡Ý°lõe󋋯‰šªÓ!ÃÀ¿\ñ0œÑPÉ j�¤­O¬^¾qÙwÞ {ƒ  uüko쌇£Ó®sù¶Û퓯^­‘ðO$c�ƒMjÀ�÷oº£Íà5Í{OÞ/p î�÷�ñéhØG ­ÛKdCÀôxS³ÔÞ_�XëϘÏ)yMjõ0€àˆãYåÇÚŠI  4ÓG�yœ6Àá‡ÇÃÁPŽ?§3ž ¼Šò‰<­ŠE"Q™ÝéH#÷ü±¼"KZÚº¯¼K£Sx¦}Ó'¾ÔXaIXe‹sn²5Tòݶͽøhí¬Ú›îÓ±üTDaÐ2\öGD…nt½‘À ÓþìLØU'Sbw°1³È�iü0„cЉØe§K‡p FòX›mÂ>~+…¢~36$7" Ûÿeú´¸‘«¯Ì cmÄ®0Ñ»V$ÉGäÇGⲉ­bÔÌJ èŒål…e�Ø îœÇᵉLp~`ZðŸ~*2?ypѸfO’%X²…¢ HjÉߊR÷`X¦�†´&˜X ŰÉ5z…ƒua’ƒªn7¿3ß¡†ÔË·$ÍÖ„Z%),êHxŠj†ùª[?˺¢LVâ°kÅ¢nsó³£–‘³Ë~NI;ÑW*”ÉB8�‡2áŸP;¥B8Váš|¹E�\ù‡¿#³€%,üƒšÁ"¶0a¨$ÀÇOJE Ù,ìuj ³²É¿þ­ëÀw� φ¥bØ#2bûòv×"!¢Î½EcAd˜‚æXÞ©ÂUêù Qž‹½[�Éz¦8 D%E³5œN… é @Q“å‘;-ƒbäcKõ¿€~A—¬_õAco±À4™Hk´ÄkŒêeñN”uL­´³®a7nº-Ëô‚ÿ‡™GíÉyÊ̯ éf–¥&¡ne�È®lmVóîX¶ÁJ"?ê%N±À®5Ç°Øøúë7èÈY<ç )oÞŸÃp'þN¼Á€‘€ážñ?|w0aez©7{�úèrvD`adò´]¯’s-^ÀÑUyו>©ë‹ŒŸ–¥RùöWuYv‘´7‚HfYEyR!•nÌ“ åWdÿ }`‡žj(#_ÍÆÂƒýïG¼@VÂvde)µŠSmXjs7'À:¸½…7+kÊ+â IJBšs"÷å྽¯‚iy´= c°Ý–Te�¨ vló¹Œ=å«j®"S÷‡ðc,…ùù¬? Ž®%8 }ýüœÐŒ¢÷U©�Q�þ½+¢n±Wé–�ÈÒ¡V¦;U7*]5šÞµ5�Ü5E[T«‰2õ²Ý¥�Žò´M±èÚwJDf8�ø“V*¾¼VW×±úáòúêz}¸ºyñæý�úpùîÝå뛫ç×êÍ;õôÍëgW7Wo^ÓÓ�êòõ¯ê§«×Ï&Joh}¿m€;!X€o:ŸF×Z6_Ö‚ŒÙê¬XQT­ºt¥Õª¾ÓME„¨­n6…ÁÉB-�ÊbS´iËÏGäL£óóï£GꦮËÛ¢U>jå×|KP?EÑ£Gê)cmŠ–ö~^ê�®ZƒqzQ¦„ï‹B7i“­÷Qô �Îî̶„_ÛmÊ)‰Âã ÓÏó‚ØŸn˜”¼Î:Àcì¦ ¨ŒÕb†¦Wj§Y²2»=M™¨;3¥C£DÉÕE}üøÑíñIõg¤hn«î'jO?é̺¬U»²™¢]Ï«:×jŒçÛ¶¡Oë ²HoIªvž¹žASÄZ´Éšx[’†ŒÂÑyÚ¶�ÐÜ¿žïÛ’ô©1ÖÝÕENJS žÎùÑhð~¸{â1÷e«y[[°ÄÕí:ñ`ç®0É}üz<�LvLNˆg0ο]¼óëñà½)þЃwôòÏtAħYûéÏË5=Ö{Þ6x÷ÀÁd�N[ %–-øÍ› ÔmMÒ mjÆ/¯;"m¢äïñ4RÛ–¦íÊM{kæúpÛãÉ¥NïÀ¦OÙ9­Ä–’�²"6›Å_:®˜`~(JˆÇ]}«Oï4ÒUKªÜæ�ÃöÐ…Y°�ðb‘µ@83Š™J&Ê/3h��)ä„Ö°\cØ wZ§àÐ/@Äkmج‰]dÊ,Aà€8«Ÿ½šAC�¶šŽ ×ÜîŒÛóÛ?Ö¿¡¥yîà$ ‹K£7d$O¾:@I‡ÏÉ¥ZFè`—ŪkDr!ßý7GOûÃ'} {œó5¹Ððíˆ�逪¹ZÈCZ–=g³“b*ÚuGŽ¢,V„#iÒ"8o‚ä¸_N3NŸï‡º¹5ä¨5ŸoÚrÂ~=½{™îi¼Ä¿ö|,"ÝÛ¨žÏ4²I·ó]Qåõ.ùÀø»êÔ8D"k‹;X“ã5 ½**;>+ o›VºLÞâ_Eæ�Ñíw¼Åy$Ð�{f8îüPß�/'Å|¢¬ÅªïçuS8§ £nÉBF[DôŸ“S%_wÍÒÊi¥cù½#>‹9‘Þ¶cŸ#˜ØØ¹ÚÏä67ò7ÙÖÛnkOŸ)¶S°¹Ué/à¼ÎÊÚÈ{¼;õÜ"ïŽà¦Þ–dòK�vÏŠLž“¹ÐYJ!ÈŒ2áØ`§Ép›‰šN§ni×ΔH’ºÀî²õ[PËûöÁ¾Ž/ŸN\ÈäîÈ&zsøÕîuÀ{{ÂÖ‘¨<¡U‡–0Œ”çN4ÁDjœšÔŸ  aNÍÚ¤÷ãþñ…YË®, Q­+;í„i3‡†Ù�*u…ˆ€� ¡«=Z+/fJ.�t«¤nHH–KÝ\@*FSõ¾l‹ ÑRîéΑnÉæŠ/…Ÿ„ Æeh¡q›°¼?¶¬b/¿ lÖŠ] üž82 Õ (²ÏŠZÏÜÐÑ‘ó—Ù:&‚èøˆÇ ÝmõK¾ršØÛ&ÁÎ/8žãí(3w€©8¸#m„àr&"–„oròÕÖ²U¿1bFÞØâ ZØYG»·ú¾%Í»mÿ›d§êŸe×Ðm•�Ê6‡×û k>E ‹+°ó®X¤”q%¿c%÷ÐÛ½qæ%ü¦hKýCJ,hñk¾H™ Ä9d»�9Æ>'LÇ‘vúÉ@7w°|Ý¿r�ÕÍ`ƃ¶ä„í qìmUoç=Çô÷KܤÃEƒcæŸî˜…ÁGÇŒ 8IÿÜ�?© ¹8sÏÿîEó&Ý%'ßZÂwEÞ®“É~hÔ;“Wºê>K&„“¯Z½¡"ãÞøø¼kLmÍŒüü|,fï&EµíZ:É»"Ó¡Z…ãž²„+��èòrkˆ\O�ëµ.K¶6´r†Š_àÖIâf]wd« 21â£ÌÄÞ’(v&æ êÅ?5aç¤Å øw�ÙØ›8ù›DâÑ¿³ÆM6Š¢±<)5&·ÀçT�>�àè æ#&~”™•úåÙßq‰ûåÃKº«Ù(E¨ú2¥’¯~yõ|¦Þi ÿ÷ê Ù&bO‰ ?mÕ?;âöB2JÖU]Œxç¾Ë™ëÔà<%»eT’Ñ–�†¾M~Û°�¶kì> Ðàý‰'R8Â(@†W“[!92¤¹,³iŸl]”ùt8kÓ‘—$ í N=v– ÇÊÒÔä@yÖT]U´¸w"-––2|ÁkiÜ·V™0~w@¬épó5*>It<¡õªªUH|¥Ûu�°F#ÄQŒ‘·Úb«†#>\÷ñ‰7ë4wKìØù} )f8ž{èlí£‹"z4˜�”S"…žª8Œ¤c•\àíÒ’…AÖȇéÃ3Ž�ÄÇŽ�Ј "ÉiXcát¬L½!6‚½u˜\˜ˆì¶½ p¶¡]7Ð{ðÜ‹-f ¥Ú­%/í®«AAåECº ¡­j’wÙ ™fd‰·V1*Åi5.±g%®^1`YýúýË—V�¬ú]6úP{I•„œ™Ú¹áˆ��Z˦ވB0Ç‹xÀ)\u‹3À /Æ#2‰6ñN2Ç'3õ`„cÓº˜'1ŽÕ¦E2a“ÞjÖ°Ø&c‘%ZÃKÉrÐÏÉe<þùêúꇗÏÇcm>Y‡ÿ”.̰W;#cMÓTél ç9ûšvÖ*Ùè5Ωâó,5°h°¾l›�É‹º„fúØù1#‰• 9»W°4W-»† ‘ÄŒ¼©âiÿù=n¸Ñ#–]•Á¤eÑrÙ8ò>ŒÇÌ%M¼UŠìœÙD™�¤kØWaà”﯎²oÏ'ÕÖ~Žâ3ö–VŒ#îoßÏŒZÔùÞ+ ÛB÷Œ™x¶ðj㎓„>\³ØóÙ`«<÷ÈÚÜb,Ò˾lb­€�¥ÚoYå¬À6¼N¬øç‚N�~ÊBuLÛ�EýhU ëÞ[_ÇÆ™z*ÔÆ–™h¹’45Ð6Ž ä$p)#°éJÄdÁ\8 ŠÇ„½AÞÓ*¼Úɶ!묈gŠóÈi&åZ¥ {ð– —Xα¸˜Â�4d{Ž,`éüv0„¤@Ö§/ôÈ1zÄWÖs2PÙ­ÚÐÅrFJv˜n€�¾z«+ÕU¤ÓJ¦‘Õ]‹ܣ̋ ĉ²ˆLÁ-›´n\,]nœWc¶è AW³­«œ PÌ8¼¦ýÆ@ñó³Ð펈]­±"¯ôŠ.ÑH{BX!Èk„êi¦’µæY4Â]ŸÕ ;*›JpɆv·.²5§Ùhm®›âŽ,‰¸¤XöŽù~OGÀ%PxÞò] ÿ­ì²m)”WÖINXÊ(T£à–ô¤àÒ#ï~A¼H%ÈÛ:ÀâꈬõÁØÝI† Ô&}ËM·J›©GÁâ¿K{P"å_ ŠgÓ…c‘Q´p9Ëàãn~º Å ûÜŸyà¹D.1@`ø µÝBŠ¿ w\R´3kÏž ±f%eÍ–�}Ü™ËZ‹gc—æµ¾×o"ôÏ¢b2A?Ö�# 9$cR³¯2HƒÄ$~¯µ&$|=vžO{@6SŪ"¯5U×£e™®V:ç¸1 ]ã¶uCºGÇqXþÿ 1ÜÂ^3(;0Q» ¾~`Åô€®X0q9LËDHPBÂ:�ð�à„«ÿ ò8ˆ�¹Ç�—fÌèí[oÁï H_sË€بDL™’i¶.A¨?¢§ßÏPÞAŒ=ó !o´KÖqRø¯î¸ô;J^ ÕE²ôþú™”59;¹P¹ ˆÇ,P?Ïä‡yÌB"†ÆÇ4Þç…!ë Sñž_G4’�$س›I˜wpÓœÈè¡¡5‚ÇÌš²LÂÛäD•EÅô�¬ÔÆÎšŠíçكķ4¼ÞR4¿&o˜Óµ¨�!„¤Iu×!·b¼ –Ì@¨k±BY×ýƒ}à©ì7¶9­w.bc-5AŠë8T†;[oZ¿WfÄÿ¶<ÂKÜî,qAZâ…�ÔF+\1ÃÐ(€�`KûÙ¯Š£M°)x”|Ó9É»Z6»ö÷Z`.û[ݸ­èðwB3#ϽßáòÁäçPÆ)ÙÏ\(GTDYÂÇØqaƧSDnשº’ÀŸ#›\�vLv�Â’Ðß'íUHôŒ¼�ןHe‡„·„´áhÀ¶OŸ�ŸÎ+}üHaAYW+v¢BŠm«(/ÒV��·äšÔ^·O¸ZDÀJ�æVçÊô�}n†§.ÝŽ÷=à•n�‹« îó®yÉ)’4p«‰å´zoè°þ«îšJï�eþSÈ'0ýDxê2 `á}¾š»gTÅH<êÊJÝœøÚ'_†� >³™Í>ïJï¹\ªä[ ¯xªt;‘ªüÄe¨#›>@„ZûmˆsA ‚�fP}áÅE¤}üλ6z…}f¥€À®/öÛÔžµ\ƒÆ {÷wH ¡ï}á¬À2ð0³Hðb´I¼HÀ‡¤cƒ»´)êΰ$µf2ÈŠsuqäáp}w8D’º �RŸÉS\ Ò’¶Ê#ãΚ̾ÂGh„¬‹Ô6ýšpÉaìkw !©Ì4o«fqR.H¹ò ³¯"ù•a\ä˸t4q4V™™ânË!+¡$<–`·Íl†"¬M~ÉŸ®:]E‹(‘þÆŠûPI m¶b*6�½ÈÄ�˜Ød øÐ`ß°ÁÆ*vßý“ sÿÄyMû¬ŒY¿Ý舚 •g©q³Y¬Ó¢élˆ@"E³ÀŒƒ2Ô�óï=.C±¹«*I;¦OßS€Ý¨˜MÊV£¨ßĶFáMÛx1êëFä4ˆé}©†ÅïËî3?ÉÇ;}/�JXä„ãÒ1GÛd}k^ø~ÔC"ÄŒOª’7{T* ›i†²Çïð¸ÂN¨è«x,+NryÀ9ž—<Ì6šÜwÚö±CìlG±vkEÝ¸ä‘ Î’Ø…R‹ƒ�@n¡>Les4sß "L²žç^ÑV*Ci›b¬'eì~“BaWV—j°G Æè '¾ Ø®RòÜ+Ø¿ n|ò.z‹le™ˆãÞo"›Ø·Ež Å~¾(©ç|›Á¯(›*G’ŒÍµd_N¬(hµ$oŸ §NN¸{óÆ"‹ýB|èb¨Ó¿ŠPB�;±¾ƒ5mßWgÛØD-ov.]ït±†£�Ñ€¸4clƒ5ÛùhÚÚ ¨uœ”©!�\àcó”º$§[$¦Hd”£âW:­è®ü� ·‘eÉ›tµBÌÓW­Ò¦î*®g^·ˆöI0+rÐ�²Ð U§ú“Ötjà—ˆ6,¥M+ `¨LDIQÂ�`¥¤Úóþšp™‹˜˜ìºkÙí¥›þ˜$M,sªÜôâvË(z„?îæڡ΄ïg¤�X)I†I$ü™sóåD~Ó6ö—¥àB€pܰHÆmÂ6~Åߎ਄'6OáÚÕ¥/ë¬ SW, �Ÿ’ëó¬î� `å„Èå5dD ®.»|`uˆ�uVðÍ,)õ’ÎdSä9꬜‰Mørˆ”‚?‡Ýâ*ÔºÈ´í¥”Í[XAnZÉ¢Ûo8b²†ºt¤¿JýV¸Oñ½ Ø A‘äSqúž™ôK”pÞ=Ã�žBµqGtÊf¥«§ˆ¨–îÅjƒ::62évBÁÏ*“e«4êKáÞº¾ó×û Ð70¨¹U|×òŒ™OèiYlmyÀééïJ0ÿÍV*9îÛ’´ÊÖÜ8�‡ /׎p7\ßÙÂ;þÉ4¢§EZv¶Ògû‰ÂMjùËÚtßVU-ma~ûÇ×凑}Øåç>õýLå ç×5eùæ*ü…Ý7ÑÄ�u°–A‡”hŽO¼åvýäà-ª,ˆã)žxBÐx[Û^(ÝWڪ ßúnÀÃI-€|n4‰Ù*=œ4Bº¤àl8Ã�J›®¯Ï´Éšbëꉗê#fteYT%]íÈ^´ð~"»£hz(”K9»ÂϪ— X`ù¾ …¨Á–T‡t¾ríJªbÀèU G*¡0°-�dep´øa[9šª7•]ÉÀ 盋7¶á9 ŠPJo?húBï`òÔŽ{âm¼Êw[©A"Ò@i‹_ç̈Ì2#`D“ÙqY±Äîí÷.Íq—Í&ö£Í‚‡WˆÀ*|©ç@²a”×›Ÿn‘„‘USydžMßë¬ë�;‹ˆ”2þò�«OéÞ»Gg·å^ìètbùñÁƪ”AseX.Äv>·ãï!í—…<„�&¶z‰Kú6e fºÅ¶©3„8mM yí®+Ò>›«@{$!Ÿ åBbét7yÁ_6º€øæÍ³73káxèb4@ÑQg¼´5‚У†ŒyÜtš›â‰Š�ÂoÝ45]Ùº^ €Ý*ʉëµòÁh0�$‡õ%ò»‚8p°˜e— Þ)Q÷!Ý—ç4u[gu9šp’K§¹Oj6ðÝŽ‹èJ¢ZÐË€k{]–?¤�hXžÒ[Ý•gÍ…"3§0%MøðòCíÉ7#×îÆ‹D$ߎ †ìp7dæé4dT2ö‘Éx$A,£Oš»Ä¢p\@¯Ð8JÖ~Éù§š³)…ÏÂ*©Þ\Ø*3ÛI¦]Ì}ß•” Êùå›oüÈè‚ËKï¯ÂŽô„ÄnFÌŽCopÅYÕ5,•Ù!›•I$ã@ÆnöX®¶Ë$ sʧƒœd ˆÊ=‡rÈò#�Á%nî z– €Ï“™ç»” Äí Zhâ@~ímÒPØPaÍ›íJòjǤ8µ¡7jpµL‘H¦øþ7Ð[Û:éîw²¹Üi¸`ç.l)j.,Æ>Ä8Y¾L”Æ' }rHµDüÎ0iA;œõ‘7ó¥¨H 6ž®s[°œÏÁö¤ùî³2-6ÆÏ}B÷”HöáäO Ñér¾J¶àôa­«¾¶äÏSN“xÁW)îMC¦Êù}Á Ò(,‰VµÕT´x˜Ã³À­Ý ]­Ÿ0è?Çô„¢—)ãz¹¤æçð|:éc‡­dˆ¸HJ’ :¾ÇæÇ_¯�Uã6þ±|S|ån¿{f_òw2èÀ OÖç@Ím6é_é^ Z¢�–úÉmCP¢>í'½_Ôi“£×g§ÝUZ¯X’Zqh€VYî9ò9ÞÙ­]9ç»æ<€r“HɺV˶&ãšuDÄMO2MhpýSü?ÎÍm#!ê­núySÓ…Ľ®ñ)6E�É^·#©¿xóáùÏÏßÍÄÚ³ OrÉhœû4½5pÐYçÆÀh® ‰ìˆzRÄʼn«L¾ ‘t‹¹`—N1€K*Ä€ðæQ¹‘2‹4Z<æ9Å‘¤Åf¶Ë«äÏcÈ ¤±-�dôé‚à!âxÌ}líÇo¡}äcf“` ê‹£Hë¶;Ïš“™Ê%ÿ_ð�ÿ4:çÚkÓißêÂF�6xÌÿ#Š?]íJ^i|,‡é’��¿›`®ÁpëŠüñ…úõùõDÜ0Fðù;x É—’íB?gäë^Œ˜zü8@4|’²àó»´ì€­wÙµ»Iôq§Õ¸ûFÝÐérE¢.!�cšœ"Ô7ëŠîê� ¾¸Oþ$BÞEƒL1h‰^r`6e-]Ù� sµÓYÐÑ á 2Òá óåÿÇáE\§c³ ÿä�Ú9!{ävÞÁ{\qø@Æ/.¯Yì“2æQ`Ù­dX7o;¿jnO Ó9¶c§Ž+Ʋh Â1(‘ðÄô|}È ñh±@–ŽälG†�›Û; p�×%AùØûðj­³[#2åœ+ßÃz³n3žËz~ZAœIïÒ¢D‹êO,†ð}¡XbmaCç3dB…"1¾IŒÑ³/®ðô¼AR:Š÷…RS¬úa6c½›ÂŽ\Œ¢ÿ!#®o½×xœÕZms"7þίèõUù€Å°&©û`Ç[ËzqB-‡ƒãl¥R”fF€Êà §ƾ�ÿûuKæŒÛÙãƒñH­~yÔÝjõЪÃÉs} Þª´êõ ÔáÃDø–JøMŸõ�.Î=�ÆPukÐ~×þü<¾X,à3ã—P½ÖߦKÇáMwVË.Ã%ßÁ�a8E9ýþ©Ò."îÁ2ð�‡šqè,˜‹_v¦¿r‰0€vóT‰`ÏNíÕŽ‰Å]¸„9»ƒ T°Œ8òhsø­Ë Dn8_ø‚.‡•P3-Çri�/–Gè(†ä Èv'iB`Ê*=SjµZ«ÕªÉ´¶ÍPN[¾¡‹ZýÞiw0ì ÆvÅeàó(Éÿ³­uî€-P#—9¨§ÏVJ`SÉqN…¤ñJ %‚i¢p¢VLrbã‰HIá,U°X?4;M€�±ö:Cè ÷àcgØ6ˆÉUoôÓùå®:�Á¨×Âùœž>õF½ó>�Agð>÷ŸÀ.”Ão’,@5AÉ=�Û�óŒ “Ð¨-¸+&ÂEÓ‚é’M9LÃ.´\ÎED[¡‚±ñÅ\(¦ôPÁ.ÔªTþ!×_z¸ýÚKg{©¡|á8,â-ûÝœ½OMFÊóø$;¶bw¨šw`x½O³Wà·ª†U¹ê±>u"ÇÏÇëÄÕxò¼�Y¦çÍ¿f>á£?kº”’›HN²¬‹’2ÏESc ÞÐNT6ßšAuÂüˆ'*ÜW’¿’«¥ �ì¸rÿü®¦7;åjnÜMÅx$¦óógF¡n¾¯{DHVëx8b>Ɖ2'5kc­ÅŽ˜f†`‡;Ú„4+ƒãZË1ó¼jJÑ �Ú« ŠG`ØÝRXl6¦ý—Lz€ë‰#„�0% fÌ þ©Àᜊ =£�Ät¨žœḋ?ÿ„²ñƒ÷ä©M<_ojÖmÐǹHò9žžÕýâª×�4Æ3s"íê¦O4ÌEÙ�Ôì%Z…œ@úì8^æ)¡zð´­´ñ9“9 ŠV¼Î®”¨òtÄKs™ïãQ ïŽ7Qø ‡âã)ɽ_;ÈŸ €RÈ6�ú¢0_­=5 U_ QaâÙ£³LÂNQº©RÝ­À6[ùÚÑ[¾—ϲS[€.‹îrJAº¤fÐ4µ¯÷1NxžõCdG$tÁ¨=µÌ‘ß68~2>B·kÉÝ�wf} ¼rã Ý:?H>Ù’Ašqwd�׿yæ O Ç9±²Þú—¼ÙÃòá´‡;úq8Æ[ÿ¨Ót/ÆçgIÌfna›”w6ôÞ¾ÝtŒì|„Äÿ¾Hnô ÚJ÷¿|Ïæ/pïÞàeĺÙeüÁ:„Ÿq?Î&õj.ŸÔk©¶Á:¯\Xk“´¿Ðd)i³Xýs7}'2†ÅáRlšdk�rzr2á3:UÄ#’mYàY‰£ñ*šÅ¥Ы~ÿ12»ø°—Wv¯QnÜ7R«bUcêµY*ÜùQw8Ÿv†Ýñ°;¡ ô«µ ¦öJVÛˆ“�y]qÔø�æ2쎌ïPߢaút(¹ÜBÛzîð†3ê@’Ãa‚סø¼IÓênÁ= nëx_Ë:;t©:Æ;`ýÍfuz\¯&bÝš!wæ^L$9µ¶"›Aœ;“?ƙ͡9‡¹×Y>ò&汚1e’]þ4Õ}v{¢o]ëÔ23½V$¢žj>iåÝ¢™rC-¼\ÁMÍ®ÒV¶ÄHôÄh(I* “‚Ø«¬fgò÷Öý/¥t!þ‹ñR¯Yéðö�Ó]\¹nܘŒ…Ë#3ÃFóÕ!Å.4×-<Œ6Pí QÞ˜©j¬Ô-iµ]‡Û¯Ý‹ÞÙ—ñèâ²›¤ìX½¤|ÞÖÛÜ÷<¡ûZ¥Z­Th÷—êZ¼kõk¹¶…Àä�"Ê9|ËjG\*-d_6S¾ùÍ™p˜˜@¤¹’¾LÝd-šf-¼ø9i‘n†¥ýMÃÒ~YXv`ÒÎ1i—c‹Ìó-éã³NøÍ¢ÛöeÃË\+ £í¢Sü†Kj”¯k-[ÏGÀ$·Å½àÅkN†XŽ,�È•bAïËÏ£­g%_ÇØ[­ù*´Dò7C_ŸVxBùm{Æä:ú&‹›‡8U¶Zp&ddJó£tw|¾Ô5h’E³æN�5Ÿ’löÞ€†x3 <+áÔ,jÐ ã`k§ˆ€Ï…¢·‹4iÐö ’ËÛ…Ö.t%ÿp·#Ã7ù Z7›bœH‰„_FÌn™<Çyu†»^™¹F§PÍ„ŒA<8ÌCtÂde N'T³”¯²ä…ÆcPl?€ uˆw‹×GÀA൛™ÃM?õ9 –‹m>˜3#¥ý.Ô‡)aÝ[.]™_CØ+íç‚ ›®ãNºÎÛ¹õ›ÀïÄÅVön æÎ�å'}!ZMz�‡7WÂÃ]?�ïðÿ׿Ó9�ïá~û&îSÿo§mqüÌ&^õÿ=úœYpÕ¿øxþ[aê†*}¯$ÂJ$H\hÉÓ¯ÐÉ1Ïš7=¹�@™ùaK¸�°Ü+xœí}isG’èwýŠ27žò�EYòõäJ¢lÆP¤—„Fë7;�h ²W@7¶»Ác<úï›Y÷Ù@@’-„-ÝudeeåUYY�wÈËûú��Çïì< ;ä/“d“›$g7½<¡GÙü.O.¯JüõZü �Ñ6ÙßÛN~¹J¦É|NþÅEœ“ÎGú÷/—‹á0î�®¶ÍjPåù)Ë.¡§““×¼““d§E<&‹t m”W19œG#øÃßtÉßâ¼H²”ì÷öH lñW[Û/°‰»lAfÑI³’,ŠÚH BߎâyI’”Œ²Ù|šDéGY^Ñ~x+=lãWÞF6,#(;É&zA•諲œ�ßÜÜô" m/Ë/OY¹âñÉñë£Ó‹£]€˜×xŸN㢠yü?‹$‡ÑïH4ˆFÑàœF7$ËIt™Çð®Ìâ›<)“ô²KŠlRÞDyŒÍŒ“¢Ì“á¢4&àƒaëeQJ¶/ÈñÅyuxq|ÑÅF>÷>{ß'ÏÏOûÇGä으>;}sÜ?>;…_oÉáé¯ä¯Ç§oº$tA?ñí<ǘ ¢2S¼]ıÂ$c óx”L’ -½\D—1¹Ì®ã<…‘yœÏ’§´ÇØÌ4™%eTÒGθ°£Çü[’ަ‹1L?§Ó«-íáÿ›&ÃaTÄ�ùßÞÕ�ž—xQÚ¯ÒÅíã$�/ÊÝø:NËÝQ6Ž ³PQއY6u&ié>ËœGнý,L˜Ïn¢;@Öx–`  ÈÙ Æñ$Icòáä|ðþâhðþô¢øêäH¯9Í—ws <ø6øß ³([­e²E ã­/1˜F°º<e2}�/9¢pÎ&.ˆÚœ ³¦¨fMzk>e).À87Óé1Íât1HÊxf>"Mþ#«�2Æyß"äñcrüá×÷džG—³è€|Œã¹^؆U3)§¢=`—À~É›x4�rN®+±_ä¿ä%gr“�ãc �Ũ$ƒ›é¬ü8`ä>(Éo|°Ê+ŽÍ.°‰hË(Ï` è¢A”bX”Õ¨‡äG<{!›<.�¤Ñ”\'y¹€¿³¸¼Ê MÊ¡8'd <*H<�aJR,`56ŠÂê”\ÏJÙo] h ýõ‹†×À¥3Ð]„#9 ?gÓ1°µ–Ex—̳ùbN$¡ ¡¿ 9gà·ú˜ùÀoµ�ø0€ßþ% æÏò�°õxP°!€é«81Ÿ‹~†Å`T°æØw5O¯�÷Rˆ¡¥EI�ra~ÖJ—ƒ-mõ‚‹—¿‘ ³ŸÆ\ žîG²œmô¼¤z§Àï$¡ÏØŽhI¼œ—¹#6&'˘!¢‘z×#§ïONH†¢ì&)bíjÂU×ê¡Ùé/€è8G!ιhúç¢ì@㺢o¾ÊÕÕ·¯žÆ9–ˆQ¡�¡âô C„•Y”wS{|”WÒh˜ rÇzH[6.¾¯y1`½ƒ�@Ë}OcÇT¡(ȬuT"’‹þeO@E©Pfw+úséCðoN\º ðvÎU´õÌUtRÜ‘�á0‘³Û®)§t’S=t.�FQx¹Šrâý0ˆâÍ€ý–ˆmƒ¥‡ˆxÎÊß Ù.@éyºï0VøÌeã Aï²1è�¨Ð!ÿ¿„¯³ì§YõÞE]užAÃðv ’¹AgÐH<˜ñÖÕ°±“_²"Aæh³K¥"Y<ç †¿gó‡†D’Š–&‹é´�fž¢ò;‹nAWý'¬þVÔMi)gEƒœÑ¡ó J“<9 ƒP9dÁ âÔ`TÝTi¦?*©uƒ� Cγ~œÀ"fŠt6­è£âl34» ÞF¢ÏÅ ÕÇY;nñKàh¢†ƒ6ÛÖe!*Ž”E§£+@*LE<¥TŒë§Ÿ-•UA5Ḧ ãF@èxüÍVÌÓt<0Uµö(‡õqV ¼Ž.úÎà' )ÄšHMéd1…KSû4UŠ›!Lãµ!ìÁT4$vÅ+h-YÄ]®bjÒ¡q˜¤œ2·ÃÁ%LÕ 4'A/‚E}‰–[:åÕ¢ ˜�ÿIq2‹«l1Ó¥“#Ë£ØKq¾¥ (J´ÚG þ‹aΆqyƒ‹ÏmL X[¡•ôËèQ+n2Zx.¡áGbiÀ�¤fTàRSé@‘/|"—i¯¾/Vs€5RxTvH15b”?½Ã9Ó2nЫªQ=6»'›Ö÷$k´è¨¸ŠÆºÖF-Ø¢åhT&׈aÒÁi¶3,ÐÑ¢ØnУlàŃO/˜=×#z·ˆ&1Uh4š@5•G…Rr€Q.Pô…m?]µRVàáˆjþv(Ø…Ò÷Ì�¬CÁÆÆ˜¡UR}F´q›R)}AlÀǪ ;·Á²ØXx„7…Æ3/˜zgq@Gÿ£ÿ²9@JLFlÎÌN¤ÁT�ÁpQ–YÚñ!;šÉÑÕTPnZÒšÌPÃÂÆo¨±ýB€q�%ã̹Rß}ecÊþ5PL+Í€5ßl„øÌ��Û'ž‡w¾‡·û¾’ûì ¸Ñýy7P¼±c9;„­Ãò“:ûŠÍH |ÙvÆ �òìnexD;+„f¨Â2<—WÕmU4æÑ3˜Û*ï„4”ñMÑ mxg•Q}�Œ/plt$CÛá†eueWA š¨¾â|8è*ž¯jV"Yt$…[Û~dÅíÒËøY}³Gzÿæsë™’)ì×ã#×9 éà³8<$/¹,è™Üž7aÊ 7=Æ"ƒuØë®½Í)­+9He(5¬Ë€ g— &WvFTÇÆÕßÄ@�ɼ̘b�k{,Ìé-æKÞËp„Ë=ÊïÜ! ‹ùWÔÈŠ\l£¿ÿCBþêâ—“Á›£‹×ƒ7ǯûJœlõ‘ŽŠò­.Xö‹¸–Î]Í #¿YrIúWÛÝjÎ)ol�d¤úW åisÅ7�€ùÜå_«oÖã,Ê/“´QÇïhÑ&³FåßÖ_�ö�O�N:Ûœ‘=Ýγg¶Ö–Üáì‰ìÞ×t8®+èJ—ÇÝÜ­vtš~N½°ãtõx\·üV##`N³ËK�(£h:ÍF�']jgKˆoo3�9™�uvr5±D_.ò”úÁYAõr÷G(èÒã¼u‹³¢Ì@wÀÎ:æhÝ–\”@“!Ï´¯¼#nE%�Å—i4$iRvê½³½•5]Ì“1n^ÍЃZô|]Z�í{ÃÉo_ËÐcZÖÕugþt/,Ln¿;{süöøè|pxÒçÞ'$Êïµ”²üަQêÜÞ&—Y™‘8ϳܭ¯¥ ¸#Š­¶¬¦p�6"9"~Œ¡H×xûáä]ÿ¯ƒWgÿ9øÛÑyÿøõáI7\[®@å¯Ïo²¸ Ù'>¾q[ $ß 6Ѝ1rnÖ±G¶Ý tÖ|Ðõúþ½^É/ijø&l¬ÆæW�µ£bŒv»Ne­ÃmN@-zuˆ(Ø] =pû… \×I‘€’º$,TöÛŒÿ»—Ä#Çôé ƒá€z'ª¶f4K ”3“ø|k]zÒJïˆij˜ÇÕ �;˜ä™txé·™•Ú¹n²2Ò™ì1yMÕÝ�Èv#ŽKth~Ä0·«d<ŽS²HËdJ’ô:ûHƒ´,ôY›rEÒ\9 S¿rá4êrÝF4Ó”^4€EUÀóÁb²4�G¥tRpQ¨C¢uA¥ae»?º@€l*6iT�¦þAuN5ݱƒå¹¨žC;tž\ETx]B�0]òÓý+ðÔÃâ%ì¸ò©Ò¸ê¼*†Ö1‰¦…\k:­$q›�RÊ'…M\<±‚"*ømS­ke–»Ü2²jº4âÖôóq—)Û䓚‹Œ·7�rS{' oc: à’àS5e5Ök˜þÚH_gæ,v¬-äØ+ƒ9ãÌøŠÞnû¥ˆ[Öµj–µ ’ÑQ>C©ã/åÇŠ|¢b_צ€ufÜcROã¨0ó i8Û¼Ö¬‚Ï£8@:Ƹ bP™°áôúl`­©Ù^‹üñcîx7H\Xz,ì5®ù´|Žó%Ánf�­JìQa@”5´ÀNª9¸Wƒ×g§ýÃãÓ£óÁÙ[ÅK óÇìºKšaa=”iE.9áYzPMƒI¶›ÃlÝ܇  û9•«DHŸdZ’Û.¹óFØüœëuêÚë’‡ÐÎÃ;!0tè–üß—äV{p‡îLu—ÆtmDScþij`«9v¸‘pÞÖ¢¯ñ¼,ohVÈ ö¾[Ù»hfÑÕ"Þ´ÙN«Œ&TCS J ©«•—SáÖz'¶�™¶1ü±˜ÿf¸ÓwCÍó;ü›nƒé;¯ðR/·–1V@è°I ý`ðþôýÅÑ›Á U’QŸØ í»'šÍ¤{ì`ÇM£P÷ÒÿŸùV—8„[¶æ5ÒaE5åâ¢^Ìe¼XxÛ…®G³¹ßñѵzÕÀµËÍ"/ŒJÜÖP/>5w¹¸[Œ:òŒž› �²r=*ɳCmV6ýiÞOýˆ ¼¬M-bëÈa¯||µò åË‹£ó~ Uö´"T‘d�E‹8L˜0 Ô�X0ªtØ,Ð*è‡D"#ž5 áñ÷GÞ CA'v¨ŠæÔüNÀ{É|zÐUœb,fž ~@èöÏÞœ9‰DÈ;^„ÇøÙaŽa|]'Ù"ï|Ì»s£äµù–­Øˆ!ñt‚ ÉÉ0yÙ§ÍÈØ¤Ð'¯©Éâ¥Ë ­H=~©�b`)Š;¸GPá;Ñ�Ì5iTQMEèGGÒL }‚ÅËå«"œÚÅëP�2g´jжæ "»¤ƒHÞÙÙÐ<òCtͧ1¡§kA÷íÒ¯LÛõz Ì£¨/IÇœûm5O\™~):ôKÞ<ù¤"'.Ê,�Å\~¢öxBnâGð8¡9rÄKzXN±·*ŸyøPBó�ŸƒE¼¤WëZ3†ìßAñÖ³Pã1-­óH �ºfÝ$½Ž¦É9'ß…mÞÚgð“Sò]ŠoÚ‡š7¦Îè'ž[ú;iß’ÓÛ ÑNSdÅE/±3 7€�N|x=4 ‡ï̾�Ê¥¨À<~¾QJ} w¾ 9µò@ÖǧoÎ> ~9?û´û_¯OÎ.0uÓ¶½çÐX6±(¡m3³$¥|µr†ÅìíØ5Gççgç`·£ây@ÎYSD4E�ØMù�ŽN –åίy–¿#Ô^Á…=ÆlL  zã–e§ ßs2Î0wKtSQJ�P€§˜Œ¢”ýr¶�i§ïчvƒz=ºÑhVR t­‰Æ�ÛÚëÕ|Á•žP,W¥ÿiÂ|P5t<4»Óì }¶ÖµžÉC£y϶T踤a³émlÊÔÑH)n­õ&•ä{Z²°j~Z`V‚Ñ4Zñ9ÍÀÆ#,>¤èhšÇјfdˆoçñ¨4óÓ„�$\Ú¯ ?†¹¬tm¶7+�A[ݶÓ3É>ÅØ²Üƒ­1CœÆþ„± ¢­z® ,šÂásDÔ11' S5‰J¢(éD´�Ä¢JñÑ(àÏ #½[rPo€ÜÖ6sפ™;ÃZ^‘s5…Šß7ñÔé‡È>§ž ­Ë¥ŽŸ‡XÎ;V1“ÐÔ% ÌÓË8K­ Y)ÏËTÑ* è±Ãôn©ÊœG‘ªŸjÀ½VÐ0“Ë”Ê`̃$Á«egÿú—±‘Ç„|³…õÙÝ@øñûj"µÒé, Ž‘~ ¼¼¬ôúêÒ[Ø”Z (})­@CÝ&•‚{YŸAo&R¼úá[aikX|þÆuz³Õr‘ðz•¢²+(ˆ¦v²•plÒ»G(*¼üY ¼µDôµÑX®¾TÇr�å«` ]¶¶X¹úý}1î\ï#½´Q˜öFwÛY¿–Æ>4hø =§4¿ã º�®ã*¸=Xy4US­øN™}Ø�}r‰N ÅÈß¶"­ý¾ýÛ^yXKñÂP`~#KÔÝ®[¤¼ áÛÔÁ×v¢yQŸ„ úTPX9@: Æi�ŸâRÏœ,s�y o�K2[Ü16ë]ÑX‘`ä`RòtBx`L¯–HR=­®‡’D"ó•ç0ž_Ãîb÷Gïá8c�ÈiÉæqÚ©èµ+çC3çÅ#=²ãþQÞÂ÷”’‹ÆE ‡"k‘H:u“€Ê?šÆË×Ds0šÛûu‹ FɳH%)òc{·_ãmÇqÍ O£B娚e4ÄŸ²¡Þ­`ñS‰RÜZ¬°Æ-ü™§þÝÑé{L­q48?þéçþë“ã×­:$%E¬h.ƒÿæfÿÍ(¤[Y§�ªK˜éµtìKºô’Zž]µË¨ KöBAUC‘GÜօ˵�hÄhÇNãÃåD†E´Ü õénnNÉ:!­Ï¯Ò�Ò¦j†=¥�ÊÄÊæûE Éæ¥JÚxÙÈäÛh²ÓYÉÛ76<!ßh€¾È{Ãd��QˆÝÍ m«ñ]Añå™`>,UMXŠÕ˜ˆX‡BmßäÂÇÀîqa'LDKž�Â|–³±¬óŽ;H><vûh¬*èˆeï6y¼€žoµ†yÝ{Ï܇©y¢0ÍE1½£·IY9´Y:–¤z1G’ÞÓ–¥q¯> »Qá Ó$=ß²ùUâ Áç N|cpQêq<®5; ÒòkhW�L°b—,‚ÃOMúµšLjf/· :�ÁuÌÕì¯Ý?€‚‡búµ4¨²`# AÔ@.›k–.%ß®�ÀˆrwÐòG5ø¢±fÐÛö{`Ònt�¯´Ÿ`ŽOkXX:öB6=ì.«PµÌ \²ä×ë�ø´¡²ä!ƒ”':C-ÑÉ…àæWìWÙXá­/+<õœ~äyÌYËâ &µÈ{Rft«r ›È¤•ÅÝkÖ, îîySfø²S�L) å l9÷T:«Ž•VOb0ñ‡ÝGE^eÞb—‰WÏ´Ðk,Öv¢«½k²°Fë°ôN©¼îCþ™6'Öí÷‚x¡†5À·±V6ºJš">þ7æ±CUŽ}“ÒFK—Àˆq‘â…Q*±üu4]!ªïª.»üA>¬å¾dg=¶²_è|Ây—ùsÞ+‚boà³€Ø:j$Z”P|Ub§øsÁä¿-£(Û ‹g‹cÇqyBÐ\BPø»—›LíhŒ˜ft[}ĺös×"·ZP¬A3š·›v©ç&û$òâŸd àÑ% !Ú^6;þZòâó;£èF™¼ ×-»˜c®®yCí†]ÅüØ� zë7õUó;gõ~¶–š¤Î_s‚/¡·ƒV‹g«vGÓdô‘#; ‡éƹíf“]ŽêˆÞÉ!¶¹2¼±�ç |¥7áñËõ†¸)oVç�bgüZKZJ´Ç·-yºXq# ½H�_À‰÷JB}ÒÁ‹ö¦ñ/ï£ �oeŠÆÔŽæ¶øpZdA¨Å9]À^ƒƒ(Ú&—¶z•µ®7çÏ5ÞóÑ wþ=—w‰rù«þ)—·]¬s¶z´Ã~øáyâß÷ûg§ƒ÷¿ø«âuëvÅïòëÀe6bë�îÛÕëÔ¤[Ðl1öB̪¸¯•“2J „² tÙ«ÙÜs’E~mLˆ½“£·�‘g Hâ´ê*v|ÕÅE²X¡7& ÷ 7¶áQ¦>ŒñVa²\¾hšºBáÙNܨ6ÝjF?Äœ3à)›<Å 0†‘BÝwË/6o´íÑ2÷�ÝœsÄ>œ¯™Ó�#¶U¨g;à ûødh•+0—:Ʋ• ÅúOqY˜×÷Kv€Q07»ž5K§wÎÍî_׃ëSêS;‡ó@ˆ^0€ÐSv÷G}g'ꬉ¡o[àÑ °Ñ Þ‰‚w5š·¾+Zfl�@Š:¢1™Z º‘“ N—CÛ%òªI¶Š×³¼�zI½Òq�i ÷ðÐ@`¦¬&»Š�ñZç":9Æ(Äé4¥ÏÀÆv�ºØªyT}\дtI¡_«Þ™gˆ·$šn³Ë¯YEVFv‹Íè7s ÿŠ~4ӗă7h}{é—¢¨7¹S— ÎvT@pDx¢“@úžfrÊ“ó˜�¡µïh'<;Což—<™e0zB›0š†Uá�NöB[¹À¼[£éb ƒ”4hqŽ ˆªëÌ* ÄE£äÃËõb¥$¶M#ÃÂCz?ÖÑ›ŸŽ¨J¬£—f16ˆ�%]ài?vºzZú�O­zìŸý¢wxê�ç Q=šyCøÄ³ãò<-ÓML`¦ñÐò¥G/ÈŸ=“QÜÇdàL{î)S),ø¨Ö¦‰ÎçÓD¹WÔ*€¯ó˜žß@Ÿ ¿‚\»6}Å;’}n2~©#s“ Ù_$�†Ä@¶$LÛÇ¡Ü7:¥ÕÜ×ì¨â¶Á—ùo4­¤îê倖ßvgÙ“7ÚÕŽZú7ŽAÜ¿=¦¹6,s_«ý¯—¼zÿ¸rôêðÜnàWŸq¿82ïÇR°`í#$žÝx-iwpK^O¸mGصI³aýMEf»&¤ï×åýéŸZ{¿Ž.Žÿ?ÏÃR³�´ ÂkWRíã� ˆñÇÁ�‡C±'º‚âõ{=ºƒB‚ÝæÆ<ì‚@ƒÖwó î�l�+±p¥ÄšGÁßдÇÑx¬ ·êôØ&c5§ÕÕvvc¡ˆä£_ékÖì6�Úi�àTÀ_{R/İ|G化�››q» §ó–Ð9žÀþϳ.·L¤Erxà)›/æl�€Ý—Y� È Yë)’g¢’ò˜¦Cºi„&ÀôŽdÃ4°ÐH¢¶ë4Ë>’l<îõ0ÐsUÇå£B´„…Q¬gà•€ÒhL£˜YO�6P:©ÈPº¥.wl;Oëã oKnÆ\}§ght‚5�¦Î÷U±ÜÕL…®1»×iòTó]Xg­ïî³¶½q[2HÁ\#4þõÉ+"5xªÆ(GÜPÕe„€yZ-LH}šF…hwbÚ‘6oF£�Í×L›Î7ÄÜjiÛ£�Þ7{3l†`¶+§\뤊š6_Ã9+Ãʬ³%ø*ú¸§w]T¬ÜµòÏ Ü£i%ÿyT°œ¹èŸEåèªÉž—ßJ©¥À^Zkz-ïhkÏhMgÕY!Z䌷í5Û–>JÿõíÒQ˜'ܮ͓ž¾×De?Ó¨šBß𩻢q ÈÓÙ,x½³E-¯&;òÎê¹'“GÎEe´ú΋ё³õâݺºØiÝ=›�;Ü-Ë\Š¡µ¯ÇÛwxœ®Àû™Cžé�)ÅÕúðÆsá2¼Ñ ÅhŽÒ.Ç€ š—&Ï� Œ*]Ù�¼D5ôY•Ëb˜0]I®rÇ›ûvnþ$†C½RïV9u�çwì¹äcÞôí<¿ˆR/Í„K¯ˆéS,†eF¸%  –•^o낦á=E*lpˆšÅ$´ˆ�Ízúzv-˼«7$”\óýbZ‡u?-#'²%|w[b;²‹)�tî�¦œ“-.mdMcÊêÈÿ祛4KwL©z«ÞJªkKmv =}�–ê»Vûº“Y�3ÃÂá�Õ¥5®²• ØëÆÚ¼«NÛæ±j•YPr¯´lV™ºý 㽘· 8åå­¢‚¾‹©�avI7ìy0\:¦I+eÄ-P*Ÿ"`‘iÆ8".êX#yüè;å´'ùÍ3¢Ý—úíp»”àzÖà|õäníÈݪå̵åµ"õî6 ]‚’E³Á‰'ùk�Xiø $eþlâÇ¿ÉúU0’ºTÉ”^×ÍJ*/–?¿qé&ÄgŒ‚ŸšpÁ\"ÊwD@îŸñMKNs¿  Ù÷iRÒ�þÖÇXœ#-<³.]Å%5˜ SS¶éc _ú—Í·SK ‰hXƒ�¸iis·¥a%fŒ6-­¥-o]C»ý—/ãöM,R °¦õTÎÚ¦d©›6σðY„|ÓJ˜¸ª)8t·ºMaqÒ_çAøÀÎá˜5SP9ÖëaÑ`ÏYN#<²à•‡ôÈÌ(*â‚�f§@±Q| €˜ÒO¾)þþ`CŒ³üFžtA37ÖÖV×·âȧ®VC­+QZ‹_2J²õ$J±_f c ‰‚ÆC³<[>¢ ûe–PÔ+Ji*B ¤³TܪN‘`[tÍx ÏÍZrňâÊN6±DŠDK„j¶¤/ÙšþÐ*ëBƒïVHܲ;æ¾çÄoãÏDéWƒþÑEðúðâhpqtÚ?>=:él?À‹×Lª¥÷¨—fá9óŽÐv.ŽúLÀ­€ƒ•ܲS!(j§«ÖÍÍ¡ChRú¨y?‰Ó’©’PVZ—Ç¨Ò ôúEÎjµÕ:t<°ïÅòëÝôQNösËGýŽÈžËS:AOn¯ÓJRÅx²'*ÑRÒùi{f”b1€¬ˆPjž%˜l–Ø%>ÑED¿CZªE³´æ.fF&ÊT#>¤jo%J“2ž™X’ÞßÓF#Ǹ·g `)·(ðÆÞl <Þçðp*DìÌÔ)n-Q‹y†[ß~˜ù®yg­T¢ÜØn 8�=»ÐJ“÷�ßP{T5k—޽o¥ —ÆžæU sM}uçÀ×ÚìÝ£Û8%(ÞG%º@Í‹/E6x4Š‹"Ë eç6Õ� ?ÿ$úë´6QÖ˜ñʤÁ‡ÿvt~üö×ÁéÑ ÎÎçGý÷ç§Ñ»Ö­K&2´Ï¡�å®…‡“Ly(£u·7ZØAYd�¼Ã ÍÃM#v¾fÁ®_g'¢1^)O@he:t“ˆ¬2V¯ýów‡ý×?+–=[”¨ÙÐüêlíp¦}õ{ogË›*IUÆkçðÛ–×ÑhXà…»a`tåQZcž¡å(ΘPª£ÙúèWŒ"¤¨ŽíU.·Œý°€ÞX Ò ¢�X¬½ªl„²w_þÅe!àYµœ‡¾Œô ;ƱVëË`œóit0³/hñf•õ¥µi§×ŽÃÊ/ÁÓ1H4µßsÀÜNÚ-F_¿¾d@eŒ—É0t �ß>ù�‡O˧NŒ�X'¨ä?,‹¥†e¸¸ .·€‘0¶&·FK2}a—ñ—Ò¼9zI=HÃXUÌ-q³�qIFÖÖ‡S/�5³ØÞ¸×­ Pò§=šß£Ù2šöhæs¹§ªTÅ;›^è¢ÆµíA=Í o¡•béþ‘´§#©¶´X^ÖíÐ:&ñl^Þ¹ËB DY§zƒ@ ´fƒÁ°‚ ô.KñãA¡ÆíD1“«¸#’ÍyèÄ[KSX“JÆ\š,:l£Ãô ES «q8~,Ðca³ž€5ýý~Œ¤Ï®³Ód"ÓéÝîŸÙ¢ V˜7R·Q=_Œ?ͤÕýprþêì?ýÜ yž@eÓ={¦FvDC_Èn5óÈÁ;q§.…‹tÁ Àm|šŸá/)‘à~áO‡c„‘º“¶NôŠ!_Äj˜x¯Ê KݪÂ�|á‘é‡ÅºµÑ•mF‚øMRàô`–¤ãó:J“â*.Ú¢_Ä5mœºÅ`Îã]oÕ#¿¨0Õ({©LFï¡ÝDhL§_}µœ®¿Åy2¹cñ�ÌT!žÈ…éóHO�°yJŨÝS^à—f!OR\RÆþƒm4$Í ¯Á|¯|ïY ˜s órÝ=‡ÿŸ,ÅfS™g=5¯¢gÕQ«33Û±Ò™´£±X}&Ī‹‘ʆ+aæÙòÔ³tí'^LÆ{6Ñe”¤]šTŽ”Ù|—¦šc (…ÂDx�žÍ€É•΀üËʧÐF'zJñ¸üäY|«þþ¨ÒRáfN¶u@uÌû^ežn˜@žnŠ3€Ú…#O�7¢Šîé ïaÁ;ì� å±XNE ½¹ÿmÿ¨Ñþ‘ô’ó?fËÞþ÷]- ä‡çÿ?  Ý˜Å¸è ‹ñrÕ'»z…ý�+І(ãÕÄÞ=&^•)Á‚Œ—Þ÷*>ã†Wa8ôÍþ™÷¨ÃÊHÎYéë6FrþnÈÆþkòlø°×äil®(Ï¢¹ÜÙæxj <`ÝÐÒrU»Z …—‡†›RÊ«¾WlË0H~ €`/†‡“¡)a€=³ÝS̯œ|My(Æ9�â9U†&*ÿ±Å›ž’0øÍžÎnžíß–�Ž8ºMÇ£é[ËÝ*ëØ¶Á)hϨ5�çà'Ò�×,áÙ§ˆcÊã�e³YbD®ßxRUتŽC|ÕÆS[¼íÙx«Y(S6’,©ˆ]“Šh^o¡5hÊAc*|þ>Ö%ùâ¸OšÄ&‡ ¬�†mnIC­›ðKPOßÑÙ«:¤ÞØ3cK/¤²5SÀÖ옯UáÕ–<½ö#›`ƒ·TÙšÓ)ßÔÙoêì7uö›:[­mòÅšæTy· àõ$¾w Ý>4È+haèÒ¾áV5Ü!�·À½Ê²6Ô+sÂÛí^ÓßEs-‡Œ >—þ­â'Z…žujk`~òoòhÁZ§‘×¢Œvb1)q¹˜ó{ª¦<™@:&"‘|�‡ˆ4ß×›�L%­0*„6í/Z•þ^M©ØÕ\mGt4‡'ûí÷D—Úðã­è�¡FŸ&ac)ÖÖ#8Yw1ÚP2ìY3­ ’Å»ª\Fi-§M#²fMV0v�í^NÕ¢ {nl²I‚é®Tcõ÷`z%Î0°1š”qNØZ: ¯ô|m«ì¾± Àå¼Þ¥m7ü»a½+ÎòFøL“î£i P™@ÌÌÚ"4O33É(í@–*Qï i]AÆòH5d®š´Ò–áN§ƒYvv¶+Ú7—@3Ç`5·³Ež“ÍA—PôŽSŒÍ.£�x¸9™Íâ1MRO& ƒ6MúþíbíýÑ$ TŠ*à}¬éu Ño˜¦ÿ¨‚ñ¾Ç•DÖF‚îïÃ!XÓ^ã»u+aÎШ™J긟í¿®Ý’¥ -åÉDáÏn"4KXõmWá>vž¬i[áÉ û OäÆBäû&äÿnî·?§?—|À÷…*ômSäÛ¦H£MŠW ñùrú¿ÿë“ï÷VðÀŠÚ�¼¢uGƒ Óó;%‹‰ˆ1…³£ú£ÒŠq9ýû¬Õ­ÊYg—±òçÍ­£&"Ú\Æç·ò8–™d~´8`¢5vlÀ¢ ’%?Â7Wií0õ—ñë¥ÍÊT¦†Ò„'_Âòhå;o7õ+yÄ7è ª°Ëj¸¶!þþßžwɳþÔŒ½[ÇÕ4�£Íiµ/^ÙøúNÌ­ÈQ¿*’zý É}ìNµ1¼ž{ ¯°s¿‡Û6©O|&CïysCï«T'o—Ù¾îsoßöºäMnW½Þ;œÖŽÝ¯¾Ìn›ÚhÂõäunÎ ÿ®66pµ&ÍÀPÉ_ÈTªQ‡ÂK…)µ¬rë—WÍáDX©¿Öm°‹6‚¡òKd¿¯N«ºékœÞÊ€†“²\vZÅG«3§®aÎÖœÕoÛ‹@?»\L#hQ�Ìfû�›¾.F׈¤¿} Žl[ì¬w�ÉpZ9?.­ZÔÙàµÝÍŽ–^Oœ‹“Võ„®x·¯ôœ€ey-Âöèñ!\ÙQ�û$–!‹ØZQK³»@6I,-醈å~4Ìõ€™eãd绳Œ&Zše×qalКAQX:Šå¾Fý£ °¿î¸(Ýžą̀cC&ù«^oðGW5²ŸöîÙ~Zg’€:μgIEu‚`÷ÇÛëßéñ,ÀÉ-~Ÿ(f@×s�‡C†Ul‰Í}§Š>¶e|/(ãé—GhQÍDÞ©’Æ7*v¨v¥}¿âJ}Úp¥¾f7˜²è±¥«_Á „º šÕH¹Îr† ô’áû_|¢e)úxQÙÒ{À»‘ÍKÊÑù…À’âOÎ~:[E�ß4µï�Ôþ¥�1¬¶.£Lvm4�ç(ËóxTZ¤]�á°èŒnôº3Š¥sÔ"?-×3f$Ó?<퉜¾ŽvŸ¯›ö½šá·TÂ’§†=ËÙ˜æÓÁ_Jt¦ Û–‡G_ÚÀÓ )ãv˜ÈÕcÞÝÍî�ô5;y�kÊ ÁާæÖ_f9ákfT¾iÒ ²ÞõîrS”ÊÀ5„—¨ ™;ÝXª‘ãR_´é�B9®´ç«8j·tK¦ðåþNË t𸍋n¨ù<.ÁøéäþÝR­Ø4ŽŠ¸ã žð«J¸FC£t»n’Á½¿±%(Ù\Oòl†ìðŠQé##º�<®¡¦¨LF@‹)´3ºŠrFE�­ß¶”&¶EHcr^E8ÙÖBÞf£ºSÞ"#ó¼'äu6ͰêmEùåðéþÁd²G?ÿµõÂ-ÝgzõÅÙÉñçõ'ç‰è¾ß–žŽØÇÓÑ«éSÒµ‚ú9ýÜÔ¼÷ÐAìü,\ìûûn“ñ?ãéá’w^¿ƒÖ“Ô?Ôp­v“ç›�´ô÷ù£¤_ˆ¦×1Ð`äéúƒí«l:ö¼¿`wý>yV ˆýûœnõ…š“„oèýÀÐÍ ÷Œ\NÕ/ ¡¤�ÀòþŸ^TO–ÝØ«,ÇTm¦\èðÄÇÓ½ïqbœ&ÔìË&ö=Mìñ6hz Ÿ¶׬@7÷/�n¤‚µ2@V‡|l ©È Ùð¿ãQ©úšõÌß±r”g`90V�OKíê-§22€)O¦pwÆ]K¥l»Ñ‘7T{·‚Ça=Ù›p¾¼ÿô.œé&»ËÏœfÒ²Çï®­ÍÓ‹þ¯'Gƒ×g'€Aº«5£,¤E§‚™ÕE§Öš^¤¨U�›ipaµŒ-¦”½|IŽÒ1F•r÷ø-ñAÍê@/†Ž½Ü)xœí=ýwÛ6’¿ç¯Àfßy%W–#9íuí&]'QZ¿&vÏvšööõ)´Ù¼P¤Ž¤lk»ùßo_@ðKŽsµ^K$0 3ƒÁ`°»Mž­ëC¶wíno?"Ûä? ä&Š?&3oD»#xÈž�¢Ù"ö/¯RüõRþ ­Q›ôŸô¿!?_ù�?›‘Ÿ<šÐ˜´>²¿ÿ¸œ_\”«¶Y ªì‘¢è{óæ¥hä�?¢aBÇdŽFzEÉ!`Ä›ù…Ɖ…¤ß}BZXà±xõ¸}€ ÑœL½ £”Ì 0ü„°>ÑÛ�¥ÄÉ(šÎß GÐQ?½bí(]„ñ›€]¤÷ö�D½ ñR�ôUšÎ’ýÝÝ›››®Ç°íFñånÀË%»oŽ^ŽÏ;€±¨ñ. h’�˜þïÜ�¡· âÍ£‘wxÞ ‰bâ]ÆÞ¥b|û©^vHMÒ/¦fì'iì_ÌSƒ`?è¶^Hæ…äñá9:{L^ž�uÈû£óOÞ�“÷‡§§‡ÇçGƒ3rrJ^ž¿::?:9†_¯Éáñoä§£ãWB�\н�ÅØ@ÓGRÒ1£Û¥ “ˆ£”ÌèÈŸø#èZx9÷.)¹Œ®iB�ÈŒÆS?Á!MÁ1‚ ü©Ÿz){”ë6´ûèÑ_ýpÌÇ0ü«^=Öžø^BwÅßîÕsãe8¿ÝõÃÙ<Ý¡×4LwFј&f¡$CeóÙ�·€NŒw€µ¡P+¦îóÔðÍ£¿ŽéÄ)yÿætøîl0|w|v~øâÍ@¯Ä»éb¼߆Ñ<´,¨ŽÃÀf/-8‹ü0¥qY‘¨jw ú»t|iÓß]ó)È^àøLò}ÓÆg…8‹‹ÇÚãI25°Á0Aíj)M°!»»äèýoïÈ,ö.§Þ>ùHéL/3Þ¬ÉXd'yEG� .[Ip¢ä$gÀ°TŠˆŒ%ñ=ÌÁù(%Û`š~ªwÔüñˆÀ‡UŸÃ<„’d„¤$ÖÆ¼¦"$Ô”ŸëeoÀ³~èäÚ�Ó9ü�Òô*q‚"¦KÞÓ¿Å€.Œ¤�,dæÅibµH:…¹1¼ž¦ªÍy³ÚëƒGªå7~ø¥â10‡GaâXÑ…”—¦(3QÒYÍ‹*Æg›=œ¥ZWC"0AúçšÜßWÍeý»H†ã ñ8 aÞë�Œ|¢õçØ› ,–]y1q~¶C¨hbz“å–ÃÒ© €ÁduÂ7PSCêå<Ž±Ã¯ÏÞ’YÏ"Ì.›ržiÞ_Q&ÊóBCä‘q‡øuJãÊ»u7Jýk5É Ýh´Œ¢À�µ€”@±64ë¥ä* Æ ',‡ÉÛÌ.ªç/îš…MNæA�Œ@Ç*ö�.�]Eó`Ì{…(ýÐ&­«ù –Ž‚6%Æ›i4 @Û ñ=�cE7€g9:øÕUJkíü*?ZVd;:»ª*ò­ÁºtA7b:BƳ€‘›€Íò)­^†µš;aJã`1tã` q{`P6è—¨àìÕÛå?ùµzNYÉám®îoµë.²ºÎpK éY”ø ÒͰ¢Â�Id*"Æ@óÄÿW=1�µͬ©ß6Ò”Ö«÷ŒÂÐÒ�?«Zkä_ØNºÆà(GW”-#ÖÓ¦Ñ í2â2„cKnÎ2ØÐÐ>ù@Ãù”  Å̤ª½94¸×ÏMTøpÄxy�­£ è$fEQ<½Ím‰&â¶w�øÛ ÀQšFS—n+aÙ/º¦}]¯qÅ1  .2>ÚSs4‚Ùð�®}D>Qøä'4VКš<)D9‰@Ò3¸ZÛ/øe �ÇNÅÅžY ·/TñÞP‚Fõ¡±ò.HŒ'ê‚Eå‚qT‚ÇõáD¼‚KSÌÙš­u˜ÔIº`‰�5öaðتð@¶#ìhÞÖ²òð&ûa)]ŠqÏ­šö÷Ù²/é‚^f\¦²¾øÕ‡¼š*tànVSíÒXæÍ¢Š‹³a@ÁÚ¨�€„`Ô[ \A-�‚Ðl‰]©urAaV1gLtrWGnÔ͌јˆ…mk Évîç±O€ZÅþˆ\Gþ8¿ô’ý€%G‹–I ¬:šÆíƒz°.)ÀS\Ûr,£L¸%Õ¶QV[�pšO˜œ´ž‰én`ÉÌñB,åÈ]Ì¡fXK0j9óxMÎEXØøÍ1)'Ìœ1ÌW×|jñß¶ü–áÄ€o�½Ô«3J¢QçºóVù´Y{³ÜÀ�©ÒPüI˽âÝæK »�ù¸;‰¶¸|Ÿ%«5á¦ÓàFò”m­¸lÃZ lgv¿Æ™¬W3\%£á;¼ —~ØÒ—ÎÛøa–‘% Ft9Â,\ bU4hZ¯®ô¡?®¾ aúÐiˆÉ‚.¦ Ðe‰¾FfvsÏÙÏðîløóáÙÙù�§'ï~ø±£=~{òË@ÿ}:8;úïÁ£Oð�ùM†éo|À´¢rY×l}0|1øáèØjL>åMêÏOo‡gƒWú³·'èh7K� Î;9T…È•ߦB×Ho›š hJ¡‡Nêkømº¹óF—êyW_^wÀ;òLt¾+T'<¨Ò®¼g]S?–Õ3KŠê¦â*«n–ì0k ö—{”»ËÈÔ]¡Wž9çù¢‡^»0¹û–Í�XlvÄ^(ÄlÊçOö$ùçïŠP8¸4Ï::ÏÚr‰|ê�² Šw ªsqPV_ñ£ ËL*”ƒ@fmV¿�bÆS¶“—’6<½šM7aL(K’ÆRŸ8ïß¼=ÿi%‡ç§‡Çglûmx68>?:¼ÑØ—3¹EîÏþ- ؆ÛÖ¼ é ól›óE/ØÞÒ.ȳ5“nÌfX#3`èþS%ù·³(FŸ�'Ëi!ùÉκ>Ðe%X�F1`Ù_õº=Ì|ÿÊͯ¿)Zªngß™&}ôG-Æ<{$W`AtyIÇÑѨÕëäçhÒÊ�h·ùŠÐŸ�Öñ»7opÔ  mÓt‡ßò²Æû�çN¬º´®?³;ï¬ ‰ ÅEO�”ãù¬%_Tõ*ÑØE`µî3r Êç(óé‘jö/ö6ê�Ö–……µ1×^36Ò¹7æØp«4Æ-¼ñ®±ãª¦µ&�æ-â†+ é(U‹›aâ_†^°dû]¡—�å›)«Üc’Õk´Z¼ë>r]U¿“–O¦y/9€öAS–·÷à6Âó/ƒò^û‰ÐbbÛhq4ˆlsl³îzã±,_{P‹¬¡ætwm>ÞÚ»Pûìô¯FJŽ�UÑåªWò•?Ñ- Þ‡ª¬Öa®–>ψê�“CPÙ¦ã:ôÒh¶Äàb—:+ckàÇ#5TU�ÕÐ:îÂELeíØlˆ¡¬Vî/3ÙˆÞ#­ÎèËô®’ñ*tjñ˜±�·3š¸·ÜebyW¬µ¬EÒ¸CM¹É¨]�•r;º›a§\3÷–¥ò˜Þ#‰UÄïKõ«)wå q˜ò©ºÍÛdÚÉ9X.5Ür 3'·ÂK(ž×¥Õë0WuAóæ{¨+àW‡&’ØbjU}Z¿W�m@Nü ÿ™égc¬1ö›;–$œ¾`cÒì/ËH³åæ¶§ :[ÈÄ”mä–N�Ä̪ò®ï”Ö®úEéÒA¶p/� S¥¹Þ¼G£à´jŽ€Y·�ú¶1Ù€òÕ«�¥Là{Dÿ"¿æäªŽ‚cÙØd jùê †ký{¯¤d¹_wP\ ŠÆí$rn%ŒNµƒª;ó04Y£©s7@ò¥œiO.«�Ë=ùy—üJÀ-z��qõ|±ÕBùŠðþŒv¥ëÚé rå¶ ¾¢²½ÅVMf×ÖàËžñÖf£c–kû”¶ØÍlß�EŒÂzL� ��ZlgÁ¤ìˆ™²”Íî²wÐWq í®zs¹\oôÝyµ=ovv›õD_dOHŽoD·Y1^¾dvæo4ÒB.¢['µ¦Þ­?egLp›7mD¶\$„¡‘ÊÂC4…OŸ²ðãHDÌ“Y]ûc:î’ŸýÑG]ûS/^�ÖÄ�ñh EØw»›w™hh³K‰ÐÙ!�ÎÒE^ĸ—ôüQbì“áG,hEŸ²ù/´¶“N°¢Zhè]^œ _žŸN‡'¯M�Üßn˜™2º³pà‡ ë'615Q—͆K¾!d<`„ÂM’’@š<š{E ‚µ#ʘN¶,¢#0ßùÉ«“\Š}òžAOsͽ X°4sŒŒ0Š O%sˆ¤0Ñ@—iÛÉ/&ULK#qrÅAµ55bsá] \�œ˜«`ˆY1ü°‰ð(‰ºR†å$ä‡ép(µC¾xF$¼a¬ÆÁh5îF‹L³ëí]šK”¬iã$ˆk .üÿ™Æ8ÝÛ¼@¿íe Ù]äß/ô÷œIò0úd§®à‘²µ8­³�ÂÛìë¢p½¹ŒG:ÏI3Gªðøå:’%(c�Šã–1T=†¦À+È^.14ê…„Îþšñœªaw¼Mrã§£+Òâ5ży îߟœþtöóáËÁðÍáoƒÓá‹Ã—?ýpzòîøÕ¾âk#Ú½:Þ'›1õ>T6yr~~ò¶^sÎoõ›:?ù¹V;Ž põ9ùep ßj5äHçhlL'Þ¡ÈœM87´<¬n÷7×ð¢ÔR¾ï£áî¼R Fº=¸F±Ê–Nè¼÷cùå’ GKñk)6v5óXÃQÓç|Cf¬!ì†Ä|�ŸÊx*ihä-ļq¨”B•‰ªú„ŸâÝñÃñÁŠ<áùŸ9K÷˃Ïðòކyxux"Q*» €•à‹—?ˆÌ„!@Ûá„xbžéIŲѓ�åâ¿?Ö½ §WË…ÁÚV=å£wŠD‘pe>t4¹½˜í¬c F–nF‡…e?ÒÅEäÅc2‰Fó¤C`œFü‡yÅw1CÍ(š^D]òŠ^Ì/É™ø·l§Þ¢e×ç+»Ãr Â5ÎQg„ëæçé½Xh¯ÄøkžzŸ�¹Ä\9g3b'£­JBÎ%ÌÌ�IÞe »Œ_Èa�D:´˜N:JyRz?4’Àgêu_æ)v¬j—ò1¸Y´ÐÏ`¶¸’g½aËw·ˆdÅ$'2’®ÅÖ)ØÔ±Ý/9£e5ã§�¯WO?·é%b5áyf¶uš™*…7Ë·þ™†Ãò‹i àq«™'‚_jÜdž¹M�»aDæˆåæü�5÷¡KÞs oGŠTf$zqàÓX¿ŸÁ9øÎmî5ÆG<¤LŸqTHXoÂ(q£:7Úó{Úîqñ=†ªØ^(sr6j§ÖîEÎ)­jµ„?Zn@pŸ'¨“nÅÑ9/k-¤]Ý­B_ó©k;ݼË M��(¼Ï9\6~ëØs3×])%[f1½ö£y²TÏÊ÷4Þ_A& ­£¦L®±§Ñ@öcç9’Fg>£#ÏŠö2L|�&`Ïëè�KA›1YuC¡l›=·Ý‘ß=¹C»F¡�a„¬—c=F–�°ž¹=÷'d=ÜÝŒIµ.‹øîöÈ?WD˜æ³a£¡v}£!&úiæ&)ýÔð¡l̦Ðö²sÁž¦ÃkÙ}�Ò,©외vÒi+ểX³ ,ïr—œ!ÞX HR¨x÷ñ`ùóF{—W‹ŽÉ©dÏ:öÞäm ›3Í Ì¬Gº>©´ ôà„bu§Ùâ�}lB»³%O½Ž©$ 4ò&yiñ„òíe3Âo$<:/~7_°�¨ù(�°_±7c»"Ñ<&cx”ù)V»+ªvB÷âÓ*úåK®(‹ñÅEò6ä/:eœ•˜—’/¦^ :æ{´îÁæ,{Ù¯vÖE×IÕü’@ô¾­èà:Àš¯¦HÔÖÈå:ך¯ªQR'«ëlë¦Ø¯�g—〲-2íÒ;~= ^'�þ1óboª�öÔ¾âLÖ4»J™Š¢üþrã%,Å|JÇüÞúÏ~9Û}aï²Cú$á·0yc<-î1Q}så�®a¯¼1I�3/iLÇ^Ê®(¾¦â…co0ñ/çxÏù<åÛâ·>îe‡c~áOK Ö|ö=î‚OºÝn[BCÝ‹•bq �QJnÇ—Cy�óþ>»5w�Þ,�¦³dw÷Æ[€@×›Ívgq”F£(Hv¡ÞNrEƒ sEÛc³ó\ÞmÄ??¾¼>'[[Ù>\®<^J…¥yÖ‹wçç'ÇÃw?/û//¼Ñ�«çy,ºr¢ëbãã8® Ü�6ú‘åÊLäAY™ì“w³1¿oìF�wì/ð‡,Œ,)QW›¹Åp ÓÓhÃ5?«²YÈ×»<¢ ¹ ô“¾Åg[ÝgXy’wû¤jNýᆚHºqë,²Ð‹,ÜPú:Ì çÿpCÕ«,´*Fj€ÌrŸD0[¼ÑUÕY8Í'QqÁcqÂù Ϩ&×Ûž²«®ŒËתg×Ê×u‡ïŽß�L?ÇLk�µn‰ÓYÜF7¿¦¬,x­:´ÍïD½_c[–4°ÖàÊ[ë6;º§Jx$Z ž.9�È%x­†5¡íéw1K­™×™´Ô�°æ¸Õ=ÚRtR¦ÆðÏÇšÊâé4bº#»‚á`¨Ḛ́~%ÿ¢qq0¨ ç®ä?ÿþwÑñÂUcÑ× 8ý¸È'…èë,²™“â{Y…8ñÓ.ÚÑ£h:‹—-ሆi i­‡ºé,£{³ÞÕ‹—.ŠpˆéÿÎi’º«å¶Ñ-{a’艌LlQþ'�K‚Íl55c O¹[7Rô°Nuoå—…ÖŒ{”ߊl~c}�]‰Lõ7îðøÃYÀ¿8’cT<Œª^£QtÕªÄâ!t§)¼÷(†¯Þà�àâñÆOè>[8š)Ï´‹p[I Ã×&L�$tS÷hB¸B9ù©ë2¦o Âf´¸ÀSH ´¸)moÉr^°íkÕjÙ%—‘†9"dçZ3)  'Þ¥q4¿@BQ‚ìT•_E‹Y€iÜeÓàpŒE.,îÑ–ìïF&»QÄÈ.éwŸtø“…|%¶ñw™¯õF}_h�·ÞBwóE0‹þ[B¸Y4&‹’õQ;Ç2’l.Ÿk¨ cÑÐkJCç`å׋Ȝ¢2|­Zñ-9ŒîîjC‘a`±¿–~÷–|GZ½¿“ï¾#ß¶™q*©’)¢ñâ'ì ØB…Ò�1XþÃsý}Ð/2i‡ò¶4'©Å¦Û"±6V±Ü©vIXÕcYÐ8lð” ° ¦1 ˜pò†žL ¢±„x‘ »,L$³Ù0>Ô<‡¹ _Ù�¸¬û. ÍÃû*÷ŒW*ngáBlQZ!‡˜�´%˜-˜‰t�È·¥ 'Lü–±ßF[G [½4ŠÙÕò²Å�.>ì“WM¤ÈðRò![À| üTììûâ=1KEÁd•õ–Íë´ ¦½L²Wg>®~—\¢Y-3PuŒ«Í¶Ûé¨l\–*]$]ÙbZê…Z[v%T¥ˆ [É�­Ñqké=g í‚÷×>îgyüc(mù.6¤ÞDÑÇ„½óH…;‚9f,µ1߃å!òĦÒ\ÑÄíï ´lóÑáÇW’ðSöÞ°e&ت!Ë,™p�ˆF Ì`À€];M#¬(S 4ÏÜKUwÍÏð6{è-ÌëÔÚ¡3tžþ\aÿ¨Ø÷dæC«Sñ©œ³›±r,êp÷YÞöÂ\ ™a ª){$óÛ–¥KÎ2�6[Ò•EL–.áÔþ<ë±–I²u�Ùä=å–×]½´:˜é[DXüÏÈíAfýëà¸ß_½Ó)hŸ>0ò“È–Ú^’K%|í‡s-_¸‚`¨‰%ÏÙ9RZú¢€Äô�\AÓ7l$-q­ŠR‹«[¹È²òÒä@”ç7±«ê6¶üdg6dÂÍò›‡˜ø ML¿U!¨8XUCãã„{–ßþšQ¤7ï°gGÇ-3§ü.4´Cú;c¼x.}ªÙ€ÅU@€Šúê+òBÀÕºÝ_>z•#¦y|ø6 ;Ù×Í9¯�B@”mÏzl§¿«Ì#v?$; Ͷ2¿;ÈdüÃDO‰³y©Mر?ïÚîT³®Ô}ÊÄr”; ·Úq®&‘²ÝÀKrÛl7¢`›TY¬¿½…3Ñèe¬¢¤Æš¶äôóÃÛê"ºÏ¦(J #Xв�)Xµ0hJZh4}¤tÖE`e%³ˆ)f L¯4�?õ.…çI¤<Ó߆wlùp�ñPåxh{IQÜ*,›rÚ®Tñ´ [Ìi=xõÃ37ê«qDô«gœÖÙêáá›ç2¿-z$ðÝ‘Ë@‘1«Aûvß•¥[!e’±1Ý1 [ï5CTôùÖì3{õ\œŸhË.q„�òdYÒŸýð£�éF"&¬ù¶"GÛ"|Õ…hR—È. �Žv_™â*U·js±�Þ [Ùîí]è¡Ê£âò±Œ†F é½ÕåZ»ÑŸ41:s‡ õzöŒ¼ƒ™GÀäNÌ Û¼Œ¬qÈIª£mÉ�=Æ3_,J„ÙI¹ZxQËE^»žQ¯Y–ßCÁ ŠFEò«š¥Å]I5KËLMËG‹Q©M¨y�ú2Cn,S7‹%­[Ý^뺫õ¶sž]VfÌ|ÖÂV;îâ)õbh9lq˜¬6³lD†‹Z@8×€Q*y¹~­)e19vŽÑ�˜�9ÑÂ#¸o[ß(7>¬ê?WšàÒë�Çæt{ÜqMBò©£ÕPSMV¬r0QTt§ossJͽ0ëñ)% ó_f >�d þ ”³^DÎYH%:ûä*%gŒ]Z>·ºí˜/Š Žw%µ³ã�½6aXÓFÖµË: ™ÎgçׇgƒáÙàøTÔ›VûÑ'Év~€ŠsÿΧÃ$7Ç…Êê@æË®Á-šcYÉ‘ìÂ\øõÍSÕúK>騮ž` ÀÚ,àxQ™CGp¯üdx VÕ0”†cþFªe<%ŸeF�+jÆÐç�Ù‡« NWe0ß«žÉw yC#CWWëŒ~¦l@£!ÍC¯¸‰ÂálpÎl•Á¯"Ë.*d+Y ªDˆCÅb¸¨¶º\Þ­ß”¬Ä�(d¨šZÄ ±)g¿í‘0«ðF4jÁEF�&-þF]Æ«exà/Ìä|3SìÜÉœOêÞ­"¨vå6¹ŒÒˆÐ8Žb»÷™@G /oRYµî†Jr4T‚—e\çB1а)�=}œjá�[UB«¤3Æa½|'Ä˜ŽøD}Ä^í—LeŒŒ4"Œ±¡…“œ0�ò9³¤%µÔDÊ�q$Wp�†\xr“q€ @A F3Ьëijøœƒ\•Tsdà,Ÿgú»€Ýž‚3ãÛ_ å�Þžz“kÈ!:x “˜R56µ«Tâ)yåÃ>,‚§˜mw†îøc‘Ë�»áÕÁç=\g—7¯l¬ˆ�3ÐR¹lôRÝÁ-�G>®¤2q&.°Ø’ƒ)eY×=a‡ß*Ûr»¦Ê––Ø/ƒÓ£×¿ �ÿ5<9žÎß�·d3ò”N~–4>ä_2ýbùFáX&5ò‡^ÄXdžÊÞ“'ºƒ²Ï~&`Páz¡gú'Së ú‰å©Åy6BÞx\”¦@Tí��´ºÓÏ\iÕ“¥Ê4)hm‘Ú6H³¡°† Âà¿2Uý ” – *ªX«J·U� ð#~w�ßúø­˜37˜š›e’(Å9$pqöˆ¿TÄw½oŸÈ|õÏFZÒQKÎ’ŽlX\e�Âý'æ±Æ²Â{ßt²Íåå{²‚:•QAÇßûFÆLMˆÐ—L"ð¥÷èÊ‹ÉvèMi¶ÜÇ­.ÐpôÖ­oq•>¦˜j9Gœ§ý}Y\gK6ްUw—ÎÎO�^1ñ#«Ö ÂÓ~‡HðN‹ÕØ€¦ºÙ�ð �ý î³»Z¦â<šsüRSü%¬}>é-K |Ë!‡ß�µ­¾j‹iþ{#Àj+ÖÌþ¨ï5hغÃV߀âl‡³š�ÑT‰ öÀ¥u¹ÊÍ“F„PùTkÓKÛÐò]™•ñ¢(U¤%»ì®V׸ë?Õuø~óíg°îPOô,Ûn”ä-ªÄ1\T`zy÷ñ�]¤HG©YQÏ •Sܳœ¿XY6²Å¿´7Òˆº½™lɯJ�7bŽƒ~uÜ�éâƒ2å� T5ÆO*óæÚm׿7¢£Ñ76ÄÔ)¶!*¸¾À~}øæl •vÇMÅ}ís¿dY\ˆ­Ã W·‰L²Y { úùé»%:Uj:• c�Êœýºè#OjÔ¸Q5Xd™Ê¥/ë�…>θ҂žÃŒÀlulFàß:vè~­ss¯I!ìÍqÇ’<¿ô,¯ÁR>­‹A4I 6…[˜ÊÞâšÍI.eçuª¸²®Zmf^«Vd±œ™’3À ,-ƒ{dÙ-ÚÚyžÂ ¯k$_»éŽ¡è{Í#|m�ky€Ïƒ{nî¹û`Á=ÑÃܶwro�rInÂê'9…”Í�lW’˜WWy»40ï#Õ³LOÌ…‡ñz.Ÿä%Ž:1“¿O¨t·€Ùí•UFÞÖD3?~cƒ¯ÊRKªt°¡à ÕdMû °¾e8¸Âèq]B¯3w©t–*o¨Ë,õòê\heó¬dÄ-ÀE™ï˜b;õâTïµ³\ðû&Ù?ív»ÎQÉßæjï~«ŒDmAœž NßL+õ¶–³‡ôëpû^°0Á,q}—œñÜë”'ÿàDµ®[ æõŸfè¸Ìe®R™g,ÓåyËí´åzÃiBGPêéSþ¢tìTãUC‘-õÔ ­.‡ ÎfIò¾�b*uH‘ /i¬2Œ#uÍœx sO0æÓÿŒY¾æÑær]ÃÙ¾À¥@'ÚʦÎý<>h§!ËÒs^ ޱ©9k¹õ"Ó,Þ‹÷Áâ½ïS´xŸ<˜¼_œÉ«JÕ1C¾8û÷óSê˲QªZ»¯æ¤ÓÏû'èeèþ@»?¡iΘ•x™KNx>XÝV÷ƒÕý`uiVw†ª3ú%X2E¶ÆcQ°1=�0|ðE[ü†ŽæP“÷oN_œüZ°kÿ„ýÇ™´9óú\q«žs�ëqPÚ^y¼�³3åâ‚9v5V Bï)¾í mþmfYù“¬O#6áÞðÀq£ãÍfO’¤t ѽ¿C/¿- '*±²sDùáµøhq�… t*'yh |°V!<ïÜz�õï0þ¨W/þ¨¯Å=l±Ï¦6‰¹þTË)–�—S ‡ YñøOÞZ`•$ÅyX`=,°îÿK“Cœ•¥`7é)o†—¡TÌ :ÞÝ�™nfÜÍË $Fo#Ñн‹–ž»…Ò(R?Qyt€zºÁ2`tÚ'‡â=×QÔ‹ŸÆ;<¾2»!¥šÐÚm† ¢ K|ÅÇgŸ¼¢;*uPfV7ü5’í-?�±«¶D¦ÉPžÀT‡A‘qþ-ã­Öêo„µŠÎ±¹X«¿2�úÕ¬%¶ÝÿgžÈÃä‚ÂR‰Þ{HŠÄù¡ërü™;r–I¸½ÆnšTcI&7½ZˆëØ\6×ý·¹Vqj×?R^¿çª·Ï/Él1Jâ¢-vœþi—zÊ{r•÷úÊ{Ÿ ‘.p——Ú›[Ìjó"iêº#Њ‡×hTɱÅ{ð„iðì9ññf_·]¾Œ¡¼¤*pëK¾c¹çíÉAìÚ¥³žìóÀ“Kª­1:ì| !¼üÏ0>Jy¸¿!î7äá~=.5l·˜ÕxxM–kSC¹Ñ”é‹kš4ë3�æ®=æ�ì°[S¥ÙÒëâ¢ø mà+äâ06¦ðýo÷GwÒhg�æ‰Î°u6“¾–»I_Þ3÷j ^"IIv6(¹0›ºS,ØþaNÝ·0K~_á~uòþX9€×7"Âa\="…#ÒPd¯A«Cc6ÜÈ_}”啊©|yso8�÷“_ë1õÀÌUéXH?“þ’Ý]§_¡|5\èÂwê˜:«K,§Íõ¬Eeíô*Žæ—W2ßNÁŠ´4uýÃÊôaezÿW¦u\Zfê‘\ÅÝEGú•¬»²õÜZ†i ¹�®Ð1³üA=§ÑlÅ�†»[ 9 ¸wù9 ‘î”e@•õÚ23©Ø×½¢ÞØM¹þÒ”«ëH_Á%ÞPÍ®�rýú”Û[šr{k¤ÜÞª”Û[åöjPî5®éeºCåÂÍÖ:{dç90$ü6àK¡F/ð`Bæ T‘6Sã5È÷8ˆå& /ù®^ÝU¶�¦ÔoˆÙ¶ ¢Þ)+ZV}a³R> 1Â%¦ÌÊlšl°÷ëauØÉîí Öu^Jöî¼×w9‰pbPr}½ñâ±X»²éQ9ܳ˜^ûÑ<ùr‡üÔóiãMq¢ç­êwŒ…ëjéµëØz‹¶½Y_k³r¥í½½Ò·åÛ¢_ÐÖ ¼’èÆóŸÝ54 ä]4¹ô§uïËgBÅÁ1M€µyX³­Ù>ûâ§hÕØ[{žö^Ñà�¨o6¶g5öuóÆúš¼lõÚLCÓ¹ouàÕDÄü%t:K\”rV®�ö;ÉÆ»]±l*óÿx“¢úríg„^¡ùêö]I¿ï–%¬�È>}`ŸÃñ˜íœIv9å÷ôHŽŠø•=D\,íKmÜÈ1Ò3î"¸C”­òlñ,üg`ÅMaÑÿÒ&DF&w™´ý,cƒˆ¬kPp†î‰šÐQ„Ž]¦Ç¹™êÁR³³�çx´]Õ‡¹yçs“Ó‘!ñí73ôÞÌP94Ìxc&[~ž>…yzJy®?NRs¦†Ñ Ó‰‡èÂ’¶ŸÍä‚Ùʳ떨ӻ�¨%Ìù™Í¹?á-aÇÏnYÞ§iYÓ{Qt}`Ù^äý¸Fø§ˆcÄÁŽ –£&W4”Û£>¦Ai2.s‡T\uþ°Uýàö(t{¬¼¼3¿ÉЦ±Ôö‡$cr· ƒyÁ�zåQ§w9}?âŸk;®GÑtêëú¡…ÍE¸×�ï«XÚ†Çëv§µ�'PF}vÍÃ1¦2e'ìí:Y:áD6Ò’°i°n>9‰]¯*IIÏ‘¥¤g°nÅS/PlۯŶ÷,Xz�%ž(b÷; •ÞdœsOOÔ�‡y]I¨Úük„3i;�·ìÕâ–½;ã–ºa[©ÕF½h7¯ÆÎŸ!ä][HІ2n¯±ŒãÁG¿¾ìcDE· öáâûõJ¾=3ò¿\ðím@ðí9ßž¾µÌçÅîë:Kn ìüŠ:L©ÂB]b:¢þ5 ‘:RXæ¢.·ã .Es=Àm>è$à6¾z»æÒùôjçó)[F |sµËëq›Mý2oE±$Q‡'kaÓ`ªÉ‹‚Œ€:ýùÐ�"¡%s?ŨD¶9õ»ä0Ä£ÐLûe9aä™è*Úí­@»Ì'`šÑNLg^OÑnÏM;Œœ¿ˆRuÌ ÝE¡ �K/#¡GBzC�lÝeTHŽ/A+5Ø\g­Ðœ»F¢Î+ÈÎ<�]2^ytý=¹}B`F¸LÙ9éí d�y€KÁ€zq� ä¶\£ ™j*ð ¢6m'�%tªJgÓÌ©Jk…Ñ”†º8Î8UÿR!9ë<1±ÆÓûk>_ñ%¹ÄŒ Lêc-W—mfW»¸ÜŽLGo¡ÖHX¸�x¡x½¾æ[Ó³ñÍ ~˜Rc6/ÕfÝ–øŸÐ?2âìTæÔaNs^ç—ÿüú÷œñ§ñækÔé-vý8fÌ<€?ß‘¯ÈW_ùmí�õ¯³ÚpÓȱÎxZBÃL�¼d¯ÃpKõz—r‘ž7áA̧, ¦€#YpÖ^¹x“36û�G±ì´qÌ,¨É©Z½…(žÃ”-Ýg„ Kc8¼˜GüÓ„¯æl.W¾ç`ÓK´ŽÄÒ@ï%Žpœ¸î2ä�…3�¹‰$z‘$«ø¬Ù|zzj¸Dm#ŒæM_ÀÅÍ^÷²ÓvŽ€bùÄ(ðy³ˆÿêEÀíø™¹+ hâŽ�Nß}baÄÜyÄa. ‘â§ÈK¼`n³8œ%OnÄÍÔ‹“ȧ‰!0E°­€ÈÜ€ÕÚCÖÖØE{ØÚˆä®{ûëÍè–ݵƒvÿ¶Û²›»¼é_uo»7}øvÍÚý÷ì·nÿÊfÄëðO«92=%Ÿ’܆œ$ÌBAR¼âoæM€µ`žºsÎæá#�àˆ­x´ôbTi N�ï-½ÄMh¨Ä.ÔÜÛk6›,æ ­:²ê²·7Ãî;  Æ[cï‡)ŸygÍ8—Îðf4¸ì€éŸœœööö~ð‚‰ŸN9û›ï�ÇnÌ›ò½±xU1 |ÆIq (�ͱˆÏù's(N¦n4/��ÃÐ/ zAR KC@Tq,qšcOî3H|zûÄ}4 #b e7õap'ds3î&)¨�üÉ�¸Êx×8£aÇõ‡·í‹^G_Á�šiâùM?k£¾få'òGj�F3^€7A©K §ÆÐÓ½{?b«È�/Ý3öÀùJ{Â� -¤66v'<˜6å{yr…iq|N%‘;áæÌ\Wa$ fÞÜ›†“sKl¡ðœ%RsXèÀKÜøÁVe ƒ][„þ”öãë§õut$hu�˜îŽwÏ„M@ÌìP:r,ÐÑ�U±sÖõz­ÍØßy·;n²�pçp»ãα~ÖX3cûb+L­»Þ›öo��ó{g0÷è¼iÿßÍ Î>ÞÒío¹†•ê{?ðf­8Ùh4(šÉ‡÷ÁT¸óŒp“I�¥G$�¥û¯9©¤ºµ/¨Æ€Lí„a–‚~Ê®I¸?ÀÞõf¼‡‰%銅+áú³è!vަ?µ„˜qäN²fVÿc�Á«ãÎ'éa|æ‚$m1ùäM!8gÇòû‚S¦’Œ½`ê`Âà€· ó§¿´½pÎ|þÊ1Œ�d3ŽÉÎÀ–¹\�ZÀæˆç>|Ìhýƒ5w©L±vÕ¹½®ÙìbèÐGöÅ®ëö¯o~Z¡¾ßíKlòËÐÎ`p3€ô± 7 Î(A\¡¥z({܉�m-AÚ>†�A Û,Y/¡\,ðM ·`qïüÚþ½ã¼»k¿ïµûWu¢ ¨l^;77=‹ðU3•^³ó™²×?cw N‰ d[ÎÞÝ ð»òbÔei@ƒ›úICC¢Y¾ªM°`yu[Z>žÝ9Þ@WošC¯fì­‹ùmH9,‚¦%VL¸ÄîŒ<Ë* ½)ŸÚJö¬–ãzBÿO y”ܳàÛ³ «�Y!HÅgkqm ™ËÆ©ç'G”„kOêEkªhEüªÛ…“¤m²!HI=Ub“¡0zˆ!©ç±Í0ücŽª3„‰Cœñyðz™®¤†MÈT§¦(KM€ƸAtftÞ"¹<Ün“ܽ€½¯Iz•$0µ§xìÅ© l'Ͼ¨4n¹Ï¥/Ø,3,ãÊ|3‘?îÆ½™?láÞL¶I€jË)�'Pi)®£°Ìš¾µ6s©|¨ „9Öݘ.'5&ã�þè�Æv†t޳˜FÌB‘úÜ`7m ŠC6Ã@c3FlFaB'PƤ2‰ZØ;þ˜Ï[PÖ  @ Pû>›”<¼˜·zxw:e/€±ðsc¶ Á{b†CõÕÇ3CŒXNéÚ‚’ rK8Ä+ ,¦])sÁ¥U·.V¦2”è¸@£ïNNxØVŒ1Å`JlÔíß¾<Õd&²]VW<¦nD˜&«4a ÁÚØ™€ÊÐà ù…ÕÂ'€ÂÆ/2øÏÌÃ4\¤UP¢C¹nƒôÂòÅxL–�Æj׈…_qñ��¬©·•}¬‹æØÆ?Á¤µï¶KIðµE:DôáÐü÷ËF¦˜ÿ;Â!¾ î±Ó¿íö;=«žåw\–™›&á%7@EœŽÁÏÁpÌ�ö28 A“HðÔ+£!{L€ÈU.ÃåÊÃMñyê»Y×I¹Dþ yl‹ |À(Å @†³(\b7‚Nõf4 „È'kIVKk‹NË·’ •Júë°’G®uN ©ýóþþƒõþŸ}ü©ÿ­ÇG¥�ïïY-“[�`XU Ë,…Q˜ˆIžW5€?�$²ä j:Æüž`qªH/N~xyú±•=x‘õ\€E?Œ²GSðç`~™Ði¶µ÷%—ºZÒ!úœ¤¥êÁç,²®L„d¬eDÖÀð�|jZ2ѬhˆV]E‰±òD¨UÅÕqZ+Ã.†o{Pq /�Ë_Ûƒ‹Ñµæ$ú ¨�@„Ü^Ï�MÂRÿ_žÚ²³=¦ÐÃV»0\Ð%Ê Ð ç¿ ?I4{;.¡.í`ð<ÒK;6¸‹\È„Éà„ ³ $®16žE,o¨“„•¹Kl‡Q‚Mxmp¶L´oXîiZ| ½)aOBGàòªZ­êäxµ%„Hô6¬$]jÁÑbõ=¡õBòæ¢>‚a¨D‰°1D\Ȭ|±º´�‰‹æé°ÛœQ±w B9J°Sæ”@Šj^¡o˜ÒäYåâlq÷¡eÂcªUDZjhJÕŠà4hÀË„d+Þ/Bج} &µ¤¢,…«ƒÿMÒ(�‚a`v 2§³vxºó ¤&4<0ó縑;€ù«ön^c$s`'‚XÿÑ‘è1Và)“™p6‹½ó�>×m´)[hW. ®ué‚Î@­ôÎã?KÁ¸Ž°í×k$—z+àô­™êÑ$Ÿ‡èn_Ü¿h• N+!0¤üò3¨£­`+6.„4Øç@râ8ë Œ1A]òq›�K¢s„`koº}ËRkÕ!‹¢JÁ(k’¾Oè¸&«ƒ2£´(–7õla�¨^éÏ(›ø }ù¯xmÖsÓמC:>’I˜ÈWñ êšý7ua³$�Q’FÑO°DÃLKâ,ÝÍN–Ôø£â3w®ZÆ—°Ãx%ûˆæÌ\K‰ÁÒ1囕Z�ççLਰ^åÒ@Ö×.¥“W¹�Ü×êšR䫼|nà"F+3×� ™ÚF‰%éüBE_s Aã÷’æm/dË*Ü/ƒŠ"Œb¨˜ù ß';@3PÕM`Êû@'¾‚ø<µ_¾ÜcÓz[)©µ§Ñ…ÙÓÍÀ/I¹±ÖÅÊ<ïÖa­FÆM7Ì%s³–†‘?jI†1‡H²\jªÑ7`L(¤PY!”ødl@âF‘‹©Å¡ø ¶€B?ç‰ÉsÂÂrZ"×*öD†·^0A2=šB8†Öeað²<òªðö7�\œ´rÄ-öÓOžiÌšx¥_X9áø_:,µUÜ$_Ãf^]3g�A…_' zGµ“ GÔú½&»ûVàØ¥s eM[Ž,õQ�õ˜ãZî#¶Uöqm²Îb““}Y’L©P¬•kwS…ñ%®oc»èð¥| ìz±ª¨ ”d~üwZ�N•£!êÿ !%w`0ùa5E±'M³Éw×Ñ+ÌMD£ÄñÝgø”oË œ®Ì±‡2}L@TkLÏ t»ýI©¯á' :’ºï-R7¨ Db�‹fsl çù GÖëù*—SK'Èd¦¤¾ñFèK¦>²`[… YÚß§\¢v�Î ò¾Fg&¬ú7{ˆ²ì0fn˰Â|5ÈÇLÂ[…Dm~w:ÍíÊÒ1Ñ´p(†ÍY¹ŒÑ£ Å ®7x÷ "øKñï½XÈn!Ì.5Œh!¦AËqFýѰså8 Ç Z§ ê)%ÄËù£Š¤J;x6g6öiH¿É¡Ãã¹]ž†ÖÁgWu²‡ó¡âCÈBõ‹ ¥ó®{ë G——�á°¥ͺ¥Ñ%È!’te!¿Pdõ³@pÝîöFƒŽªi1Ô¦xµ1ß]kª×rœªh:æPƒÎk§óî¶Ó¿ê\ii†¨“õ�D ‚üÑÁBæ/'ª?(,lN‰¥=ÚëèR�f¼ü®ÇÿµûtBíbLQËýâ3†u–B_NŽM�jû"k*^beÛlBÕ³ÚSoé]L¶éžÒ˜Ss‹Å„j嵇•Åܪ7²ÎûpØÜZFzMD¬M¯ëõ–Ù€ÉÑê7*lÕ×ssÕAµÚøuJ»Žv—®Ô™ì¬ÄÉhë)¡_sÁã>¨™j#z•“Æîœgøtê¾B5”«âNÃDõD$ªÈA)%¶Š5bM–r;yPŠ×ðÆ_Õª© b!â¬%¶Hp¶»Õä†w¶6Ò"/L•ÉÑ@4dçÆò 0îvf~Œë ôÊl-£Û¸¶#(^Û ïz}ƒT¶EâQ°@û›£—ÌȲJ˜I9nXÛ0!Z[«…ó%Åiv�/Q¢ÎEl x»\·‹s��øCM1ÇÌüJŒÉÔJYGUE˜=¼Sÿ¡ý½Â� ëWlW£0”÷x´ú0‹ËbÆÝiÆ2²�ýWº Tàwÿ|#4›EÜp�Ûð�ü tïè%‘vÀè:‡ëCd+Ü}2=åW Òìhr;‰±p3h³ Àe!nÆFžO'ù›E(ú¾NtzYºQ¼þºP™‡Âu"�‡Z³¡#R+‡n)›EPÁ´€û[Du�7�²\]÷ò�¬±@§¥çì�/9¹YF)¨"kÐŒÀ¤[ ˆcר_V‰¸�õu*ÙÔ-ÓÍY «Ç’Ð4î7öº«U–™÷é6dÚ�ÊÝÞ\Ý”~ßtÆF ”©‹—4Ò¹ÿÜhˆl®TŒÑ•,“¿|¼Èbþt9m¯¾äeÀa»�.£(C !מYkÊ~4¿/–㢅vÁ¤R¿{“´ bÑíoƒù‰‚Ý®x`•¤”#ÇåÚ8[·p᪂ÅÏP‘ŠPßÖ(x1GW²øs¼Y¡¦“ >`É´ ˜õyX ^¶ŽÍ·Ã*ÚÅEvvJðøÖ£ó¢ç¸ñ5;ŸT¹É^ìqk›rGÃÈëárÒ‰‹bí&6$eðwA<ƒy«ØŠÁq"i(T¯Hë€ïö‹Ulmm\míÞ²Z?”·¾bæ>‚´ñŠà~©õ´9Ç­P`1ÅU×±€cOÝ´°WÌM ýeàÎʵ…fÌ…^�‡ ¶ÄQ?P»I_†eîá!çÒùºÓ¡É¦Œ?‡{F*›èõëý›†Ñ¸®w�”1°¬Zã©Ûëtkƒ‡ÖiÕ(ï‰)Û4¾?yϰ¦Ò‰)ã§Æ~§Ý¤‘ùÕ0¦Mº„VTè:Ø€N£e:ƒr€Î¢vè[€¦Ð| hIóÆ€.£Ž hQ÷:€®éÞдî-:�Z ‹¨kšT”&F¿P¿転@«Š2=“F€f•º äК’Ë€®(9tUQ> §Ú+ÐOÔËú¡äPKÉ6 ¶’=@%Û€ºRí;êi¢Ðš³õ5‘ Íë[ZÐD ¦’ €Ö•¬,ô\oд¡¨Ð © hSÉ> —JÖ;C¯”lz--z£‰ €ÞjsTè�v2è½&RÚÒD! m%kgÑŽÞ¯ 胒#@•ìÚÕYˆíi'+€öú¤y+€>k7@_P�-r U©ÐW%먠oJÖQAT¤_{¨“bI}@Ëì+Ù´¢çͺ¡5€nêŸ!tKƒ@·•º£yM@wõ­ èžîõÝ×` Ð=`ÐC-2ôHQrôXƒUè‰Öìú[ƒË 5ÆKzëÿéOÏLîÏÎ¥&öKËŵIýôübòkBÿ½z–]¨�÷3+«§ŸÖxßv\/ççÇú™‚Y?oøcýæåÕõÍ­7Ú¿/µÚË�ÿq¤ŸI›Ý¥ïêõ‡ûOÏÕÛ—ñõ ^ý–›|í׋_AèDåÑ~\1?6Úþùpsëûb{çrwo¸¿ÏÅÁáQ²=Üÿ¹:¾;ù7ÿ³˜ø +P©ºœ xœíYsÛ6úÝ¿ÕNÉ•¥$�Ùy�­bÓ®&Š”êHÚÙÙ¡)’XS¤–¤¢x3þïûá"A¡Ð´ +±Eù6Æ8�ÂÜc(klÚsÛÒÜÅÆX`´ð>aߊÐû+; " A‹€qì•Rí ²t‘…Ú''³]ÓÙX þ”¾.kÒË8ölf¸Í·–¯/�€ L¾ BkæyNæ!LÉ<³ÝÔä­q„Zg þ@å †Õ6¡Í–°ðÜv1úØéÓ±¦OãI÷M_“§8~;¼_ƒ:Á_:Q`/°CÏONŒJrFÿô’&�g—’8jzîÜ^NÆÏBàÆ�¶ùïäK²P°ÄŽ“}zkÂô øð[è ›Žásùä·ˆãBcP,Œó׫“ Fì‚¶ù-2Lac†Hß:+ãg¸Ã‡ê!úr‚àC@O"0Òó 2Ρ+Ø.zyZüZ_‡þùI´xÏý›!ñMfH8ƒNB06)a=À¡¾ò,\oD¨m@A|„ÕOÅÛF�¾Q ®‹Nã¯Íh*v7«2|Èjˆ.yQ6›ç\$o…—(à®�ÓLy‰�Â�ØÖ[Æ.ø öÖÅ[f·Y¹éŽ˜È?9S£qç9KZ 'ß»¿%*y[(ð*XphÒª'çLñ»2Ç–àOL@Lœ�Õù½š�ˆJŠ}[,A’ÅšR¢TÉFo óî̱Ý;�TȵÛSÅÓ¤I%EÂ/p—ªhRZ•9‘— s?MQ¯ÝºÝGUÔËBð€ø¯lü¹aâN–ý¨B&Ÿ¢3P»zÀ’Y*òT#Ñ$‘0Ñ'϶¸�è9†]ÏCàTüE´‰y? îÔ2Bƒ<צX†#zX7�@bmÊ7¦�!*Ö2œæq­>Å¥¬\Ë'[ú–äTRÑ Ù.w‘HEI«Ñ?‚.¥Iç+&íï©V;ŠFóž^ÃF9à{²2q€¦o¯!+&ŽF% õú)À¸(“àY"e¼€³`íè$Ë!¤›º”˜öûD„êÕ�‡ßEd›¤xöZT©€`Åj—çøyKËntÑ^<–‹0)ªÊ<¨€Iƒ;Ÿ ‚á8‡ßçœ ãN²Ê•6R¦ ñÏc‰/@ä�zJ9sœVÊuåy‡µŠ,@bÖ,ÐA›ëoƺ6 GMTxèù³€ˆ8îòUÏŸ‚õYÏs(Ax:Áeú•13š¯ :æl…pÖTAÊ¢ËsãA«xKä;Žy1û¯ Ûaýk6<F-£߈h„; ïtÒš%Œt±×·z`/\ÉÅóýÞv~ö:n£Ñ¸•S–6Ë+ëTÅ Š+`Μ¯Å.¹ §{ù€� \Ôyý¡‡ãç E%nZS âR „‘)-·úÞº“Õ‰�À§%%$2÷1Î7Ñ‘ ö=HMP�%˜�}sÂGÉùžéÏTáü¨‰�ÛqFÌt Ý&ÓÊÃbÓumøÆ ÉuµôX”Õ²'�{¼¥ÝÌ“HQ­1vx† ê£I*—‘>¼Ž=Obíòr,šWQá+µ½" Ú_@‹`ËùIW2Ý-š—vùȧ¨�ÇV¬Ð¤#sò.)�ç4Té´¥]9u ÊFy“FãÆ£xä<{�zÓ‘}×8O¤ ùÀö°®OÓ±v¥ëß®�ev!vñø�!ðKj#2»eIAª‘“”›�Èl$ È™eÉ© j\ðû¾ç3OüÑã/VD•kÛ·QÖz(m9ðü$ÇòrE�*Î|~XR¥³×UÒîªévzäÀRbWähR@ˆ:èP#OŽ,ÇXæ‘ü>ä‰w{pS…ÍÙkrÞéì5ßЉØÇ¶‘ —³ß+µ"ªìSí£»�Q®Éy›È• )-,« äÑ슭¤ûòeM$"U¶^+ïºrg¿Ë®ánRéà®%h^é¹¥Ïà°Ò9Ícç2™ä5y„E‘ÁÔ~;¦°GÞXß7—UWGÄ‘�£º0Þ›çå®ÉyMõY�¯ëå)˜�ŽÕ¸4ç$`l…’¾µCs‰êYƒR÷xhpä­`så îN„:ÉáȉÔ_¯nôÉð}_û õåy^êï†Wš>4’çÉKŠß1ª†-¯�{WZÜ~ˆ{Ù�XßqaT$ˆíÔ~5‚ØòÇ (¹Ýt(w÷@!ÍÓCù!£`á¹±qÂx¼qÝ�tûMT›ºÌÛY(³ÁŽ>ΣgVv'c'£‰qƒu»o†£I½‘Ø‹ jT”Ù®Ú†…Rb3¦*Ñaá“"ÊýHS>áû�r ±—�höiY�$‘MbE¶øËªÞ¦úØ zƒ›Œ¥S "BÐxKÎG<[£mÛãáðŸY-P!ŸÙzJíÅ Äz þ ~©óær°Šðበdj lyÛáÙºƒ.7¾¹2¨n­±K�x�¿s‰­ Qvò- +Šð®…Æ8ìd4¿B–ž¯UbÐ>ñÌdôµ2ÇnöÆt`‡õ£€Kî8¹XU2š˜=»›õ–¥sتKÖW& ªˆßí¾�æî£§ÆOØáUeŇutÕ�ÜûîOS‘d]†êÍ‘Á¹‰óD0mÜ`3# gér¹²¨�~(÷›{N¯çë·¿èÑ“ÊÙPb›ªÝF½øåwh+ñÞJݤ*úör$d$¢n뉚dÖt[-6EN]'ÁiñRà<’þÑožŠìÛ Ë·èC )°‹ÎmLÁØØ…ÅCùÓsòÖiHU›U0uȘéwý½Î\g �%ô%ÿ°Çì�[>ŸžªŽˆ§…®òH:›K^ëa®"ˆ!�t&ý ?~^ã\ çÑ%¦ ‡fr˜šz1Ký¶H‚ 0‰÷Ä„v}ºc-} =É©‹#˜‰MXÛeÂÌ‹cšœQË7²ˆ·Bh]±Y‘ʈ޷'7o‘8$î«@þ"um8Û›ßíeü-¾TwémÜ�§·d¿”ó)ÛùítTÐ@ dЇtã5�.î Ž[ÖO&Tí·Båf„¬uÝKè °_ìþónŸ¹ÁEwŸÓØÏ�$=Ùÿ�bÀΗ¢ãH²Û¥h]Êtë~”E—t{»¦Üç‘à‡ø2qgHV�ìK‚ÚŠ;Ç|;ì��‘j�îÃfÍ’4lø–·uIMWp7¿Z܈Ä`¤/²˜ä¢Ê$5…À£w¬™¸Ù…jé9ïrÛ´øA.áÛ>h£Þõoú@ûEŽô‘6™Žu�1KéšHy�žæöfĆ•ž8aÎÖoHÞ«Ó}¡“ÿâ.êæ>xªVCΔD{¯òU™xJâ~Œth�’ÂȪ{�}¨°*RU¹ V³£ó^”VEØÆ«)a=N>Ž&˜Þš‹4–xOÕkb‘b¦Ld‡\©ê‰³U”º�O+êáWÛ!ŠÛM-éRP¡/gMÕÌ!NQ{l‚ÐC$#Ö¢äֿ߭ĉˆFÙ°äi—TÔKÒQKfÿOXZb̨ ´%vè0H¤Ú%3ÒrÃGÈæYI'ÖŠ,€ý€rØJ3Õ”W™¡:Kuùo%]íÏäÌþO”±5î[rM\xH™|¾_å]¥P÷H`ªvÚ/’·x )o+sgE‹DÓ£';6Mã‰Òâ X‰�Óuj“ ƒF8€‡ûWQŠ–’^U‘W¢9QÄŸWß*XŒ>#ýnÖº¶t‡×!ãG‚þdk]ŠY{m~™ÕOÊ4v©;Ë4²ç›s™=Ûo‹szeœ«‚¸8«ö¸­¹¼T Y¦—&å­�¿Ò�ÃÒ�¹áî|à�I^ªqåÑÿ|ÓÇ eD$uS›_Pm€¢ÐvJ›¤eôãµ:þ§5½r¼/Ær9ÖO‹�he0»-A*E  ³:.ì�ü&sբǾÁCð2>àxäÎI ¼O#Ùzîó0Åö4C€ó/Ε¡.ëüi åÑòÖ¿¬ìÏceæhäŸÔ@UåÍ3ÿ)yǽ.’nýÜÌVð¼ÑxœÍZÝsâ8Ï_ÑËÕÝåÀÞ\Õ=$7SÃ$d—Z¶ Ù¹yò[€*ÆòJr·5ÿûµ>lll dkýÈ­_·Zý)ÑmÃûs=Ðî^tÛí hÃÇ9 < 7fqw|Ôã>‹7œ.–RýºI@ÓoÁ»Þý~]Ò�Æ1üìA84õçÇE2›‘Ž¿l§á”Á�Œ-�Ùpxc™ ©O"AH¢1ä’@/ö|ü°oø�pAYï:?@S4ì«FëZAlX+o“�‚T€^yöI,�Fà³UR/ò ¬©\j>¥£0¾X 6“’{ Ölž'OZ¡—RÆâªÛ]¯×OKÛa|Ñ �è7ýÑ´‰ÛQH„N~O(ÇÕÎ6àÅ(‘ïÍPÎÐ[ãà-8Áw’)‰×œJ-l.×' & Br:KdAa©|¸ì<ªÌ‹ Ñ›Â`Ú€O½é`ê(�σûŸÆ÷ð¹7™ôF÷ƒþƸ�n÷ƒñÝAoô~Œn ¨.äCžc®V€bR¥Jh½M )ˆ0gF$ŸÎ©�K‹‰· °`O„G¸"ˆ _Q¡¶T €�‚ éŠJOê¡Òº£îÅÅßhä‡I€Û¿5Õe#7þŸ�Îfž ]ûÙY~Ƚ2˜1–‘¸8¶ö6(tp‰¦Œ_úŒ“½1g’ù9 sø<œ¸Ó¾û0šÞ÷> ûùÙ!ïÊMŒV‚ß\e—LPÉø�•@=ì Q PK†e¢DÒ°;cÏú�Òí¼,FŒ,pKüÐãvNŠ,*´Àw”hB½?R fÏÔ{4ÒÄ—à®Ã•÷HŒüú­+á� ÀG|ÂM’‰dfYdšð¹çk›W£>‹$Rip5�士¬ �»¾Èx ÑaP¹<ÃÿʉÞTÂWty¡ZÃ×TÍšÿÕyBvâkÊÏ.hºa hŸ-\öêz÷€ ÛT²fÍóÎaîî(ÖY›Õ�‹yx{t yÞzXÛ¿á} OKê´ãŒó”x² „Lp-}ms¬!�±ˆ9'¤¶¤y/s} ù–! ÛöBù–¶qC†M4MËØzm3÷&mœjÁÒñ_äNÕ0ðt²`zÒIŠë>Œ¦ý[×Ýmr_ÓÂa¾�î{ƒQâŽï¶ö]`\…éT�V×…ç¯ÌŽëçl'àlÞÆûòæc¡�h¨¼+˜èø.Lb2¡ÝQ×éjÉÈ^q¨ö×±°Šè_#À¡Öï�wjžp}ÿdê�˜ºNú¯ôö`íaaq‘�ÂåïÎ}dùgïUm pä!²Oe¯Ýe}Ë&;ƒØs@f`*�À=çŠÅÆtÁ®|&°!æœåj†JiT1bæ8ÂÏC÷v0ýuØûâö'“ñÄ�Ý_ú¿Œ'_Št�;�b`²átW �FéÜ£¢ðÓA8NÄò„ÃŽš¡¨¬ÚF(_÷_Áˆ¬s]@~¹•Ò8e­¾½Ëg7A…+?tj[–œúõ‡Ù�€| ºU^r|R5�·:IÅÎ*=XʹvdóÌË+èG(1ýÛœ.ÔwªÊ,³(PN@å²SòüTê4‹»Ùì]ß|‰èYõ6†¦þp0XÅÆ�ôm·ºÄ©¸·Ñ×2WWY�Þÿêº>ý“ ¦o¹ë§‹Ì<Ïr™uVCÌWÛaF©ÿ@!’˜ðmƒk[�SKug„ùUŸûëÑ«n�”Âÿ-9žº–xœ­Vmoâ8þίÑ/€Z²êI÷áz{ZJ¹-ÚˆžØÞ~JM2I,Œ�³�¦hµÿýÆI(ÉB÷^TK(d^žyfìÇÁû·Z0òzÞhÔƒ|H¸@xŽÓÐd(Ä8#a%�T¾Ó<ͬ{›î_` áòÝåÏðGÆÏsøÄР†Á¦z~H‹õÇQ6캑ËOðQ©”‚ùþ´ âó¥Á †Í&9‹èÑhÎá3jÕ„Ëñ;8ƒ~£ê¯ÄN°e;�ÊBa�0¸�*'|Ž0·À%Dj› Îd„Pr›Uq”±ÃøÒ`¨µedÎÀå*i³ éÌÚÜüâyeYŽYÅv¬tê‰ÚÎxþ|:[³ bÜx¬¤@c@ã_×”íz,'F[OÁJPXª‘tV9Æ¥æ–ËôŒJlÉ4:˜˜«ùº°�‚íùQÚm*“П0úp= æÁ¹y˜/oïVKx˜ÜßOËù,€»{˜Þ-næËùÝ‚Þ~‡Éâ |š/nΩ\Ÿsí2 šÜ•ãªnb‡B¢jJ&Lj'<¢ÔdZ°!UO¨%e9ê-7nK ŒŒà[n™­DGy¹@^ïŒ'¤H ÿ¼ù·3ßoðwFB.ñHN2EŒðkÉvD#¾ ÃI."¥qœýöâùà߇«`®ÁrríÏÚžB{v—Ó–Ò¿°Õ"ä]TtŽœ]WÁ-…u5ˆÎ êð�U:=vI9l‡B›VDÂRlÙ[‘B 'dW­ìú–™M(hßÇYßÉ“ºJQ. ã~=|¶TyèOûðµw†2æ €çulþë±ýÚZíšE› ÁåÆèêLÔHU{ð×(”L UØÎ{�[;èË5¢ÜjªÁ>Ò¬ÍÒi¦�Ík�:²ï}ª&Jâº?Î�ËÐ:a>‚á©d4ã¦nåÇØãqøj*Äfµð^tÿHÀª\àŠ7c°lQø1ƒ\åEþfá+´;FBR»7‰Ü`µâ~»êíï©FfÑÍ¥êT™S-°¿s¦Ù'r/×h ê·%­�Ü]¬|ßÍhÔZézÆ�j¨Ñ±0ªHŽÚ¤ÕÃCJ7u¾¦ªä«}ÝMªÓµ'Å㣨)æà$óÊðêäpúV�¢Ãˆzu@5roôý¤¯¿dèËf2vWvkLÿŸï%‡÷7§~°£xœí=ýsÛ¶’¿û¯Àsç\ÉO–·Ó{g×¹º±ÒjêHy–]7ïæ†¡%Jæ MêH*ŽÛú¿Å'$Aê#NÇšI,‘À.X,‹ÝÅþ.:YÕíîoíïîn¡]ôÃÔ<ôi2sÒhx½ ;†çäÕ8š?Äþì6Å¿^ñ¨5n£ÃƒÃïÐÛ[?ðçsô‹ë%^ŒZÈßf‹›¯;¾m«Õ Ê7è§(š¾óóW ɹ?öÂÄ› E8é­‡Nçîþ°7ô«'~¢ÃîjáÛìÕvûƒxˆèÎ}@a”¢Eâ ?A„,ïÓØ›§ÈÑ8º›¾Ž=tï§·ƒÒÅ0Þ1ÑMêBqaÚQ4• "7e�¾MÓyr´¿ßuIk»Q<Ûh¹dÿ¼ÿª7õö Å¬ÆUxI‚bïÿ~ ÔÞ< w-»7ÐÎÀ½GQŒÜYìÁ»4Â-¾�ýÔg”DÓôÞ�= fâ'iìß,R¥Ãxû€l¹t™¢íÓê�¶Ñ�§£þ¨ƒ�\÷/^]¢ëÓ‹‹ÓÁe¿7Bà ôj88ë_ö‡øõ�Þ¡_úƒ³ò »�÷ic  ™>îJoBúmäyJ¦mR2÷ÆþÔiáláÎ<4‹>zq¡¹ßù Ò8Á`ÿÎOÝ”<ÊÑ…íom}å‡ã`1�áW¸õv[zõ}àßܸ‰·Ïþvo_J/“trEA¹g@}vï>-“=àp doÅ^i�y¥ÑXǦ"ï&ÞÔ=t}~á\�zÎÕ`tyúãyO®Äûéà ¾9˜•£ÄO£X­”Šé|‘V—pæOiÁFA§U)€Ç#¹õ�V(ô‘Î]òì4O§4°ã(œú3<¤Ù3?„Vî�q‚ ο¹sC`/íï^ùY8w^¸Ð?|ðÓ}öW}‰éšGóÅœ0Ù>ÀèÌnÌXu)Œ%0×{XÎÀLýíì'ÄÙšH€àÎýTàÂ0«ã”?rä€þØBðÁÐ~Ž‚I‚’Eá}Y"¡þùŒVz« å{>À'=½‰>¡Ý¶Äˆ#/ö3É{%)¨'°½O•¦­6»A�§8÷¡å±üIÌc ÙØÚbá5�åúÖ#v)bìËvÍdS;õg‹˜Ò`ËËì1‰[ú‰{MEØâÑÉ㥸Én×sD=©oãhîÅ©ïþ[7E ]¡¹G÷°³Ô¾mhé@3¼“~rloè¬'†Q™®»tÞ^ ß:oNë¿éÿ«w†NÐ ôý÷è “ƒô:/nsp^_�Ÿ�^]ôzèEÐH^5r FÐ ^ù0_ù”Î�RjN_]ö=½$ÔP@߈‚�ÍÅoɠ宂þuÓE¢à0¹«*°¬·ìAeâQ…uíOÒ[lòD·6‰+ ÌsàWé°ò*´ÓœâPÜ"!©(ˆGΨ;Sò¼ò„!éÖ�;K @½€)?1§›RHæ¸0¶¢ßxâxŠðDF÷nB õ^ ý߽8ê²ÒU3’¢?Þz„f—ÄvÓcFºÀñ™n•”Œæ¤ŽÙÂ%¶:Ë.Ùfu\3„ú‹¦N�5¯³ôÊKžtþ˜B1ë4X$·N& ´q5ñÇý§–ø4~ÀûªZP-ÀÞ‚ðÂBleôLÙÜåß2æ"@w'nêZÓÀ�éºêf±Jšåf©e&° c�>n#5pm§nÓÚ ö̘µ9|Ä„µ9tÔvµ>{„Â^µ!|’¹jC=*›ª6‹’í«6Ó±&ûÔfÈ5Y¦6ŠY6J}Ä›^ÞŠìO˱=äî}šGqf£hxÐ͹ÑÞª>Uá»%jÿZ´ýöݨÇ^ºˆÃ‚ñ2n;(ä"œ¤©ÚžÂXCÙ\T”Ë´¢ žBEÄïl}åSŸŽ_{£þpà ®Þ —'¨õâ¿ð†ým�Áȵõ•$^y© .É©(¼ú¸zæ"óÁ8ˆ\O·Ú0®¸Iœ šµ~9ýÁëamŸQ Ø@¤øzüÇ|»ƒ²Ýéúk雷Iö^bŒ½—xâ¢4¸:?gyjß^¤>´ØOÆQÏ ÐÚÉÀVšÑyCBTÂ2Ÿs,Ù0õЦ]·ã NŸê6Ê|xÖÊ20ò‡•õû$ØZ›’Ü)qÚ w]•'õMj¾MÁ(nM�¬`ªºÙ4¤{©Ô¤»q†µ©…Å#úÛI&R%ߨ¶di×½©¸È7ÖËL±¦×B(ã�%MɿԖ8ôˆÜñÄÞ�d†ç ‚€Ãš Â’œmªVwRÖMY_«€Zæ:%M”Kn…ShiØð3Þ†)°d+[7ºº­ÇtÜccõ3Z1fAs-¡¨¾`µtpÁÁTn+¨*Gž iÇ¢´™B>а8�Æn€ZtwÓnº¯XËŽŸ¥¼":z¢Ñu¨£zнð×ÞÉÁeBÝ^SéªÒI÷ùLâ¯r&Áæíþ>úiáÆÀ�»H¼#ôf‘¤èÖýè!—øK‹c9!ù��‹ÕyyÈv½‹‹álÞà€ àC\Ò贀ܔì#‘�Ì›LI„³�kNf7y › Ù3X~ð,‰Æ™œyÑ!§Ñ´lÕfíRûËŒª�oy‰,3ô2�E¨lVe‚Ø¢š:ã ªúÀªz6ßx}ýœ¹9eUñw«JbnñšÚQrIuiváÊÒOI;û›¤€r�‚vPê�o·Ñ,!ìÅqgÛ•¢AË÷ɰc„’K§b!*š3 »ÁšÕ^Ý ~ë¯Ïß\þâ\÷gÃkâ�Ô»¸|ç\ôFýá¨5ôgEÉþ«á ÿºoUöÕùp„ æw½ãÀÇs>Eô ̬?óÃl–W±—D‹xìi;KKc˜˜øî»An Ë0Ô¡Þ¡»sÒ?ò�™?)5Z®D"IÅ¡V &u_®ïò ÂɹtPgÞ*–Ó&Æ]3u0{€"ôM¨Ks—mß;6k„n:É*Ù8®Xv¹¦ë¶%+ R–Äèõ×F•é Æ‚.ƒÍ®&eÒfmm#ÆŒxõFL·üÕ1V}TÁÚS�"ÙH[—¨»6J¨º-šÝº&5´öÚèÑ­ôµ(+4ñפQ‡³rj³c +ú § –e5×B9ܱ&A= ªA©¸è—5ÚyX hMÃ!\S"èa1#E÷YQ’?´$DTTéÈNO))òÁeŽ«�©Ý€ÎImG$«ª°•N‰z�»ZT„5©!•²X+g>†Wp!==¯E¯và^“`Z»)ÅzÌ Õ¥�­6ÄçÀŠÚRgKšM0š‰ÄÆ„k>Bö„›]>ê® l–pÙý¥É€7Ñ· >Õ5·7ÕNHÍz`ÙÍÎÒý`pɳï†|$q³^Èøªm·•Nf• ™Àav]Ø1x+PÓKÎWÁª¯ºœ‹=È�˜üëŠN’(‹°ã¤òˆ©æ¨¸'…Ï„"znµ/–×o„½&E�æ�ú±x…nÝ$üÿ™èóàR6’w Çû‘@Ü�ÀC§(p1|Jˆ@Bóí‚»(ÀgW"¯08øu×%™èxŒÎÑë NϰÕqï¸Ë–pYyˆ;-¦G]6N�øÌw2q³HÇe§GêšC°büåuµTªé·¬�E¼-²ÄÁk·;‹;.n¸îh¿Š–çVŒúMÇñÎÆFk1ìèD£'‹XX%4`¸¼ �&nL©Ën ‘² ºt…Ž!”´ø¬SÀ¾q牔N/MBžå(j§ÿt'\�æÞàD®NHÆÿ#Îa€s(¸sž3§…£ÇÃÑ \‡çÏäSM#Çå`óF’�G¿6—u4h;e¸„æT#å’ . Š 6ãs¤Øc<ÀG/*$:П"ÓK6 ò26;VÓZ!ÆÖØ «Í«`—¬ì‡|J޳Òã¸EL w“0ÉÔ(R1ë!6ÐæµÄ�OÐõL’ÇPÍl™ á§z„HLÄ�:”¹$¼4Šº Ú<ŽÚq®W£Þ™ãèqPŒ�›ú@�6új8¸<ízÎðu6ŠJÊÀwr9³1n¤xnjlÙÞ#a PÔ|Jë P_r8é”XÑè•9ÈËsµ4„O]1Mî¸åê$ �/ëe›â„;ž×Yb¾ ù¸µœ_ëÎ"°YæO˘D*Ì Åö÷ùŒF$éòB’><«yÛ T.ˆÓ€ÓLÐЧw¹÷¼çóÜô,gaÏF¶á@H8=öŒ°—ò`={I7ϳÔÉ—ˆa=ÍÒI¯šu%§Çÿ^¦6k:@ˆ¡Ák¾ˆÑÖAÓg”Ììܼ¶dn�LãI/´e!Hš‰‰ûb_φGè‚ÍD^±ÒÊô¹F:úX”k&/ù2FX‹ž«§¥·um–¸¨â)&£ªØ?Üx3?̼ ?ÊØõ¾n£>_Rï�ºœ¹t’›'Ä�æ¨Ð‚^š®Ò»ò/Ö4Nâ—ÍÙò/ÉÛ²Š¥öÖÞKo2ó’ÏÄüØgH¶‰œþ›É»ô™…u3å¸,$;¯ÃQßzoBäÆ�,8…€ó'šƒ2˜Cdw8ØÞ ³åÔ4Iý¹,y«YV­'½ô›s”ªuY•'£Ð‰á$Îm5F³vβ'?˜¹æ�©DxýI_÷˜œ»nœÜùõÏjðCý¤rOž!òiG*g7­òdf·;™$ÈÅwF z×”Ýx®"gßCcö¥Q´)“ÀÓ*Ez¯ÀC‹�æ«×É*еj¤A¢$(T�­…hñ@+(ªñ;1Çg Yp–zŒz}îœõGoÏOßÑ‰à †Î›Þ›áÅ;µÜök×=IŠ�% Y·lç"Æ‹|̱£ÄR!ªÚ¸pXj­o~þL¦°0O¿Î®Sç°da+q›e¹|Jçù¥Ô£wzÀ ‹)/?K^k’y É%ll åÃvj€ Gèsk Dã¢Oq@siÇ †tê‰g 8­y2ž‹4»zÚvN­ ¹ZHl’Q™µf®“™�º$5Y¬~ÎØ§Ècù y¦…]¤ZÑÐÙ®H÷Sj¾%3/’=®Ê<ï˜ÿEªÜy ¬dß’—'s(ÙÙAâ ÷"1¹BÉW¹pguÌ„e 8‹ƒ¨Y—k4á•DÌùþ©#QÞ=ÈYÔ”¼eyw+¹,�(ÕŒ—�ç¥~hyV~‘<’C)jñd|ëM‰Ía~®ˆªû› L¿×¦fL$p+÷ãØ.Œ¢Û+prlÒ1éßNtYK‡ÝOä#MK1­°UµãeQˆºDXÂD–bGꆂÞÈÜ-štF¥ûìr}‘w¥Ý€¾+.VB‡¨Ò{„^Gñ½OîéÈn¹¸-››ü‹2[[æœÕÚ“€T•;†ñ^ kÔföE‚O\q‰Ój™¤qù/‘I ³çÌêø\žÿ<1xiWåéÊyX£â$”ç‚¶ê@yÜŒ¶ÿ¨Ž¦ÍKp¡ ”¸¨×‹Ó]A}NO2 ŠË?7}×Âçw½³ÈYzô“žþ3[±§Þ,ŒbOvÂ#¡l+H3D∨±»Àºöbާºy�O�yY¬¤êìÆ h,k6æ?xpl.ÌöRiùö¹²ˆ Yüàp˜zÓSV¡Â‰0‹ØÕ�µ�S×qÈ“›ʼnÃI’ã]é½Ù0oÉ9Y XÇ)‰05´Çùydˆñ* ]S�^_ïÖu¹_Ã52OŽsóÕ ãf¯—â[sYÀ¶2‹©8—á5’]û*ôS”b^¸õ(Øè–\{�áˆïÐó&ìÞÚÉ<ì†Ì“î|£WÚbŸ&9÷µ ¿qXÉ?ð%1áÄ›¢³áoï~ê œÑÏëó3gôKÿ­sùs¤1ÛÄOæPW¨X»Ù³¬¿¤5™6Œ££¶DåYI-I�ã�Žùm3�Ùå[„ULÔѬµeäï¦\�5^î$Ã"ùñí`mÑˤy°p¢EQ+•ö·VÐ|6­ÙO`â²Zmœ§’”OÅvUBr+°ÃWÃ1©¹¶ÈþÌDµ†È0ø,ݳú\?ó-@fß*ƒ 47º©¼©Á•ü䱈 ©Ó³‡Rn7tïpüäö�¼Ùî ¡ú½88ü~ åî?¿ûüLÆn€+¼ xåpÒp©Ïhræ|›øF•‘A™hº ÷|[]Á¤�âOùN±z¤¤7<�®”JyZΣþ(IS.+™ •@驚…1‰$ýF]še°—d{dt¯�Šk�J�’QÞˆ¥žÂ‡Kú®Ú&¨Íœ'Y¦`à«ZHÜp >ô 7š]%ŸÝ%/m~,JóÍ/o(ËËg‹:¹%âØ¢Š¤½Z×!Ͳ.�™–XùØHòçÕK\Y É­ã ¨>˜¯ ÐÒJ´»è_2¡j+ÆNx]ÜÞP ðÀ#S‚Š—bHÖ@²þeFuUE-šv»)W •T#ò ¸�\-.C)>ºõטéïWÒÇ[5 ÒftÙ)Ü×6iSöŒ‰>[ ±œ-;ó›ý@pR¾þˆiã‘IB>¹·\ÿ‘„~®ŒPŠdCWí1Íì¾ËlÑÑGói–eOùFU[±l'™ánQ<#ì.��¶8^únž>TFÈ0H@R«¥÷«éº ¹b, lÇb?•Ù’[jQ²‡jLëÔÿûwã~Žàï¾ýôÍág!8Û¯|§ìV¾9D�ÒmbÂÞÑðfbÉà!oÉ îè"ì Œ¹˜·p±¢}|¾Nê¹ñ$º[:yF®qÉü<,!‰„7 ÍÀ•ØòP¤õ{)8ÉÒ-É$ÜR`°Ó̲œ0šyQ¶‹åÖµ±›°«Þ豯„Ÿc±.~$ÿó¿ø®Âï�=qmóA„m¹ÅP£ÇŽZ=½r’y\‘TbOL˜Åh•×Í�uÐÍUDÏM•èØTV¥Å8X±.{£KçÕé¨çŒzƒKXºÎ[m²ƒQ‡�„ ™�|ù{ºx£Þ%Y {¿]RÙHÚI¾mËuÙl3tJì»™p©,ÅÅ åËõ]‚è2CM�nàSŸj:!¨4ËVKZW ±½V®’év% Ûp�WE¤µ­ËnÚN%s½\L²—tùÚx‚v �Ìu…-ˆ\ï§ÕÌc„®„�#x5ÛOd» ~»•üM_p3lv�ÒUo©Õ¤Çj%ýv"ÑѬ¹ù–ü™ÝŽd®¡]”•¿7‡ä›+¶m‹Î”ÍÓ¦ëÖtó°;™8î"�$qR×hn–òþ–×Éë¿Ø–e*ùåX7Dßñèòxî(�m,NØwì… ðß%vû†!ÔŠóÌvj޼œ¹¾G§~WUS!î™înây�Užãm3d<ɇljj7;·+Ðʲ˓SîW 5Ô“/>°®¤Ý/P\O–œžry•RC幂8õï¼haC«‚|sEƒêÂï»YuwüÁ‘AXÁËG£Ôk^†Õ Œ!{ÃöèW=5#ß±Ô „v±Q3 …7Õg¸¶§>õÖœúõÕ+kØÞKóœ#gŠ>Žr"j ¹öû,ãëwßrÕAà3'«O‹Vƒêµmã2°«ØI“¡¯¥°1_ÏJ–¬(dÁi‹F§°v|Ê4L9Û¼�…�i³Š¥l�)æÑ%ΈQ«ÑìXQ®²7YÙFêît†|¾…«¹î'LÞˆÃà‚ZÇ9ÎË+/¹kµ3%Œ&YÖÚÜ¢/«2ÀÒ =»¸g–âcr£²[ÖF¥¼øjXRv‡ð/ðÜDO\^x9o>%¤­¸�úV@o¥ô¾¢�ùÛ|K1©ÁbS�lÆ­m‚aó¦°ñRÀÝñû`w˜«›Õ”+5"Öœo¢K9§£QïâÒ /ñ¨ èl2¶ ÃíK”K¹£_Š˜‚ÜÉ2DëUø.JU)Õ€…æ�/�¥¿‹ï‡©^SÏ�x’‹{-u‡ˆÌãH› +ó¿¡�zìBáRÝ1£$¨/mÏäkÕ2] dQM½CÉ¢‚áÆ©ŠZ¹cÆÜæ÷kï¢ÿú�3èýÓ^8½Ë«‹�`hz½H§n®Úû§TþE‡sx—mIù}b®Rüz€¿W:þ=OƫܳS[©ïÈùoËš|ÐQÎæ4¯…€{tïÑð†üµ3]ôŠRu·¥0�ýÙÌ‹‘‹Þ‹Í#¬h$]‹ÂSM´äâ|.&ÝjSJ�ógN‡å¶½\Mq@õ‚W-ãŽÜz$^ØŽÄ5N{!÷t³>þï³îJPLˆpïݵã hè­å@kºûI²À.Ëx}Ìå;È÷ºÙCèôy”øiSZ©c�’½ÄŽ©¿¦-ÖéÊl±Qô)�]EfV6Ý<†vîV9½ß¦ôD'—Vò¼^ùLþÜ“ê©�¢ÆeP\QgÊ�XÆÖªK'š+Pƒ+NÁ7¨?«„}•pùÅiª,N+”2Ò �iýÅB÷†|*«Å_[ï+éuÂc7áHöò–â î{€lÛq…ŠÏ ÄvÊ�ÞìåI™}›mfJºòuÑ=faÇ®yßòÙYÉRQXëšÏ¥½Xñ­ÖöBÏ´çUýyU*«ºæ{C²@=Ùñõð ‹Ê[ÙÂOúíÜÔ—˜™Ãýób¿ ­ôwe]Ó`²-*š*‹‹bŸÔ²�ù™ÖZÊ}ŽŸˆçâ©,4dgÕÛ>µ_ÐŽ[_ük‘9ÏkÀ&Ö€‚NÒXJ�NgymŽÚxÁÏ"xұŠtÄ7 )¹ …t¤†É˜³>¾ö´+„ÛF&ö'žr�;»˜ ‹®q„gˆ *@òfÁ™~*_›J.GÀÝBX2LŽ;¢9ä&åZR‘ NN‰ÍœŠxÛF>à$8_ñt0b>vÐÍ"-Æ×Ó-uëàÓA;Ë:PN^ÂÊÄ,Dîm&ùß¡……@”;M‰éƒIô{D©¸mžÄÖ•‹€ÆZ(²Ñ-É /ù—¬K2×hÄ—¿& �ŽÁ\Ÿ¿¹üE�t}~ñãð7Ü•B(*ÆÞ~à Ì�±é4]â…¾ŽÁUæÝáüø�\,‘û*Ôä³h1‡B|~ ¿^õDi2ROƒ_žÌˆ>Im {B†âsØÌÿX�•Ò"´õY×zÖµ”pÿg]kI]«‹F’ž%æìAç€èP&‹uçÝxuvø¾ò+|–r‰ˆ|¿W;ñEÌ3Í(²„à¬8¸9‹ñ§–tà WZÈ .º¡E ÍZÔ<»$YÀšm “ ÍцÐ`‰óç˜çD±\ç©`ò.…YY?µ¢ �µ 97 Õw�‹}E`¦½2&ן§,Ë.³h»R-�´ÒÓŽïõúÁ¸Žˆë7¦YʵÅÿ*„B¶ó °ÅlžR*J� ØRq”aJB©„Ù²Z.LÉ'3‰ÐF‰ya¶Å‡´÷0d,ƒ¦;oÜ„kwì�;ddæMîÂéfîhä¯?†p½0¸ñ&^àéÜàî½à¦Ã…~øs®ˆÂJnËÆœ@XÈ ’Îy,"FjÙ²`KKù�« AÎÕZhJ©F€ ™IÅZfìÕ¼È‘Óø (X@=Ìüè.Šð(2¾wCJPÝ&­™?pïû£èÎýÒG ÷Ñwƒ›3TÉâ´H8ü®M2—[ãϽËçxî Ui$Ý70šEl LUô\VíéÂ6Ð%ºãY‚Áq~æ•n¥ÂD&¨§LYºŸ^ºs^wf—èšMΉn *Ó5{â„"�ÏÞér¿i˜~ŠR,‚îªiÃQ…,ÎÓBÓ_ƒ?£¬Š^þÞÇ¿¯óž0ZB½kp�\ƒHxf0ߨ©èyV¬Ñàô'ÑlͰ¨ÂY4yö£  \¿sJ|ã¹~øù¤x:ñ|oòxR> CÿÚ�”�‡¾;¾;)ôƒéih£pˆ‡àm­a8œßV!�oxR„ïÖë¹Ô˜�†ÕÂd�)ÚO�§|�ÿw­€º`à>Dî$DÞMã; þˆa~¢]~l¹ü«²µS\qf¸¶]ùJÁnFŽU½ÜØêQnÔö^qS`ÑSß§©Ä•’ªc'äÒš¡¡ÇHqqXÛ]ÚÀ' ÎÆs;,èÁù³4¥qòÃK Ǧ‘›jlöM{ÿ[R­íU •vpÁlCpSY}g È~§ñMŠä5€­#lõ;äó� HG6¶Ê·ƒú^=ª`ÅhÔ¾,´Rd‰,�ºTŸ¾½ymö©Õ˜¶ÅŒÉ>74ê3FÛHsCî÷\CÉQ¸ü¤|Â@Èܬ&Xßâ×q„+cZ};Ÿ÷}ïÇÔ“ªœ×Ç*=_EA§á%²uõ[Q3­A±¿:®Pg~Ò"Jr¤X“¤R*†gFZ²½â†‹rZîLOC”‚k£xÞ™œú †‚Ï Ð+"`ì§¥c¸÷Ó(�!ÆK´y\o7ðâ'‰o¢0…uÁ:^F›hã×øñwxŠâå ú„mØ{«,$É­‡¬°†¥Œý@`'�„tË(ßqŠºDÙ‘’A)ߘ(Z¦®í$5Ò+¥æ 7ĸ�ŸDy#Ú�=Ë.xÓ5Ù‡�“oa’"]¸ö>_OG¤(2ÝåQ#ÕaRó<'šMGØà�Ôµm�º·æh•|ãzô6„ ‡üŠª×[Ø`ó“�xöIÛI1¢4ÞE´c¸°†íHWÐÌØ995vaœ–�Fb×¾âÔEM¹ ŒØ»•‚º…�iÍD‰YDW‹Њ(B Òò� 1PH¦Å'½T{gð«W0½7²}uñÖžË×Úeí”lnOñ,q|]îaðç9•<˜¹•é®L‚éOoyñë G¬„ËËaã˜õpŸ.aˆ÷øØóÙôµ�š9KÐtEÔÁʤRìxÙ©cœÝÐ 4:Q£)ˆiÕ[�Ð4tÑ,@ÅHëDA �棷ž›ï€>± Ýø›(€ñ/ÿ_ý§0 ¶IÆ›,]o“ Ì–Qò¯Sj<:žŽ Q%3™ý @ײ³AMFóy{�Ï¿¢8ʾúf«(ïn¾|¹¾6·i˜açàén1ìø«Õú%KB»wŸO}¸ uW`Ÿ!‘œ�úàrêô¿ü‘9,P1·h¥9ƒçdý&öãÎÇ£¸¹:5ÉC&¶ØÃ?âÿ;_åB¾½xœµUQoâF~çWŒœ>€”ƒS¤ö¡§“ê·ç1›‹"�d-ö`Vïvw]U÷ß;6&ØÁ”J¹¼ÄÙÙù¾ùæÛYöÆBîO6úÑîÞßýó O¹”ð…¡Fýmõý-ÉW+F›Aï¦�"ÄÏð‡IŠ0�Ž{7´;åfcȳ˜ÌÁ–,¢O½s _Qi.2¸¾‡~™`Õ[Öà1ìE;¶‡LÈ5×°æTŸ"”x‘ØÉ”³,B(¸ÙTej’!Q<Öbee3Ê—´Z7ó€™JðÆ©�Š¢²JéP¨d”²ôhêŽÏwÞ‘Ú*™¥¨5(ü+çŠú\í�I±ILYBKÒž¥ØBqóä´X›‚)$–˜k£ø*7-§ŽÒ¨áfyÅ2°l\ß‚O¶ïú·ÄñàŸgËìÅÂö×ña¶€ñÌ›¸�;óhõ;ØÞ#|q½É- ùDeðIªR?‰ä¥‡—†ùˆ-kq¤%F|Í#ê+Kr– $âoTµÕŽëò$5É‹‰%å;n˜©"gM {=Ç!>a”›Ò«þŠE[ÌâР6Ð\ £A•£¤fGÝ™=è¦4aʳ-ýY)¦.ç–”e¤ïÙ÷N« ™vo{“Vpp&˜g27ÀéßN±�LRµbå™–¡K‚¯æ—OW€ª¬yÆ+‹û=hÈ€ùò�*X�ãáÄìpâ.>~³~úg¾˜ý錃П-c§ •=Š™aß,ë¥5 Ö£1§Ð¹-FˆtËk@sQZÓÝoä9—gQšÇÔ+ݯȈ @½¶0ó…ûÕ¸Ð^M1ª1Ö5ÛÜ?ÎÉïÑËfðÜÍ"ݱ-ª¤¹è´Îlòùš™-Ü3µŠ¬KÙÉÓ¥ã‚ñmü�3¾Å{4¾<7>F½5B†’)}DvÄ:�¡ {¼ÈõÞ»ÃÞ%#É‹’S´†S æ½æl—¤WÜ1¨å$?�ÞóâÒ ž²�ÖзŽÿÇ|ý/Øåñ:Á_×=_÷y±ÏÉO³ é½ …¬Ê ¨Œ&µ©=Œ¦>Rh?3Tgî,Ê—ºÚ‡®7ž.'NøðÙÂÇÙ2\úÕ5ì¬ôݺR«ù¼m¥¿ÅoZëÅOÌ›Öê¼4oÞ^cD_]‹f�&uÐû:6#z°xœ«öKÌMµU ÉHÍMuR²®/·k°xœ«öKÌMµU ÉHÍMuT²®/³j¹dxœI¶ù‰PNG  IHDR@@% æ‰bKGDÿÿÿ ½§“þIDATh�Õš[oE†ß:³–èH ÆëCNNlçÐp¤-M+Ôª~üTþpâ$hëA9\ !šÄv|Hl¯½vC%¨w7´nçåbv7ë�TíM½Éñ»ãùfžñ¼ó}+yOT«MÈÀ»÷:|íw ^;�´pdÕ^¯Õÿý�r¥¤Ú‚qpÿ*ï}|ùò¥jc¡Wl¶ã Ö?üX*—ž•~íÍ7NT«M¿ýö'éF!ŽjjŠ'ˆc‚gç2ùܬcïŸ<ùB}§U)7žrdï¿÷@Áƒ>oz‚�F‹’…üœå8Ù¹i�¥rýièIª]Þ¾Ž�^ÉÆN‹d¡�µm{nnšD©\#Ù3;†±{¤súý¿½ñ!|ÛŽ…^‰F£E°�ÏÚ¶“ÍN‘²T®'ôTµºuýÚWýûøêGŸñ�ÜĨ镍כ�,,fmË™ÏÎ�Ø*UϽu‘Ä�ë‡ÖpÈB<ÊBÏ—^‰Z£)Á¥Â¼e;óóÓ·¶ªgÏ­¼qýë‘xAY‡6aôJÔëM’K‹ó–íÌgg(¹Uªž9»N¢xãk?ûôôq(èUx­¶ rqqÁ¶ì……R­á<É›Åo‚RÇ[ 4~z%ªµ]I./-X–“[˜%¸¹¹}æìy€7‹ßŽXÈ«!¢W¢VÛ%¹²”ØvnaÄÆfå͵·‡Ã!| �ÊBÇæ¡qѽ´¼œ³,gaaVRnnnŸ{ëâV¹v`!* ©¬&zõ»ºCby9gYv>7âÎF¹TªúCøu ŒôJlW¤\YÉ['—›#yg£ìÓ«:ðD �•^‰íêÉ••‚eÙùüÁ;wÊô2éA '½•í�Ó+ù�eçsYJþ¹QRÛ ¼¼^z%*•:)O¯,V¡�%Ü-‘ WCT* J¬®»�Ÿwçäɼ{¡ÐÓ«!ÊÛ5‚«§kbb‚#·Ó¡§W¢\®‘òÕÕ¥ûËÙßÑ¢W¢\®“xuuqpß X("ôJ”ÊÕÏ>ý„`L%¡hÑûSŒEšDLÕ�ÈÑ{1Qz�±ÈÒS‘o¡pÓ»QÇZ(üôt¿÷£, z7ü �F‘Þ­ôKAÔèIu"Hï £,Iz¯*!E‘ÞK£GnzoÇY(üôžpë@Dé1r;AzÜ E�Þî!Ž*=À‰îï‹·$å‹/¾)zÞû«B<~,5MëvÚ”ÌLÍD…Þ GìÑ£¡”2®Å»f»ÝÚ‰=Ý_hˆ�hšfv éôtè=0|<Ìdf†ŠxÜì¶�v3*ô…jHê’F{WqÓ4H™ÎÌ„ŸžîA]OSÒ0šš¦™½‰Tz*´ôÞîi AèÉ4I£ÓÔ4Í4 ‚©ÔTXé]nq¨�éÉ4A£ÝÔâZÏ4(e*=Zz± }Çh’L$RéÌôðáC!´^¯Û1š!¤w{±Àb`šÆµï¿�H¤’©©áp¨ ­×ïv:­ðÑ«#@¡Ê�‰Û?Ýxá⻺žÑé¶4¡õÌÉd2"zïBèÕžàöOE.^Iè)’�NK‹ký^‡R&SS¡¢‡û´Ê½»†Ÿo®_¸2™HìvZBë÷M’z2z·³ª >°j 7I®_¸29™”Rš]C­ßïÐõôøé=çžZ â~ùù‰óë—‰ˆn·¥‰x¿×•”ªr�•ž Aÿ©EwG¯á×_n|ûüåS“:Én·-4±×7!™H¦ÇHïo‚ðR(HžY[ûøêG€wŸ·ÊU¶›•S“º„ìu� ¡õïš$ÿ÷Òó¥gðÁWÿ> ÷DãÑsLžJRÒ4 MÄïÞ5%¨'Òc¢§ò‹ð�ó”sœšÔA˜f[m¯ß£dBO��ÞûâÁ3kk—.½óçxÂo;h¯ÖjÅâÍ^¯¿·×Û»Ûóösdz©ù¨÷@Ç'«ïÚð;{M|ðÁ‡ÏJð•—_~ýµ×R©”³¿ïÞ½qÑø ìñfK9IEND®B`‚6ø¿]xœß ú‰PNG  IHDR@@% æ‰bKGDÿÿÿ ½§“”IDATh�Õš[oE†ß˜–¼?ä9ÙÁ‹v<3†�2Ó3†ˆå/!øˆû%Üìª{B¼$J·‡C$V„ÌxÆ“8r0H°»—‹‚êᢺÚ=ÆŽ’½ »GÏ×ÕuxÊõVU«ß:1ïJ’™_÷=|�fÊ^g3Ϥe%1Ó‚MÇ'Ä‹/ýQÂæKîergÒgªúäò?çr¤—xù¥ö¾{ðÁކãÿƒ~kk˳©7oþ @•6HðÈà˜ÂÏ>×ð»çeøÛÅ‹ÕJ½Ñxö1k–të«-�gûš =b:½�Á÷;Hq#ÕëK�Cï®ä¹qÍ�Þ†Ó»÷ßï¢8ÆPo,ßï?ØÛ»w ¼D8\¸ðÖ?ü׉ /ÕU.ô6˜Nï#º~)Šb0õÆÉ…Jm<þöʧ¡f?ï¿÷îßÿ±–öÊË�Þ;;»2øþy0Q¼ªÕϽÑ]½r¨‡$ÄQú}ém0™îÑó» (еÚâÙs]‰«W3=H²Ò¡AȃÞ;;»@¯×AÄQŒ1µúÒ™³]ÐúÕ�£wóX¼ƒ>åMo‹O&÷=¿+CoµúÒ™³àÚú¥¬„ìäõ2ÊŸÞãÉ=½~)Šc¤jµqælGâÚú'³-&û@�èm0™Üú½."ºÕjãõÓç>|¨TB¸eô¸u(/ú¬–ú½®PES­-ž{£÷ípâö…TB‰¤ Doÿn�ï‚z½®ÁÄÑP©6îÜ'U$:j-½ ¶ÇS0«}?©R©»*„ðÄ#%”+½ ¶Çw�~ß\�bƒ©Vé ,£Å¤·Áh{ŠÔ_õ‘¢(’a¡Z·Üž[W‹KoƒÑhL¿ï×£°e¼RÐÛ*F£)F««>"º=åy¿<üÅ= žÞV1Üž Vû=àÆæ–f§ OoƒápæÍÕ°¹yÃ+½ †ÃЛ«=AFB%¡·Á�áøã‹!æì"T.ú´i‰¹RÓ ÍÙ} tô.Ôœ-TRzÁ\ié±äÇK¨ØôI©c%T|z’ÿûQ*}Rü`-#}²�ne£;JH缾*%=J÷» •‘Þ-£GVZlz׉ã$T|z$û@Ié5ó8]Bz< •�ÞÉ$.+½„wóæ—kkkÕZ£Ñ8Y*úD7Þ©Sö¼¹0¼„¡±x²,ô®¸¼ùùùW^ù“P0€y"¯=ÉÛi ç•·Z¯á`ªÕ Kïn¤‡=@Î+o·–%‚`€¨Õ‹J��°o§ÝVŒóÊ[-«¥Ðzå…¥Géy!<ØÛ­Õ—¬¿Ùj-ƒ ÃK€íC¡è“ÊJHÚßß»}ûëþê;Ö+o·›²ûƒT­6 Fo§€Ýp�@ñU ¿÷Wë•·ÛM¤ �jµQ zwáeèí˜h#^ù½·­–ÚíeD„Ö+/½fœz;$I›ë]ÿmë•·[M a8*©–r¥O2»} ­BégsãØ>LÁ¬¬4…‚0`Æ+Ï�žÚËÐgð%I76?uº¬WÞ^i"‚ œñÊs£G ÒóBɈîÃÖ�Ï$Îw.X¯|e¥‰ƒ0ñÊó£OÁ�’€3§O¿ÿÞ»îÒM 4Úž~s{d½ò•v C†ÀÂoµôûÒ£Œ„$ýïgì‰ÆãÚ°^ùk¯µ$A`De¡ž=V/^ºöñÅ_on}¾vù²›Ãî–ídFKéÔ<ÿÂóÝNgÿû u0ž3âÃ-ÍGýf2>ºpŠ‘!H1�žú˩֓Ò#ýû§ÿܺuËóÌÓÏüøÓ�yÑKúµ‰à'rm4IEND®B`‚Ô³õh°xœ‹öLÎÏSÉHÍM�åòÌËH-Ê,)¶óýsS¹¼> `¿ xœʱ‚0@Ñ�¯xynŒ.& 2�A£ÁÚB#RÒ–ÿ^a»9¹+Öp=È%  ߇nÌsQÕN¦qJ U/|�ÚÀ§7/aÀ ë6Þ- OìgG¾Ûîq†ŒÞÙ¥"€“õy·Ð•žšWÕàza6VT ËK‚­¶SÒÕ¼mítÛö½�FiYÐ(þ“°½I¯5¥»xœÓPòÌKÎ)MIUÒQpOÍK-J,I ÈÉ,.ñMÍ+ÕQPJN,Q(I-.)ÖOI,IÔÏŠê€ä•4]…”¶xœÓPòÌKÎ)MIUÒQ€²r2‹K|SóJu”JR‹KŠõSKõs�"z 9%MûgÕ·xœÓPòMÍ+UÉ,ÉIUÒáRPÐPò,IÍU0TÒQðËÏKÕD3‚‰qi“ê æ0xœ¼hxœŒsù‰PNG  IHDRPâxíûbKGDÿÿÿ ½§“AIDATX…ÍWÛoUþ¾33ìn)Ý]Ú"-lÚ"¥jD#ÔDm �'cbbŒoòl|ôÁÁß4¾cbT$ƈ·pñr1m�¶eÛíî¶ÝíÎÌñançÌÎ’ªDݧ9û�Ëï÷}ßï7gøÞŸ’�$PÃTÈ‚!P°-D‚¤ìŸ…Ï«�â›�w†ðÑ‘Ó&�×Þx‹ A A P�(H}(¼gr„úOl‚¡¬õ¶5Ô!„ÛM C_D…”m”Ž?yú×3"d>5„?ð‰jE=¶¡¡Þ k©Œ"”ÑæÔÑ`ºrzðè£T6W&C5ZP 3 ÆûW„€¨žF" :ªŠxZ’jžñ$u MP2ŠÐ¿Á‚€H©“X€:! ,Qêÿ”j,L¶ô«c�Ç2¦ ŠöȃO5ÇFì»Æ‚né´Xy¡ÿpF¬$$)ïÄ‚fxÍ´­–V¤FÔ�Ó€Az„jÚÁHÙ’F¢Ú–n‚˜áZ[š¿UGl˜ ŸíþzsjªÅ´q1u©ïTºAZ^ª׃À¦+î– FÄ1°Þ�®Šp¶žd‚˜«6­�:0/Ö¶ÙҌܱÓÆÑP³dL%ÖH.ã3îÎACPHäÆ,šJ‹"mú‡e,wtp©?kòê\Çé90ˆ—ÿz¢oצÛiÞo¤�ß*TV2Óy~ë/^}´é·u �¥£ÅGT,aØøÅg3ûvçÈšåG»Ne—*ê�¤ŒÆC?öš·9eÿÖx Agæíóö3ÃæIJ·ˆ‘qwσâó.kè=Ë—™WK7t®fiE.”eÝF_Þga “s }9÷Þ·ÉÏ/æ¾üc}oçÊã……P®�}¥o¯õ¾:$(wô•#gøçê• €øfþé;w¦:z~ù!{:�BòXmÿÉ¥½9cz{êX(õfqáøÊKg�ýž5Þ¿,Ÿþ¯¤Q�'ôÒ�¤q¯‡®“Ÿ•[z}†ºÝ‰’—÷uÙëRÎwãëêælÍ:9™é©Y†ôÖž�Y{9³ÐH_ž_ßi5þ´ÒE‚Ⴢ C걦;Dõ§å'«2?ïÞs®þÔ€uÞÄŠÇÙç±Eä¦1\•½3rdôOã¾4Ú5°¶–&01+Ÿ{�Ç.3¿–)‹ÅŠê‘ÞìlÚµLùÒÎR¨‰Œå.6MåzÊËbÅ1•S ¯]‹³ÐiT-6÷uRÊZ¦¹\çUlð·™Z�}ÞZ‡i +ìpG³¥ÓF§«‚½]ØœÇä]õÕ¦#æ—Ì�Ïçc7AS€ ÑÚ¥U�IZZwµ.0àH«âä¿©P®Š(L„/ Õê9­]šDòK!%'JêÆ@�;YAÙD¹nä2vØÀ Ùúؽ³1­´& °„ëMÈY‹ ÅaÓ�ò ­:¹N£¼F4¼ ÷×v¥kI†Qż“liÿµ”`iø®æ¶�²{­¼Q6TòJ‹æLÕÛºÐÝaoÊ6F åJÃRÂÕS%Ò´¥x 7•6›…µ¥�Î…“š$˜KBȲÛ=oox¬ãhθ½Á¼¾=õý¢Ì).Œ2QÎ óŒui5¬0¤† òFkLL•Ùtü�¾Ð _]Éî¨íÝV’àøüÚŸnæ.XÁPJqjæÞÝ׆³Å†ký^)ô¤+A—Võƒ¹¾2xÇ9«a_ªï8½4¶=sbwæˆo:ך{4ûG¢Â{™D›+æß?täõ7ßIúlùF»€¦!Ó�ñ>•ÒMÞ|Õ_ˆ·~~×T³×¥n‘+pú?¹`9® 2�·q­Ó&yaլРÑm9æu=“¿ÍBÔZV{ͤŠRORI#‘U.X‘¨"8_)]m�j,h�ªi$²@ªy*kÄÔ劋ٖ%îvR«Ÿ‡ax«4m²˜mYˆ›6†þ;, üúѼžhéÿ qÓê,Ä ¯O ý„“Jw5,¨™è�z 1T“+©t¡ËOC‹¶Ę¢æ¡?©—'ü1IÏäô‡DpOL‚ "@úM˜^½P¨P´‰7ô–‡ÏÔ o•P pèï¯CÁ†DBÄ ?lˆ­Ö¨2IEND®B`‚ˆ·&ï»oxœûù‰PNG  IHDRPâxíûbKGDÿÿÿ ½§“°IDATX…ÍWilTUýÎ}oºÐ…i‡½‚l$Èb¡”°I0E%! Œl&cD" ¸ X$‚E@‘EB"(`T„‚¶ÅP ˆìkÃRJh� ݦ�¾ë�·Ýûæ ©HÔþšûÎ]¾ïœó}½ë¾Þ€D@DFDë‹C‚�!,$HØß ²~7rnÜ¢­Y¿« š>w)À Àˆ1€  ™þÛ˜lBæ&~qLP„µú¶Š8$¦8v“†L‘ךÑÚ!¹Ek§cL.=~˜Y,’A ÈD…¢:Û$¡ú 2×BÙ(ìÍ!£ætátó§�BØ\˜hÅh B˜v0úWfB r®,Ȩ(9Ó�’ót&)³@Ò!#½ �„‘Ú�'X�¹JýŸ²‰Ü-}<'ödntT$D4=�ŸÍ©[<7øÀX�-í�­Í_µÝ_KÆ8¿ ’á%Ó†ZZ�šì†,˜DáAÚL4mziZH®^[º.bZk«êª:WTV¿¹{ÈcWCLëS–ú^¥k¦¥',zÝ ìØiíÉ�Š(c4¨¯vì ³fËIºˆÙ`ÓhmP]¿£SM­j»£!¦u¢–fî,¨B¬¶\D”�§M§¨ «×@@¯nT]C™7Þ8ÌçÕfM­êÛ+ð¿6úx­·žS„‡n¾ôú–3_¾•<9jÞÊÖE×¢}Þú]Ÿ<)¥²ZaÌÐ’!}Ê^YØSd!&:øó'»žžùÔ‚©;$Ýžóâo’î¬ÞÞ3!>àæPJ§ëÏ9’¼b[Îá©ÏÊülù¶ÁãÓ(LË;ÝùÛ¼Ôé£w$·*)*múáÖ±7î$ˆ¥k9W²´ �+äåw©wOƒ…ô4ÊÉW8ç:ALÁÊùUœã¥Ù^ÿû‰=©�5¹Ü’Ë?¾ì�Å­ÆÍn§ªÜÿ|‰p¤à ã\¹2‰4mñ ‹W½ 6ôýò§î ôÑ”\Î1}ùÈ9«‡wm[êÏÈ·¤ÖëŒÅ ‹¶Œ‘rbÉäõ_eš±f‚/®btßýréÚR3§×M¾9Gö^>l€ÁBzª–“Ϭå½ &µ¨kI\a±z¢À“¹Â;fDEL#®¯]½%ñô…èÂ+Qße'¶lRGÖŸTºäbx³`¬�zt,má»»pcZQi™âæK·Õït£ÈZ�³oö<~íOツä¢Ò¦ûÏv>UüðÅ’Vû º4‰/×ÀÂZDÙ{ø’yÈ\†Žíà�Ç‘SlØ®Ïn÷�Ã÷l,31F¾­ä–JDç‹"õ,îVªB�‰…®�9YhÓünlTÝ÷ó7éˆ�'ÆUß¼AD—J›é™T"Ï_k©¯­ Dù¨ÂQØÖŽjH§µO;z†<tëB©)´û‚õv_­¬fç‹Ô1þÇM02‚ˆ(¨±Ð.-ê¬(ܲ´ìj¹¿U<…×&-Î®Š �Ez8Y“DÄ�ç„viè”Y'H–&�¦!w íOCúk¹ùÌ,{¨ðŠÒ¾uÐgh4°Oͧoßth%5a""ЉÖô ’*�m\¶´èåo›æ·ãcú„~]‹3'n‡= vTï¸[Úø·äbiýWÎ^d ç�Û󽇑¼³Ôž%sË»´öO Ìž|ûò5�®œ*P^¥êØ„QW}ÞºÁ½Ë†§Þ8q¡‰š4®òxø¹«¾³—›½7179éVŸ.WüyÜò .´3εòtti2kX`H “€}‡).–öAu�ñè³¼ðZfã9Ó*¾ø LÓ°3/fÙ†— –9¬²ù«:øÇ?7´¤¼Â³qWëîÉwÌ.-jDñË¡¶GžŒ� ®ËêõÎÚôWŸÝ¿hJ–¦a÷ñä5;SEû›^�Dµ¨‡ø ÖoÊšñîJ·`ÈíA<£¢8çjmPù@¡tÝ7oð ñúѵª˜½,uˆ\&sÿ䂨¸£Î6.uZw/4ð‚eyÁ¾-;¼.grß,Ø­¥¡×Lˆ(ä$…4\Y�Qá‚e‹ÊÌó…"�Õ–X€Ä‚¨˜†+ €˜§°ÖELY.§˜aYâ'µø<´Âk iÝŠ˂Ӵôßa�¬×�äuWKÿ/XpšVfÁixy`$ìVº aAÌD$ê@%¹ÜJ—d¹œiHцˆÂDTÝ´ùÇšÛEÆ€nr0C÷D7&@ Œ& ½^ÀDÈÞDêË­ß� } khì/Cæ†6s@â"¦ÈÔõ8ÜIEND®B`‚Kc<ºAxœåû‰PNG  IHDRPâxíûbKGDÿÿÿ ½§“ÏIDATX…c|öæ3ȇ�ga``¸õäí@»„NàÔé3LízƒQw€ÝÃéåKÓK—üüýYðÜåG  ¦/:H#§|ùö3¡`Á§Ï?þýÿÿÿ?MlÁÿÿü½zã²È¹Ë�˜iâ 6V– o#vv††žÍ§.< …-8=¬,/zöÒ#8÷ßÿÿ®>QV¥…# €�•ÙÏU���…vV000à4ÝHOnëžËÿþýcbbb``¸sï;³¼´Ðço?! >}ù±lýÉ«7ŸýÿÏhi¬hÊÄÈøçÏ¿”’EyÉNKמüôõ‡¬”`j”­¤8ÿ§Ï?òjWLïˆæä`e``Øs䯙 *r<�müþãwfÅÒIÍ“æî}òìý쥇ž<{ìm„Ç¢˜ ó ;.üý÷ÏÌ@ÁËYwÞŠ£¿•–ÈNpäÆê/œ1,#)ÈÅÅvãîK÷ü•GFºòÿaIúßÿÿ}3w3þg¬Èõ,Lu¾óàÕÒ5'ázWo9›“äØPäóïïÿU›ÏðÈ ¦À[FJ05Ú.ØÛ¿EGNß­+òI�¶=xâvó„mÁ^FUy^?~ßyà*.Ãqz˜‘‘ÑXWžªÏ^~l¤+—½yçå›·_R¢m¥Å”DãC-÷»ùýçoˆ¬¯›¾¢œˆ´¤ ½•úÛ÷_Iõ02Ào‘·³Ž˜¯‘®œŒ” ‘žœš²¸¼Œ�¡žÜ»�ßpˆ/ÃéÊM[t(&ÈüÙ«_¿ýPW;sé!DêÅ«�ß¾ÿÊ­Yáþÿ÷ÿßÿÿŸ>}äa``�“„ˆsq²Qâ[‚ÉJ AÄ99Øe„á쟾ã2Ÿ‡•Åþüùóàñ›«7ŸëkËB23°s°ÊH 6•ú¡iùóç 3~oüûû¿"-BÄÕ øLŒŒF:rç.?:wå!rzf``�çöòÃXvñêãþY{Zöý;4)>yñ�Ç‘m@ ¥e¤+wäôݧÏ>èjH#‹+È+Ë‹N™·ÿá“w—¯?]¼ö¤¤?s¸¹ØÙX™·î¹ôñÓ÷3—?s¿½ŒŒ ï?~ýó÷©*=-5©oßi¨IbV�¹ÉNKÖžèž¾‹‰‰ÁÜP)ÄÇ�9Ì̌ɑ6«6�Ù{ô»‡ƒöíû¯ð¨·4RÚ¸ý·ᅡ< I²ˆ `|öæ3=»‡?ýafbba˜6üæµËhÛ¬Á´nH£½¥áF=<ÜÁˆó0ËácÇO�&¹C3t彨ô«�IEND®B`‚?wå¹qxœæø‰PNG  IHDRPâxíûbKGDÿÿÿ ½§“ÎIDATX…ÍW]lW=çÎÌþØkïnvíÄn\;Ií4Q“6©œBPÁ ¨U•¨â•>ô ñ€¼ñÌoEð€"UTÒ@”��6×­JãŸÄõ_Ö^ïÚëý™™ËÃÌÎÜ;;™Rû´wÏwïý¾sÎýö^þäç¿&  I ¿¨�? `Wˆ5HY? ¾ïŠ.B>Â/.Þ4 |ë{?$h�B�¤ R ï»܆ÚBý%`(s½e uaDVÓ†ÂÐç¶³ SŠË6,Ç^¹ý¶X„O á|¢:Q�mh¨7í¹TF!Êpqêh;\Ù½ýÕG©,®¶‡j¶ ’f˜Œ÷«%Q½ŒXtTKÑ2´"Õ:£Eê,@ P* Ñ�Á‚€’H©ãX€$+õÿ”j,Œ·ô7§2/O¥MA+ʗ϶¦ÛŸ º¥S¢ùÕá iÑHHR>ŒÍðši;-­H�°!+¦%ƒ) Õ´cEHÙQF¬ºÝ1ƒ¹¶4ïTÛ0A~¾ðæþäb‡i£bz‹”Ê>j9,ø«^o'¶Rq! Äèw¥*‚h½È1wmZu`ÎlMØÒ ݱÓÉ�KOdn $–ÃGY0•\C¹Ì®º'Ç A!A�û²h9(m‹”éo–¶ÜɱÚpÖ&äÝõž›÷s.`ßxrö͹¡Ó�Ÿ;\ܲ éÍýÇêž;éÍFêƒ�=«…à£]Ä>ô¤P´VzDõÖÎgª2¿áî}§~vÔš6Ñô8ûÐ9µ�ܽj_EWåá%÷ñ™æg—q4…�£©+æ¼€»[K˜[“_|‚W?`¾—I‹Ëq èÑÆlʵLùÒÉR ‰´ån·LåzÒ«¢é˜ÊñS^·e!cT-¶žë?¯k™âN� U zsm$61äÍm ¯æ6f›'öYwõ-ÌÔNU�\°¢ÙÑiÃÝ–«‚ýØŸÇü:]öÕ–#6j毦󑛠)À…èìҪΤ ,­»Zp¤Uqò—·^T®Š(Lª!Â}‹/L>šKV–/'ªëmó"þO!%çJ8PÀhÑ�/‰ö±'ˆrÝÈ¥í ��dëS‡Ö"ZiM` × ÈYÛÑ6®[:H¿êä2F9!^À^cátò‚V$´ÿ€ý½âÐ�õÊ—ú¦ìüÒZXJ šVœ¥á»šûd¡WÞ/Jò,m›«Ukê±ÍB�ýH¶19R®4,%]½T²!M[Š£¹Å”Ùé-�fVNbh’`JÔ„�e·°ažê¹”3 š÷Ž%¯lËœâ°�$^ùrîôDjkG~÷g›¸Ýl¹çzg8¬SÝ™ ï—‘0±XfËñ}�þøaöÌèÖ¹‰’g7zo}”�¹`µ‡RŠ«‡NÆ³Ë ×z¿2RLUÚ]: (´ô½æØ‘žw¬†ý^ýÄÍÚÔ±ô_Τ/Jð#gü½Ö3šý)K<{¤÷ÉÉü·j ·ëi×i™jT`^ð§ç/~ûû?Ž{v¼Ñ>‰ iHÀt`ü@åèÆ.~jxáë/œ¹p³veºá¸|È qéo¯šjõºÔrµ�þŸ\°×�FÛxØá»x�ygýàw^]s\’Fx§-fËî]šz%›…°µìöšI¥^¤R†ÂB½©\¨T¹`…¢Šöþ¡>µ5¨± %ª–Ë5:/XjZ¢Q´; JÞ肪Ïð}ïδñbve!jÚúßaÁëGóz¬¥ÿ/XˆšVg!jx=€ô Ž;º»aA­DO”Ð� š\qGº\Ñ2´l;Äì`�*jžíõzyΓôLNAH´ï‰qA¡@H¿ Ó;/*.â ½éÁwj�7K(P0ô×סö‚!DBD õœËjo®h0IEND®B`‚áßuW¿wxœ€ø‰PNG  IHDRPâxíûbKGDÿÿÿ ½§“4IDATX…ÍWklTE>gî½Û-Û]îÒB� J ¥€PŠQTBT Áˆd��(>P,! EA±B�D0Š/Ä ¶…P*﷥ȣ´‹@+iK÷ÕÝ;þ¸¯™»wI}DÝ_3÷;3sÎw¾sv×~º _XH›L!r³¿dŒ[Y7A¼1›Šö‹ðìüåˆP@$@"$ˆ Aä§DkÆ:¤ö‹Å@`ÖªÛ ìˆ`Ù�›�_«{kºdç­Žf\wô 1X�m¢�ªl‡ª+@_‹ÌÌDÑÜyT7gNׇŠÌ挡>e½dÜ4�Q¿`åðe�G9GÁ$§5Hžà ˜ˆLô/°@€q$AªíXÖÀpÌ6Õÿ) ȱ€€ö’>Z’r¼4Ù™„,šŸG+KZ–Ì�þc,ð’–S"å«·¤z"HEBoÄ'xN´ñ’fR fCfD‹à�0ïVŠ6?%. [-$,]›dkƒaqõ7½a _Ú>fÐ…8ÑZ“©n. eÎaÉcA ˜ÕºîØ‘ åŽÛÃQB`Ô0åÈ bXóAÚ$³Õ¢ÕÐHT\÷}¯PD4ÕÑÑ$ý^Ú/eo{Ç%#�Œà­,ˆŒ¯fº ¸L™9ES³ûA0'ÏÙ£–*+sž ËŽ"Òïw´YºFŽQpH°ïósÏ-îôâW¼žèéç‚U]ª/&§Ê±­==§9( àä±µc†Ö?µx Ë‚+9ºí�­_¼kÑ“û23®Í{tofFcá–�^OØwß�œ^—iÉ¡¬•›GPŠ)VTðÞû›GOÍßC ¼»2«lŸcá=ßftlª®K_´éÁß½léÊå$ͤ NUц&á[ùÈkïu—óËó�•�]º~dº§þ‘Üm(§\=ÕĪu�oJ±x7Rc!?W))'ÆòÁ·D3:Æ^^æ®ò‹ÇNJ+åÉw^wµ¡êÚÂ/ÚUœI®:ïü²¸]§´0~\é‚�àMMjzÖuLmZ¼!¯ºÎ{Ÿ¾|ÓÈ{‡W´IЍœ}¶sÈÅ«òŽýîsui»+{¬î³Ó[ùɾižßû:ËÚ‹5”ÖJŠwÒe °`ö쎲ýLÆ�TiÃî7)).ºsC½–pB Õ«Ô^àtu’ES³È”[x‰˜•…®éM)Ζ¯nT?‚´�;x¹Ñçê:¨k›CŽÓ;©kCî¶îð¹HvG©*Óí¯ iŠÉÆŽb\§5O;|$ ûõ�Üؾ£1³¯6Ééjq²Ïk¹ &9¢ ‰ïÒlž�’æUÍ÷W€`Xªºä�¾dsUI’DÁ0ÊŸÓ¡CÚÄa]夯Úަ«ºxˆýŸ* ( –þcGÀ˜Ji9ÑË¡ê¼Ð£KTvk9º}hèÝW.[rÅ5ap%+ªAfF³µ�ó’6X¨©•»¦_ó¸ÂªÁð¾þ‚i[Ð4Ó‹Óz\$³“4ûnw…?ºè+ÉÕÑhZv’VG%»pÒxÚ»Ýu@`œÇÊ3ⱓҲù }zDGä„çθVsQbÜåCElˆáòؽRå–уëÇçþÆpbCLk�$zêBjeM‡×§•fe\Úç¼oRÙ¯WdF…f$е½cö=òÐ^ÎëA:⣑Å¢\µ†Í8Ù“:îØ}ƒ!íÑghᙂ¶óf]ÿðÍzEÁÊ\+Ö{m.Xú4% Wgúô?0¶¶áº´ak—þY�z—62%ýã�nÓ&OqE×e¿º&ÿéûw¿=³HQpûѬ�Èeå¯þœÕß5f�wÛ¡@ ÊH�µÄ¸"1’Šë6=ÿÚ*»`ÜíŸx:�”R1þÄ�)]Û͇tö?4qø–ý�²ŠpLÁ¼/^#²Ñó©ŽK—®ô¿sÁ ‡E@‚h�ZÛ¸Ùáj¾ÚcΚË1óNÁÿ韰K#É_fÁl-­½f"‹"$ÃB(Â\¨”¹`™I%úùf~,ÙæX@ŽÎQ6 [�c!þ‚ņÁ9jE³Àø Pöyh¶ïÖ‰Ö>™ Y°ŠÖ‚þ;,€ñúá´n+éÿ VÑò,XÏ jÛ•nkX`#áEൠ\ºìJøtYÃà¼�Kf È¢âÆÏ¿ ]«Ö戨Š jSÐî‰vBD­ £Z/HXÈÜD�ªË�1r�ºŠ0�1Õöç!}CBb�þ”è}&@ÖÚIEND®B`‚LJ3¸Jxœ¨Wû‰PNG  IHDRPâxíûbKGDÿÿÿ ½§“]IDATX…íWmL[U>÷ö ¤^hÁ–R UXdí²KL­ºé¶h¶§25N–8ÇÈb ..òcšEÿ˜hÔ,F¢fËÂÖi¦D‰_,Ó�ñYX¥«ƒI”Rà®]Ki{{ÛÛsýQ‡eÐÛ– ‹ÈóëÞ÷¼ç}Þç|¾q8gÀÿ::¹€«v×�Îd™ÐÓkDïtË�UÁ+ Þ÷ºaß¡ST˜Ž5ö›m»ëN44þºD©øÔîºÞ™ 2 Ã, KÜÓË #ÖÒo¶¡²$Yàó¸•z\ à޼×ÒcI²#„†�I:Ǭ*”ô Øþ Ê0&‹]U$I2î"Àçq¶?¦ð¹©vìúåÇ+Ævפ=Ùq£ãEk›Bˆ¢(`øOBÀçægϨ¨ƒ×<ýu·eÈÁ0ˆ®\¹óéûQ¡i¸÷µÆÕ› MÝ^°@†Õ¼°Aš'òμñEÃ;Uéi<@[û Ñ4R¿k,# ¿\o8úöóG��µ;ÜŸÎÛ7vèq¢]•Úo~0Q!ª\-Ð2¿8å¦äÒìW^Ú”ƒe,¨+î Ë¥˜PÈ´NE/]¶áêBææ’† óþ'?# R_»íÕš-Ã#„á«îÙ¾_~×·Ϧ#+`„9ÓbL8ê·àp�^.Ãjª6îÐãìDí½Ö7VèÊ„]—ÆŽ5[Ÿ{R»ç©R§ËÓÜÚá³W0‚ åjÅìªî3�ájÅlëÐð”ÓåÛ[µ!?ï.U‘äÅguç:†H*m}âñuÅ q¾{äÁR—ÛŸªàX°é·¬Ígäòdw‹p�B]VôÀCZ\Sètû®š:œ¶ù²Ù6 ®V|Üx~W¥ÖAxü�`©*×80mš$¦d¨öðçÑ_2�a¼^2Ë(¤XÔ.LçߎڄD²ì¨==�_,ω~geeúȈ¢DMدy{Á½�03)Áªâ\š¦GÆœ–¡‰ueÑÍ… �'—aoÚ~Kš†.—Ã.F’=TÙ‰æ`î "‹5¥ÅA¿—ðÁPŒ3[á�"¾VÑo¶õ_�]ÏYžÈ1åñÝ<À~³Œ}ði[ÂìIòï¥hŸô$t^4�‹f¤ñ”RÌë§Æ§#¡¹C“ ÒÂÕŠö^ë¸Ã£^“k/’ç¨ %?7j¿a¾2~²©[š+b‰“!ðyœÖ¶�i/ií4ZÙy¸§ýt¦D”.à)¥–™NG eä:áñÏ/_\z÷•ÈdhM‰tþõX[½ùTS×» ?¡(ЮW>SQ·ÃAªw>|æ[ãÙ‹ƒ™BÁÖGËþ¸F°øëpeó÷¦ªÜ¶>"ŠHDB• #ÜþëÓ~*‰W©!çÌr>©ÍAQ.÷߬á]ëF�–ðœ^’½"mi:�rYs›XD!•ù=¿Û\IßË-x)a ïŒÕçáJǪà•Ξޔ4ÿ]üÿÄäÈôÛ_IEND®B`‚Z M!¶ xœë ðsçå’âb``àõôp Òb ÌÁ$�lɱRlIÞî. ÿApÁÞå“�"Õž.Ž!½o';ò5(ð°„×¶ÿ74üø_ŸÑfÆÇ~—‹§Ïã’V›}Ékÿ>õ¬·bþ¿˜ÈlsAýÏ=›rμ„ó‹óJ�ùl¹r7‡“ó«wB%ê±Wg‹ÝmœßvMýÆd•EŪ+Oòj­ ÐÛX¨½MAðÏÍ©ÿ¾ò�5p·Ryuþàb 3<]ý\Ö9%4éFL2°xœë ðsçå’âb``àõôp ÒF ÌÁ$åì;6)¶$ow†ÿ ¸`ïòÉ@‘¯ž.Ž!�ogxó5+ˆ¸z&_š»«ÿZànû廦\îT;"¿`‘þ—y†6ï8•䟷8q(¶œ` `ä½À¬ ¿"”ßô×´ҴkðÏÚýê Û 1&•Öˆ‰ /ŠÒæ·e„;¹œÌñ`ØnÑãøªWa¶#§Å†{eŒ{˜÷q×Þk´›ï­º'á¢Qšêž†…þK;3‹Wñ8ræÆÜNMëÔÏ•S7¦H(î‘S8<«bNìKw—Ùém…ç›?è_Kßv~†Bà¡e>OvE>#'tg£ò­;ÖÆ†Bq·Ÿlžiɹ¾_WÒËòXð…£«f¬Î3ìçOµaçTú ãÿ#ö¶ËÚæ/Ч_ƒ§«ŸË:§„&EÑ‚z»xœë ðsçå’âb``àõôp Ò@ÌÁÁ$ëE^RlIÞî. ÿApÁÞå“�"ž.Ž!S“E‚>¦$¥%§¤)÷¥¤&¦=OLRòÒl9{èŒ[³ö&Ž:Þ53í0Iìòfö[:CŠCÃõšÃ” N†¿elB^�„œ€f1xºú¹¬sJh`Z*V¸ xœë ðsçå’âb``àõôp Ò@ÌÁÁ$ëE^RlIÞî. ÿApÁÞå“�"±ž.Ž!S§öoä=ì pXáÞÆúÆÎe'ÿI/õ`ºQ˜X£Ù¡´Àfž~ÕÔfîå1_¦¼b·d žkÄÁ¼Œ‡·íßÑüÓ�¯™ŸÏü}ÆêÄ4;ÕêªïùYÛ¾Ô/ÿõ ùÒ'*°nØðуǎ÷‘ÉöÆcR OXK9–•1xºú¹¬sJhÜãa¹xœë ðsçå’âb``àõôp Òb ÌÁ$�lɱRlIÞî. ÿApÁÞå“�"vž.Ž!½É ¬‹d MúñÿÿÿúE< )G²Ø„wê)±d¤ø9ø<6þÒ”t%Á"á ºgŽš$ëâ F ŽŸå¿œ€&1xºú¹¬sJh")é³xœë ðsçå’âb``àõôp ÒF ÌÁ$åì;6)¶$ow†ÿ ¸`ïòÉ@‘ž.Ž!�oor1 8\œôZÞÆïrq©ë•*Ìß'®Þû7‚3Jsv¤Ì�ÿ¿x>Ï`š¤ây‰=÷ÜÂŽÌíObç�[»õ´Óæû®GDNÏ|öÆ$Åý_—âU‰ ³Ë>ÿ‘µHÕa’i̽´æýž˜ýÛ�Ì _ϳòø„,ž•Ç@ij|§ªøýÉ�¾±¼#mİûœ¤æµ¿²*ž“–”V2t�lÕjã¾t#ƒ§«ŸË:§„&åƒ^ž¼9xœœcü‰PNG  IHDRPغ×bKGDÿÿÿ ½§“QIDATX…í–Oh›eÇ?¿çMº9-Å2ö/‰Êhé–40êl›ÖC½ˆ"ô2'ca‡YÄ‹‡†­Š Ã<ôàÅé<¸yP®‰àD¥ë’2‡�I¶nÿ”ªUæ’¼_Ke¤i›@Á|.ïóð<üžÏûåyhРAƒÿ/¶Ô„T:wèüÏÒ3ÝñÈ;«9~\ÀÀ€•Ö²G%�¥§X´Pîìîf€äÙ�+­¤u{þÐv*}eK_çÖ«k×ãß,`Oghßb;•Î}ô›1Ú �¯Nuy¬�JªØ�Õ‘œÎ¶9ß½!´øSpì§‹¡�ÂD&ßç¤#ÀNà7Á„Wä…®]áëåãÙàQš�˜Îïê�†ÎÔÚ#•ÎE\Ò|¨ÍµÌ¶Ë/e€‚o~Ôɽ·\·ÉJ>ûz¶Å|ûRhØ Œ¼ÁPëöü«Éo³�ôû¸`°WAŽH:\0c¿•üKõðèŽmØĸÏ5矖_ 4Ú»÷ÂJ6lDfÑGvoùŸõ‚—œéÃT:7^PàMà~àçRðÎóV¾¼ ïå/¦.oª‡G2©NoN¦!à] ¼¼9¼Xc¹5 ðÁŽ­s2÷0pÚà)Œ訂w< �ˆGN6ˆ¸ �)ßY¢£u@Ž×�s�•ÖR×|yl'bº'~¿ä¹a ˆ841õ}¬V 4hРAUü ìLɈn'ÒIEND®B`‚R3­õ³xœ³Lþ‰PNG  IHDRPWÜbKGDÿÿÿ ½§“hIDATX…í–­KAƯˆ &Å~p6Ѧ(Úl^m6£Mõ/°Lµ‚AÐ$ˆpAÑ fAA£_á9ÃÎÁ¹Ü­àÞÜóƒå�™÷ÝáÙÙgv�@ ´’®•LÑÕ•týIÛ’F+æúUÓIt'äž�×îw×§ǵ©¨)¹8L—ÀM�t¶7’6œƒ Ôå$ݹÚ|Ù…�ìÀ®´ÄgȸÔ20WãžI{’%½J:‘4’VKË’äÀ*ßÀ‚ë/T«‘Ô[áÒ Iç’J’>$eù\õ µÿÁ‘K�ÌlÜÌf€ Xo‚žT$"¾s1'I±\Ûmãf,à·‹‡Ài,÷Þ`-©ñ¹…{jŒßºøefy3ËOÀ°g=^ðáÀ7W%½˜Ùq,¿ ¬‹’†ˆ\7Kô¹ïA�W|¼ñ-àžè ÈÄ“fV&€ LgÀ´™]yÐ�@kò›9‚Â.`�IEND®B`‚å#¿±¹xœë ðsçå’âb``àõôp Ò<@ÌÎÁ$7_°.RlIÞî. ÿApÁÞå“�"zž.Ž!S“· §à¶Ù[!!!qeIÅG�M…Eé‡òyX.D v/ßÞß;ٺΕÁËžEfÍK³ @� ž®~.뜚…�$Ê»xœë ðsçå’âb``àõôp Ò<@ÌÎÁ$7_°.RlIÞî. ÿApÁÞå“�"ž.Ž!S“ï쯔dŽÛPy—KŒ�­­uoh…Èݪo39÷ 8¤»êD_[¿çëã:W†Wx}•�Wˆõ2xºú¹¬sJhð’&]²"xœ"Ýý‰PNG  IHDR�wSÞ„iCCPICC profile(‘}‘;HÃ@Æ¿¦j‹TÌ Ò!Cu²ƒıV¡B­ÐªƒyôM’GÁµààc±êà⬫ƒ« >@œœ]¤Äÿ%…1÷ã»û>fUѬž é¶™I%…\~U½¢aðˆbBR,cNÓð_÷°õ.βüÏý9Ô‚¥�8¡¦M¼A<³iŒ÷‰y¥,©ÄçÄã&]�ø‘é²ÇoŒK.s,“7³™ybžX(u±ÜÅJÙÔˆ§‰cª¦S>—óXe¼ÅX«Ö•ö=Ù #}e™é4£HaK!@FTa#N«NŠ… í'}ü#®_$—L® r,  ’ëûƒßÝZÅ©I/)’z_çcí­†ã|;Në>WzÇ_k³Ÿ¤7:ZìÜ.®;š¼\îÃO†dJ®¤É‹Àû}Sºú×¼ÞÚû8}²ÔUú88ÆJ”½îóîpwoÿži÷÷²ÆrÀ>ß®b pHYs.#.#x¥?vtIMEè.D¢„tEXtCommentCreated with GIMPW� IDAT×cph8„�÷´¸tIEND®B`‚€Oݾ¿"xœ/Ðý‰PNG  IHDRZÏPBbKGDÿÿÿ ½§“äIDATX…í˜ßªª@Æ×öLb‰EÑT/Ð ö*uátÝd]D—!R!©PšŽûb@Ží8œsÊS{ãïjX³¾ùp­%óaÀh4â`8¾ZÉ[€1f^­á½(ìÈPØ‘¡°#÷5Ôï÷Ëåò×8!d2™ ˜N§aÖëu„�mÛžçÀ¯[¹ŠÎ�;vAÀó<pÇq!„^�—Ë’$UU%Ir]—Úñ¸cÇjµ¢ ]×[­–çy‹Å"Ý�Íft¡(Š  Ëòù|þŽÜ±ã÷¤¡ë:BjµZ7vð<ßn·eYfYÖuÝÍfãûþ“4çÈ¿·Òù|~:�`½^[–•9”az½ž¦iAø¾_©TºÝn©TzPë —É¢iBèp8˜¦¹\.w»˲º®ç‘ë¹üu±ü t0)ŠB+‹"Šb¹žK.vй³ßïÇIƒqç‘ë¹<¡XæöÚ5“$ÁcŒÃ0ü�ü:¢(€F£q½^�Çc·m»Ùlªª*BEÕj•bÛö£bóç!;¶Û­(Š!:qSâ86M³ÓéH’Ä0Œã8–e}‹“Ã0Š÷JñÞqKaG†ÂŽ Üx<Æ¿ZÆ»ð ÉäÆ<Ò®5dIEND®B`‚±þþi°"xœ ßý‰PNG  IHDRFòÂ}$bKGDÿÿÿ ½§“ÕIDATX…í—ߪª@Æ×öLb‰EÑT/Ð ö*uátÝd]D—!R!©PšŽçb@ÎÙÆ>í]îÚ›~WØõ}¸ü†y3 ~ƒÁ€€~¿ÿh%wcÌßl6,Ë꺞G¯,Ÿ¼k �©( �RŠ(ŠyôÊ’‹%š‡ÛíÖqœ´Çq½²Üaðæý!4 ’$ÁcŒÃ0ü¶ nüJQ@­V;ŸÏûý>­Û¶]¯×UU!Š¢r¹L±mûV±×q“¥õz-Š"Bˆ¦yJǦi¶Z-I’†qDz¬o»¸Þ Ãx½—ž�—¥Ÿ71Æ�–qOþÁÆ<+Qh§IEND®B`‚ìõŠ»xœ{„þ‰PNG  IHDRKÖûlbKGDÿÿÿ ½§“0IDAT8�ÍS±JÄ@�ÉnÊ�ô+ 6Bð'.ø ÁÊþ8¹:{+ Úo‰?!6rXyØßrŕٽ½ba‘MLÔ47Õ2Ì{óæí 2Æ`@0Æ(dYöo ι3D‚Žƒ¥|/ð½ßçi³î~v‰ˆyQ-–+“�£pš&J©ëùÓz³íQ�ˆ.%Ó4‰£ð;Þ¥ûYo¶ó‡çZH—’›49?=>;9Òx!äÝã‹%¡exûøÊ‹JÃ&㸔ÔBZ£u ‹å*/*­¥ÿ#ÈÝμÀqZ\袈£pvu¡û×BRJ&ã‘q·ŸÂòÏr·Ÿ"ð=Óÿ¶¨^ß?µ»FKs»ZT(¥,ÿŒ»J©f=2Ƭc×}šÿߚ眷ìEÜ�?Øcÿkв,9çC(ö>y¡%^£jIEND®B`‚69ª$´xœ´Kþ‰PNG  IHDRKÖûlbKGDÿÿÿ ½§“iIDAT8�Í“±n‚@Ç�+w¤ƒKOIôh‚;¹0ˆ!q"}}‰® > ¼‹w‰1:º˜Ð¥š¦`¡†Pz[ÿÓÝw|?þü¿ƒ“¤Wpƒ £ÇZ­—#t]‚·XÈu·QDQ¸¾^Eˆ¢àyŠï+”’r�RâûŠç)¿) 0†Ž#J‰ãÈCŽc|È!j­ö\쇯Õj¯i Œa·ÛØlöÍæ£ã´1†ÇcfÛárù^îWURE¶Û�õú iu„`§S×uc˜$©i†³YT± ª„=‘ ˆL3L’!ˆÐ©?ªýg³È•¦Y–épðüôÙ'”’ɤ�ûO’”ç9Û–+3º„P”§ñXFè”ßp¸ˆã!èºmUeP÷ÂuOï�Ói4Ÿ¿YV˜{±,ùª{‘e Žä—§Çi‘NY|e¿Û}ó|Q®AÔï3ê ó¡Ëõ»ýÙÿ*Þ0zº.Ý‚øv^”¾cýýIEND®B`‚&EÈ�µxœ…zþ‰PNG  IHDRKÖûlbKGDÿÿÿ ½§“:IDAT8�Í“=Žƒ0…m C¿H>é‘•»$' ÿis è“‚#DŠ,úPì\ ±=vdoá݈Â&K“W™‡ùfüÆÀ²ä`†öû£Øí>ÿ�à¼BsZ0z[!!ÎóþAˆ“ç~Qø”º}ŸR·(ü<÷ï)#]@l¥©w¥PꦩgÛÂ'Ò4]œ…P£,[,—®ï$‰‡1º\t’ÔMÓ K–%¿¿¦,ÆHJ0‹0¬k;9/Ç'ÂX†µ” c4ñýÃ,Œ”ÒZÿ¶ !z<ýñ7”ºÛí¶‘”JJeY0޽ÁŒ¦ýüâ¸ÞlnÒýAˆ“e?õƒà|:µUõEµé%м§î…Ö@ˆ›üLºB¨k:}Yƒç¦éÖëÊ,ú>cíj5â� F7Mûoû³¿*ëp8r^ÍA|j Pž¹¬äIEND®B`‚ý¯Bµ"xœ%Úý‰PNG  IHDRZÏPBbKGDÿÿÿ ½§“ÚIDATX…혿Kq‡ŸRIÌ¥+—jjp)‡—¨„¦úZ›šÂAÐȱ­µI B„ )p ¢!C$i;:JÈ_ É•Duwþˆ{¦/ï{ð~x¸÷x¼ŒÇÇi3°»›ïw’�@’²£ýÎ0X:::˜¿–Âafg»\Z¯³µÅÑÀö6OOLL�Épw(ZCJ¢H¥`³a³Ñh Š� ·Z-€…\. YÇ? ‹Žƒù°¶Æú:Å"{{�îÎŽ|ðzðxxxø'Fºèøž��€¥%Dñ³»�� <¬VòyNO¹¿×"¯ÎüýQs{ pxH2©hY,„Bø|T«”JÌÏ 15¥.iOÐåÍâ÷#\]‰°¿ÏÅcc¬®ê1Jc~½,?afÀë•7ë�éi=FiŒ.:Úm€ËKnn:ÅZM�Q£Á²˜LŸ+¥@³I*E*Åã#N'£ÃðŧêîxyX^æù™ëëN=“ae…ÅEj5ææhµˆÅÔ%í ªtœŸãt"LN*꯯D£lnâvc±�Ë‘HP,ª ÚFâñ²ñ¿ãI:†…î!††æ““´$eûcPxЧŠÖqô¬=IEND®B`‚ëVîܽxœMŽËnÂ@ E÷ó–ºm“@³@©]€�‰êÂd XÌKcG0ß”¦¨Ë{îÃ~� :z3PÔR_ar€ó¨pJ‰r<ƒ^šI\5eÈX„Jc«¬ê_f‹�ì«Çè6x¥bº´m—0cRÈIX§mŽ’¹L�ö]ºÁok}§Á¶£”Ö§}{äØÞ|xx}©»y’êå_Úô5“]æìy@åÍÇ�¢ýK¿³sí ½�Ù¦KöXgÝS Ñ[-#™•Ω0‰ý¬¢Ýóù…ù³’rü±ixœ­TÑn£F}ç+®H¥5R »‰Ô‡ö¥Ϧ46vï6Oh ×x˜¡3CˆUåß{q7»›n·Ò>sÏœ{ιWœA¤º½õΤôàâíÅ�°Ú‰FtÜp4¨arwxþR÷› úåÎsÎ^Ü¢—p­TÝ Ìç‘sFÕ¹(Q¬ —ØBØñ’ÇÊ9|@m„’pá¿…Ép�%×û™öª‡–ïA* ½A¢¶‚zàC‰�!¡Tm×.K„AØÝ¡Í‘Ä'ŠÛ#…ÚXNhNøŽ¾¶/qÀíAðÎÚÎüÃ0øü ÔWºš'” æqÄ’ŒMIí¿– ÿì…&Ÿ›=ðŽÄ”|C>€ÒÀk�T³j;ha…¬ÏÁ¨­¸Fb©„±ZlzûIRÏÒÈðKeÅ%¸aqæÂU˜ÅÙ9q|Œó_—ë>†i&yÌ2X¦-“YœÇË„¾ÞC˜ÜÂMœÌÎ)'jƒ�õ“H1fˆÕX†ø‰€­zd:,ÅV”äKÖ=¯ju�Z’èP·ÂŒ“4$¯"–F´Âr{8ù”ï8eËï°h…mßÏ N>°4#½pé¿»ôg+dUtZÕš·ÈÞíš|V£ósõmtu5º˜NÆ×êÛdúI]�?Ž®NÕô^ž\•ë˳)<6¨ÉÅÉù×ÓÉÅGzuòùËùd|“¸œ�ÕÙå�ù“™ù2>™ŒÎ‘Ìéäj|2UðãÉåÅõø¿¿EøI�Ž>�>"3ô–ûHoû4š^_ÂÚW°Ñë¯çSÜËÙÕågu~yM콆}žŽ¦#|ýËÕ%° <û4&¯`Q"3ºP£“éäò߀ŧWðq .ÆÏ'Ç'c|ù’Þ˜^^Áƒ_¯å6³ÑÕäW¾ü:E —Hé\Œ™*i¥Üã+�ÁçQ>ÕN®½f†b¯"ÃÏp¢žg3-(ø½ ƒ€~K9bì{—k;«ÍšœÆ¶«UVoßÍj�5hÇDвÿ¢ñ=ë¼ Þ½kªµ� Ã,ÜfY‰̆øÍÀ½¥8 }Ë,ªº*ð!´)nÜÚCuÝΖ�Q¢‚ØÕQ8rL�»¬Ácv„X­Kð]x� žÏõÜàËß�s½†oÀ±sOê V@Ôõ1` .¿Ô´ ’ÌÊÙ²ªyy\Qç �ÓYU—"ŸL�\5` B�-ÈY[ÂW€–�³z>lfLfÁ÷‹á�¥`ôÀ5t © ¸…0"zQ¤w`jµ®JT ƒq¦në*ËUÙ®nAòÀ+mÁ°•ÁL¨‘Þ‰ŸlÖ°ü"½Ù‚^E&ô;Ñ»Zÿ»Õ¶S\èÆY"«Ñ[̣早ä%5¯«ʵCì.y^ùç i;!0`Œ�ïEÌlD–6DÖçe¨Òuˆºuf,*Yù\C×5¢1hºdù$\;ùÖsÖ* íF–vXæ-ûW›¥ƒ\f¸öm;Ÿ£¢šŒTçžðJ^a,ˉ5#OdEë`ð¢^­ÁA!±ðÏ Á˜˜‘Y·KU–å )6?75¨7‘ÓpV•s³hÁAg�fxr«žR © ر̜�¤2šƒ¡©YG6^Õíÿ€›JrÕÐ#¦QízÀ™í&]:ØÀ �²„Æd…ÈÉÇU”JGªCOƒSpkØùÆ€3AÐ/¶Iöm_ß+:,l–Å쮬6Âî€RB²ÃÓ³j#ÀFüxñ{jðÓ*[ljðϪ%ÀZƒÑ©‹¯lÑ ‘(Ò—ÌËo s'À§\Œ-Ø�­Åå£l[Æuj1·Ô!ǃ‡Á€*”Qël¾Øz(0Ž=" -3‚»¦Ø�Ô¹Ùe]ª,¨OªðÔ¼`eRˆ’/³z!ødò#Õl×ðW©77øÉcpG%dA$}ÕCÆ“Ðb{Mh¿î%Àü}÷ iò¸l‹âýQS·ý%22½aýÔ®üq…Ð@rIø�×5U/e¯Gsïr‘ ºs1+�:GõÝ+‰.t ƒ²!Dˆ` õV~B°…t&+Zøð:Z6‚NÍœ£�ĤL dL~#2šov¤C?( ÐôÂrÿ7 “�Ý}û£]JP=H ÅÓ#*fþñ°šÝg¦ ,BÒ8TŒK¬¢p xŽE,ãfsL \ ’7}&‡ðI–)9o ᱪùGˆ;ße]¬:›juŒ@ÁÑÞ«³Ü ÏÔœ»8î<5B°83#$ŠW¾ÕÍ.45&ε'$BÌu@ä3pIÏÒuˆÛ@ÞÓhó´©ÏmÑÈÅ ‚°%å© ëV'oŽÃ$ Œ]€XàЊžE3kKÉT÷ÀXŸñÝBlYÔ¼9G¿�²ˆ"{ï óû`¾ýÏTPVÛþ¹ô›ë«?¸Qÿ@¤1\8÷§�°Û¦®¶aè#øS…ÀeÊ-ô¸Å …€>\ I*‘˜<Ü­îLÞ†˜&᛬;\Ý$DCØty ÔP& Õ%}!Ô–Ô·hy3I…š%(l±tÕ’m ¾Ð‰ûàŠ€bÉ#1-‚RüàãÆÓõaˆk‡Vˆl¥+ÝdyÖd!Fy™ŽÊ¸òÃ�c^rU.Œq²;H‰Þz¤|øª/ö{Oæ‡ë.ÀÏ;ø^ª”ãÂÜ9qêòÞÔU¹¢ôL¾;Opx°î…8¬5ß©�‡õÞ ‰YÑZ¬¿CÂ3ðW½0åÀç6æÞ4Û—èÀyÕØ3®ëœýߥMTmydqõ–YéHCÉV%Ç�êkÕªÌ ?Bü0–2î¨ÄQá,s\¨`NK޶„t /Hä+~³îKÁW:+#¯ˆb€´í bÝ”¨Ö°§(Ñl¡Ì-0å†õÚ:yVØë5Jb‘IðHWõe|ÓÖ¸: ¬(ƒ•+nÒeT?D.9Ü)<�÷D)9Âu­�;95îAÊÄná ¤:a{ 6™¡â„ÊOÕ© È\€²zÛfÅÁÅ4Ô>7h—O@<Ɖ2>ëÛ¡;¥óõ#�q_—þ<.ªUO¾L‰ØHŠòdºeRnìŸZ ú¼ïÃH×ÕišßÙ×âªp2W[¨j¨¢yMBÕ†R: 6ƒn1CËK›ÉÄm Û¡j©ÉFù9„Ü+SJ‹8®¨°j�â«u‡UÜ@µ¦Õ]'HHåàÌ¥¥GK‚ùüFPžWÔ�¨¨T÷äÒÆÂP}À®¼$a¯Ñ$!#À3‰H<ר·>°±ºJ­ˆ6&o–IáÐW,5ú]ßc;Yý® ³øžê9u°‘ŠÈ× Àf¿M_ñZqøöá´¦C>%5Fzö�½!Zc³�× JT¥ûWÇ^èáb¨žCôyžXàóBÏ›ç/\ìïì(.©)œXìK†ÓŒ”/^ħ¥@YØL;¡/ãþÞÆHÎ/™æÕ·‘“âAŠפ˜szû^%VýX)ºmÇ­^ã§Û¥MOE+¿n>šß`4ÿ1; ™ÀBW�ÕÑ9Ïšã}er«ª¤°Ñl)c�$Åu» ½‘ç–òQÇÃ÷…M0—´cŒ½oL ÀêŽAGœbZدoäPB¼@".ë 1kZW~1ç©ò1†âñ5sUb ‡ôö8o ´Ÿ$ÒØ¤« ñÔ¨M&�\üÝ‘N,!<ã̬-²š}ƒý3¥›0.k :«aO ¼º‡¢z‘‰W½ /ޏðr†Í1íÁC�tó ùa€=äD<;>ØøÂ6‚¦�#|×uÕ/âïâ†r¸zà>²ƒŒ�8…mÏ‘IlYHªƒ}Ç�&9 “PYâ›TÕP¯=N‘)åÇŠFgþ ÄÓ[r#rëN9ÒcÛY�4‹!CY ¢ SE KR¹a¾F´±�²aÅ*k=/8ÞŽ—¹&¯tº·˜¡ã7ƒØê¢„£Ÿ”\/K3DÃ*ÇÌ–ú¶½îïŽÔ!ê‡FÓ„>³Á,¬ê œ†G6éú ³¬‡ ƒì5+Á®W”dã–q€¡£iXâÍë&5QÕ†ÅуYA†’÷õYP \0ÐTÅ B7ŽÌå,*Ðzbý°¼ì-Uu¨Ï=ÖR÷6€,¾éc‘ÙÃN @@ö@ü�)¯³5†\(<ªÙ�;ô ¼Â¦îám¦+-vpÿf¶ÃÀV=ZÁ½®«‘–¦tG aÚ×ÑãtÕŸ�)pJ,þ±ä�‹æCJ˜L ìiqþòh˜õ¥ˆ®€?À!X�ÌÐ7:dC:Î9feRƒ' ¢k9•¢Ø™B;•-°ð=�(@`¸¬”¦6OGÉë1%…\†|–NôÂg×OW65wt�£‰dЧØÓÝT‰éA¨Y­›¿Uy“ãÆÓCw>.üå`P¨õ‚z5z‘î‹:¤ŸÝá7t“LUÄQ6n*¨6Ü÷|ŽÂ5½ÆÜµà©<{ Ÿ!¤“4™R€!fµ…»†½4hÜ7VãŠÆªLMÊ�—÷7ã]ðÇVnb¤+=[f¥±«0ö‰ÿÀV²ÅŽÿ¸³ý¾8ææƒ]GäЧœ2 Øga]Zï‚Så~ê ,ÿÙ¥…_#ZèÁð h ¢èdˆ©âÑ& Ï`ó#j¼¨$0ºÕ�‘ñŸ½;e™lgŸÂ8éÀ†‰@²¦'…B… `ᣪ°„¢qsê:F¤Pô†Ï�1 ÒîQƒ}ùÃÀ²o0•7Öºö%ƒûO_V=T_­¯¸*öeLYé�XØÎmø,/eƒo>ð ä>ÀskÉWÜY_œø*–ã�T:±[ ÿ¥ºÅÃMoÓ£=j²íäýt–^`ûæCõÁŸ2 <©^c”àNÄãe~s-dÞ.È×Ss×;°ã°àAñ¤æMMÉ”K¨2d``¶4€gž L'ñ´w]­Ûu¨¢xõˆtrÚo=óTíã‰SA¸’Aã¤æ³O ‹y× ”s±˜¸�$ºÒO‡ÿáAâN �¡¢T8¡»•G#Æÿ«?wúéeþkì9qÙóäaM ¯«§fÖÜ…—núYîSù‚é`ÿ]—;3=nÊ;~ô¹•‡½ñ§3òjjJGwÇwîMF¤åí_õ[ ó²XC¬lœÏuÍÜÈUè´eÊûêŽæÚ:µ›ô}KÞITàó€3ÍEÄm{Í5Hzá Â{�"3N*av�™ÂÝdÚö· /.e\8 ¢;j`¬õý3®~™- æ<÷”5A„ÜɈLæÍ`ïÌlà]8ñïgGH'vÏY„âiëO˜;t£cà¾éuºk†Á,Þ ·ì´Ð¸�˜‡d^ÀNý”gSä+*±ÈÑXü`QgrÝ“�ã4Ù®!TzÝ7­ã&â‚çkhlIŽö(w’�ås�;É61Rìefäø�¶‹¤WHx<ǬV:7à@œÇ�¬.“ã�½=F®ðÀŠR¨ô‹™È n£8&{vôC]pwGòÈP`//v×çé„"aС£“˜7 \BÞôK¡}Å%¿_ÌcOÔL‡»dõ~Ü Ï‡sp¶­4b„~k0>ßyïúãa~¿:ô‡gBw[¹1Ë 6¼qÀý‚ÝF5I™Y?®l»X Ñ´5åKé¬ö½³±>)+¡ù.4ÕÝK#îî…„8ºSJIž\Å—©�f©7cÂ9dI6ý´‡ùp(`ýÜ6]º›ÖNMe>�é�…ÄÓζ¢? ?¯µ–[ÂxÉ—z·r_·w”Iï¯ øwˆÓ�GB͈õ‚b¬õWXº“upGŒše7\ôœ'¥‹Y™óÊÔÒ”|Å¢¤øÒ™SŽö Ùƒ®�,à‘º¶åóF,b„¡Øh1ÉE Çå^`ý ƒÅË8<®Gçq³×YA£×Ô8¸xø ä°€v"®b»Ë$3zé ÍZ¹é:™ ìNW©çÜ?“¹ª�@Ü?Ú„ =»ëX«`í;Jʼng0RÈ(¬ß{OtBB7ªÐãðYïgÿ˜ö—ýýy=2ú^°**ÈîžDªî�ÕÎkÎñË�UqÄÚî´�\Z”¨° m"ÌcÀzÜÀ17¾äºÆØ�¥ xóußLƒg<-]±wÒ¢0�Ÿ/§ù�¡:kkš7eì È©y &‡Ë‡1þˆXâÐ~5 Ö1”¦„.¶ÿoFà?’'Ò¢»ä¡�ŒQmq’|£²�‡¤TÇݳr—£Ü‚þÞ‹Wþ¿]�ŽÒ?y_ʯ€3Í}·¯òFÆMöܸ’Á�[ÓÌ�.òÃiýu—°Æž#ŽÝ¥öÞärplÊõ¾'/ç¼édYt9;'‡g½£“ÁÉY~;&�ÞGòî¤wÔ$ØýÐûiˆ#2]d%ußú”¦Hœ¤hJmwäÚ04ÿffÝPr|¦¡#"SNÜE¡^ Ï�¸±³G¹qaGÛkkÿáú¶7s@ü5ÚË_\ßm�ÕxîõµÑmñ3ý2¤7ô>ý(ЇŽrÏÜ ÷0fŸ…0@|vvLލíY¡Ø£ì ™ô�GŠÇGÇbÍâ`bÅ�ßÂFÔŸMˆéŒ,3b-¿¬ø d/'–G&�CYK|n³?è ºfïìü}ç´™4ïF¶5¥¬y“X£b‘!±Ç0~+ÂÑív>t�’ÊÙ:ŠZ¿íœÖ¾¶ù¸%^ìŠÀ‚*�‘µÕÆøÚ²o#x6N÷wxÚé÷Í×�Ãw}øö–�EcŸN\;ðî L¿ûþäðìô¬0» ¦wqZÆ ‡ Èž‚èøà”@õ©Ñ"ˆóÓÎ9@½àP’3ƒÐò#õgK@ífvÌ™+Múô>&L%’γª¢>>4æÛû fz<óm†\º¯‡„({†ÔGW;�Åw¤Dð LÖšà<›Ó8”] èi HÿÌbD%Lª"‚E݇4ž…>ðãnq_œOˆ‹4j�iØàÿê5þ@Є‰Â�x—ĆùH ®ëƒE3Ø‘ë¦î­ØL¤†0€û of$Šlç5&châ ÍJDË]žÉ‘˜¢‰ ðš˜?p˜�Î(ÒHƒÿl+c…Þ9Y@ { ÄÄA‚,ƒ†7åŒbß‘|ô¨:àøºŒìÃø9pÒ€ ;t¬ØbÌÔÈælŽ(Ó·â—¾5¡Åo>[ÞŒ½�rø Ó)HÄôÛò¨yKj:œ—g#Nÿ:©«Ñ th?Â^Pu"“zå™ÂÚ¸ B§�;¢¾câ“EºU L™»vV†÷gÞʰ×ÌåVA±¦tT踮âUZ}£ƒ�ÌÈ" 0®¥MnG÷1zÄl'˜¢ªÀ$Ç„ëþ›Íº‘è0›ì͘ä?ÐÊdo•AœÁílŠoøl$°B\%dÝ„X"xÄ¡¨Y oB7€ØMuËPøi褢6^¾¼Ò( þ ø B)„˜2ÍÑ¡LžŽŽd»1=ê³®4{½/g°l6AfÆSZ+7´¦fÒR”Ö,À�¯©å?zΧçÅ=zzOí•q³çYÜØ, $[å�×”ÅNc°´+1Baê¨�Žjó%5 ÁË�ˆ oÏLd|äðºÈBâbHa î@š€’ÁÀÙhÑX3ŽBÖH=§ Žxï~¦rZ–“T:4ȃQ” ”Ò¸ùl“8ð$~0ô9�’ç&`"? Â&ÂIdŒ(Lîb6y98$^ �$£8&=‹ÉEDr §˜Ö‰âÕkÅú–×µÄO³ t¬U­!ô¥žW'± ûø5�$G:§7u% +Î\×®Ñ9[¤�_ä^Cìšð°®)(ŸåÒ Ç§TzrÀFgü¯ÁÖ.;[ÿcmý{gë¿¶®6ëòépxy O¯¾ì5_|ÅoÐ š˜¯†ÃÖÖUc8¼ªÿ&Úþh£éØñ &/-~�ÙÚZ�˜çp�O…ã'£0˜C˜qT7ò#(I 2¿›¬£ËW0%@"Ìq[æŒeNs’:½ ¼Ð�'é¥X+‹¹5Ç–ëE„-Ó}j‰ê[ñ+3âTÚbÞ`&ëà€GüdlðNZc6�!:[ çL^øúR™�bk cU@?€ÀÉPLhÁ\�|©ñ·uí5Î8àüŒjEiL¥¥f�¥¡…“@‡ö"™ àÕüÈ0¥vy¼u‡úå\ï—æÃ˜Á­Î‰<ü#y!"ÙìèGn4õ¬‡rލ&K3ÅL‡Ã®Î˜bß„9bÕЛc3²Åòv# a;¾�΀·š£.øvyM (õ¥iæAG!æ§ «k$£U€Ô0« IS9³ØÛ¥™…‰X–YË ¡à |«&!ìl�¡…óù$,of0Ç>ž]eXž�el>Ó>Xñ¸œcìíÒìšÔãXU„ỲéÞÀhTÎ,­ÍÒ,³9,[uŽVç[)šªÌ‹Vä^”bŸd›ˆ†UjܽAè„ÆãÀYq Q¤Ædë©>å«¶�̸ùr{z��/½£¶®qÅ™»Cl�öTÛm²å�¼0yúXŠpG¤Æ oˆÜö�Çb9[íàb ¤öXP[çm·~Mòý&I¥þMrÞ}cvÿ{Ðíu�êuMë‹U¤&zo'íRd0XK“ ô(ö�õuÂHl6PhMíuŠÈôú�!‰•pÞØE)üó_k/ê-¶À&V£XúãFr� 2­‰ŽNîÛ_šò€E�•\]¶ìGã«ËW“ÀqG. ¯Z ”�¯dC`Ä;³iM£[ñC•¦�lþò˜óY~ËS·5ýi“¯ ©ØûÃMñx/Ó¾R-"� _1 ¯Q|,}Ç%H‡¥Ã‚¼À×­–ëÙÌ�-páà€®ï­Øf« lû£ÅJ@¢)¹¶ÌÐ!37N“m ¶vÈ÷P0Y6ÁÒär» /0¡.Ø«KI:=°Ô šD——6àÒ÷Ý#²«JO»Ë÷Ä%±ì�vVí(3\ ws],�hÃ�_ÈË6ÙÜt³vÊÕÉ&åD¹Wis-kÈv¨\�M~“!ƒ]'û@A�Y�BJSš^O9Xáäž÷×§ŸÙúv‰Ÿš;¿ÉI°—õ™b�³‚aì™×¾ˆ§sÚ‹Xe±*1îdrÃhÊϺ‚�âú³ Y�‘zT3xp¨ï¹Í`¾ú`”.L(¬œPE™(h^¥þ GÛIˆ$H!­-9H7'1¡½ÞÜ/²î1i/÷3¾0û¾ÆÊVu%¯MղȜoäÿ6tù²¤þå«Ä·,†¸7Q_5¡{–TN”ˆBšumÙ·²þ,Ù|aEi¼$L�Ýâeè¶­M0 ÛŸƒåGäŽÇuü�\3ŽÅ…¶Ïí��v�A ¿7µ\QöŒ£ý%¢„‘7¬rEZ™×£Ñ¨iš•"ù$YÁlU�Ü}^'·ŸÁqâv²söù¸©±ÆÚ]ɤ?ÿLÏ߀%óCJ*… õ,,³c!APF4´)ûÑÀ0cןÑô2ˆVäæ4BéÐû)x®Ä¬›äz¦ÜÐØÂ9tŸ[®×J »J/\°îñ+[ü%/Ã� 5€Glðܕ֞Y¡çJÀ×c"\L¼Ç|š~HhÊFTà7þa¢àžL̓Ôjü•\Õ/³ˆ3 ØwÓ:©í’_4&r?Õz$�ÂÅ$üZJÂ._­{«®ùu-ÅÓ�’Ä�jZQ]6Ý”¬ÖG*Q)+Öú.ü[?¸%Ïèä¬&›?C¶ cG¶Ê„3š—”MëUÓÌ5g¢C�œÊ³a5×nŒ:ÖCŠE(˜½D™¸7¾ˆÅOœ è�ñõ7R;Æss"M-Eîãðì¢78ÿhÔ›ì€ß5eè¨&ä½XZBÞŸ�ŸtÏ Úë!è‡ œÏر Vš)öÔÈ8ð$ž ìÒD\]¾’ø¯Œ¦ D�M,ÙQA­|JóÉûÄbYIÄöÚ“œdá%ÓJ¯ž_5OMÖbùÍ«oî‚“¬ÅjE.ÆGûêàó‰z>‘Ï…}ªRJANø–ú×ü®M’Øå·m" Ì�G2Ëc³g“?Eò?Ü#i¦Ì™l˜d XÙï±ø-ÓÇ`¼RMñ÷ ÿ­žJÛÏiT²\—ò]^+ÆCZŸë;Ä…ŠŸÄÇTgìLÏ¢²ç§2Z 39E5Œf6vâúö „ö%äÇ)úe²�ß…¾HÈÔîÝ�Úë¨ú@P•EéSUëQ·ÿnpöÁì¢mšƒ�ºfçÇӓÞƒÍ9\äŽð¹ì _žŒS׿}\ÿ§'½w+t|ä†y€/|\ïG'çÝÃÁÙùÇy$¬UFwÑ{×;û£—²›‘øFVcQ[µ…t:2÷aÆÃ Þ!ML´RšMÇÆCãíAði `à˲Œc$ÝÀ]h-WZ¹X®·Ô¥Í¶ÓR/SɃ(’W�™ã4eÛ$»¡šSÏÈŸ)J†\mé(µD ï’Úø; `c¸³�kQv‰ Á²â�áp#›~Fàní1´È@±�Y`KÑÆ>Œns:Ü €^[kI7ôµ†C^ËXoÏkê-Ãy-alZÓaqS‡Ž¬™ï“íFr”UœÝ~"&È”xþêY„Cíñ o“fš ¡•ì|8DÈI·î-¾‰ùO`üîÔ“ç ",tB³�ù½*‘¼Ò¡�åPíôRCiü8O½Yhy䳸_&ÉÓTà5XíúxÜ.~âÉ×ûhéªi„’Eµx|ᢨöK* ¼ƒÈ,�¦ëÜóÊö 4=ikpK}áFÊÏÚ¯ëÇý%$%q‹ \§åkÀ2�MÞÔ‹«Y>½3ÅàÁ’ØèñW\³CØ${u ô ¢S##fûÃÏ¢ó¶ë °�ƒcþ]�%ß /‡¼3À�©¤±�{y)F½¹‰UI—å óÅ"øÑ%[V«§OlšKÇʃ�ÚF{ÙÜÐ'¯M5ш(K4N]ÁÝ—`£QO‚Z®ÌßËÝažOm–ø3jù¦§ zÄê@rá’öH?óß…}o}¶B‰r÷%]èÙÑYî*°}ð¬.ä3Q«Õ"^E-òn èA;Ò'®>‘g�˜ �g'$<žƒ)_„EW,¼æòg0çÛY¤�’¥ögZÛ{º¯Šw]å€þ÷Šî”÷.‰ðâ1NýkÀDlòMi ä‘ñŸB›Û~PÑ¡‘ ñSFQâqTÊUeâg^¤ÉÍÂ8C‹âŒâ N5þ¤5þ´¨ñ�Zã5®MâG�(O|¶U®;D–OŒ�Eì̳§ ®LCB.'Þtv­!ÕÐüTˆ†-…cî<!Û>ž]8G~ϵo?O lÔ"nEÆ @3ø~Ð1¨»܉MZ°;Rc¬­‹ë¤~Æ- ÏP\âôuàµmxЈÀ°Û‡Á„Ý„øo]º¤þÕ~ÙÕžÀ Ùðø›½ôþðEòСWG4dW8r£ûO�;Í,o³ç%§Mâ6É:­K6eÐõç)ŒF2fÐZÚJ®Ü)¤%ÌȓۤÊDš…Èy¿¬¯¢´•¾Ë¥ñ\ªFürçª"¥XvK°¤PÒ¹w%¥7÷(&H¹]Ô*¹"ãyΈæ½�…g pO^Ö©ëW þ.>&uòžË±ð³xV<ÖÕåΛX·4”L*!-EY’FÏóé£ëàFó<@JÿæÁ¢f²û"«™ÃÖƒgùÎ{d§�ƒßžEá6‹(¶¯][°k¿ÔJ§î[–†!¹b«ú¸sÚïjд%®–¬ØZ»o±bp~‘Hî!,–ðÿ;Ë¿²³,¾Âÿ1É‘p–bß \Ògê»Ô·Åi=ÝGÝøšë̪+É~Lj�±¼µ;}vdÞouq?6)HpÄ‘åê¡ÛƒÏc«ä†Êƒv{›~¾y�Ÿ®ýºÂ¼1¶ZÉ­%R$Ynà9&¬Ñ³Ä^‡«ã€ ¾qm}l5É žwAŒy‚÷F]ë¬'®õÊv¡í²¿ê’ÿK•6½~)1ü£$\Ë?4-˜ðž+Ëgµ‹�^Åw®TÙ%˜‘³»c”My G#¹éïVÑîSƽ»%Ý.)†,zqÒq·q�•qŽ#«Ê‚§O¬û½êÝ?i"²·¢@ 4©D({« eUÍqfÕž1�e�¿¨Þù‹§Ë‹ÕŲšRå¨ë4 ^V§àå·È¯ŸnϤE9.Ì‹åŠ/]kÔõù¹ZÀø¬¡\º Âh_ûËVèæ¯uÿäya�— LÙÊ\n]æ 3F¯‡Cÿp8Œ�†Ã°;„ÏñpxÿæÉW̪¨b‡¼FZ€¤äøM¡b‡-2Zä§ùé‚ ‹ ‡Ÿàÿ¡ÿüˆ_†ðô» Ê‚6b ‚„�lÒ=gPòBþv\î?/®@æà/@æß ]•.Fœ�aÇn¶ªe¬Õ6Ò´¬Õ £xŸôH^âô¾nÖò’¼¡…ÕýCê,Yb²â¦Wé¾ÿ.¨*@ɾæ·ˆ«Â&¬ †a›÷$éRÛ¯ÃÏb0"qz“_pÇ7ý—pø«vªQ;ߦ­•´ÛIgíù½�—iê_ Ú�;óûàlÑÑ‚Ó ? Gê"X^�C|Š»…àêúXùVæÑìgþÎ Mn¬â)ùµ�•GJh$Þܬ—ˆVÔýêÍU»|…ÑœcDüJE »À_B‡ºÖ4µ*¤r$Y J«šl ú­»Xñ”é.{Œ©¿Ï÷ Ùi í¯‡±xÜ�Íø*±(4ˆÄÁ , ]SüiDÓiêN¶J߈ðO~—¸ÌŒ“[| -� =V�—DßM0¥K�‹„Ø0ð{þ`Ѓ¼‹EÆ!âúÑÈâ¬`Js奸C›K±)p×­B,¼Wœ_:axðêc@JôŒ„BÉO"âÀŠ"!3Bæg¤J<±Aä¨gc7`+L*r`ÊM!µ0h�呵Ê\U†FäIW)Ûð¼Ô\ò‚ç‘Ôô�‰µÈEzñO^íþ‚Ç\ñ<ä?ÒÓRcŠ}¿qŪÓ^Œ�j žT‰ßpÁs£*ÿï;wZãñ�.׺ì¯yžÊÊød¹¶yQ*4i² Ö¥È Q¢Ë¢� ÿM9ä<äZ3UÁ#¯´Jr�¤}%¥±ðHíÞ±*#J>²G¼X6¾gY…|§Ì ¡aV¢Ó5Èÿ ~ƒUUðÝÓL^ ]d¬Ú-]‰(âyçW‘³l·2þÌÃŽ ¶é(¼`†'R ®;TµdÎLÃÉûR!6Má™ÆÛ+ĆFÐ¿Š‡ðU9ÏóšháJ{NnE-�DK?/7\‰°±z°Je™¤Cæk a{%Æ wñÔk­ðV;«ýzâŒ%6üÚÐs‘¹¡5K5G„¥á9SŸk‚í](‹J‰$56 š8 OáÍë7ßÃ<™( ¸fœ²éäÑ~¿KÊõš{azzðØwðAÊšN/¬oûZjÛSòœ€–y„ª ר`!~Õ;Cø¹¤”{㽆 ¼Þ꟞“ŠJ–°aä¥åFh›Ö:ä…™ÍÒL0L(›¹ÖN­Å²z_ë�kÃê´.ð)î 3µÓM>m·[�YomúeNNûÓÉÅx¶Ÿ¡Çõ‰Û«ŒÅÿ[ …h×USÖègƶ@õ#¡|¦¼C¶JPý‚–±Ù2eƒ8x»b�Ù%¬ñaw�2–C´„ɲ¿Œ–“å�”ÜMVW7·+¸-£Ùj2^ÂÍ.nf—“Õäf†Oïa4»‡ëÉìr\ØàŸ E¤Í…LðÈò¶ä|Ï Wznk&V€¤d ‡ÓGåT, ?MWª).HM&6¸¢öò{¯DŒ1ÁÝôãèz¼.ÇËëÕÍ<˜�K|¼ ‚Þ+”9ÿ²ªru~ÂÒ»fšûõ·—þÜÙÔ&ZK™=[¹¡5òÈ9Y©é¯Ç?cn!ç}ø½÷ +¼ˆ|O¦W'h�Ô�kQç=jk0¯¹¡¨øÄ²’»Bû@eñ-–—�¸À"»iÏÛbP-A›€Ÿòx¶ZÜ«ûù8¸�]Ïnîfð^�ÉŒæsŒÛ…Ê}sTn:™]£À›£—“Åøbu³¸G©o‡½?jhW2‹ô^%d2o[8X$àŠìF�ŽTMr>?LRÇ[tÑ£FÕ]Êm|Û¢±§ R†yÊyŽË§„:ÁÌ\cš%¹Ä>m�Q”ÀÁOj›Õùsk’j“�(kØŒ•ÜXLõ�ð—êsD®?0�z°g%Šm@•¹-ü¬-ýXóHn_`£9ÑáìŠgE=Teû¹ø’Ó§FÛob’ü˜ƒN£\P(�]ØT­ÓVU[Ž—Ëчñë,µ6ÜrO¾=m*nJ,òs‰1îr¹[‡ÇP!q@]˜+%qþXjCõ%�’·��>8 ÐTL@Pyü¤3µ¼kƒ› ž -_'vt”vœîx½tªõOøs‹Ö…ORDG|<9âžûîžÓݧ�Ó�H¹‡®šq¡Yˆ›$יּZïÖº·õš.B—!½ÃØË!s%$vŠ5ú¸Å¢éf™æ�‘‰ÌM9~|Š˜ŒtÍåÄŶ¥û‹ ;‚î^Znë`ç´¨P˜#}g¾¡Œw©ùOùt§ÿ_„ÖÖÿJw8þ.§ ìôŒXµ/´\ËR…ıÖ2TÇšl?Äg—©ÃYâ$”3ò4Wޏ…¯_À9ÑÀ�ÿÜ”é ¯u@›nº~ûÄf³y~p`ýÃŽ§»±õèк·þ…áÚ§ßz`Œ¯{2~öÛÌßüˆ°þ &ôÅ*´äxœÍÛrÛ¸õÝ_�¨Ó,¥eäK;}°›Ìjm¥ÕÄ•³’œ�L¶ÃP$$qM‘, Åq³ù÷ž€$@‚”¢XžòAÀ¹ßp÷ÈËÇzHïøè¸×;"=òÓ") ]÷=øÍ_yqò�ËÃ_—ùby]rvrö7òv„A’�7.ÍhJ¬;þýÓr3ŸÓ¾·ê6,ûG/×õõ%q#¿Eâ¾<eÔ'›ÈÐlEÉ q=ø’#6yGÓ,ˆ#rÖ?!NèÈ¡N÷A<IJvH3²É(À2¹¤Ÿ=š0DÄ‹×I¸‘GÉ}ÀV�„ÒGï%ŒxÎ\˜î ‰êDâ2Iôб$;?>¾¿¿ï»œÚ~œ.�C1/;¾]ÇÓá  X®¸�Bše$¥ÿÙ)p; nyîè Ý{§Ä]¦ÆXŒß§ ¢¥M²xÁîÝ”"?ÈXÌ7LXN°­N‘¹é ¦d4í�ŸÓÑÔF ¿Žfÿ¼¹�‘_“É`< §äfB.oÆW£Ùèf ¿^“Áø=y3_Ù„‚¸ýœ¤È� (©Ïå6¥T#a ’²„zÁ"ð€µh¹q—”,ãO4�€#’Ðtd¨Ò íÁ„Á:`.ã¯j|!¢ã££?‘n|P?7ÞUGyõ÷Œùa0ï¯^iïR@§¿ƒIs7£ÇòÛ<ìe ‡ÀgÀÉõB7•ä}—¢’Á(s=F|º¢Á¢¥¹dM£�ðLr š[Å¡o³Íln!¤ô^„€6è>\³8|O¾xÑ,& ´O4Tnl |"±Ð¬OÆ1ð xJ颀€˜ÎÏØ¡“°”ã@hóÌq?…üuËFò^㛋éi0ƒEò2¦¢¤« ‰_ù†> cûܺv!×E1â­Ü”TžÞ}À©zYÀBˆ4’,NÑùÐÑýxnú" ¢;˜À­cT¨ŽìŠpá#ଅqøSøÄêùt?t-“j{ø‰»%ÈK$§¢37M!Š-Òxm@1Ï’Ðá3@Ú=«ç¥,B¼ÙŽðë…0ßåVzNÜdM–ÈM¹´Ä·¿À Á�² –E:]&ÌâA0ü÷…jÔäÜõ<€¸ö\³l›'¢aßm µ t“)3M_ 0iGºq@¶JãÍr¥ÛÁ¹°Õ>Äðešqs‰Sˆ`;¯àãBÈÎ`)2üC&ìQ×›ˆfª�ÕôcÒ Êèá0Šy‹ôâU‘|úRñ€­9.4¯VE^¡‚3é0òJV)áYJ­ÍmÎE ·%À®ÌÍø=ã¹� Y1«Rj_k¤çzä´—……$¾Ts+¹Å¬Ç§·I[2µUÈþä†j™(kÖ{“Ú`©N›Ïx çÑC-V÷ÜF«‘FwÁJ¤1)â ð-®ïóí§ 2&K[^îñbAó5,x ßXìr_žÀtŸ)îDM™Uw:»0GÃSFhi€­³Ÿ—Óåθuú 3Ú-‚ÿyQ©’’M¶ræ®w§úR 6& :QDI«—ð@Jô`:žpYÙ¡à[’�’¡Ÿ�|Øï�Øš³‰ˆ00Ò¸aÔ“Ð’2'Nó@,‘ìl"¦ªX†¡¢vëÑ\l)LæBÊ*[ ‘Ï^·¥&~ž:—7ãÙ`4Nœ›×–+ø°Ë½U_šPW åFÞ¥t$¦\Þ@3„uem%í–ndv×Bâä¹D�ôª(@¶PX Šádr3±I組¢á îç–1lOëü9û­Ó©yŽR�ôº:?ø˜óOaÛS¦|¯¬úx�Ý>Çåûå\�Õ®SY˜Ùµ!¹Ò8–Ò,?A�–¸l¥z‡ ]fþúo0©R�Nðw+8Å�*N>r†#5ŒÊ„¿àT�ЀÅL!«Œ ±OR0ë:ÒȪIPKê—m5]å.™�‘?þ ù»r�ÁI�mžž mK9^L2Õãå`ÝOб-¹¶nYÚTŽ×7À’iJÑ#ÊK:ñBj¥�1em—,cˆE4MãT¢À”¦mƒz�“$¤ÔÐÉC?¹ ?þˆßzÜSÛw½Ì\xóåJXÒ"´N™:å™ÂmY)˜¸k-DTRãùïÔc¢Ì4ZY·ÛL‚‰f¡_4dFu·Wá™"Нºb E¾²¶Íƒ•:o¥tQÌûzÔfM5œ*}ʼ’"陀ˇz£ž×àMzÙ"„:Sê{n‚ŽÁq�ó™ /ãJ½6qäkÞ¦ºŽ!2K´Âºû6«Ò¦â§,6ѳx@(ksêz+«k“<êåÍnâ2ò±lk~ìí®Çh$;Îíøv:¼r½UjnÌè›#P~µ-ŒFÁcbm�ñÔí7qصNð$¶8E£dðîš`eÎE|˜fú÷Êu‹Tõ*?/;YФ×wŸÊæãé„ÜpzÐÃ…V©Ÿä�îb¶fxg&xg»À«èëTiÁFøLï"Jc¼„ÈÔÓ_Sâ£.¶fpÇÅçâ =>–½™ÃäJîƒ0$sÊ/QmÆÅ;mçb¶ˆÑ2¿ÿ‘Â\º.l/›û% PHä—¼¸ÂãH”ôâòÛÎgÖÿ_ÔVwÚ£×#©Òñ0Ò‚Ò¶P*÷´&-¢jnö¨çí&_= % Ø:”„áSƒ§�n;út%%äRR.Ìäì46^eS^u^?p£05bw;0ûÆô¤n;ìšž®Ô‚—ï_ )ë[.v˜½ÍКûvã«·d,s‡«’²TÝ[ ª'·æ,S»¢¤€çÃ#βyQèÃ9\™�í�î.ªÉxKüÞïjÏÓڕ⥠-öRötn£€‘øÊwÜš–=�ê-+÷ަ"@Bšgy�@Ç‚Œùw¢^¯jZ+än^ÊÕŽÏÍ žÈu]ÜÔ7'i&�49¢bO+Hƒ…üJ£׋±ìCy¼ò…×éŸ?»™WòÕÖ—p6Ì+„�Ê`³átæ\¦Cg:ÏÀ:®­®Í¯<ê¤gT¿³Zgf;㠧Ù°1IÎëT“eɸ¼£öèn*,Nheð?Bø¢I·áÇSzdßݘv, L篼—Û‘•KìÝp2zýÞqn&Îd8»�Œ­›¨�m¢TÌ{·œ£ãSâv¦bl/*ÕSdyQ÷˜L)X”O: ²Þ€•)pæ®ñ_u”Ã_J: „ÅeùrÖM7·Öžh9™í¸`ì�9~x{‰°ÒT¨¬�Mn‡Ê’m'ò°œ³R0[üë„ÿ …O:È�T̊þÖâÝÂ÷gdù¡�ï׃kˆ6%çu¥~?µ`dè@7Oj¸L¥^K¦Hw¶»HwvÖÜáØ+÷”Iÿ²9:^Èÿhï½uÂŒò?½uè¶Ïxœ­WÛn"G}ç+Jø!€XXíCâ$2±Ù,2Æ‘Á±öiÔ3S0-7Ó³Ý=Æ(Ê¿§ª{3\"K»HM_N�:u™b؃ß~ÔzÃÖ°×kA®–R!H‡ëAFÏ~)ÑÅÖÈUæøéº~€NÒ…O?ý eRÉ¢€[� tžýïÕªŒc$Y÷̵?µ^‘­éôDž T¶§2ÁÜb ež´ËF…Hè§ÚéÃßh¬Ô9||„hW[íî%Clu k±…\;(-†´à½Ä× 2‡D¯ %Ež l¤Ë¼� eÀ_+ ;Aǰ$ —ûA¸Štæ\a7›Í@x¶mVCÎÙátr=žÍLjquã1Wh-üVJCÞÆ[1JDL<•Ø€6 ViÏif¼1ÒÉ|Õ«—n# 2L*­32.]C°š¹½€$9´Gs˜ÌÛðÇh>™÷äi²ørÿ¸€§ÑÃÃh¶˜Œçpÿ×÷³›Ébr?£§Ï0š}…ÛÉì¦Hr‘|- {@4%K‰©×mŽØ °Ô�’-0‘K™�kùª+„•~A““GP YKË!µœ £äZ:áüÒ‘_lhغ�KÚXB=MïF·ã‡h²ßE_¢¨uAë2ÇS[t-OT™"üªd ‹Ãêw�ý~b“<³®¹e]k­x‘II¡JËß¾:ò Ú×mø§u�y*—Ãaã ÕÕ2Ü`¢„©Üü®ZnQ”ËÄÁF­q‹¸œ/ט——l»G–iOo­—Õ×>£¼h™6 ¢4œëœ€‡ž?Q8Ó­@Е†c^5ÆPäDÁœ“wg:”̧ë Ç$Kä�Gz ‰Aá0l¾‹†ï=þ3aƒº­$öœt ùB½lÞ眞=N§\0K!Uip Mž(\öÖêD²�¾whYæ Ç�Áü‡—'u„uIºÄ‰P Ó>[p•ÙŸ¬WfÒ¹²TK ”¥Ñë(¨æKàD|ßÔã§J½NB æ É„�ÞÎý:lµÿL„¢H…eT‹ê­‚½’'¬† œæy°ÀÐ3kl”¦\ûÁ0õ;ß²OÄ®¾º·T‡ö>‹:ÍJ¤é`[jöJ�Ïz“Ó{$“Åà ˆ2r`l™$Ôá‚Ô\ñ 7"ÂõÞvZlñíŸ:r6yïÃ+‡+$„¯:hWT¯«Âè™R_Æ­_õï©÷‰FWÎT@e¦QhŒ6ïM¹ºH›ºn+¸w‹2Õ7¢^Eòd]·ýû\øºk‡Ðú6v¦Ô÷sƒ×81JÛLn Ì7ÛÁÞõß¼?òkOÔÉ)¾N|é‡ó.K£™¼æ‰@0žŸÊÄ¿XIK`}À¶ÙDÆCÖ:œ°á¤Å¾?œ ']òn8ýáì|ÊގLJ£ép0agcvt6:N‡g#ø:a‡£÷ìÇáè¸Ëˆ ðˆ�q‚™>ŠRx$·‰ ³H‘”ÆÂõg¾ ¬…óŸ 6�®DG,ÉÒOqHSÔ øK_rIE¾Qoïo~è+†Ÿ”wÑ2Šþé‰ôRFñÓ˜' [}û³·øÖhzÁAÝüRáL¦vAà_`þþ­¯9¤²R5¡]–Jï"Š‚J¡UŠ {¹,™•ÊÖi?qa©!_Š%‰¥�žƒ 7à‰ê�<DZr%»–øpPþX À]†¬1Ǭs¸ç90€K‘:1—‹öƒ§ëPóX&]jâ©’¹ ž°vİñ�¡Á·+P@g£àJxNÎDt2� FSÖ�Á¡,j¯"ßc�•§ÑoÁ¯uíîœVk‚Èå�ØI /Ѧ@�Aȗ™%ÑÒ!ËPÄ4ªYkÃt¹ó(Y+€@Y²6ÓBÎø¦zÖ±>3Â!ò±Ó(º\ÅL’¿E/•�gHtŠj®!ˆeÂÃ4 bŸ5B:Ê:Ê—èsW¾(h|èiü(þôjy(2 s,@äA8R…‘ÀFwHn¯¤-.‚÷Y³}˜ÌWP-S„õ*³-ªcÊ¡±¡Ã“¹!”7ähs÷_Dš“]5埱¢Œ*³Å2Ž ¶[ü×Ù˜z: 4v2s3D»ˆ@¿™²©:9–Ãú2y’[å’ß=1#·JTEË%&G0 Ó„¯‡®’&¾3ú0BÂ+h@ô›IùB§—¢ft~zÚ¥¿X–¹µÂ+‰~‡‡k¦¢@æ˜qB�±º~CïÝ DE˜#£§@³;Ã�ÌÔ ûΕ˜ÿŽ-ôLq“.¢UÓ�TÌüù*AsÙ�´ ‰ãj€°þ*Mú~ØŸ�‡låmð1Žœ0,…\DÞ-“EíÑØÓûzàEƒ—K@›É:ƒ�e Èn!ű€<L¥>ÕØû´9)³BˆÈÝDü6j«F@Nxük“JÛˆ}˜áÉU’ÂìmϧjŠ-72Ò‰M�“®.°TåBøXä¶ÏÈû§u$Ô~Ê�dq��^&ѺmRh!Ì6ËQb2�Ÿ÷lä_½dù¸ȱþ«æ)D‰ÉŠ æÞø¨åEñC™À– Ľù`cuáÍYÐë�Å]&z¨†¾T­Û�‹æ_œ‘üõ"Å^N J63czUjhÔik0€›)‰zÇÞ©î ¬aªtì'•O,v 0èÛ,Ó�ÄãÇyY÷€}ýuö^˜A«‚/•mƒ—.kãt¦³Ÿƒ*II; Å3Û[ųvëQÚ„Ñ6ïæcÀ/EÒ·í«n°ã2‰÷:®ǶPÔ\48gL®€¹f#3ì«„l!¿].$³M®þ \ú|K/JˆJžô''ÕqnÖ­ø?DKÛÀU§Qm¾„O»»šTTdƒz|1ÇöðŠÐ G”®ý•àÿgü�€�¡1"‚ Æç�‹š le1v2ª^lõ"êµ^Q}¡U7aðÎÒ’ÉI<Øûü‡åæ¸ÇAkÐÕUÉ�>›Ò÷2÷}s"\MÛo™³Z«ÁÕ|µË Öîb÷‘¶ÂÜsi,N¢+ßÃ9òPÒÐ1Ü®aÿ:~Í<\ðòŠ4®wÏio-Å[sË LÉ &Ã_6ËÜûMU73ÆæüpcjÓ‹7阥\;§jÀGÍÛßOœÁx|6†uÂý@íC¶l­V-ä2Ô@FA®œ Ú)gs¸%ý�ù.¾xW7 CŠÅÚjgÿ¶+/²æ¢V­Â+Òú#»Np…%ÉÔA%j�”áCæÛ>`“™>ÐòÖCoê耊u¡ö�k\:ÕÏҽ·:=Úžž8Óñàu—Í8¸Æ®Õ¡q§±[ÄŽ\›�íþ¶™Ô´�¡eQÅ>DÚƒDpÚ ò@í8q£WO³=’‘I*ã’}¨ì$î2Î7Ú”tœóÑùdpì87ÙŸÜ1MP­òeï#ò (µ[ÒR’@ÃVSH‚vdD~XñÍM» fkÒ‘p¿4æ5er<¶«S›rjCHç?¹:‡ÝºÍ .ÛD9ﰆ o™Ð¼aE°‡£r%«)™Íptr†+™—~ã‘GÞ�¬ˆ”ñº¿´˜¨õÜ9~ËÄ da ¾´&´ÞÆ›~™¶…í°;Œÿ^ G¯ºÃ õ¥B­ºÞÂ+÷ÊËõ6GÔ¤èõŇJª 4Ö�U«ÞEFÖi¬(£7Ä™f)vÙc Ï0CÊ�n�¯ÙæQâ:3� ±Ì%¹úÂáˆ[Líb _<âì`�¡ÎKaÈ`dñpÿ“VXE¼»ô¬#¥¡d"I¢Ä üPT͉�ËÈ<>ƒ�¦äé·Õ¡.”sƒ´ðyì C-Ÿá쩠œdÊúîp<Ž^Û9<™ö6þæ6�RøS&ÈBˆ×G^«l'¾¶³WpÙ5ùªltaÚØé:L~œž½q@…Æï�éû7çðÍ›ÓáÑ!¯¤Ù§èÉu,Øï¿ç¡(Œχ) _—*>„½Ðk‰=uÚ)�à)Ì—¼R‹äTSjÚ…[Ieê_ÙƒžyP·y¸Mï¨3XùÉ+4mtM[GÆ`g ‹›Ú€ˆ6nînYΩ´ìÖI€›À&�•¦8ð TñQ¸Öꃪʊ·Iô'<= YÑ“txBg«ŸL“µúòCËYÞƒ€3&�x}ªÀ¤°ðKÆŠˆ…2µ*ÍÇì¶e±}W)yaÉï ~½« D:öµíûÀUÕ …l»BÐtá&£>‚1ηÀÕ±,“Èì„êÚJâBó™�ѹ٤E9.�óU2ã�s�2 ”"ÕÏ@Lµf!Í2?^©E=ñ³“¬"7¹'¹Ó”+Ó™Ú m““·çQùEe;ׯ;ãyÐå¼ 0&<˜ª³ïlþfGbÓEtmž‰ÍW,Ô"M�{úÞ¢ºöƒÒôXæ-‹ˆêÍ)ÌÔ»äJÅG™�ÿVtûâR¬AZ#: ˜¯€è<`È<ß•=6œ™s8} �k]%t—#ªùC¨A’r¨ÑÅ„+Qgõ¤ditûbH÷S¡Ðd”)ÝÔ²¨ë*®W!É(‡… ¯4�Œ#“…L’¯@=ø2ŸEd¥Z+C˜£ãt Ï‘)¹!ŸÊ)*RË®&UÚ±å ø¾ ”ž‘æÑ˜¾Ø„´v_]¹—óç:yï÷Ùi¦ ÔE©€NT°”%¢…['Ö^±­F —£õ[¡N红LfúPªn£Š`#êøŽ�“¾ •Ò6`Ue½·¹Å©´¡° •º9fY,«0³* †w©Ókm:üXÇå¬�%AÍ î1ÓSšæÐ`¶3.»zLË‚À ? Ü>3Û©C©áAE6°l"+Ðh!dÅ2å¼ÞÊ]hmÔ:h,K²óTäíI𛲹^W‰ß¬rqÝR¤‚“Áέ?óÍž�²2;š �±` îh�¯‚¤LÛ·u Ä^lx¢—½‹ÅZyxð+ÆžF¾]Ynôô[ó¾Gj,ít*òhb·­ïL”pÇÓ-o*@Ûöw æe9RÏãÆCÿÏ~­žõb®,fï&�(†ê6)=@»¬ÛÙTk Í@¾»ŒÛ·»|e3¾ä˜°_Í=›rk{Ô<ŸVüÕ·%7v ÏCˆþS‘Ê;\ä˯òÑÁqZÉ*OÁRYœÜP7Õ¨B ýÆæÝ­­@¬ãõ È"‘5°Ä×+u.GF§aæP¾ “AÃ^²:ŸÔu©uµ“dpû%.!pmEq%A)møï*‡ Ôé`2uŽ'—‡£Ái{_Ýq°yTGs‹§Š Z—ÈÉ`ªüަÛÕSHR£pÿ~ö­Hü™/ÒlËØRôE-tSw��Û€»ðÏ$ùv0ž¼wFƒŸ�³±3LÏÇ£v†¬«/§,÷ªsg}B„Úâ µ9StmÙÇÿJ§ãó�ÑxËq<<‹a¦Å‘ˆÆ£›N1ÜêèÃ~&„‰ŽßºWM§ÁÏFË¿wMY¤þo¢Í÷ëûM¦c««©6#‹t¸ló.{¶_7ø4Á|¹Ó=½ÚaãY«ŠÌüÝ(V|³�«-"Ü(ÃÚNuÒÜYœ¤é‡[D™Ér€(Æ­À¾ÉæFÒ|þ×�æ÷˜4�†jjƒ¬?P¢®2#bèÿ�ã‰y®óIq/øîùˆ7rä4ñmµ¶:à†“±±R¤[©Þj�ñó›:ô<t···ûÝþøNnÕáñ—ú@½UiÕë¨ÃÓqqXF3•$‘íã�ž$³‹ƒfÆhƉ€—ì2ÂQ8g︰sѧ~’^Šp2UÙÂü,¿‡€Ó0 Óž3.¹ë�þ~:™�FÜö§µk–�%É-ëvO€öÜÔ’ïÝ }KÀ<Pµšrh§Ìǯ|¦¿r!Ã$†Cû!X$PͧªµcRq™ÌaÆ.!NÌ%:; %hLø{Ÿ§ Âüd–F!‹}*TS½O®…P€ßrÉH1g@�@26�©Üè©R©¡�[›Žs*!‹—"TaT?æ"5e(ƒ•f„(^$ï°4Ðj’3­+Ô¤J¯”œæ;R}Ï«m.·SG°ßÔ3À…IʳDÃbÅ}]±ÌîR©Òª'ií–¼:Îê&>‹°-§<.jªgþh6#=ù©™cT+*àÆ®Ô€s8³+Ίš²—-&gð¤zúdŠÊÁû|¾ +G†4ÆpXmض²[nX©ÑIg=U° z’¯ß¢©Yüäzíþ™çú�Þ™¥‡èSͼª6Ö#úÐXtž" ²¾‘ ê¶‘ê4£ë( ±�êÇêZ�¡Hb2ù; ’+½†TtO¼sÇuÛgŽÛ¬”»8o†Äë§oËÇPklxæ`ƒì9]«¶Žì+![°0Òí;s~»2¨�6`ü°2È6²§« a8`oå<^#s–ר ²7Õ¶Q¯ßT5¤eØ ¶aëvi¢¬ø{EÂ3¦l8u^ô�“öÀ9ÕG‚ª‹­Ì-ÎQ?n¢çœ&êv’¢7×÷-xÕ¸Œëöü—ÑùÇüèNùf öòM;d8x«oFßÏ«f“hÇ�ž@õñ䀒(cgVà|Ù�5“Ùwû¼-îgA.¼� ¹è†WØ–ZÈŽc¬M<²óâAe]·°²2QÓQÓ$ødj˜—uh~©O~Ÿ„Ü'‘äÓ󆽡ëœz¬›©1Xî«•ì”ÆZÏ¡’¹j ý2ƒ³h9+SîKû¾뾬½1Ï�>;É×-"Ä®n!úTË(†àj.bÀjΑü£ÈSDoC­ùúgÃT$ h꛸]j³úeB¢“V¡�ÅZ,oÖõm·`Â*Ž!h>1 )x»uÔ)¦`—È™ëÙ�Lº8iÈÛ‰�UÚd=]ÃC`QGYmSÛr¨ˆ¾à2ÍÇhþ%Îyu¼¾‰ެeèŠ`}‹¿pðððû¸�ß¼´¾2ö ‘\ú�ŒþİñèÅ·Ì‘±Hf*ÖÚÖÐ6ÆaªË©g×%¦…U —Ø�'jŠ>u=à]i#ñ¶c >66?Þ‚†®F–aOŽ£X|åäLÐ;Ê‚°!n�q8ÈX½Õ”;rÐ|§rç>q}ð–Ò²œ{ÅzÜÚFÂÝQQžƒÇþj|ÝÔ@˜Ïé�y*’‰`³Ï{ÉY¼Ë™u¼S(Óà>Í…àºWt]&$·Ì>Û(îöç§Žç¼ôÛôè"ê™^­êÆžó )Iö¢^«¿æmÊf¯ÙÍ´¾,À@ÚyÕxÏÚ�î°ïl4›,•[ä—$BÅFéœQ�?K‹¬ÛfBµíu†(™è…>4á ÃÀA ~Ìt‡''ŽëÂÑ‹ÏU¥ü«Ìâwq²Œ ‰µÙš¶mÕŠíºSRtNÿ£ˆ'«—‡ëã¼ÚŸ«~~šm~7~úé�¹3(W…À!f>^ÿ/ònÅ€òýo7yå.°7xœÅ’1oã0 …wý š±�[x¹¡íž¶Ë]/QlÚ&"‹EÇ—_9qšt¸:uù=RïI3xäk8XW ù*\k÷(Æü6 %ö رh€Öú#t¬è•¬§E5yR: E*áíJiOÀé\±×‚}E5Ìd¡°$¼HaM¾@Ђî5P‰†{�È«°[i¼Qƒ®«zÊú® aÒ�×þúóã55æ‰AzisÓNŠ‹]”X®Ìv»5ï™ü6IrEó¸ÊMÕ‹(ò4;[KÇê?�Ú:Gý SÉ5™+½ëÉ•Y�"»¼ÓxY3›Áz«ã{Þ„Š†“û¹à¢>Sø�YŽÁ¬Ì|7<$‚¶hìÎa~Ä05P„%qÔ’æž§bÜùÑ£ýÆ£%®“1”Üã°¹•¦±óYÈã?ZA£Ú…U– ´§tø;PY£†”¥Î^¦ì6ëëÔÍ}\µyäAÙ¼¹¹4Ò£r³w üäxxEšb·—»Rwlmaker-0.8.1/.git/objects/pack/pack-1ea372b300770a0afce4787817459a62b797bb52.idx0000444000175100017510000002522415222270710024641 0ustar runnerrunnerÿtOc  !$&'***+-004557:::<<=@BBCDFHKMMMPQRUWWWXYY[\]```cfiikmnpsvvyz{||~��ƒ„‡ŠŠ�Ž�‘’’”•––—ššœžžžž  ¡¢¦¨ª­®®®±µ¸¹»½ÀÁÃÄÆÆÇÈÈÉÉÉÉÊÊÌÍÑÑÒÓÓÕ×ÙÚÜÞßâââåèèéëîîðòòòôøùùùùúûüþ    $$%&*,-..//13668:>>?AACCDDFHIKLOQQQRRWXYZ[]]]_è« ½1Ϭ² Å5wj³L[Óž#<þ¿JJßvƒÈOöæµ,×}·k³ô`Ú—‚Vâ`liCU‘g­ÃÕr3ÀL5Ó+C^Ej9E•nD‘¸žÊcþàôQ¾>£NVŽ¿;‰ø”Ûÿ·—÷™;ÀÃT’yé†\ƒrÖÏD»›U—9€‹¬Sú…ƒÐbý¼,ûòv9]õ•°o¸èˆð×ñÛu~PK–½G‹ßLØ�E��Ï F)Œ¦a=‹ï�YÆÌÔþ„AÙÇ1æÆ(qÑ]ZQ@ˆ¨Û;C%öV¿‘‘¹F*ˆ_ÊúÐJhÁ]ð"dUª*jøjm� ­,én‘ÓcÃã#¤záy“™ø9Êçð,D»Of9K#¥@Šó¸~sÚèß;Q•&“IÁÍ$|"U‘¾×*18ÑšcD€$R<•öBÁ̵Á3LKÅ–¼öù$ ñ†VŠy(�HÒÀ³¹úF&1!˜Ò§:WF ÎD *4—þS&¶ÔPà ”˜ð»}Þx?æßOÉ¿š&Ì‹?4¯ý:¶Wœ#‰¶W€E&؇Ö/hÞíͳzg…ÿî.:tγ߈ ìèÝ+.V‰±¼–û>Ͻp~pæ‡ahd ØÕ²ÖÿåÜ>øAÑòš^=~ÍsæõqŽ3ÌæÙ?Ú´‚¬—ß~fJÉþéô¹,‰ ?ù|ñ�9þ|�y¶Ê•?ÿ“Bé§BWîtrê^·y\ 6†¦b‹ß¬C�$ຠ®ü¼�.Ãc˜2r¾QE�d¦jÓ�8üë·aÁŽ—ÍE¶z{­=¡éå8HShÅrÇWäF£òüé¤h¶ ÑøÀ´Þø™0Þä]GÓ/Τ{¹6Ø)® »‚Ì8×–OH>ÎØÑ]iN™NEìßS¸ñ>ÎúT!F°Î« ž©ü–=°K\Í\jcŸ=›óOëÕ+ƒ--~KŒO@ ÛçñM~¤«‘Ü‘�J�òK¸M�9õŠgü.eæÚ€~�›2WLQ�=i;<áôþ<`\dÖê†LR„�ájøë‚Ò\¨�ä>câYŸLúÄ hKK­ïOü×Àf$âM+h@þ<Õ²ù…o’ÓpÄ¢v£M,61Y‹Ã¶¶Ô8Füó @�M¶´§0&ÔUºl�û'n ›ÉaOezËe“ÓÈ ÿZNz?ü'O -á6"LÀ7ÚDíu8ùå$¹êPpÁJ¨hû)³ú�H%:eS‹½PEº�E:ÈéÁI××”IZµ®~®QøñCKh—�Þÿ•ZôâÙ5 ÇRÚ¨|„›íá…�Èù,††)RÕóß…© ˆÉMÍUo'Aš,ßÇS ¹=Ùm‰P©Yùa³†È¦nèLSƒ§yéŠG —4h—ÒYJ/¸Ö?Sµ/3²‰�J,ì¬k 9œyP"WVú’ãL„n…@C>ªÜ~ÀÇEÔäWÕÐæ}þ"ŒôêùÔêW{Í7� †XIÍÊóŸ|N€‘Œu´Yà�ñYYŒßƒËîÍ×°WT©8?LÚX»°[^àúBwé©7$Ó1ý<&9�&>[¹ÑÕQÔ©ZÞ•#»Ÿ%A£7ÛÈ\þ­&WKk6¼µ7d²hs&õ T]7ò9JÅÿNfÉX¬k¾œÔ¿ø]×*¼p#Öb ǹ>µ–æj¥_.®DG9’�ÆcÏéOšÃA6É_—&#ñQýZ¡•£ye¿3=¨Ðg`ªÈ1)~«ø™iæþ7¥ôíOˆ¢BL ûä�EZíª”ÙH5z]™©N=†ýß)žs8Ÿƒ+*{ç-|‡p®\o˱ÈÑw®³2¶{êåøí€œ<«Žk‰´„†åù&ò| 8?^€ ×ÖÃa…ŸfaÍq|;jtô÷ƒ%ÛÔ†û'�Ú§»}K ߯R¥íŽ7õ#�p;Q}RÖéÓʹ Â46ß[eíaË� }ÞBf'ϼ*…'=`óü²-9¯~23ŠÅ€í©9 ;Û1‰QÚ®�� Rj£ß>¼Ï4Ô;€€Ò¸�sÌëA�P;±ø{ycFµ�º>‚]Þ!™ªiÐL÷B‹ˆâ‡¿‚7‰›‰G:Î@eÖQ­: dñû‚kS:þ«÷Aµ›O¸-�=[q V‚Ž9¢Ð&/‡*ˆ½¨@”—Søª‚½X‹Xr˜ê9‰á?w΄ÕãçØƒ á}Ø;vh»¦\Æ1ôùT1«Zƒ ùM½¸l„þ}äá" Šƒéüþ~Ÿn½/òZ9½MÀ†o¨,„$]IPÎm7N“k³²†xÈéVJ…]H(W=’9Ø�9£âã[,üug�…Þzÿ2U ¤:;"¦áHŽGq³†? ¯h[äXLKPó?åžßð†–Z"ìÒ:°½»þ ªÏ–Esõ8§‡CÛKÿo�×¾�Ââ¢U?ñ©‡\’$GaÓ+›ãY¡õ¬2[ý/õ�ZÒLŽ,>óž}óBÉÏ yqê+µ�Ù: ßgéÎèïš¾,àQüЧŸ”Yœ:èC_‰×+bòn›æ–3ZkØ¢zýÍ�¨9¶E é¨í–LönðúçÙ‚ Ü‰^¼þj×—$ìÊm†M/^�yVÎÅH°ü瘫1‡Ê¶ªw3H­ ýª&˜»×/úxY6�UëÂiL+ ǘŘå9ècß\€DA R{¶!sH˜èaT†À-QbÜjåa88cÖš›Õgc¦%O�yvxÕ9hôlz›eb-޳ ¨®:Ml?BÆIêQ�Eûíâr£¹óøöÛ‘Š”¡fß�úÅ‚í[c™‡Èíó„RSÅ!Ü£ž`�%™åDmp3Wéüéî~„ˆž·š.Ìë“êg&�mvÏ÷nqJŸQr0šrè=DýùU»0)\@Ý›Ÿ~g£'ë»®0mÞLâ>MYŠ… â˜³«%üêüÐ oΉ)îCç K¡'^�_³½�¢dµÚ‘.ˆ"Y¡SD“;R~\ÛìCoÐT…,,Àó¢§ÁzãÕi²)ö;俌X|¢D�Ê8ž3t]øß³Lr£ñ¨‡�ãÀ‘R ]¨<š j Üí¤+þ?©XÁ…Þb¬¬÷8 %ߤ‹¼£·ôxtï´iÀZ@§%Ä,y¤Ç/(h÷S°±ìÈ_°-~º â§Šh­Í[éƒ œ�O²™Î#\§x€¶ªü Ž~‹º“ð¬7 {²½V§¼h Eݤ†Ìƒw‚kip¨0V®Gþh··YÎaèýòÒwä¨ÐP'{XÀlÅN—9´3 )aÖϨä‰,êC¯yjÿößúaBߪ‹õÉ»pßyUÊã< ô6¼Œ×Møï6‡Ê$çQ£Ñúý¸ÿœ½¦aMëç[—ŒŸC#IÛ&<’⤽¹×¢‰£Ýây©¤yk•÷¸Ó¿¬afyBB˜xlPëâbûÖ¿´ŸnÓâ¿è)ƒ¸ð�Ê1§ý›ýÀ dÔ¬�¨ÍWãXëJ$"g1Û*ÀìDå5hÛú?[ì€üw#ÐÁp�?º­˜Ž"Bó—¥4eþÃQú½Ž²:{“ ÝR ÜïýÃzy†mª^$ òjX¨7anÞ¨µ„OH-Ç¥{6î]bdœµÄ¼P«c§”¬û2µD|Û—‰Ì:pŶ�;—‚HåS9„À"·hðÎ2�¶ëï¢ÿmxÕ'ˆl•‰ÿçhZÎôK8±:öóy®®CVŠQ*#ÏŽúä‹,:/Ç©âZAµððÐ�—šáBˆ)Öòh·`ݾÒOÐŽ Ç¡F�Çp¥v¾4"~ZÂØÐªMkÃοãD6,C�VI/3MÑ„›½Ÿ‰”SÈÅÒ+äý 2HÏÕÒT•»�Y^`—¥Ðm¥Ãdó@Ò}º–e«š<ËÂÆŽùíØëƒÒµ ‘ó@ ðˆ)a†4,«ÚÒØÏ2_ÿ™×Çøb8*÷$ Tñ8ºÔ+h*TÆ•(x]ÞÀ˜é¤©-ÕuíßZ¦/pÐ÷–/7B¾J¥MÖ/Õ�Ñ‚G¥¸ s•Ã)p™£€Ö¨7²VÎDÞ<¼†±ÛƱ½ÖÃÝ$àÂV颢åðŸYÈÉ`:‡Öât³Ü»”ácó˜é9rc©Æ×(ë……Øs­ °ËéË\¿f­*×±¿i¯ÌQ3ÿY[�bNËæyØ9EêÛE¡‡;Ò}÷Å”å�l 5ÙÂ.JEÞFêJ[jßÌɽ² CWÛMQ[Yz@œÌïO¸‘t¼^r&ݲ¢Ê§ o‡‡Ýð¥S¡I!z.„Ýð~Zîo²E{øžkàR©Ø&Þé@3¹Ø0Q |@Ç”#ºÏM£ÞÖ­ x“¶À‹ðïcU¢,x–½ÿß08}A¶E\ÄÊ+®uÁ(`i�ß™üxLb%KÔx¾©f?)ÞÅßõ´´b^†ôZLŽORiNAµuáV˜wK±}¤\N~Y—CÈ+è;᧬EÌ5!� §îÅׇ*e^¼ÝVâ}:ò‰tøöiµÝ- d_´Ý[âo_»xÄ3\+މðþMÇa’ã$08€këbÂn‚¿ÀåNo}ãŒð³}øeÔIâ/ACôQãá= £~)‡K™f4$nõ dÐãöÎht_»9 íPˆd%]�`ïŸÐå;OSá%䟉cRÄ>šŸ¿=€Ðæeß";½9-¾D†cÃ^k$ cæ�⛲ÑÖCK‹)®wZØÂäŒS‘èYkÞ7¸àS0¸N²³�œÕ }Zôèq�çÕ×*è¸ë0ÁÇÿÞ:ˆHr êfRÓÆ'Ÿ–)Âp<³Ié:t¼ìÙÂß¶˜¤ÖÂõúç@€+̲ì£ô\Ž#'š¥NóŒ/L÷]�ëí› ãˆ÷O,×â`QL÷ Ö,^í|‹¡½·ñ#F*6é©‹î÷Bkx·'Žg l‡Zé–Šï/úÕØLŠÂèˆ1òD�ÑÂ/9ZïY¢x^6çw$ì±Ar�Fjw|ÕöðEo‹7cÎ<ùR±SøÔÃ:o%ñ œçnCÙ°wx0?ÊÎ(ñ¤×+0Dôž`¨ÏÙñ?í¶€Ò½ÿÌÐ85Œi´8"y¯»Ì¸ÿ´u>6Rwí¾5$¾ØÃ½~¾¸ÞfÈ‹Û�g[úIW?º¸1Íéè[°·úÒ¾þŸ1ß½Œ`j§Þý®œå=kÊ%þžfÂî 7”\†D~±~hêö˜®NÓôÿù¶Åoð’ü&üfå "—¥}¤hîù3¹�2_$ðÉQî`Š3ûãZòÂ~HJPnF+Ý�.y'DáZôƒ¹öàuö›Ð9ó”¶8†âkv= ô ˆG>�jtzs„ûþ2ÞODm­€ç)›é\uÈâÍjˆ[È·6œ¨ƒ­¿oB‰–Ï’ÛÁuû—3Ä”dúõ1ƒ ¾S"¿6iÍX k…ñ’ ^h¿ƒ=7®ï3%)+ÿëb¯IŠ4œfûw\šRÆ©Ÿ>GŽ/Óø‡‚î|–oÖ–ÛŠ«7ËZÃvØ5ð…Ž…ƒÊ�3¿G� ã™�!÷g.¯d¿N,QÈÞ^ï£zk¦·d¸µ��û;âo#¹ÚMñžmñÊ�)m´‰àwÆ*3'ïìÎì!aDƒ†Âø\䲉Ìb&¹²¿.h¶èTpŒIõ ß¤B’ÕS†U3ƒDºûç‰G+*ÆL-*�Ùï– ½H¨áÊÖÍŸ]†Y‚ ý›t’š}rwp8�~˜Ù‹U•€Û©èf—6ÔiÉŠýˆºûcxs­à°T­Q>ä>Õ†¢~ŒëVYÝ3�ç&¿ÊûÁt¶{°@1Ì“ÄJÎ¥¶ŠHqH9Å«/Xf$T€¬.ó#(cWÝ»DÏÎvüƒÊ[ý ‚-÷X >òÚpÝ¢ÃX7åpò¼VQþAXúù·'�:DT*»ÇÄ{†ÙÙ ø%%¢ÇÌŠUÛX˜ˆõ|ØÃ]„6o?&á¶¶a/jWÒ‚ûnýù¶õ¶ –¸™ãéëY²*°Æ¹û Y޳áÒTu‹2¦i•Ž”jKŠgS;ZiŽX¼ ØMÕÌÙ£ ÅUù´ç�“= ©ýè‰| ¥L�Íùk#¼RtŠüð©J6l|ˆ_”J,JT£]|˜7s×S—Ó¹T^ÂS�“{<ô.¢À^9ÉÖ[ÅCKi¬’Ú!�T�E•5v¢±‡wõôÙ¾Ò €iÐVrŠ\îéAláúçI¹† gtÓXeJØÙ}ñðSOgr[1¸yŽx f”CKõcöQ|F½¿CkòË.Y‰Ëè ú[X ™ÿ.,Ÿ²ZwKwTy¾Dcþ£¦%A™–¡ÊÅž:Þ¾®›i¤wõIëw0ë!UéXZ“ ÄF™…" ®.žתžO…Äïp!ŒÜäÄ7푎+îþI‰gov'´ˆ¶—î^ÞÓÔÛƒ{Å&hå zÞ�¨=÷RUÔï÷ÚÁجÄÿEñÆNdA˜@ K Ø¤×…w­òÄÁm^µf—ìSñƒé¼hìï`³ wäi� ¶ƒq0DºÖÒ°<.°ºp›/Ü3Ô –×LÈÁøûp*?+‚ëYiP‡ÿ¡|ˆ0\rï¿é3ì–Q¥'2–gDÀɛޢ±Õ—“"¬1ÈHëdÙÂ0›h~'4ÙÏ«Ø:ÛEYâ #U…RœçE¿Vößj€c­œÓ»,Q¤ixÎþI$¢ïÛ¹Ÿµ4æŸlŒ�Xß•Œxè�ø™üé�åÑœ€õ]tñçæhÕ�#�ª´½€”j*Ÿ'S½øÁÀ5¸ß“0ˆQmçF�Ûö~‚õÚêì ,nv)¼µLáæ+××—¨´£k­j?�6g»ê‡b—Dö˜Ô½^9ø­ÜKJ(Û‡�·1ç¹üKŠ�Rü‘) ÕŸe†&á2\Å™ÅÎwŒÐQ¬¤u$—uY¥´�Ü‘�…ˆ‚9'IÐT©ä,QWU¾Q…ÅÍÓÊ‚1ãHë#l… t3ôgØò j� BlÓ< 7„jbž¹Rÿe• [ek|v +Ù®? ˜ uÉp÷æ.¨ë Re ”_ýi 0÷µ¾­6'Ø@ài5§±¼,1‰y‘¶x…è%ÃÚ� ¥&Û‰a7±¶ž ¨Š –wnH Zoòýd…Û)·Ö ™p;RŸ@è aY—žÜ¸�MÞq ªœŽ27°å”&ºÖºÛëQ �ª%RÀ+ 7  §SÈUÑF ŸÂ …ÞaY¥›«¹Å ˆžŠÙX±� Š´ù) mµ ¯B¢œ!ê žÔ ÊÄ"f  ¢ê¼~1 Ùîî­öÃ-90R…* n}Æ_}*L,(v[k@… ý •,Ü»ï)ó!  Ë 7Š"e vŠp²Çs›(–‰,4iÛÌÜ 3ò¶èÓ ¶\|÷·Š« ä8jê ¬.pK›,ž gɵ CU O\PœØ ’Wpœ%U#Të#ÆÄ‚Þ((kš as ë(ž•1ìþéb  »Á˜QŽt`çä±LJf| ˜}n5îæ'ä@+À¡… [­  =xD îiAGûÓžvñØs2ÓA«–¹ŒK´ ÷Òö:¤Öž-Š#ÔþyN�â1äU¢ †«Œù‹&k¶ì xý[ý¢�eÓ  @¬Ã ™Ì ; Èc1¼d}Çâ nõ!vÙóìãžý¥Zã[�Œ*Œ¼æ£,’ù"œ —•òŠL üŽ9aúžû¿ a? o0Tc ¨Û 3ý“ ®`Z~ " ïS (2òL…Cxû$߯4 ²c ; -LwS7\ mÛ(›M³ž­#u�™2Îø s n¾iȺƒTÓQú§pŠY o'3 šY v¢‰Ÿ’êqp¯^Ú–ç 'Ìó ]Ö U¡úÚ €ø ˜å]µ —³< ì -’G Uç£íæ­ |Ñ긒Œ£r³w üäxxEšb·—»Rì«­ú[€B@¾¸U˜qÝüŽ4)wlmaker-0.8.1/.git/FETCH_HEAD0000644000175100017510000000017415222270710014766 0ustar runnerrunnerd08e0c08c7a14690c770a576be341f227e5ac2d8 'd08e0c08c7a14690c770a576be341f227e5ac2d8' of https://github.com/phkaeser/wlmaker wlmaker-0.8.1/.git/refs/0000755000175100017510000000000015222270707014374 5ustar runnerrunnerwlmaker-0.8.1/.git/refs/tags/0000755000175100017510000000000015222270710015324 5ustar runnerrunnerwlmaker-0.8.1/.git/refs/tags/v0.8.10000644000175100017510000000005115222270710016075 0ustar runnerrunnerd08e0c08c7a14690c770a576be341f227e5ac2d8 wlmaker-0.8.1/.git/refs/heads/0000755000175100017510000000000015222270707015460 5ustar runnerrunnerwlmaker-0.8.1/libcairo-fontconfig.supp0000644000175100017510000000206515222270710017420 0ustar runnerrunner{ Memcheck:Leak match-leak-kinds: definite fun:malloc obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 fun:FcPatternDuplicate obj:/usr/lib/x86_64-linux-gnu/libcairo.so.2.11600.0 obj:/usr/lib/x86_64-linux-gnu/libcairo.so.2.11600.0 fun:cairo_toy_font_face_create fun:cairo_select_font_face fun:* } { Memcheck:Leak match-leak-kinds: definite fun:malloc obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 obj:/usr/lib/x86_64-linux-gnu/libexpat.so.1.8.10 obj:/usr/lib/x86_64-linux-gnu/libexpat.so.1.8.10 obj:/usr/lib/x86_64-linux-gnu/libexpat.so.1.8.10 fun:XML_ParseBuffer obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 obj:/usr/lib/x86_64-linux-gnu/libfontconfig.so.1.12.0 } wlmaker-0.8.1/protocols/0000755000175100017510000000000015222270710014612 5ustar runnerrunnerwlmaker-0.8.1/protocols/ext-input-observation-v1.xml0000644000175100017510000000755415222270710022161 0ustar runnerrunner Copyright 2024 Google LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This interface permits a privileged clients to register surfaces for observing user input, surface does not have the input device's focus. Destroys the Input Observation Manager. Creates an observation instance for this pointer's position relative to `surface`. Permits observing the position of input devices with respect to the associated surface. Status of input devices will be shared only when the surface is mapped. Destructor. Reports the input device's position relative to the surface. If the compositor displays the surface more than once, it may share the input device's position relative to each instance of the surface, identified by the `instance` argument. The compositor will issue a call for each displayed instance, and the client may determine whether to act on any or all of the calls. The position is reported relative to the surface's edge vectors, and irrespective of whether the surface has input focus or whether the device's position is in- or outside of the surface area. The relative positions will be reported as signed 16:16 fixpoint values. Positions within the surface will also be reported, with values in the interval [0, 1) for both relative_x and relative_y. wlmaker-0.8.1/protocols/wlmaker-icon-unstable-v1.xml0000644000175100017510000000636715222270710022077 0ustar runnerrunner Copyright 2023 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. This interface permits clients to register an icon surface for a toplevel. Compositors can use this to present the toplevel in iconified state. Destroys the Toplevel Icon Manager. Creates a new icon object associated with the given XDG toplevel. This interface permits clients to configure a surface representing the toplevel in iconified state. Destroys the Toplevel Icon. Acknowledges configuration sequence. Suggests size for the icon surface. After creating the toplevel icon, the client is required to commit a surface with a NULL buffer. This will trigger the `configure` event, informing the client of the recommended icon size. The client may chose a different icon size. The compositor may chose to scale icons of non-recommended size as desired. Once received, the client must send an `ack_configure` with the serial. Once done, the client may proceed committring surfaces with attached buffers. wlmaker-0.8.1/protocols/CMakeLists.txt0000644000175100017510000000317515222270710017360 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED IMPORTED_TARGET wayland-protocols>=1.32) pkg_check_modules(WAYLAND_SERVER REQUIRED IMPORTED_TARGET wayland-server>=1.22.0) pkg_get_variable(wayland_protocol_dir wayland-protocols pkgdatadir) include(WaylandProtocol) add_library(wlmaker_protocols INTERFACE) waylandprotocol_add( wlmaker_protocols BASE_NAME wlmaker-icon-unstable-v1 PROTOCOL_FILE "wlmaker-icon-unstable-v1.xml" SIDE server) waylandprotocol_add( wlmaker_protocols BASE_NAME ext-input-observation-v1 PROTOCOL_FILE "ext-input-observation-v1.xml" SIDE server) # Needing XDG Shell, since the toplevel icon protocol refers to it. waylandprotocol_add( wlmaker_protocols BASE_NAME xdg-shell PROTOCOL_FILE "${wayland_protocol_dir}/stable/xdg-shell/xdg-shell.xml" SIDE server) target_compile_options( wlmaker_protocols INTERFACE "${WAYLAND_SERVER_CFLAGS}" "${WAYLAND_SERVER_CFLAGS_OTHER}" "${WAYLAND_PROTOCOLS_CFLAGS}" "${WAYLAND_PROTOCOLS_CFLAGS_OTHER}") wlmaker-0.8.1/submodules/0000755000175100017510000000000015222270710014750 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/0000755000175100017510000000000015222270712016353 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/STYLE.md0000644000175100017510000000547715222270712017612 0ustar runnerrunner# CMake Style Guidelines This document outlines the style conventions used in the CMake configuration files for this project. ## 1. Command and Function Casing * **Lowercase commands:** All built-in CMake commands and functions are written in lowercase (e.g., `cmake_minimum_required`, `set`, `add_executable`, `target_link_libraries`, `install`). ## 2. Indentation, Control Flow, and Line Wrapping * **Indentation:** 2-space indentation is used for commands inside control blocks (like `if` / `endif`). * **Control Flow:** Closing commands (e.g., `endif()`, `endforeach()`, `endmacro()`, `endfunction()`) must not repeat the condition or arguments of the opening command (e.g., use `endif()` instead of `endif(config_DEBUG)`). * **Argument Wrapping:** * Short, simple commands are kept on a single line (e.g., `set(CMAKE_C_STANDARD 11)`). * Long commands or those with multiple arguments/keywords are wrapped, with each argument placed on a new line and indented by 2 spaces relative to the command (e.g., `add_executable`, `target_include_directories`). * The closing parenthesis `)` is either placed on the same line as the last argument or on its own line aligned with the command name (usually in block-style commands like `install` or `set_target_properties`). ## 3. Naming Conventions * **Local and private variables:** Written in uppercase in legacy project code (e.g., `PUBLIC_HEADER_FILES`, `SOURCES`, `DOXYGEN_IN`), but official guidance recommends using **lowercase** or **snake_case** (e.g., `public_header_files`, `sources`) to distinguish them from CMake's built-in variables. * **Options/Cache variables:** Formatted using either a lowercase/mixed-case prefix (e.g., `config_DEBUG`, `config_WERROR`) or full uppercase (e.g., `IWYU_MODE`). ## 4. Quoting * **Variable expansion and paths:** Double quotes are used for file paths, generator expressions, and strings containing variable expansion (e.g., `"${CMAKE_CURRENT_BINARY_DIR}/analyzer.c"`, `"${CURSES_INCLUDE_DIRS}"`). * **Identifiers and keywords:** Unquoted for target names, libraries, command keywords, and constant literals (e.g., `libbase`, `STATIC`, `PUBLIC`, `FATAL_ERROR`). ## 5. File Header and Comments * **Headers:** Every CMake file begins with a standardized Apache 2.0 license and copyright header. * **Comments:** Standard `#` comments are placed above the relevant block or line. Inline comments are generally lowercase-first or capitalized, providing short descriptions. ## 6. Target-Based Configuration * **Prefer target-based configuration:** Define compile options, compile definitions, include directories, and link libraries on specific targets using `target_*` commands (e.g., `target_include_directories`, `target_compile_options`, `target_compile_definitions`, `target_link_libraries`) rather than modifying global/directory-wide settings (e.g., `add_compile_options`). wlmaker-0.8.1/submodules/libbase/README.md0000644000175100017510000000134415222270712017634 0ustar runnerrunner# phkaeser libbase A small "standard library" for C, used for my personal projects. ### To configure ```bash cmake [-DCMAKE_INSTALL_PREFIX:PATH="${HOME}/.local"] -S . -B build ``` ### To build ```bash (cd build && make) ``` ### To install ```bash (cd build && make install) ``` ## Contributing See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details, and [code of conduct](CODE_OF_CONDUCT.md) for more. See the [idea backlog](BACKLOG.md) for inspiration. ## License Apache 2.0; see [`LICENSE`](LICENSE) for details. ## Disclaimer This project is not an official Google project. It is not supported by Google and Google specifically disclaims all warranties as to its quality, merchantability, or fitness for a particular purpose. wlmaker-0.8.1/submodules/libbase/src/0000755000175100017510000000000015222270712017142 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/src/dynbuf.c0000644000175100017510000003743115222270712020605 0ustar runnerrunner/* ========================================================================= */ /** * @file dynbuf.c * * @copyright * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "libbase/dynbuf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool bs_dynbuf_init( bs_dynbuf_t *dynbuf_ptr, size_t initial_capacity, size_t max_capacity) { *dynbuf_ptr = (bs_dynbuf_t){}; if (0 >= initial_capacity || initial_capacity > max_capacity || 0 >= max_capacity) { return false; } *dynbuf_ptr = (bs_dynbuf_t){ .data_ptr = logged_calloc(1, initial_capacity), .capacity = initial_capacity, .max_capacity = max_capacity, .unmanaged = false }; if (NULL == dynbuf_ptr->data_ptr) return false; return true; } /* ------------------------------------------------------------------------- */ void bs_dynbuf_init_unmanaged( bs_dynbuf_t *dynbuf_ptr, void *data_ptr, size_t capacity) { *dynbuf_ptr = (bs_dynbuf_t){ .data_ptr = data_ptr, .length = 0, .capacity = capacity, .max_capacity = capacity, .unmanaged = true }; } /* ------------------------------------------------------------------------- */ void bs_dynbuf_fini(bs_dynbuf_t *dynbuf_ptr) { if (NULL != dynbuf_ptr->data_ptr) { if (!dynbuf_ptr->unmanaged) free(dynbuf_ptr->data_ptr); dynbuf_ptr->data_ptr = NULL; } dynbuf_ptr->unmanaged = false; dynbuf_ptr->capacity = 0; } /* ------------------------------------------------------------------------- */ bs_dynbuf_t *bs_dynbuf_create( size_t initial_capacity, size_t max_capacity) { bs_dynbuf_t *dynbuf_ptr = logged_malloc(sizeof(bs_dynbuf_t)); if (NULL == dynbuf_ptr) return NULL; if (bs_dynbuf_init(dynbuf_ptr, initial_capacity, max_capacity)) { return dynbuf_ptr; } bs_dynbuf_destroy(dynbuf_ptr); return NULL; } /* ------------------------------------------------------------------------- */ void bs_dynbuf_destroy(bs_dynbuf_t *dynbuf_ptr) { bs_dynbuf_fini(dynbuf_ptr); free(dynbuf_ptr); } /* ------------------------------------------------------------------------- */ bool bs_dynbuf_grow(bs_dynbuf_t *dynbuf_ptr) { size_t new_capacity = dynbuf_ptr->max_capacity; if (dynbuf_ptr->capacity <= dynbuf_ptr->max_capacity >> 1) { new_capacity = dynbuf_ptr->capacity << 1; } if (dynbuf_ptr->capacity == new_capacity) return false; void *new_data_ptr = realloc(dynbuf_ptr->data_ptr, new_capacity); if (NULL == new_data_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)", dynbuf_ptr->data_ptr, new_capacity); return false; } dynbuf_ptr->data_ptr = new_data_ptr; dynbuf_ptr->capacity = new_capacity; return true; } /* ------------------------------------------------------------------------- */ bool bs_dynbuf_full(bs_dynbuf_t *dynbuf_ptr) { return dynbuf_ptr->length >= dynbuf_ptr->capacity; } /* ------------------------------------------------------------------------- */ void bs_dynbuf_clear(bs_dynbuf_t *dynbuf_ptr) { dynbuf_ptr->length = 0; } /* ------------------------------------------------------------------------- */ int bs_dynbuf_read(bs_dynbuf_t *dynbuf_ptr, int fd) { if (bs_dynbuf_full(dynbuf_ptr)) { if (!bs_dynbuf_grow(dynbuf_ptr)) return -1; } BS_ASSERT(dynbuf_ptr->capacity > dynbuf_ptr->length); ssize_t read_bytes = read( fd, (uint8_t*)dynbuf_ptr->data_ptr + dynbuf_ptr->length, dynbuf_ptr->capacity - dynbuf_ptr->length); if (0 > read_bytes) { if (EAGAIN == errno || EWOULDBLOCK == errno) return 1; bs_log(BS_ERROR | BS_ERRNO, "Failed read(%d, %p, %zu)", fd, (uint8_t*)dynbuf_ptr->data_ptr + dynbuf_ptr->length, dynbuf_ptr->capacity - dynbuf_ptr->length); return -1; } else if (0 == read_bytes) { return 0; } dynbuf_ptr->length += read_bytes; return bs_dynbuf_read(dynbuf_ptr, fd); } /* ------------------------------------------------------------------------- */ bool bs_dynbuf_write_file( bs_dynbuf_t *dynbuf_ptr, const char *fname_ptr, int mode) { struct stat sbuf; // Create a temporary file in the target's directory. char *fname_copy_ptr = logged_strdup(fname_ptr); if (NULL == fname_copy_ptr) return false; char *dir = dirname(fname_copy_ptr); if (0 != stat(dir, &sbuf)) { if (ENOENT != errno) { bs_log(BS_ERROR | BS_ERRNO, "Failed stat(\"%s\", %p)", dir, &sbuf); free(dir); return false; } if (!bs_file_mkdir_p(dir, mode | S_IXUSR)) { free(dir); return false; } } char *tmp_fname_ptr = bs_strdupf("%s/dynbuf-XXXXXX", dir); free(fname_copy_ptr); if (NULL == tmp_fname_ptr) { bs_log(BS_ERROR, "Failed bs_strdupf(\"/dynbuf-XXXXXX\") for %s", fname_ptr); return false; } mode_t old_mask = umask((S_IRWXU | S_IRWXG | S_IRWXO) ^ mode); int fd = mkstemp(tmp_fname_ptr); umask(old_mask); if (0 > fd) { bs_log(BS_ERROR | BS_ERRNO, "Failed mkstemp(%s)", tmp_fname_ptr); free(tmp_fname_ptr); return false; } // Write contents. ssize_t written = write(fd, dynbuf_ptr->data_ptr, dynbuf_ptr->length); if (written < 0 || (size_t)written != dynbuf_ptr->length) { bs_log(BS_ERROR | BS_ERRNO, "Failed write(%d, %p, %zu): %zd", fd, dynbuf_ptr->data_ptr, dynbuf_ptr->length, written); close(fd); goto error; } close(fd); // If the target exists already: Verify it's writable. Create bakcup. if (0 == stat(fname_ptr, &sbuf)) { if ((sbuf.st_mode & S_IWUSR) != S_IWUSR) { bs_log(BS_ERROR, "File \"%s\" mode %o not user-writable", fname_ptr, sbuf.st_mode); goto error; } char *backup_fname_ptr = bs_strdupf("%s.old", fname_ptr); if (NULL == backup_fname_ptr) return false; unlink(backup_fname_ptr); int rv = link(fname_ptr, backup_fname_ptr); free(backup_fname_ptr); if (0 != rv) { bs_log(BS_ERROR | BS_ERRNO, "Failed link(\"%s\", \"%s.old\")", fname_ptr, fname_ptr); goto error; } } // Last: Atomically replace target with the written temp file. int rv = rename(tmp_fname_ptr, fname_ptr); if (0 != rv) { bs_log(BS_ERROR | BS_ERRNO, "Failed rename(..., \"%s\")", fname_ptr); goto error; } free(tmp_fname_ptr); return true; error: unlink(tmp_fname_ptr); free(tmp_fname_ptr); return false; } /* ------------------------------------------------------------------------- */ bool bs_dynbuf_append( bs_dynbuf_t *dynbuf_ptr, const void *data_ptr, size_t len) { if (len > dynbuf_ptr->capacity || len + dynbuf_ptr->length > dynbuf_ptr->capacity) return false; memcpy((uint8_t*)dynbuf_ptr->data_ptr + dynbuf_ptr->length, data_ptr, len); dynbuf_ptr->length += len; return true; } /* ------------------------------------------------------------------------- */ bool bs_dynbuf_append_char( bs_dynbuf_t *dynbuf_ptr, char c) { if (dynbuf_ptr->length + 1 > dynbuf_ptr->capacity) return false; ((char*)dynbuf_ptr->data_ptr)[dynbuf_ptr->length++] = c; return true; } /* == Tests ================================================================ */ static void test_dynbuf_ctor_dtor(bs_test_t *test_ptr); static void test_dynbuf_read(bs_test_t *test_ptr); static void test_dynbuf_read_capped(bs_test_t *test_ptr); static void test_dynbuf_write(bs_test_t *test_ptr); static void test_dynbuf_append(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_dynbuf_test_cases[] = { { true, "ctor_dtor", test_dynbuf_ctor_dtor }, { true, "read", test_dynbuf_read }, { true, "read_capped", test_dynbuf_read_capped }, { true, "write", test_dynbuf_write }, { true, "append", test_dynbuf_append }, { false, NULL, NULL }, }; const bs_test_set_t bs_dynbuf_test_set = BS_TEST_SET( true, "dynbuf", bs_dynbuf_test_cases); /* ------------------------------------------------------------------------- */ void test_dynbuf_ctor_dtor(bs_test_t *test_ptr) { bs_dynbuf_t *dynbuf_ptr = bs_dynbuf_create(1, SIZE_MAX); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dynbuf_ptr); bs_dynbuf_destroy(dynbuf_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_dynbuf_create(0, SIZE_MAX)); BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_dynbuf_create(1, 0)); BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_dynbuf_create(2, 1)); } /* ------------------------------------------------------------------------- */ void test_dynbuf_read(bs_test_t *test_ptr) { bs_dynbuf_t d; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, bs_dynbuf_init(&d, 1, SIZE_MAX)); int fd = open(bs_test_data_path(test_ptr, "data/abcd.txt"), 0); if (0 >= fd) { BS_TEST_FAIL(test_ptr, "Failed open(\"%s\", 0)", bs_test_data_path(test_ptr, "data/string.plist")); return; } BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, 0 == bs_dynbuf_read(&d, fd)); close(fd); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 5, d.length); BS_TEST_VERIFY_EQ(test_ptr, 0, memcmp("abcd\n", d.data_ptr, d.length)); bs_dynbuf_fini(&d); char buf[6]; bs_dynbuf_init_unmanaged(&d, buf, sizeof(buf)); fd = open(bs_test_data_path(test_ptr, "data/abcd.txt"), 0); if (0 >= fd) { BS_TEST_FAIL(test_ptr, "Failed open(\"%s\", 0)", bs_test_data_path(test_ptr, "data/string.plist")); return; } BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, 0 == bs_dynbuf_read(&d, fd)); close(fd); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 5, d.length); BS_TEST_VERIFY_EQ(test_ptr, 0, memcmp("abcd\n", d.data_ptr, d.length)); bs_dynbuf_fini(&d); } /* ------------------------------------------------------------------------- */ void test_dynbuf_read_capped(bs_test_t *test_ptr) { bs_dynbuf_t d; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, bs_dynbuf_init(&d, 1, 3)); int fd = open(bs_test_data_path(test_ptr, "data/abcd.txt"), 0); if (0 >= fd) { BS_TEST_FAIL(test_ptr, "Failed open(\"%s\", 0)", bs_test_data_path(test_ptr, "data/string.plist")); return; } BS_TEST_VERIFY_TRUE(test_ptr, -1 == bs_dynbuf_read(&d, fd)); close(fd); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, d.length); BS_TEST_VERIFY_EQ(test_ptr, 0, memcmp("abc", d.data_ptr, d.length)); bs_dynbuf_fini(&d); char buf[3]; bs_dynbuf_init_unmanaged(&d, buf, sizeof(buf)); fd = open(bs_test_data_path(test_ptr, "data/abcd.txt"), 0); if (0 >= fd) { BS_TEST_FAIL(test_ptr, "Failed open(\"%s\", 0)", bs_test_data_path(test_ptr, "data/string.plist")); return; } BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, -1 == bs_dynbuf_read(&d, fd)); close(fd); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, d.length); BS_TEST_VERIFY_EQ(test_ptr, 0, memcmp("abc", d.data_ptr, d.length)); bs_dynbuf_fini(&d); } /* ------------------------------------------------------------------------- */ void test_dynbuf_write(bs_test_t *test_ptr) { struct stat stat_buffer; const char *fn1 = bs_test_temp_path(test_ptr, "sub/file.txt"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fn1); const char *fn2 = bs_test_temp_path(test_ptr, "sub/file.txt.old"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fn2); char content[8] = {}; bs_dynbuf_t db; bs_dynbuf_init_unmanaged(&db, content, sizeof(content)); // Write initial content. Must create one file, size 4. BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, bs_dynbuf_append(&db, "test", 4)); BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_write_file(&db, fn1, 0600)); BS_TEST_VERIFY_EQ(test_ptr, 0, stat(fn1, &stat_buffer)); BS_TEST_VERIFY_EQ(test_ptr, 4, (size_t)stat_buffer.st_size); BS_TEST_VERIFY_NEQ(test_ptr, 0, stat(fn2, &stat_buffer)); // Write further content. Expect a file with size 5, and backup. BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, bs_dynbuf_append_char(&db, 'a')); BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_write_file(&db, fn1, 0600)); BS_TEST_VERIFY_EQ(test_ptr, 0, stat(fn1, &stat_buffer)); BS_TEST_VERIFY_EQ(test_ptr, 5, (size_t)stat_buffer.st_size); BS_TEST_VERIFY_EQ(test_ptr, 0, stat(fn2, &stat_buffer)); BS_TEST_VERIFY_EQ(test_ptr, 4, (size_t)stat_buffer.st_size); // Further content: Rotates the files. BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, bs_dynbuf_append_char(&db, 'b')); BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_write_file(&db, fn1, 0600)); BS_TEST_VERIFY_EQ(test_ptr, 0, stat(fn1, &stat_buffer)); BS_TEST_VERIFY_EQ(test_ptr, 6, (size_t)stat_buffer.st_size); BS_TEST_VERIFY_EQ(test_ptr, 0, stat(fn2, &stat_buffer)); BS_TEST_VERIFY_EQ(test_ptr, 5, (size_t)stat_buffer.st_size); // The file is read-only: Our write must fail. BS_TEST_VERIFY_EQ(test_ptr, 0, chmod(fn1, S_IRUSR)); BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, bs_dynbuf_append_char(&db, 'c')); BS_TEST_VERIFY_FALSE(test_ptr, bs_dynbuf_write_file(&db, fn1, 0600)); BS_TEST_VERIFY_EQ(test_ptr, 0, chmod(fn1, S_IRUSR | S_IWUSR)); // Must be able to clean up the file. unlink(fn2); unlink(fn1); rmdir(bs_test_temp_path(test_ptr, "sub")); } /* ------------------------------------------------------------------------- */ /** Tests appending. */ void test_dynbuf_append(bs_test_t *test_ptr) { bs_dynbuf_t d; BS_TEST_VERIFY_TRUE_OR_RETURN(test_ptr, bs_dynbuf_init(&d, 3, 3)); BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_append(&d, "ab", 2)); BS_TEST_VERIFY_EQ(test_ptr, 2, d.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "ab", d.data_ptr, 2); BS_TEST_VERIFY_FALSE(test_ptr, bs_dynbuf_append(&d, "cd", 2)); BS_TEST_VERIFY_EQ(test_ptr, 2, d.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "ab", d.data_ptr, 2); BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_append(&d, "c", 1)); BS_TEST_VERIFY_EQ(test_ptr, 3, d.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "abc", d.data_ptr, 3); BS_TEST_VERIFY_FALSE(test_ptr, bs_dynbuf_append(&d, "d", 1)); d.length = 2; BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_append_char(&d, 'x')); BS_TEST_VERIFY_EQ(test_ptr, 3, d.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "abx", d.data_ptr, 3); BS_TEST_VERIFY_FALSE(test_ptr, bs_dynbuf_append_char(&d, 'y')); d.length = 2; BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_maybe_append_char(&d, false, 'z')); BS_TEST_VERIFY_EQ(test_ptr, 2, d.length); BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_maybe_append_char(&d, true, 'z')); BS_TEST_VERIFY_EQ(test_ptr, 3, d.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "abz", d.data_ptr, 3); bs_dynbuf_fini(&d); } /* == End of dynbuf.c ====================================================== */ wlmaker-0.8.1/submodules/libbase/src/log.c0000644000175100017510000002363415222270712020077 0ustar runnerrunner/* ========================================================================= */ /** * @file log.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* == Data ================================================================= */ bs_log_severity_t bs_log_severity = BS_WARNING; static const char* _severity_names[5] = { "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; static int _log_fd = 2; static const char *_strip_prefix(const char *path_ptr); /* == Functions ============================================================ */ /* ------------------------------------------------------------------------- */ bool bs_log_init_file(const char *log_filename_ptr, bs_log_severity_t severity) { int fd = open(log_filename_ptr, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR); if (0 > fd) { bs_log(BS_ERROR | BS_ERRNO, "Failed open(%s, O_CREATE | O_WRONLY, S_IWUSR | S_IRUSR)", log_filename_ptr); return false; } _log_fd = fd; bs_log_severity = severity; return true; } /* ------------------------------------------------------------------------- */ void bs_log_write(bs_log_severity_t severity, const char *file_name_ptr, int line_num, const char *fmt_ptr, ...) { va_list ap; va_start(ap, fmt_ptr); bs_log_vwrite(severity, file_name_ptr, line_num, fmt_ptr, ap); va_end(ap); } /* ------------------------------------------------------------------------- */ /** Writes the log mesage, taking a va_list argument. */ void bs_log_vwrite(bs_log_severity_t severity, const char *file_name_ptr, int line_num, const char *fmt_ptr, va_list ap) { char buf[BS_LOG_MAX_BUF_SIZE + 1]; size_t pos = 0; const char *color_attr_ptr = ""; switch (severity & 0x7f) { case BS_DEBUG: color_attr_ptr = "\e[90m"; break; // Dark gray foreground. case BS_INFO: color_attr_ptr = "\e[37m"; break; // Light gray foreground. case BS_WARNING: color_attr_ptr = "\e[1;93m"; break; // Yellow & bold. case BS_ERROR: color_attr_ptr = "\e[1;91m"; break; // Bright red & bold. case BS_FATAL: color_attr_ptr = "\e[1;97;41m"; break; // White on red,bold. } char *reset_ptr = ""; if (*color_attr_ptr != '\0') { reset_ptr = "\e[0m"; } struct timeval tv; if (0 != gettimeofday(&tv, NULL)) tv = (struct timeval){}; struct tm *tm_ptr = localtime(&tv.tv_sec); pos = bs_strappendf( buf, BS_LOG_MAX_BUF_SIZE, pos, "%04d-%02d-%02d %02d:%02d:%02d.%03d (%s%s%s) \e[90m%s:%d\e[0m ", tm_ptr->tm_year + 1900, tm_ptr->tm_mon + 1, tm_ptr->tm_mday, tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec, (int)(tv.tv_usec / 1000), color_attr_ptr, _severity_names[severity & 0x7f], reset_ptr, _strip_prefix(file_name_ptr), line_num); pos = bs_strappendf(buf, BS_LOG_MAX_BUF_SIZE, pos, "%s", color_attr_ptr); pos = bs_vstrappendf(buf, BS_LOG_MAX_BUF_SIZE, pos, fmt_ptr, ap); if (severity & BS_ERRNO) { pos = bs_strappendf( buf, BS_LOG_MAX_BUF_SIZE, pos, ": errno(%d): %s", errno, strerror(errno)); } pos = bs_strappendf(buf, BS_LOG_MAX_BUF_SIZE, pos, "%s", reset_ptr); if (pos >= BS_LOG_MAX_BUF_SIZE) { pos = BS_LOG_MAX_BUF_SIZE; buf[BS_LOG_MAX_BUF_SIZE - 3] = '.'; buf[BS_LOG_MAX_BUF_SIZE - 2] = '.'; buf[BS_LOG_MAX_BUF_SIZE - 1] = '.'; } buf[pos++] = '\n'; size_t written_bytes = 0; while (written_bytes < pos) { ssize_t more_bytes = write(_log_fd, &buf[written_bytes], pos - written_bytes); if (0 > more_bytes) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { continue; } abort(); } written_bytes += more_bytes; } if (severity == BS_FATAL) BS_ABORT(); } /* == Local (static) methods =============================================== */ /** Strips leading relative (or absolute) path prefix. */ const char *_strip_prefix(const char *path_ptr) { if (*path_ptr == '.') ++path_ptr; if (*path_ptr == '/') ++path_ptr; return path_ptr; } /* == Test functions ======================================================= */ /* ------------------------------------------------------------------------- */ /** * Helper method: Test that |expected_str| can be read from the descriptor * |fd|. Will verify length and report to |test_ptr|. * * @param test_ptr * @param fname_ptr * @param line * @param fd * @param expected_str */ void verify_log_output_equals_at( bs_test_t *test_ptr, const char *fname_ptr, int line, int fd, const char *expected_str) { char buf[BS_LOG_MAX_BUF_SIZE + 1] = {}; size_t timestamp_len = strlen("YYYY-MM-DD hh:mm:ss.ccc"); size_t expected_len = 0; if (NULL != expected_str) { expected_len = strlen(expected_str) + timestamp_len + 1; } else { expected_str = ""; } if (BS_LOG_MAX_BUF_SIZE < expected_len || INT_MAX < expected_len) { bs_test_fail_at( test_ptr, fname_ptr, line, "Absurdly long string: %zu bytes!", expected_len); return; } ssize_t available_len = bs_sock_read(fd, buf, expected_len, 10); if (0 > available_len) { bs_test_fail_at( test_ptr, fname_ptr, line, "Failed reading \"%s\" from %d", expected_str, fd); return; } if ((size_t)available_len != expected_len) { bs_test_fail_at( test_ptr, fname_ptr, line, "Only found %zd bytes, expected %zu (\"%.*s\" != \"%s\"", available_len, expected_len, (int)available_len, buf, expected_str); return; } BS_TEST_VERIFY_STREQ(test_ptr, expected_str, &buf[timestamp_len + 1]); if (0 != bs_sock_poll_read(fd, 10)) { bs_test_fail_at( test_ptr, fname_ptr, line, "Unexpected extra (> %zu) bytes found reading \"%s\"", expected_len, expected_str); return; } } static void test_strip_prefix(bs_test_t *test_ptr); static void test_log(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_log_test_cases[] = { { true, "basename", test_strip_prefix }, { true, "log", test_log }, BS_TEST_CASE_SENTINEL(), }; const bs_test_set_t bs_log_test_set = BS_TEST_SET( true, "log", bs_log_test_cases); /* ------------------------------------------------------------------------- */ void test_strip_prefix(bs_test_t *test_ptr) { BS_TEST_VERIFY_STREQ(test_ptr, "", _strip_prefix("")); BS_TEST_VERIFY_STREQ(test_ptr, "base", _strip_prefix("base")); BS_TEST_VERIFY_STREQ(test_ptr, "base", _strip_prefix("/base")); BS_TEST_VERIFY_STREQ(test_ptr, "base", _strip_prefix("./base")); BS_TEST_VERIFY_STREQ(test_ptr, "a/path/to/base", _strip_prefix("/a/path/to/base")); BS_TEST_VERIFY_STREQ(test_ptr, "", _strip_prefix("./")); BS_TEST_VERIFY_STREQ(test_ptr, ".", _strip_prefix("/.")); } /* ------------------------------------------------------------------------- */ void test_log(bs_test_t *test_ptr) { char expected_output[BS_LOG_MAX_BUF_SIZE + 1]; bs_log_severity_t backup_severity; backup_severity = bs_log_severity; int fds[2]; if (0 != pipe(fds)) { BS_TEST_FAIL( test_ptr, "Failed pipe(%p): errno(%d): %s", fds, errno, strerror(errno)); return; } if (!bs_sock_set_blocking(fds[0], false)) { BS_TEST_FAIL( test_ptr, "Failed bs_sock_set_blocking(%d, false)", fds[0]); return; } _log_fd = fds[1]; snprintf(expected_output, sizeof(expected_output), "(\e[1;93mWARNING\e[0m) \e[90mlog.c:%d\e[0m \e[1;93mtest 42\e[0m\n", __LINE__ + 1); bs_log(BS_WARNING, "test %d", 42); verify_log_output_equals_at( test_ptr, __FILE__, __LINE__, fds[0], expected_output); snprintf(expected_output, sizeof(expected_output), "(\e[1;91mERROR\e[0m) \e[90mlog.c:%d\e[0m \e[1;91mtest 43" ": errno(%d): Permission denied\e[0m\n", __LINE__ + 2, EACCES); errno = EACCES; bs_log(BS_ERROR | BS_ERRNO, "test %d", 43); verify_log_output_equals_at( test_ptr, __FILE__, __LINE__, fds[0], expected_output); bs_log(BS_INFO, "test %d", 44); verify_log_output_equals_at( test_ptr, __FILE__, __LINE__, fds[0], NULL); bs_log_severity = BS_INFO; snprintf(expected_output, sizeof(expected_output), "(\e[37mINFO\e[0m) \e[90mlog.c:%d\e[0m \e[37mtest 45\e[0m\n", __LINE__ + 1); bs_log(BS_INFO, "test %d", 45); verify_log_output_equals_at( test_ptr, __FILE__, __LINE__, fds[0], expected_output); _log_fd = 2; close(fds[0]); close(fds[1]); bs_log_severity = backup_severity; } /* == End of log.c ========================================================= */ wlmaker-0.8.1/submodules/libbase/src/dequeue.c0000644000175100017510000000652415222270712020752 0ustar runnerrunner/* ========================================================================= */ /** * @file dequeue.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include /* == Tests =============================================================== */ static void bs_dequeue_front_test(bs_test_t *test_ptr); static void bs_dequeue_back_test(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_dequeue_test_cases[] = { { true, "push front & pop", bs_dequeue_front_test }, { true, "push back & pop", bs_dequeue_back_test }, { false, NULL, NULL } // sentinel. }; const bs_test_set_t bs_dequeue_test_set = BS_TEST_SET( true, "dequeue", bs_dequeue_test_cases); /* ------------------------------------------------------------------------- */ /** * Tests that push front & pop works. */ void bs_dequeue_front_test(bs_test_t *test_ptr) { bs_dequeue_t queue = {}; bs_dequeue_node_t node1, node2; BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), NULL); bs_dequeue_push_front(&queue, &node1); BS_TEST_VERIFY_EQ(test_ptr, queue.head_ptr, &node1); BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, NULL); bs_dequeue_push_front(&queue, &node2); BS_TEST_VERIFY_EQ(test_ptr, queue.head_ptr, &node2); BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node1); BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node2); BS_TEST_VERIFY_EQ(test_ptr, queue.head_ptr, &node1); BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node1); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), NULL); } /* ------------------------------------------------------------------------- */ /** * Tests that push back & pop works. */ void bs_dequeue_back_test(bs_test_t *test_ptr) { bs_dequeue_t queue = {}; bs_dequeue_node_t node1, node2, node3; bs_dequeue_push_back(&queue, &node1); bs_dequeue_push_back(&queue, &node2); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node1); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node2); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), NULL); // Build a node2 -> node1 -> node3 queue. bs_dequeue_push_back(&queue, &node1); bs_dequeue_push_front(&queue, &node2); bs_dequeue_push_back(&queue, &node3); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node2); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node1); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), &node3); BS_TEST_VERIFY_EQ(test_ptr, bs_dequeue_pop(&queue), NULL); } /* == End of dequeue.c ===================================================== */ wlmaker-0.8.1/submodules/libbase/src/arg.c0000644000175100017510000010572315222270712020067 0ustar runnerrunner /* ========================================================================= */ /** * @file arg.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** How the arg matches a bs_arg_t. */ typedef enum { /** No match at all. */ _BS_ARG_NO_MATCH = 0, /** The value is "name=value", as one string. */ _BS_ARG_MATCH_WITH_EQUAL_SIGN = 1, /** The value is "name", so the next arg is the value. */ _BS_ARG_MATCH_WITH_TWO_ARGS = 2, /** It's a bool, an exact match. Doesn't take a value. */ _BS_ARG_MATCH_BOOL = 3, /** It's a bool, with a 'no' prefix. Doesn't take a value. */ _BS_ARG_MATCH_BOOL_OVERRIDE_WITH_NO = 4 } bs_arg_match_t; static bs_arg_match_t get_match_type(const bs_arg_t *arg_ptr, const char *argv, const char *next_argv, const char **arg_value_ptr); static bool find_matching_arg(const bs_arg_t *arg_ptr, const char *argv, const char *next_argv, const bs_arg_t **matching_arg_ptr, const char **arg_value_ptr); static bool parse_arg(const bs_arg_t *arg_ptr, const char *value_ptr); static bool parse_bool(const bs_arg_bool_t *arg_bool_ptr, const char *value_ptr); static bool parse_enum(const bs_arg_enum_t *arg_enum_ptr, const char *value_ptr); static bool parse_string(const bs_arg_string_t *arg_string_ptr, const char *value_ptr); static bool parse_uint32(const bs_arg_uint32_t *arg_uint32_ptr, const char *value_ptr); static void set_all_defaults(const bs_arg_t *arg_ptr); static bool check_arg(const bs_arg_t *arg_ptr); /** Store an arg name in a tree, to identify duplicates. */ typedef struct { /** Tree node. */ bs_avltree_node_t node; /** Argument name. */ char *name_ptr; } arg_name_t; static int node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr); static arg_name_t *node_create(const char *prefix_ptr, const char *name_ptr); static void node_destroy(bs_avltree_node_t *node_ptr); static bool is_name_valid(const char *name_ptr); static bool lookup_enum(const bs_arg_enum_table_t *lookup_table, const char *name_ptr, int *value_ptr); /* == Data ================================================================= */ static const char *bs_arg_bool_value_true = "true"; static const char *bs_arg_bool_value_false = "false"; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool bs_arg_parse(const bs_arg_t *arg_ptr, const bs_arg_mode_t mode, int *argc_ptr, const char **argv_ptr) { if (!check_arg(arg_ptr)) { return false; } int not_consumed = 1; const bs_arg_t *matching_arg_ptr; const char *next_argv_ptr, *arg_value_ptr; set_all_defaults(arg_ptr); // Start at 1 -- argv_ptr[0] is the program's name. for (int i = 1; i < *argc_ptr; ++i) { next_argv_ptr = i + 1 >= *argc_ptr ? NULL : argv_ptr[i+1]; if (!find_matching_arg(arg_ptr, argv_ptr[i], next_argv_ptr, &matching_arg_ptr, &arg_value_ptr)) { bs_arg_cleanup(arg_ptr); return false; } if (NULL == matching_arg_ptr) { argv_ptr[not_consumed++] = argv_ptr[i]; continue; } if (!parse_arg(matching_arg_ptr, arg_value_ptr)) { bs_arg_cleanup(arg_ptr); return false; } // Move by two args, if the next arg was actually used. if (arg_value_ptr == next_argv_ptr) ++i; } // Cleanup rest of argv. for (int i = not_consumed; i < *argc_ptr; ++i) { argv_ptr[i] = NULL; } *argc_ptr = not_consumed; bool retval = true; switch (mode) { case BS_ARG_MODE_NO_EXTRA: /* No extra args expected. */ for (int i = 1; i < not_consumed; ++i) { bs_log(BS_WARNING, "Unexpected extra argv: %s", argv_ptr[i]); retval = false; } break; case BS_ARG_MODE_EXTRA_VALUES: /* Values are permitted, but report any extra "--" prefix. */ for (int i = 1; i < not_consumed; ++i) { if (0 == strncmp(argv_ptr[i], "--", 2)) { bs_log(BS_WARNING, "Unexpected extra arg: %s", argv_ptr[i]); retval = false; } } break; case BS_ARG_MODE_EXTRA_ARGS: /* We're good. Anything is allowed. */ break; default: bs_log(BS_FATAL, "Unexpected mode %d.", mode); retval = false; BS_ABORT(); } if (!retval) bs_arg_cleanup(arg_ptr); return retval; } /* ------------------------------------------------------------------------- */ void bs_arg_cleanup(const bs_arg_t *arg_ptr) { for (; NULL != arg_ptr->name_ptr; ++arg_ptr) { switch (arg_ptr->type) { case BS_ARG_TYPE_STRING: if (NULL != *arg_ptr->v.v_string.value_ptr) { free(*arg_ptr->v.v_string.value_ptr); *arg_ptr->v.v_string.value_ptr = NULL; } break; default: break; } } } /* ------------------------------------------------------------------------- */ int bs_arg_print_usage(FILE *stream_ptr, const bs_arg_t *arg_ptr) { const bs_arg_enum_table_t *lookup_table; int written_bytes = 0; for (; NULL != arg_ptr->name_ptr; ++arg_ptr) { written_bytes += fprintf(stream_ptr, "--%s : %s\n", arg_ptr->name_ptr, arg_ptr->description_ptr); switch (arg_ptr->type) { case BS_ARG_TYPE_ENUM: lookup_table = arg_ptr->v.v_enum.lookup_table; written_bytes += fprintf(stderr, " Enum values:\n"); for (; NULL != lookup_table->name_ptr; ++lookup_table) { written_bytes += fprintf( stream_ptr, " %s (%"PRIu32")\n", lookup_table->name_ptr, lookup_table->value); } break; default: break; } } return written_bytes; } /* == Local methods ======================================================== */ /* ------------------------------------------------------------------------- */ /** * Returns the match type (if any) for |*arg_ptr| on |argv|. Updates * |*arg_value_ptr| to point to the value -- either NULL (no value expected), * |next_argv| (on _TWO_ARGS match) or to the part after '=' of |argv|. * * @param arg_ptr * @param argv * @param next_argv * @param arg_value_ptr * * @returns Match type. */ bs_arg_match_t get_match_type(const bs_arg_t *arg_ptr, const char *argv, const char *next_argv, const char **arg_value_ptr) { size_t name_len; *arg_value_ptr = NULL; if (0 != strncmp(argv, "--", 2)) { return _BS_ARG_NO_MATCH; } argv +=2; // BOOL type: A full match, a matching with a "no" prefix. No value! if (BS_ARG_TYPE_BOOL == arg_ptr->type) { if (0 == strcmp(argv, arg_ptr->name_ptr)) { return _BS_ARG_MATCH_BOOL; } if (0 == strncmp(argv, "no", 2) && 0 == strcmp(argv + 2, arg_ptr->name_ptr)) { return _BS_ARG_MATCH_BOOL_OVERRIDE_WITH_NO; } return _BS_ARG_NO_MATCH; } // Other types: Matches must start with the arg name... name_len = strlen(arg_ptr->name_ptr); if (0 == strncmp(argv, arg_ptr->name_ptr, name_len)) { // ... and continue with a '=' and value, or space. if ('=' == argv[name_len]) { *arg_value_ptr = argv + name_len + 1; return _BS_ARG_MATCH_WITH_EQUAL_SIGN; } if ('\0' == argv[name_len]) { *arg_value_ptr = next_argv; return _BS_ARG_MATCH_WITH_TWO_ARGS; } } // Fall-through: It's not a match. return _BS_ARG_NO_MATCH; } /* ------------------------------------------------------------------------- */ /** Finds the arg matching |argv|. Updates arg_value_ptr to the value. */ bool find_matching_arg(const bs_arg_t *arg_ptr, const char *argv, const char *next_argv, const bs_arg_t **matching_arg_ptr, const char **arg_value_ptr) { bs_arg_match_t match_type; *matching_arg_ptr = NULL; *arg_value_ptr = NULL; for (; BS_ARG_TYPE_UNDEFINED != arg_ptr->type; ++arg_ptr) { match_type = get_match_type(arg_ptr, argv, next_argv, arg_value_ptr); switch (match_type) { case _BS_ARG_MATCH_WITH_EQUAL_SIGN: BS_ASSERT(NULL != *arg_value_ptr); *matching_arg_ptr = arg_ptr; return true; case _BS_ARG_MATCH_WITH_TWO_ARGS: if (NULL == *arg_value_ptr) { bs_log(BS_WARNING, "Missing value for arg '%s'", arg_ptr->name_ptr); return false; } *matching_arg_ptr = arg_ptr; return true; case _BS_ARG_MATCH_BOOL: *arg_value_ptr = bs_arg_bool_value_true; *matching_arg_ptr = arg_ptr; return true; case _BS_ARG_MATCH_BOOL_OVERRIDE_WITH_NO: *arg_value_ptr = bs_arg_bool_value_false; *matching_arg_ptr = arg_ptr; return true; case _BS_ARG_NO_MATCH: default: break; } } /* no match found, but not an error */ return true; } /* ------------------------------------------------------------------------- */ /** Parses |value_ptr| for |arg_ptr|. */ bool parse_arg(const bs_arg_t *arg_ptr, const char *value_ptr) { bool retval = false; switch(arg_ptr->type) { case BS_ARG_TYPE_BOOL: retval = parse_bool(&arg_ptr->v.v_bool, value_ptr); break; case BS_ARG_TYPE_ENUM: retval = parse_enum(&arg_ptr->v.v_enum, value_ptr); break; case BS_ARG_TYPE_STRING: retval = parse_string(&arg_ptr->v.v_string, value_ptr); break; case BS_ARG_TYPE_UINT32: retval = parse_uint32(&arg_ptr->v.v_uint32, value_ptr); break; case BS_ARG_TYPE_UNDEFINED: default: bs_log(BS_FATAL, "Unhandled arg type %d for %s", arg_ptr->type, arg_ptr->name_ptr); BS_ABORT(); } if (!retval) { bs_log(BS_ERROR, "Failed to parse --%s for \"%s\"", arg_ptr->name_ptr, value_ptr); } return retval; } /* ------------------------------------------------------------------------- */ /** Parses a boolean value at |value_ptr|. */ bool parse_bool(const bs_arg_bool_t *arg_bool_ptr, const char *value_ptr) { if (0 == strcmp(bs_arg_bool_value_true, value_ptr)) { *(arg_bool_ptr->value_ptr) = true; return true; } if (0 == strcmp(bs_arg_bool_value_false, value_ptr)) { *(arg_bool_ptr->value_ptr) = false; return true; } bs_log(BS_ERROR, "Unrecognized bool value \"%s\"", value_ptr); return false; } /* ------------------------------------------------------------------------- */ /** Parses an enum from |value_ptr|. */ bool parse_enum(const bs_arg_enum_t *arg_enum_ptr, const char *value_ptr) { if (!lookup_enum(arg_enum_ptr->lookup_table, value_ptr, arg_enum_ptr->value_ptr)) { bs_log(BS_ERROR, "Unknown value \"%s\" for enum.", value_ptr); return false; } return true; } /* ------------------------------------------------------------------------- */ /** Parses a string value at |value_ptr|. */ bool parse_string(const bs_arg_string_t *arg_string_ptr, const char *value_ptr) { if (NULL != *arg_string_ptr->value_ptr) { free(*arg_string_ptr->value_ptr); } *arg_string_ptr->value_ptr = strdup(value_ptr); if (NULL == *arg_string_ptr->value_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed strdup(%s)", value_ptr); return false; } return true; } /* ------------------------------------------------------------------------- */ /** Parses a 32-bit unsigned integer value at |value_ptr|. */ bool parse_uint32(const bs_arg_uint32_t *arg_uint32_ptr, const char *value_ptr) { uint64_t value; if (!bs_strconvert_uint64(value_ptr, &value, 10)) { return false; } if (value < arg_uint32_ptr->min_value) { bs_log(BS_ERROR, "Out of range: \"%s\" (%"PRIu64" < %"PRIu32")", value_ptr, value, arg_uint32_ptr->min_value); return false; } if (value > arg_uint32_ptr->max_value) { bs_log(BS_ERROR, "Out of range: \"%s\" (%"PRIu64" > %"PRIu32")", value_ptr, value, arg_uint32_ptr->max_value); return false; } BS_ASSERT(value <= UINT32_MAX); *arg_uint32_ptr->value_ptr = (uint32_t)value; return true; } /* ------------------------------------------------------------------------- */ void set_all_defaults(const bs_arg_t *arg_ptr) { for (; NULL != arg_ptr->name_ptr && BS_ARG_TYPE_UNDEFINED != arg_ptr->type; ++arg_ptr) { switch(arg_ptr->type) { case BS_ARG_TYPE_BOOL: *arg_ptr->v.v_bool.value_ptr = arg_ptr->v.v_bool.default_value; break; case BS_ARG_TYPE_ENUM: if (!lookup_enum(arg_ptr->v.v_enum.lookup_table, arg_ptr->v.v_enum.default_name_ptr, arg_ptr->v.v_enum.value_ptr)) { bs_log(BS_FATAL, "Failed to lookup default \"%s\" for enum %s", arg_ptr->v.v_enum.default_name_ptr, arg_ptr->name_ptr); BS_ABORT(); } break; case BS_ARG_TYPE_STRING: if (NULL == arg_ptr->v.v_string.default_value) { *arg_ptr->v.v_string.value_ptr = NULL; } else { *arg_ptr->v.v_string.value_ptr = strdup( arg_ptr->v.v_string.default_value); if (NULL == *arg_ptr->v.v_string.value_ptr) { bs_log(BS_FATAL | BS_ERRNO, "Failed strdup(%s)", arg_ptr->v.v_string.default_value); BS_ABORT(); } } break; case BS_ARG_TYPE_UINT32: *arg_ptr->v.v_uint32.value_ptr = arg_ptr->v.v_uint32.default_value; break; case BS_ARG_TYPE_UNDEFINED: default: bs_log(BS_FATAL, "Unhandled arg type %d for %s", arg_ptr->type, arg_ptr->name_ptr); BS_ABORT(); } } } /* ------------------------------------------------------------------------- */ bool check_arg(const bs_arg_t *arg_ptr) { bool retval = true; bs_avltree_t *tree_ptr; arg_name_t *arg_name_ptr; tree_ptr = bs_avltree_create(node_cmp, node_destroy); for (; BS_ARG_TYPE_UNDEFINED != arg_ptr->type; ++arg_ptr) { if (NULL == arg_ptr->name_ptr) { bs_log(BS_ERROR, "Name not given for arg_ptr at %p", arg_ptr); retval = false; continue; } if (!is_name_valid(arg_ptr->name_ptr)) { // Already logged. retval = false; continue; } arg_name_ptr = node_create("", arg_ptr->name_ptr); if (!bs_avltree_insert(tree_ptr, arg_name_ptr->name_ptr, &arg_name_ptr->node, false)) { bs_log(BS_ERROR, "Duplicate argument name \"%s\"", arg_name_ptr->name_ptr); node_destroy(&arg_name_ptr->node); retval = false; continue; } switch(arg_ptr->type) { case BS_ARG_TYPE_BOOL: if (NULL == arg_ptr->v.v_bool.value_ptr) { bs_log(BS_ERROR, "Pointer to value not provided for --%s", arg_ptr->name_ptr); retval = false; } arg_name_ptr = node_create("no", arg_ptr->name_ptr); if (!bs_avltree_insert(tree_ptr, arg_name_ptr->name_ptr, &arg_name_ptr->node, false)) { bs_log(BS_ERROR, "Duplicate argument name \"%s\"", arg_name_ptr->name_ptr); node_destroy(&arg_name_ptr->node); retval = false; continue; } break; case BS_ARG_TYPE_ENUM: if (NULL == arg_ptr->v.v_enum.value_ptr) { bs_log(BS_ERROR, "Pointer to value not provided for --%s", arg_ptr->name_ptr); retval = false; } if (NULL == arg_ptr->v.v_enum.lookup_table || NULL == arg_ptr->v.v_enum.lookup_table->name_ptr) { bs_log(BS_ERROR, "Lookup table missing or empty for enum --%s", arg_ptr->name_ptr); retval = false; } break; case BS_ARG_TYPE_STRING: if (NULL == arg_ptr->v.v_string.value_ptr) { bs_log(BS_ERROR, "Pointer to value not provided for --%s", arg_ptr->name_ptr); retval = false; } break; case BS_ARG_TYPE_UINT32: if (NULL == arg_ptr->v.v_uint32.value_ptr) { bs_log(BS_ERROR, "Pointer to value not provided for --%s", arg_ptr->name_ptr); retval = false; } break; default: bs_log(BS_ERROR, "Unhandled type for --%s", arg_ptr->name_ptr); retval = false; break; } } bs_avltree_destroy(tree_ptr); return retval; } /* == Helpers for storing the names in a tree ============================== */ /* ------------------------------------------------------------------------- */ int node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr) { arg_name_t *arg_name_ptr = BS_CONTAINER_OF(node_ptr, arg_name_t, node); return strcmp(arg_name_ptr->name_ptr, (const char*)key_ptr); } /* ------------------------------------------------------------------------- */ arg_name_t *node_create(const char *prefix_ptr, const char *name_ptr) { size_t len; arg_name_t *arg_name_ptr; arg_name_ptr = (arg_name_t*)calloc(1, sizeof(arg_name_t)); if (NULL == arg_name_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed calloc(1, %zu)", sizeof(arg_name_t)); return NULL; } len = strlen(prefix_ptr) + strlen(name_ptr) + 1; arg_name_ptr->name_ptr = malloc(len); if (NULL == arg_name_ptr->name_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed malloc(%zu)", len); node_destroy(&arg_name_ptr->node); } strcpy(arg_name_ptr->name_ptr, prefix_ptr); strcpy(arg_name_ptr->name_ptr + strlen(prefix_ptr), name_ptr); return arg_name_ptr; } /* ------------------------------------------------------------------------- */ void node_destroy(bs_avltree_node_t *node_ptr) { arg_name_t *arg_name_ptr = BS_CONTAINER_OF(node_ptr, arg_name_t, node); if (NULL != arg_name_ptr->name_ptr) { free(arg_name_ptr->name_ptr); arg_name_ptr->name_ptr = NULL; } free(arg_name_ptr); } /* ------------------------------------------------------------------------- */ bool is_name_valid(const char *name_ptr) { if (!isalpha(*name_ptr) && !(*name_ptr & 0x80)) { bs_log(BS_ERROR, "Argument name must start with [a-zA-Z]: %s", name_ptr); return false; } while (*++name_ptr) { if (!(*name_ptr & 0x80) && // same as: isascii() (isalnum(*name_ptr) || '-' == *name_ptr || '_' == *name_ptr)) { continue; } bs_log(BS_ERROR, "Argument name must only contain [-_a-zA-Z0-9]: %s", name_ptr); return false; } return true; } /* ------------------------------------------------------------------------- */ bool lookup_enum(const bs_arg_enum_table_t *lookup_table, const char *name_ptr, int *value_ptr) { for (; NULL != lookup_table->name_ptr; ++lookup_table) { if (0 == strcmp(lookup_table->name_ptr, name_ptr)) { *value_ptr = lookup_table->value; return true; } } return false; } /** == Unit tests ========================================================= */ static void bs_arg_test_get_match_type_bool(bs_test_t *test_ptr); static void bs_arg_test_get_match_type_nonbool(bs_test_t *test_ptr); static void bs_arg_test_find_matching_arg(bs_test_t *test_ptr); static void bs_arg_test_parse_arg_for_bool(bs_test_t *test_ptr); static void bs_arg_test_parse_arg_for_uint32(bs_test_t *test_ptr); static void bs_arg_test_parse_arg_for_enum(bs_test_t *test_ptr); static void bs_arg_test_set_defaults(bs_test_t *test_ptr); static void bs_arg_test_parse(bs_test_t *test_ptr); static void bs_arg_test_check_arg(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_arg_test_cases[] = { { true, "get_match_type for bool values", bs_arg_test_get_match_type_bool }, { true, "get_match_type for non-bool values", bs_arg_test_get_match_type_nonbool }, { true, "find_matching_args", bs_arg_test_find_matching_arg }, { true, "parse_arg_for_bool", bs_arg_test_parse_arg_for_bool }, { true, "parse_arg_for_uint32", bs_arg_test_parse_arg_for_uint32 }, { true, "parse_arg_for_enum", bs_arg_test_parse_arg_for_enum }, { true, "set_defaults", bs_arg_test_set_defaults }, { true, "parse", bs_arg_test_parse }, { true, "check_arg", bs_arg_test_check_arg }, { false, NULL, NULL } }; const bs_test_set_t bs_arg_test_set = BS_TEST_SET( true, "arg", bs_arg_test_cases); static const bs_arg_enum_table_t enum_test_table[] = { { "alpha", 1 }, { "bravo", 42 }, { "charlie", 7 }, { NULL, 0 } }; /* -- Verifies get_match_type() with bool args ----------------------------- */ void bs_arg_test_get_match_type_bool(bs_test_t *test_ptr) { static const bs_arg_t bool_arg = BS_ARG_BOOL( "novalue", "description", true, NULL ); const char *next_arg_ptr = "something"; const char *arg_value; BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_NO_MATCH, get_match_type(&bool_arg, "--value", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value); BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_MATCH_BOOL, get_match_type(&bool_arg, "--novalue", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value); BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_MATCH_BOOL_OVERRIDE_WITH_NO, get_match_type(&bool_arg, "--nonovalue", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value); BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_NO_MATCH, get_match_type(&bool_arg, "--nononovalue", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value); } /* -- Verifies get_match_type() with non-bool args ------------------------- */ void bs_arg_test_get_match_type_nonbool(bs_test_t *test_ptr) { static const bs_arg_t uint32_arg = BS_ARG_UINT32( "value", "description", 42, 0, UINT32_MAX, NULL ); const char *next_arg_ptr = "1234"; const char *arg_value; BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_NO_MATCH, get_match_type(&uint32_arg, "--other", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value); BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_MATCH_WITH_EQUAL_SIGN, get_match_type(&uint32_arg, "--value=4321", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, 0, strcmp(arg_value, "4321")); BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_MATCH_WITH_EQUAL_SIGN, get_match_type(&uint32_arg, "--value=", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, '\0', *arg_value); BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_MATCH_WITH_TWO_ARGS, get_match_type(&uint32_arg, "--value", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, next_arg_ptr, arg_value); BS_TEST_VERIFY_EQ( test_ptr, _BS_ARG_NO_MATCH, get_match_type(&uint32_arg, "--novalue", next_arg_ptr, &arg_value)); BS_TEST_VERIFY_EQ(test_ptr, NULL, arg_value); } /* ------------------------------------------------------------------------- */ void bs_arg_test_find_matching_arg(bs_test_t *test_ptr) { static const bs_arg_t args[] = { BS_ARG_BOOL("b", "d", true, NULL), BS_ARG_UINT32("u32", "d", 42, 0, UINT32_MAX, NULL), BS_ARG_SENTINEL(), }; const bs_arg_t *matching_arg; const char *av_ptr; BS_TEST_VERIFY_TRUE( test_ptr, find_matching_arg(args, "--b", NULL, &matching_arg, &av_ptr)); BS_TEST_VERIFY_EQ(test_ptr, &args[0], matching_arg); BS_TEST_VERIFY_STREQ(test_ptr, "true", av_ptr); BS_TEST_VERIFY_TRUE( test_ptr, find_matching_arg(args, "--nob", NULL, &matching_arg, &av_ptr)); BS_TEST_VERIFY_EQ(test_ptr, &args[0], matching_arg); BS_TEST_VERIFY_STREQ(test_ptr, "false", av_ptr); BS_TEST_VERIFY_TRUE( test_ptr, find_matching_arg(args, "--u32=123", NULL, &matching_arg, &av_ptr)); BS_TEST_VERIFY_EQ(test_ptr, &args[1], matching_arg); BS_TEST_VERIFY_TRUE( test_ptr, find_matching_arg(args, "--u32", "456", &matching_arg, &av_ptr)); BS_TEST_VERIFY_EQ(test_ptr, &args[1], matching_arg); BS_TEST_VERIFY_STREQ(test_ptr, "456", av_ptr); BS_TEST_VERIFY_FALSE( test_ptr, find_matching_arg(args, "--u32", NULL, &matching_arg, &av_ptr)); BS_TEST_VERIFY_TRUE( test_ptr, find_matching_arg(args, "--unknown", NULL, &matching_arg, &av_ptr)); BS_TEST_VERIFY_EQ(test_ptr, NULL, matching_arg); BS_TEST_VERIFY_EQ(test_ptr, NULL, av_ptr); } /* ------------------------------------------------------------------------- */ void bs_arg_test_parse_arg_for_bool(bs_test_t *test_ptr) { static bool value; static const bs_arg_t arg = BS_ARG_BOOL("b", "d", true, &value ); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "true")); BS_TEST_VERIFY_EQ(test_ptr, true, value); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "false")); BS_TEST_VERIFY_EQ(test_ptr, false, value); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "meh")); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "truea")); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "falsea")); } /* ------------------------------------------------------------------------- */ void bs_arg_test_parse_arg_for_uint32(bs_test_t *test_ptr) { static uint32_t value; static const bs_arg_t arg = BS_ARG_UINT32( "u32", "d", 42, 0, UINT32_MAX, &value); static const bs_arg_t arg_limited = BS_ARG_UINT32( "u32", "d", 42, 10, 100, &value); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "0")); BS_TEST_VERIFY_EQ(test_ptr, 0, value); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "4294967295")); BS_TEST_VERIFY_EQ(test_ptr, UINT32_MAX, value); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "999999999999999999999")); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "4294967296")); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "12a")); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "a")); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg_limited, "10")); BS_TEST_VERIFY_EQ(test_ptr, 10, value); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg_limited, "100")); BS_TEST_VERIFY_EQ(test_ptr, 100, value); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg_limited, "9")); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg_limited, "101")); } /* ------------------------------------------------------------------------- */ void bs_arg_test_parse_arg_for_enum(bs_test_t *test_ptr) { static int value; static const bs_arg_t arg = BS_ARG_ENUM( "e", "d", "alpha", enum_test_table, &value); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "alpha")); BS_TEST_VERIFY_EQ(test_ptr, 1, value); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "bravo")); BS_TEST_VERIFY_EQ(test_ptr, 42, value); BS_TEST_VERIFY_TRUE(test_ptr, parse_arg(&arg, "charlie")); BS_TEST_VERIFY_EQ(test_ptr, 7, value); BS_TEST_VERIFY_FALSE(test_ptr, parse_arg(&arg, "delta")); } /* ------------------------------------------------------------------------- */ void bs_arg_test_set_defaults(bs_test_t *test_ptr) { static bool value_bool; static uint32_t value_uint32; static int value_enum; static const bs_arg_t args[] = { BS_ARG_BOOL("b", "d", true, &value_bool), BS_ARG_UINT32("u32", "d", 42, 0, UINT32_MAX, &value_uint32), BS_ARG_ENUM("e", "d", "alpha", enum_test_table, &value_enum), BS_ARG_SENTINEL(), }; value_bool = false; value_uint32 = 0; set_all_defaults(args); BS_TEST_VERIFY_EQ(test_ptr, true, value_bool); BS_TEST_VERIFY_EQ(test_ptr, 42, value_uint32); BS_TEST_VERIFY_EQ(test_ptr, 1, value_enum); } /* ------------------------------------------------------------------------- */ void bs_arg_test_parse(bs_test_t *test_ptr) { static bool value_bool; static uint32_t value_uint32; static int value_enum; static char *value_string = NULL; static const bs_arg_t args[] = { BS_ARG_BOOL("b", "d", true, &value_bool), BS_ARG_ENUM("e", "d", "alpha", enum_test_table, &value_enum), BS_ARG_UINT32("u32", "d", 42, 0, UINT32_MAX, &value_uint32), BS_ARG_STRING("str", "d", "bravo", &value_string), BS_ARG_SENTINEL(), }; const char *argv[] = { "program" }; int argc = 1; BS_TEST_VERIFY_TRUE( test_ptr, bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &argc, argv)); BS_TEST_VERIFY_EQ(test_ptr, true, value_bool); BS_TEST_VERIFY_EQ(test_ptr, 42, value_uint32); BS_TEST_VERIFY_EQ(test_ptr, 1, argc); BS_TEST_VERIFY_STREQ(test_ptr, "bravo", value_string); bs_arg_cleanup(args); const char *argv1[] = { "program", "x", "--nob", "--u32", "1234", "y", "--e", "bravo", "--str", "charlie" }; argc = sizeof(argv1) / sizeof(const char*); BS_TEST_VERIFY_TRUE( test_ptr, bs_arg_parse(args, BS_ARG_MODE_EXTRA_VALUES, &argc, argv1)); BS_TEST_VERIFY_EQ(test_ptr, false, value_bool); BS_TEST_VERIFY_EQ(test_ptr, 1234, value_uint32); BS_TEST_VERIFY_EQ(test_ptr, 3, argc); BS_TEST_VERIFY_STREQ(test_ptr, "x", argv1[1]); BS_TEST_VERIFY_STREQ(test_ptr, "y", argv1[2]); BS_TEST_VERIFY_EQ(test_ptr, 42, value_enum); BS_TEST_VERIFY_STREQ(test_ptr, "charlie", value_string); bs_arg_cleanup(args); const char *argv2[] = { "program", "--u32=4321", "1234" }; argc = sizeof(argv2) / sizeof(const char*); BS_TEST_VERIFY_TRUE( test_ptr, bs_arg_parse(args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv2)); BS_TEST_VERIFY_EQ(test_ptr, 4321, value_uint32); BS_TEST_VERIFY_EQ(test_ptr, 2, argc); BS_TEST_VERIFY_STREQ(test_ptr, "1234", argv2[1]); bs_arg_cleanup(args); const char *argv3[] = { "program", "--u32" }; argc = sizeof(argv3) / sizeof(const char*); BS_TEST_VERIFY_FALSE( test_ptr, bs_arg_parse(args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv3)); BS_TEST_VERIFY_EQ(test_ptr, NULL, value_string); const char *argv4[] = { "program", "--unknown=123" }; argc = sizeof(argv4) / sizeof(const char*); BS_TEST_VERIFY_FALSE( test_ptr, bs_arg_parse(args, BS_ARG_MODE_NO_EXTRA, &argc, argv4)); BS_TEST_VERIFY_EQ(test_ptr, 2, argc); BS_TEST_VERIFY_FALSE( test_ptr, bs_arg_parse(args, BS_ARG_MODE_EXTRA_VALUES, &argc, argv4)); BS_TEST_VERIFY_EQ(test_ptr, 2, argc); BS_TEST_VERIFY_TRUE( test_ptr, bs_arg_parse(args, BS_ARG_MODE_EXTRA_ARGS, &argc, argv4)); BS_TEST_VERIFY_EQ(test_ptr, 2, argc); bs_arg_cleanup(args); } /* ------------------------------------------------------------------------- */ static void bs_arg_test_check_arg(bs_test_t *test_ptr) { static bool value_bool; static uint32_t value_uint32; static int value_enum; static char *value_string; // A NULL name. static const bs_arg_t args1[] = { BS_ARG_BOOL(NULL, "d", true, &value_bool), BS_ARG_SENTINEL(), }; BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args1)); // Not starting with a-zA-Z. static const bs_arg_t args2[] = { BS_ARG_BOOL(NULL, "9", true, &value_bool), BS_ARG_SENTINEL(), }; BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args2)); // Invalid characters. static const bs_arg_t args3[] = { BS_ARG_BOOL(NULL, "a-b.", true, &value_bool), BS_ARG_SENTINEL(), }; BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args3)); // Duplicate names. static const bs_arg_t args4[] = { BS_ARG_BOOL("b", "d", true, &value_bool), BS_ARG_BOOL("b", "e", false, &value_bool), BS_ARG_SENTINEL(), }; BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args4)); // Duplicate names, with the boolean extension. static const bs_arg_t args5[] = { BS_ARG_BOOL("b", "d", true, &value_bool), BS_ARG_BOOL("nob", "e", false, &value_bool), BS_ARG_SENTINEL(), }; BS_TEST_VERIFY_FALSE(test_ptr, check_arg(args5)); // All valid. value_string = NULL; static const bs_arg_t valid_args[] = { BS_ARG_BOOL("b", "d", true, &value_bool), BS_ARG_ENUM("e", "d", "alpha", enum_test_table, &value_enum), BS_ARG_STRING("s", "d", "default", &value_string), BS_ARG_UINT32("u32", "d", 42, 0, UINT32_MAX, &value_uint32), BS_ARG_SENTINEL(), }; BS_TEST_VERIFY_TRUE(test_ptr, check_arg(valid_args)); } /* == End of arg.c ========================================================= */ wlmaker-0.8.1/submodules/libbase/src/avltree.c0000644000175100017510000010160215222270712020750 0ustar runnerrunner/* ========================================================================= */ /** * @file avltree.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** @private State of the AVL tree. */ struct _bs_avltree_t { /** Pointer to the root node. NULL indicates the tree is empty. */ bs_avltree_node_t *root_ptr; /** Number of nodes stored in this tree. */ size_t nodes; /** Points to the method for comparing nodes. */ bs_avltree_node_cmp_t cmp; /** Points to the method for destroying a node. May be NULL. */ bs_avltree_node_destroy_t destroy; }; static void bs_avltree_flush(bs_avltree_t *tree_ptr); static void bs_avltree_node_replace(bs_avltree_t *tree_ptr, bs_avltree_node_t *old_node_ptr, bs_avltree_node_t *new_node_ptr); static size_t bs_avltree_node_size(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr); static size_t bs_avltree_node_height(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr); static void bs_avltree_node_exchange(bs_avltree_t *tree_ptr, bs_avltree_node_t *old_node_ptr, bs_avltree_node_t *new_node_ptr); static void bs_avltree_node_delete(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr); static bool bs_avltree_rot_right(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr); static bool bs_avltree_rot_left(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr); static void bs_avltree_verify_node( bs_avltree_t *tree_ptr, const void *(*key_from_node)(const bs_avltree_node_t *node_ptr), bs_avltree_node_t *node_ptr, const void *min_key_ptr, const void *max_key_ptr); static bs_avltree_node_t *bs_avltree_node_min(bs_avltree_node_t *node_ptr); static bs_avltree_node_t *bs_avltree_node_max(bs_avltree_node_t *node_ptr); /* == Exported Functions =================================================== */ /* ------------------------------------------------------------------------- */ bs_avltree_t *bs_avltree_create(bs_avltree_node_cmp_t cmp, bs_avltree_node_destroy_t destroy) { bs_avltree_t *tree_ptr; tree_ptr = calloc(1, sizeof(bs_avltree_t)); if (NULL == tree_ptr) return NULL; tree_ptr->cmp = cmp; tree_ptr->destroy = destroy; return tree_ptr; } /* ------------------------------------------------------------------------- */ void bs_avltree_destroy(bs_avltree_t *tree_ptr) { bs_avltree_flush(tree_ptr); free(tree_ptr); } /* ------------------------------------------------------------------------- */ bs_avltree_node_t *bs_avltree_lookup(bs_avltree_t *tree_ptr, const void *key_ptr) { bs_avltree_node_t *node_ptr; int cmp_rv; node_ptr = tree_ptr->root_ptr; while (NULL != node_ptr) { cmp_rv = tree_ptr->cmp(node_ptr, key_ptr); if (0 == cmp_rv) { return node_ptr; } else if (0 < cmp_rv) { /* node larger than key. proceed on the left. */ node_ptr = node_ptr->left_ptr; } else { /* node less than key. proceed on the right. */ node_ptr = node_ptr->right_ptr; } } return NULL; } /* ------------------------------------------------------------------------- */ bool bs_avltree_insert(bs_avltree_t *tree_ptr, const void *new_key_ptr, bs_avltree_node_t *new_node_ptr, bool do_overwrite) { bs_avltree_node_t *node_ptr; bs_avltree_node_t *parent_ptr; bs_avltree_node_t *non_balanced_ptr; int cmp_rv; parent_ptr = NULL; non_balanced_ptr = NULL; node_ptr = tree_ptr->root_ptr; /* walk tree to appropriate bottom node. */ parent_ptr = NULL; while (NULL != node_ptr) { /* keep track of current node and of last non-balanced node */ parent_ptr = node_ptr; if (0 != node_ptr->balance) non_balanced_ptr = node_ptr; cmp_rv = tree_ptr->cmp(node_ptr, new_key_ptr); if (0 == cmp_rv) { /* node equals new node. replace, but only if requested. */ if (!do_overwrite) return false; bs_avltree_node_replace(tree_ptr, node_ptr, new_node_ptr); return true; } else if (0 < cmp_rv) { /* node greater than new node. proceed on the left. */ node_ptr = node_ptr->left_ptr; } else if (0 > cmp_rv) { /* node less than new node. proceed on the right. */ node_ptr = node_ptr->right_ptr; } } /* attach new node to bottom node */ tree_ptr->nodes++; new_node_ptr->parent_ptr = parent_ptr; if (NULL == new_node_ptr->parent_ptr) { /* was an empty tree, all fine. */ tree_ptr->root_ptr = new_node_ptr; return true; } /* attach to correct side of parent node */ if (0 < cmp_rv) { parent_ptr->left_ptr = new_node_ptr; } else { parent_ptr->right_ptr = new_node_ptr; } /* walk up along all balanced parents. adjust balance accordingly. */ node_ptr = new_node_ptr; while (parent_ptr != non_balanced_ptr) { if (parent_ptr->left_ptr == node_ptr) { parent_ptr->balance--; } else { parent_ptr->balance++; } node_ptr = parent_ptr; parent_ptr = node_ptr->parent_ptr; } /* return, if entire path was in full balance */ if (NULL == non_balanced_ptr) return true; /* tree was not balanced. adjust non-balanced node. */ if (non_balanced_ptr->left_ptr == node_ptr) { non_balanced_ptr->balance--; if (-2 >= non_balanced_ptr->balance) { if (0 < node_ptr->balance) { bs_avltree_rot_left(tree_ptr, node_ptr); } bs_avltree_rot_right(tree_ptr, non_balanced_ptr); } } else { non_balanced_ptr->balance++; if (2 <= non_balanced_ptr->balance) { if (0 > node_ptr->balance) { bs_avltree_rot_right(tree_ptr, node_ptr); } bs_avltree_rot_left(tree_ptr, non_balanced_ptr); } } return true; } /* ------------------------------------------------------------------------- */ bs_avltree_node_t *bs_avltree_delete(bs_avltree_t *tree_ptr, const void *key_ptr) { bs_avltree_node_t *node_ptr; node_ptr = bs_avltree_lookup(tree_ptr, key_ptr); if (NULL == node_ptr) { return NULL; } bs_avltree_node_delete(tree_ptr, node_ptr); return node_ptr; } /* ------------------------------------------------------------------------- */ size_t bs_avltree_size(const bs_avltree_t *tree_ptr) { return tree_ptr->nodes; } /* ------------------------------------------------------------------------- */ bs_avltree_node_t *bs_avltree_min(bs_avltree_t *tree_ptr) { bs_avltree_node_t *node_ptr; node_ptr = tree_ptr->root_ptr; if (NULL == node_ptr) { return NULL; } return bs_avltree_node_min(node_ptr); } /* ------------------------------------------------------------------------- */ bs_avltree_node_t *bs_avltree_max(bs_avltree_t *tree_ptr) { bs_avltree_node_t *node_ptr; node_ptr = tree_ptr->root_ptr; if (NULL == node_ptr) { return NULL; } return bs_avltree_node_max(node_ptr); } /* ------------------------------------------------------------------------- */ bs_avltree_node_t *bs_avltree_node_next(__UNUSED__ bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr) { if (NULL != node_ptr->right_ptr) { node_ptr = node_ptr->right_ptr; while (NULL != node_ptr->left_ptr) { node_ptr = node_ptr->left_ptr; } return node_ptr; } while ((NULL != node_ptr->parent_ptr) && (node_ptr == node_ptr->parent_ptr->right_ptr)) { node_ptr = node_ptr->parent_ptr; } return node_ptr->parent_ptr; } /* ------------------------------------------------------------------------- */ bs_avltree_node_t *bs_avltree_node_prev(__UNUSED__ bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr) { if (NULL != node_ptr->left_ptr) { node_ptr = node_ptr->left_ptr; while (NULL != node_ptr->right_ptr) { node_ptr = node_ptr->right_ptr; } return node_ptr; } while ((NULL != node_ptr->parent_ptr) && (node_ptr == node_ptr->parent_ptr->left_ptr)) { node_ptr = node_ptr->parent_ptr; } return node_ptr->parent_ptr; } /* ------------------------------------------------------------------------- */ int bs_avltree_cmp_ptr(const void *node_key_ptr, const void *key_ptr) { if (node_key_ptr < key_ptr) { return -1; } else if (node_key_ptr > key_ptr) { return 1; } else { return 0; } } /* == Local methods ======================================================== */ /* ------------------------------------------------------------------------- */ /** Will destroy (if given) each of the nodes. */ void bs_avltree_flush(bs_avltree_t *tree_ptr) { bs_avltree_node_t *node_ptr; bs_avltree_node_t *parent_ptr; node_ptr = tree_ptr->root_ptr; // Already mark the tree as inaccessible. tree_ptr->root_ptr = NULL; while (NULL != node_ptr) { /* walk tree downwards as far as possible */ if (NULL != node_ptr->left_ptr) { node_ptr = node_ptr->left_ptr; continue; } if (NULL != node_ptr->right_ptr) { node_ptr = node_ptr->right_ptr; continue; } /* store parent node, then clear bottom-most node */ parent_ptr = node_ptr->parent_ptr; *node_ptr = (bs_avltree_node_t){}; if (NULL != tree_ptr->destroy) { tree_ptr->destroy(node_ptr); } tree_ptr->nodes--; /* if the parent is NULL, we just flushed the root node. all done. */ if (NULL == parent_ptr) break; /* in parent: mark the just-deleted node as gone */ if (parent_ptr->left_ptr == node_ptr) { parent_ptr->left_ptr = NULL; } else { parent_ptr->right_ptr = NULL; } node_ptr = parent_ptr; } BS_ASSERT(0 == tree_ptr->nodes); } /* ------------------------------------------------------------------------- */ /** * Replaces the node at |old_node_ptr| with |new_node_ptr| in the |tree_ptr|. */ void bs_avltree_node_replace(bs_avltree_t *tree_ptr, bs_avltree_node_t *old_node_ptr, bs_avltree_node_t *new_node_ptr) { *new_node_ptr = *old_node_ptr; *old_node_ptr = (bs_avltree_node_t){}; if (NULL != tree_ptr->destroy) { tree_ptr->destroy(old_node_ptr); } /* update parent's (respectively root's) child node */ if (NULL == new_node_ptr->parent_ptr) { tree_ptr->root_ptr = new_node_ptr; } else { if (new_node_ptr->parent_ptr->left_ptr == old_node_ptr) { new_node_ptr->parent_ptr->left_ptr = new_node_ptr; } else { new_node_ptr->parent_ptr->right_ptr = new_node_ptr; } } /* update children's parent node */ if (NULL != new_node_ptr->left_ptr) { new_node_ptr->left_ptr->parent_ptr = new_node_ptr; } if (NULL != new_node_ptr->right_ptr) { new_node_ptr->right_ptr->parent_ptr = new_node_ptr; } } /* ------------------------------------------------------------------------- */ /** Returns the size of the subtree rooted at |node_ptr|. */ size_t bs_avltree_node_size(__UNUSED__ bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr) { if (NULL == node_ptr) return 0; return 1 + bs_avltree_node_size(tree_ptr, node_ptr->left_ptr) + bs_avltree_node_size(tree_ptr, node_ptr->right_ptr); } /* ------------------------------------------------------------------------- */ /** Returns the height of the subtree rooted at |node_ptr|. */ size_t bs_avltree_node_height(__UNUSED__ bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr) { size_t size_left = 0, size_right = 0; if (NULL != node_ptr->left_ptr) { size_left = bs_avltree_node_height(tree_ptr, node_ptr->left_ptr); } if (NULL != node_ptr->right_ptr) { size_right = bs_avltree_node_height(tree_ptr, node_ptr->right_ptr); } return size_left > size_right ? size_left + 1 : size_right + 1; } /* ------------------------------------------------------------------------- */ /** * Exchanges the position of |node1_ptr| and |node2_ptr| in the tree. * * @param tree_ptr * @param node1_ptr * @param node2_ptr * * @note This will not maintain the AVL tree integrity. */ void bs_avltree_node_exchange(bs_avltree_t *tree_ptr, bs_avltree_node_t *node1_ptr, bs_avltree_node_t *node2_ptr) { bs_avltree_node_t tmp_node; int pos1, pos2; /* Store relative position of the parents. We want to rebuild that. */ if (NULL == node1_ptr->parent_ptr) { pos1 = 0; } else if (node1_ptr->parent_ptr->left_ptr == node1_ptr) { pos1 = -1; } else { BS_ASSERT(node1_ptr->parent_ptr->right_ptr == node1_ptr); pos1 = 1; } if (NULL == node2_ptr->parent_ptr) { pos2 = 0; } else if (node2_ptr->parent_ptr->left_ptr == node2_ptr) { pos2 = -1; } else { BS_ASSERT(node2_ptr->parent_ptr->right_ptr == node2_ptr); pos2 = 1; } tmp_node = *node1_ptr; *node1_ptr = *node2_ptr; *node2_ptr = tmp_node; /* Catch the case where we just exchanged direct neighbors */ if (node1_ptr->parent_ptr == node1_ptr) { node1_ptr->parent_ptr = node2_ptr; } if (node1_ptr->left_ptr == node1_ptr) { node1_ptr->left_ptr = node2_ptr; } if (node1_ptr->right_ptr == node1_ptr) { node1_ptr->right_ptr = node2_ptr; } if (node2_ptr->parent_ptr == node2_ptr) { node2_ptr->parent_ptr = node1_ptr; } if (node2_ptr->left_ptr == node2_ptr) { node2_ptr->left_ptr = node1_ptr; } if (node2_ptr->right_ptr == node2_ptr) { node2_ptr->right_ptr = node1_ptr; } /* update children's parent node(s) */ if (NULL != node1_ptr->left_ptr) { node1_ptr->left_ptr->parent_ptr = node1_ptr; } if (NULL != node1_ptr->right_ptr) { node1_ptr->right_ptr->parent_ptr = node1_ptr; } if (NULL != node2_ptr->left_ptr) { node2_ptr->left_ptr->parent_ptr = node2_ptr; } if (NULL != node2_ptr->right_ptr) { node2_ptr->right_ptr->parent_ptr = node2_ptr; } /* update parent's child nodes */ if (0 == pos2) { tree_ptr->root_ptr = node1_ptr; } else if (0 > pos2) { node1_ptr->parent_ptr->left_ptr = node1_ptr; } else { node1_ptr->parent_ptr->right_ptr = node1_ptr; } if (0 == pos1) { tree_ptr->root_ptr = node2_ptr; } else if (0 > pos1) { node2_ptr->parent_ptr->left_ptr = node2_ptr; } else { node2_ptr->parent_ptr->right_ptr = node2_ptr; } } /* ------------------------------------------------------------------------- */ void bs_avltree_node_delete(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr) { bs_avltree_node_t *next_larger_node_ptr = NULL; if ((NULL != node_ptr->left_ptr) && (NULL != node_ptr->right_ptr)) { // Node with two children. Replace with the min of the right-hand-side // subtree. next_larger_node_ptr = bs_avltree_node_min(node_ptr->right_ptr); bs_avltree_node_exchange(tree_ptr, node_ptr, next_larger_node_ptr); } // Being here: |node_ptr| has either no or one child(ren). If there is any // child, swap it with that one. Then, |node_ptr| is a leaf and easily // detached. bs_avltree_node_t *leaf_node_ptr = (node_ptr->left_ptr != NULL) ? node_ptr->left_ptr : node_ptr->right_ptr; if (NULL != leaf_node_ptr) { // swap it with the one to the right. bs_avltree_node_exchange(tree_ptr, node_ptr, leaf_node_ptr); } if (NULL == node_ptr->parent_ptr) { tree_ptr->root_ptr = NULL; tree_ptr->nodes--; *node_ptr = (bs_avltree_node_t){}; return; } // Now we need to start backtracing... bs_avltree_node_t *parent_ptr = node_ptr->parent_ptr; bool removed_left = parent_ptr->left_ptr == node_ptr; // Now: Actually remove the node. if (removed_left) { parent_ptr->left_ptr = NULL; } else { parent_ptr->right_ptr = NULL; } *node_ptr = (bs_avltree_node_t){}; do { // Adjust balance according to which side the removal happened. if (removed_left) { parent_ptr->balance++; } else { parent_ptr->balance--; } if (parent_ptr->balance == 0) { node_ptr = parent_ptr; parent_ptr = node_ptr->parent_ptr; if (NULL != parent_ptr) { removed_left = parent_ptr->left_ptr == node_ptr; } continue; } if (parent_ptr->balance == -2) { // Leaning left. BS_ASSERT(parent_ptr->left_ptr != NULL); if (parent_ptr->left_ptr->balance > 0) { bs_avltree_rot_left(tree_ptr, parent_ptr->left_ptr); bs_avltree_rot_right(tree_ptr, parent_ptr); } else { BS_ASSERT(parent_ptr->left_ptr->left_ptr != NULL); if (bs_avltree_rot_right(tree_ptr, parent_ptr)) { break; } } } else if (parent_ptr->balance == 2) { // Leaning right. BS_ASSERT(parent_ptr->right_ptr != NULL); if (parent_ptr->right_ptr->balance < 0) { bs_avltree_rot_right(tree_ptr, parent_ptr->right_ptr); bs_avltree_rot_left(tree_ptr, parent_ptr); } else { BS_ASSERT(parent_ptr->right_ptr->right_ptr != NULL); if (bs_avltree_rot_left(tree_ptr, parent_ptr)) { break; } } } else { // Balance is only one off, no furter re-tracing needed. break; } node_ptr = parent_ptr->parent_ptr; parent_ptr = node_ptr->parent_ptr; if (NULL != parent_ptr) { removed_left = parent_ptr->left_ptr == node_ptr; } } while (NULL != parent_ptr); tree_ptr->nodes--; } /* ------------------------------------------------------------------------- */ /** * Rotate tree right around node_ptr (D): * * \?/ \?/ * D B * / \ / \ * * B E ==> A D * / \ / \ * * A C C E * * @return Whether this changed the subtree's height. */ bool bs_avltree_rot_right(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr) { bs_avltree_node_t *left_ptr; left_ptr = node_ptr->left_ptr; bool changed_height = 0 == left_ptr->balance; node_ptr->balance++; if (0 > left_ptr->balance) { node_ptr->balance -= left_ptr->balance; } left_ptr->balance++; if (0 < node_ptr->balance) { left_ptr->balance += node_ptr->balance; } /* establish D <-> C */ node_ptr->left_ptr = left_ptr->right_ptr; if (NULL != node_ptr->left_ptr) { node_ptr->left_ptr->parent_ptr = node_ptr; } /* establish B to D's parent */ left_ptr->parent_ptr = node_ptr->parent_ptr; if (NULL != left_ptr->parent_ptr) { if (left_ptr->parent_ptr->left_ptr == node_ptr) { left_ptr->parent_ptr->left_ptr = left_ptr; } else { left_ptr->parent_ptr->right_ptr = left_ptr; } } else { tree_ptr->root_ptr = left_ptr; } /* B -> D */ left_ptr->right_ptr = node_ptr; node_ptr->parent_ptr = left_ptr; return changed_height; } /* ------------------------------------------------------------------------- */ /** * Rotate tree left around node_ptr (B): * * \?/ \?/ * B D * / \ / \ * A D ==> B E * / \ / \ * C E A C * * @return Whether this changed the subtree's height. */ bool bs_avltree_rot_left(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr) { bs_avltree_node_t *right_ptr; right_ptr = node_ptr->right_ptr; bool changed_height = 0 == right_ptr->balance; node_ptr->balance--; if (0 < right_ptr->balance) { node_ptr->balance -= right_ptr->balance; } right_ptr->balance--; if (0 > node_ptr->balance) { right_ptr->balance += node_ptr->balance; } /* establish B <-> C */ node_ptr->right_ptr = right_ptr->left_ptr; if (NULL != right_ptr->left_ptr) { right_ptr->left_ptr->parent_ptr = node_ptr; } /* establish D to B's parent */ right_ptr->parent_ptr = node_ptr->parent_ptr; if (NULL != right_ptr->parent_ptr) { if (right_ptr->parent_ptr->left_ptr == node_ptr) { right_ptr->parent_ptr->left_ptr = right_ptr; } else { right_ptr->parent_ptr->right_ptr = right_ptr; } } else { tree_ptr->root_ptr = right_ptr; } /* D -> B */ right_ptr->left_ptr = node_ptr; node_ptr->parent_ptr = right_ptr; return changed_height; } /* ------------------------------------------------------------------------- */ /** * Verifies the tree's consistency. */ void bs_avltree_verify_node( bs_avltree_t *tree_ptr, const void *(*key_from_node)(const bs_avltree_node_t *node_ptr), bs_avltree_node_t *node_ptr, const void *min_key_ptr, const void *max_key_ptr) { const void* key_ptr; if (node_ptr == NULL) return; if (node_ptr->parent_ptr == NULL) { BS_ASSERT(tree_ptr->root_ptr == node_ptr); } else { BS_ASSERT((node_ptr == node_ptr->parent_ptr->left_ptr) || (node_ptr == node_ptr->parent_ptr->right_ptr)); } if (NULL != min_key_ptr) { BS_ASSERT(tree_ptr->cmp(node_ptr, min_key_ptr) > 0); } if (NULL != max_key_ptr) { BS_ASSERT(tree_ptr->cmp(node_ptr, max_key_ptr) < 0); } if (NULL != key_from_node) { key_ptr = key_from_node(node_ptr); } else { key_ptr = NULL; } int height_left = 0, height_right = 0; if (NULL != node_ptr->left_ptr) { BS_ASSERT(node_ptr->left_ptr->parent_ptr == node_ptr); bs_avltree_verify_node(tree_ptr, key_from_node, node_ptr->left_ptr, min_key_ptr, key_ptr); height_left = bs_avltree_node_height(tree_ptr, node_ptr->left_ptr); } if (NULL != node_ptr->right_ptr) { BS_ASSERT(node_ptr->right_ptr->parent_ptr == node_ptr); bs_avltree_verify_node(tree_ptr, key_from_node, node_ptr->right_ptr, key_ptr, max_key_ptr); height_right = bs_avltree_node_height(tree_ptr, node_ptr->right_ptr); } BS_ASSERT(node_ptr->balance == height_right - height_left); } /* ------------------------------------------------------------------------- */ /** * @param node_ptr * * @return The minimum node of the given subtree. */ bs_avltree_node_t *bs_avltree_node_min(bs_avltree_node_t *node_ptr) { if (NULL == node_ptr->left_ptr) { return node_ptr; } return bs_avltree_node_min(node_ptr->left_ptr); } /* ------------------------------------------------------------------------- */ /** * @param node_ptr * * @return The minimum node of the given subtree. */ bs_avltree_node_t *bs_avltree_node_max(bs_avltree_node_t *node_ptr) { if (NULL == node_ptr->right_ptr) { return node_ptr; } return bs_avltree_node_max(node_ptr->right_ptr); } /* == Unit tests ======================================================== */ /** @cond TEST */ #define BS_AVLTREE_TEST_VALUES 4096 #define BS_AVLTREE_TEST_VALUE_MAX 3500 /** @internal */ typedef struct { bs_avltree_node_t node; int value; } bs_avltree_test_node_t; static bs_avltree_test_node_t *bs_avltree_test_node_create(int value); static void bs_avltree_test_node_destroy(bs_avltree_node_t *node_ptr); static int bs_avltree_test_node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr); static const void *bs_avltree_test_node_key(const bs_avltree_node_t *node_ptr); static void bs_avltree_test_random(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_avltree_test_cases[] = { { true, "random", bs_avltree_test_random }, { false, NULL, NULL } }; const bs_test_set_t bs_avltree_test_set = BS_TEST_SET( true, "avltree", bs_avltree_test_cases); /* ------------------------------------------------------------------------- */ void bs_avltree_test_random(bs_test_t *test_ptr) { char test_flag[BS_AVLTREE_TEST_VALUE_MAX] = {}; int random_values[BS_AVLTREE_TEST_VALUES] = {}; bs_avltree_test_node_t *node_ptr; bs_avltree_t *tree_ptr; int value_idx, value; int min_value, max_value; int outcome, expected_outcome; bool do_overwrite; size_t nodes; /* setup BS_AVLTREE_TEST_NUM random values. Multiple occurrences OK */ srand(12345); max_value = 0; min_value = BS_AVLTREE_TEST_VALUE_MAX; for (value_idx = 0; value_idx < BS_AVLTREE_TEST_VALUES; value_idx++) { value = rand() % BS_AVLTREE_TEST_VALUE_MAX; random_values[value_idx] = value; if (min_value > value) min_value = value; if (max_value < value) max_value = value; } /* build tree. should better work */ tree_ptr = bs_avltree_create(bs_avltree_test_node_cmp, bs_avltree_test_node_destroy); BS_TEST_VERIFY_NEQ(test_ptr, tree_ptr, NULL); /* run through all random values and add each one to the tree */ nodes = 0; for (value_idx = 0; value_idx < BS_AVLTREE_TEST_VALUES; value_idx++) { value = random_values[value_idx]; /* setup node */ node_ptr = bs_avltree_test_node_create(value); node_ptr->value = value; BS_TEST_VERIFY_NEQ(test_ptr, tree_ptr, NULL); if (NULL == node_ptr) break; /* specific handling for multiple occurrences */ switch (test_flag[value]) { case 0: /* first attempt: must insert */ expected_outcome = true; nodes++; do_overwrite = false; break; case 1: /* second atttempt: overwrite = false, must reject */ expected_outcome = false; do_overwrite = false; break; default: /* third+ attempt(s): overwrite = true, must insert */ expected_outcome = true; do_overwrite = true; break; } test_flag[value]++; /* insert and verify outcome */ outcome = bs_avltree_insert( tree_ptr, &node_ptr->value, &node_ptr->node, do_overwrite); BS_TEST_VERIFY_EQ(test_ptr, outcome, expected_outcome); BS_TEST_VERIFY_NEQ(test_ptr, tree_ptr->root_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, tree_ptr->nodes, nodes); BS_ASSERT(tree_ptr->nodes == bs_avltree_node_size( tree_ptr, tree_ptr->root_ptr)); bs_avltree_verify_node(tree_ptr, bs_avltree_test_node_key, tree_ptr->root_ptr, NULL, NULL); /* clean up if node was rejected */ if (!outcome) { bs_avltree_test_node_destroy(&node_ptr->node); } } BS_TEST_VERIFY_EQ(test_ptr, nodes, bs_avltree_size(tree_ptr)); /* some lookup operations */ for (value_idx = 0; value_idx < BS_AVLTREE_TEST_VALUES; value_idx++) { value = random_values[value_idx]; node_ptr = (bs_avltree_test_node_t*) bs_avltree_lookup(tree_ptr, &value); BS_TEST_VERIFY_NEQ(test_ptr, node_ptr, NULL); if (NULL != node_ptr) { BS_TEST_VERIFY_EQ(test_ptr, node_ptr->value, value); } } /* Verify min & max operations */ node_ptr = (bs_avltree_test_node_t*)bs_avltree_min(tree_ptr); BS_TEST_VERIFY_EQ(test_ptr, node_ptr->value, min_value); node_ptr = (bs_avltree_test_node_t*)bs_avltree_max(tree_ptr); BS_TEST_VERIFY_EQ(test_ptr, node_ptr->value, max_value); /* Step through the tree, verify it is strictly monotously increasing */ value = 0; node_ptr = (bs_avltree_test_node_t*)bs_avltree_min(tree_ptr); do { while ((value < BS_AVLTREE_TEST_VALUE_MAX) && (0 == test_flag[value])) { value++; } BS_TEST_VERIFY_EQ(test_ptr, value, node_ptr->value); node_ptr = (bs_avltree_test_node_t*) bs_avltree_node_next(tree_ptr, &node_ptr->node); value++; } while (NULL != node_ptr); while ((value < BS_AVLTREE_TEST_VALUE_MAX) && (0 == test_flag[value])) { value++; } BS_TEST_VERIFY_EQ(test_ptr, value, BS_AVLTREE_TEST_VALUE_MAX); /* deletion operation. Remove half the nodes, the others to flush */ for (value_idx = 0; value_idx < BS_AVLTREE_TEST_VALUES; value_idx++) { value = random_values[value_idx]; node_ptr = (bs_avltree_test_node_t*) bs_avltree_delete(tree_ptr, &value); if (node_ptr != NULL) { BS_TEST_VERIFY_EQ(test_ptr, node_ptr->value, value); bs_avltree_test_node_destroy(&node_ptr->node); } BS_ASSERT(tree_ptr->nodes == bs_avltree_node_size(tree_ptr, tree_ptr->root_ptr)); bs_avltree_verify_node(tree_ptr, bs_avltree_test_node_key, tree_ptr->root_ptr, NULL, NULL); } /* Attempt to delete a non-existing node. */ value = BS_AVLTREE_TEST_VALUE_MAX; BS_ASSERT(NULL == bs_avltree_delete(tree_ptr, &value)); bs_avltree_flush(tree_ptr); BS_TEST_VERIFY_EQ(test_ptr, tree_ptr->root_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_avltree_size(tree_ptr)); bs_avltree_destroy(tree_ptr); bs_test_succeed(test_ptr, "Operations with %d randomized nodes", BS_AVLTREE_TEST_VALUES); } /* ------------------------------------------------------------------------- */ bs_avltree_test_node_t *bs_avltree_test_node_create(int value) { bs_avltree_test_node_t *node_ptr; node_ptr = calloc(1, sizeof(bs_avltree_test_node_t)); if (NULL == node_ptr) return NULL; node_ptr->value = value; return node_ptr; } /* ------------------------------------------------------------------------- */ void bs_avltree_test_node_destroy(bs_avltree_node_t *node_ptr) { free(node_ptr); } /* ------------------------------------------------------------------------- */ int bs_avltree_test_node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr) { const bs_avltree_test_node_t *bs_node_ptr; const int *int_key_ptr; bs_node_ptr = (const bs_avltree_test_node_t*)node_ptr; int_key_ptr = (const int*)key_ptr; if (bs_node_ptr->value < *int_key_ptr) return -1; if (bs_node_ptr->value > *int_key_ptr) return 1; return 0; } /* ------------------------------------------------------------------------- */ const void *bs_avltree_test_node_key(const bs_avltree_node_t *node_ptr) { const bs_avltree_test_node_t *bs_node_ptr; bs_node_ptr = (const bs_avltree_test_node_t*)node_ptr; return &bs_node_ptr->value; } /** @endcond */ /* == End of avltree.c ===================================================== */ wlmaker-0.8.1/submodules/libbase/src/ptr_set.c0000644000175100017510000001437115222270712020774 0ustar runnerrunner/* ========================================================================= */ /** * @file ptr_set.c * * Interface for a simple stack to store pointers. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** A set. */ struct _bs_ptr_set_t { /** Maps to the tree holding the set elements. */ bs_avltree_t *tree_ptr; }; /** Holder for one element. */ typedef struct { /** The tree node. */ bs_avltree_node_t avlnode; /** Element pointer. */ void *elem_ptr; } bs_ptr_set_elem_holder_t; static int elem_compare( const bs_avltree_node_t *node_ptr, const void *key_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bs_ptr_set_t *bs_ptr_set_create(void) { bs_ptr_set_t *set_ptr = logged_calloc(1, sizeof(bs_ptr_set_t)); if (NULL == set_ptr) return NULL; set_ptr->tree_ptr = bs_avltree_create(elem_compare, NULL); if (NULL == set_ptr->tree_ptr) { bs_ptr_set_destroy(set_ptr); return NULL; } return set_ptr; } /* ------------------------------------------------------------------------- */ void bs_ptr_set_destroy(bs_ptr_set_t *set_ptr) { if (NULL != set_ptr->tree_ptr) { bs_avltree_destroy(set_ptr->tree_ptr); set_ptr->tree_ptr = NULL; } free(set_ptr); } /* ------------------------------------------------------------------------- */ bool bs_ptr_set_insert(bs_ptr_set_t *set_ptr, void *elem_ptr) { bs_ptr_set_elem_holder_t *holder_ptr = logged_calloc( 1, sizeof(bs_ptr_set_elem_holder_t)); if (NULL == holder_ptr) return false; holder_ptr->elem_ptr = elem_ptr; bool rv = bs_avltree_insert( set_ptr->tree_ptr, holder_ptr->elem_ptr, &holder_ptr->avlnode, false); if (!rv) free(holder_ptr); return rv; } /* ------------------------------------------------------------------------- */ void bs_ptr_set_erase(bs_ptr_set_t *set_ptr, void *elem_ptr) { bs_avltree_node_t *node_ptr = bs_avltree_delete( set_ptr->tree_ptr, elem_ptr); if (NULL != node_ptr) { bs_ptr_set_elem_holder_t *holder_ptr = BS_CONTAINER_OF( node_ptr, bs_ptr_set_elem_holder_t, avlnode); free(holder_ptr); } } /* ------------------------------------------------------------------------- */ bool bs_ptr_set_contains(bs_ptr_set_t *set_ptr, void *elem_ptr) { return NULL != bs_avltree_lookup(set_ptr->tree_ptr, elem_ptr); } /* ------------------------------------------------------------------------- */ void *bs_ptr_set_any(bs_ptr_set_t *set_ptr) { bs_avltree_node_t *avlnode_ptr = bs_avltree_min(set_ptr->tree_ptr); if (NULL == avlnode_ptr) return NULL; bs_ptr_set_elem_holder_t *holder_ptr = BS_CONTAINER_OF( avlnode_ptr, bs_ptr_set_elem_holder_t, avlnode); return holder_ptr->elem_ptr; } /* ------------------------------------------------------------------------- */ bool bs_ptr_set_empty(bs_ptr_set_t *set_ptr) { return 0 == bs_avltree_size(set_ptr->tree_ptr); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Compares two element pointers. * * @param node_ptr * @param key_ptr * * @return As specified by @ref bs_avltree_node_cmp_t. */ int elem_compare(const bs_avltree_node_t *node_ptr, const void *key_ptr) { const bs_ptr_set_elem_holder_t *holder_ptr = BS_CONTAINER_OF( node_ptr, const bs_ptr_set_elem_holder_t, avlnode); return (size_t)key_ptr - (size_t)holder_ptr->elem_ptr; } /* == Test Functions ======================================================= */ static void bs_ptr_set_test(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_ptr_set_test_cases[] = { { 1, "test", bs_ptr_set_test }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bs_ptr_set_test_set = BS_TEST_SET( true, "ptr_set", bs_ptr_set_test_cases); void bs_ptr_set_test(bs_test_t *test_ptr) { bs_ptr_set_t *set_ptr = bs_ptr_set_create(); BS_TEST_VERIFY_NEQ(test_ptr, set_ptr, NULL); void *data1_ptr = (void*)-1; void *data2_ptr = (void*)2; BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_empty(set_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_contains(set_ptr, data1_ptr)); BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_ptr_set_any(set_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_insert(set_ptr, data1_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_empty(set_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_contains(set_ptr, data1_ptr)); BS_TEST_VERIFY_EQ(test_ptr, data1_ptr, bs_ptr_set_any(set_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_insert(set_ptr, data1_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_insert(set_ptr, data2_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_contains(set_ptr, data1_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_contains(set_ptr, data2_ptr)); BS_TEST_VERIFY_NEQ(test_ptr, NULL, bs_ptr_set_any(set_ptr)); bs_ptr_set_erase(set_ptr, data1_ptr); bs_ptr_set_erase(set_ptr, data2_ptr); BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_contains(set_ptr, data1_ptr)); BS_TEST_VERIFY_FALSE(test_ptr, bs_ptr_set_contains(set_ptr, data2_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_set_empty(set_ptr)); bs_ptr_set_destroy(set_ptr); } /* == End of ptr_set.c ===================================================== */ wlmaker-0.8.1/submodules/libbase/src/ptr_vector.c0000644000175100017510000001461715222270712021506 0ustar runnerrunner/* ========================================================================= */ /** * @file ptr_vector.c * * Interface for a simple vector to store pointers. * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** Initial vector size, 1024 elements. */ #define INITIAL_SIZE 1024 static bool _bs_ptr_vector_grow(bs_ptr_vector_t *ptr_vector_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool bs_ptr_vector_init(bs_ptr_vector_t *ptr_vector_ptr) { ptr_vector_ptr->capacity = 0; ptr_vector_ptr->consumed = 0; ptr_vector_ptr->elements_ptr = NULL; _bs_ptr_vector_grow(ptr_vector_ptr); return true; } /* ------------------------------------------------------------------------- */ void bs_ptr_vector_fini(bs_ptr_vector_t *ptr_vector_ptr) { if (NULL != ptr_vector_ptr->elements_ptr) { if (0 < ptr_vector_ptr->consumed) { bs_log(BS_WARNING, "Un-initializing non-empty vector at %p", ptr_vector_ptr); } free(ptr_vector_ptr->elements_ptr); ptr_vector_ptr->elements_ptr = NULL; } ptr_vector_ptr->capacity = 0; ptr_vector_ptr->consumed = 0; } /* ------------------------------------------------------------------------- */ size_t bs_ptr_vector_size(bs_ptr_vector_t *ptr_vector_ptr) { return ptr_vector_ptr->consumed; } /* ------------------------------------------------------------------------- */ bool bs_ptr_vector_push_back(bs_ptr_vector_t *ptr_vector_ptr, void *data_ptr) { while (ptr_vector_ptr->consumed >= ptr_vector_ptr->capacity) { if (!_bs_ptr_vector_grow(ptr_vector_ptr)) return false; } ptr_vector_ptr->elements_ptr[ptr_vector_ptr->consumed++] = data_ptr; return true; } /* ------------------------------------------------------------------------- */ bool bs_ptr_vector_erase(bs_ptr_vector_t *ptr_vector_ptr, size_t pos) { if (pos >= ptr_vector_ptr->consumed) return false; if (pos + 1 < ptr_vector_ptr->consumed) { memmove(ptr_vector_ptr->elements_ptr + pos, ptr_vector_ptr->elements_ptr + pos + 1, (ptr_vector_ptr->consumed - pos - 1) * sizeof(void*)); } --ptr_vector_ptr->consumed; return true; } /* ------------------------------------------------------------------------- */ void* bs_ptr_vector_at(bs_ptr_vector_t *ptr_vector_ptr, size_t pos) { BS_ASSERT(pos < ptr_vector_ptr->consumed); return ptr_vector_ptr->elements_ptr[pos]; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Grows the vector, by INITIAL_SIZE elements. * * @param ptr_vector_ptr * * @return true on success. */ bool _bs_ptr_vector_grow(bs_ptr_vector_t *ptr_vector_ptr) { size_t new_size = ptr_vector_ptr->capacity + INITIAL_SIZE; void *new_elements_ptr = realloc( ptr_vector_ptr->elements_ptr, new_size * sizeof(void*)); if (NULL == new_elements_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)", ptr_vector_ptr->elements_ptr, new_size * sizeof(void*)); return false; } ptr_vector_ptr->capacity = new_size; ptr_vector_ptr->elements_ptr = new_elements_ptr; return true; } /* == Unit tests =========================================================== */ static void basic_test(bs_test_t *test_ptr); static void large_test(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_ptr_vector_test_cases[] = { { true, "basic", basic_test }, { true, "large", large_test }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bs_ptr_vector_test_set = BS_TEST_SET( true, "ptr_vector", bs_ptr_vector_test_cases); /* ------------------------------------------------------------------------- */ /** Tests basic functionality. */ void basic_test(bs_test_t *test_ptr) { bs_ptr_vector_t ptr_vector; char e = 'e'; BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_init(&ptr_vector)); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_ptr_vector_size(&ptr_vector)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_push_back(&ptr_vector, &e)); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_ptr_vector_size(&ptr_vector)); BS_TEST_VERIFY_EQ(test_ptr, &e, bs_ptr_vector_at(&ptr_vector, 0)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_erase(&ptr_vector, 0)); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_ptr_vector_size(&ptr_vector)); bs_ptr_vector_fini(&ptr_vector); } /* ------------------------------------------------------------------------- */ /** Tests with enough elements to trigger growth. */ void large_test(bs_test_t *test_ptr) { bs_ptr_vector_t vec; char e[2 * INITIAL_SIZE]; BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_init(&vec)); for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) { BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_push_back(&vec, &e[i])); } for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) { BS_TEST_VERIFY_EQ(test_ptr, &e[i], bs_ptr_vector_at(&vec, i)); } for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) { BS_TEST_VERIFY_EQ(test_ptr, &e[i], bs_ptr_vector_at(&vec, 0)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_vector_erase(&vec, 0)); if (0 < bs_ptr_vector_size(&vec)) { BS_TEST_VERIFY_EQ( test_ptr, &e[2 * INITIAL_SIZE - 1], bs_ptr_vector_at(&vec, bs_ptr_vector_size(&vec) - 1)); } } bs_ptr_vector_fini(&vec); } /* == End of ptr_vector.c ================================================== */ wlmaker-0.8.1/submodules/libbase/src/dllist.c0000644000175100017510000007274615222270712020621 0ustar runnerrunner/* ========================================================================= */ /** * @file dllist.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include static bool find_is(bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static void assert_consistency(const bs_dllist_t *list_ptr); static bool node_orphaned(const bs_dllist_node_t *dlnode_ptr); void _bs_dllist_sort_split_and_merge( bs_dllist_t *list_ptr, size_t list_size, int (*compare_fn)(const bs_dllist_node_t *, const bs_dllist_node_t*)); static bs_dllist_t _bs_dllist_sort_merge( bs_dllist_t *left_ptr, bs_dllist_t *right_ptr, int (*compare_fn)(const bs_dllist_node_t *, const bs_dllist_node_t*)); /* == Exported Functions =================================================== */ /* ------------------------------------------------------------------------- */ size_t bs_dllist_size(const bs_dllist_t *list_ptr) { size_t count; bs_dllist_node_t *node_ptr; node_ptr = list_ptr->head_ptr; count = 0; while (NULL != node_ptr) { node_ptr = node_ptr->next_ptr; count++; } return count; } /* ------------------------------------------------------------------------- */ bool bs_dllist_empty(const bs_dllist_t *list_ptr) { return (NULL == list_ptr->head_ptr); } /* ------------------------------------------------------------------------- */ void bs_dllist_push_back(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr) { BS_ASSERT(NULL == node_ptr->prev_ptr); BS_ASSERT(NULL == node_ptr->next_ptr); if (NULL != list_ptr->tail_ptr) { BS_ASSERT(NULL != list_ptr->head_ptr); BS_ASSERT(NULL == list_ptr->tail_ptr->next_ptr); node_ptr->prev_ptr = list_ptr->tail_ptr; list_ptr->tail_ptr->next_ptr = node_ptr; list_ptr->tail_ptr = node_ptr; } else { BS_ASSERT(NULL == list_ptr->head_ptr); list_ptr->head_ptr = node_ptr; list_ptr->tail_ptr = node_ptr; } } /* ------------------------------------------------------------------------- */ void bs_dllist_push_front(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr) { BS_ASSERT(NULL == node_ptr->prev_ptr); BS_ASSERT(NULL == node_ptr->next_ptr); if (NULL != list_ptr->head_ptr) { BS_ASSERT(NULL != list_ptr->tail_ptr); BS_ASSERT(NULL == list_ptr->head_ptr->prev_ptr); node_ptr->next_ptr = list_ptr->head_ptr; list_ptr->head_ptr->prev_ptr = node_ptr; list_ptr->head_ptr = node_ptr; } else { BS_ASSERT(NULL == list_ptr->head_ptr); list_ptr->head_ptr = node_ptr; list_ptr->tail_ptr = node_ptr; } } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *bs_dllist_pop_back(bs_dllist_t *list_ptr) { bs_dllist_node_t *node_ptr; if (NULL == list_ptr->tail_ptr) { BS_ASSERT(NULL == list_ptr->head_ptr); return NULL; } BS_ASSERT(NULL != list_ptr->head_ptr); node_ptr = list_ptr->tail_ptr; list_ptr->tail_ptr = node_ptr->prev_ptr; if (NULL != list_ptr->tail_ptr) { list_ptr->tail_ptr->next_ptr = NULL; } else { BS_ASSERT(list_ptr->head_ptr == node_ptr); list_ptr->head_ptr = NULL; } node_ptr->prev_ptr = NULL; return node_ptr; } /* ------------------------------------------------------------------------- */ bs_dllist_node_t * bs_dllist_pop_front(bs_dllist_t *list_ptr) { bs_dllist_node_t *node_ptr; if (NULL == list_ptr->head_ptr) { BS_ASSERT(NULL == list_ptr->tail_ptr); return NULL; } BS_ASSERT(NULL != list_ptr->tail_ptr); node_ptr = list_ptr->head_ptr; list_ptr->head_ptr = node_ptr->next_ptr; if (NULL != list_ptr->head_ptr) { list_ptr->head_ptr->prev_ptr = NULL; } else { BS_ASSERT(list_ptr->tail_ptr == node_ptr); list_ptr->tail_ptr = NULL; } node_ptr->next_ptr = NULL; return node_ptr; } /* ------------------------------------------------------------------------- */ void bs_dllist_remove(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr) { BS_ASSERT(bs_dllist_contains(list_ptr, node_ptr)); if (NULL == node_ptr->prev_ptr) { BS_ASSERT(list_ptr->head_ptr == node_ptr); list_ptr->head_ptr = node_ptr->next_ptr; } else { node_ptr->prev_ptr->next_ptr = node_ptr->next_ptr; } if (NULL == node_ptr->next_ptr) { BS_ASSERT(list_ptr->tail_ptr == node_ptr); list_ptr->tail_ptr = node_ptr->prev_ptr; } else { node_ptr->next_ptr->prev_ptr = node_ptr->prev_ptr; } node_ptr->prev_ptr = NULL; node_ptr->next_ptr = NULL; } /* ------------------------------------------------------------------------- */ void bs_dllist_insert_node_before( bs_dllist_t *list_ptr, bs_dllist_node_t *reference_node_ptr, bs_dllist_node_t *new_node_ptr) { BS_ASSERT(bs_dllist_contains(list_ptr, reference_node_ptr)); BS_ASSERT(node_orphaned(new_node_ptr)); if (NULL == reference_node_ptr->prev_ptr) { bs_dllist_push_front(list_ptr, new_node_ptr); return; } reference_node_ptr->prev_ptr->next_ptr = new_node_ptr; new_node_ptr->prev_ptr = reference_node_ptr->prev_ptr; reference_node_ptr->prev_ptr = new_node_ptr; new_node_ptr->next_ptr = reference_node_ptr; } /* ------------------------------------------------------------------------- */ void bs_dllist_append(bs_dllist_t *list_ptr, bs_dllist_t *appendix_ptr) { if (bs_dllist_empty(appendix_ptr)) return; if (bs_dllist_empty(list_ptr)) { *list_ptr = *appendix_ptr; *appendix_ptr = (bs_dllist_t){}; return; } list_ptr->tail_ptr->next_ptr = appendix_ptr->head_ptr; appendix_ptr->head_ptr->prev_ptr = list_ptr->tail_ptr; list_ptr->tail_ptr = appendix_ptr->tail_ptr; *appendix_ptr = (bs_dllist_t){}; } /* ------------------------------------------------------------------------- */ bool bs_dllist_contains( const bs_dllist_t *list_ptr, bs_dllist_node_t *dlnode_ptr) { return bs_dllist_find(list_ptr, find_is, dlnode_ptr) == dlnode_ptr; } /* ------------------------------------------------------------------------- */ bs_dllist_node_t *bs_dllist_find( const bs_dllist_t *list_ptr, bool (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr) { for (bs_dllist_node_t *dlnode_ptr = list_ptr->head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { if (func(dlnode_ptr, ud_ptr)) return dlnode_ptr; } return NULL; } /* ------------------------------------------------------------------------- */ void bs_dllist_for_each( const bs_dllist_t *list_ptr, void (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr) { bs_dllist_node_t *dlnode_ptr = list_ptr->head_ptr; while (NULL != dlnode_ptr) { bs_dllist_node_t *current_dlnode_ptr = dlnode_ptr; dlnode_ptr = dlnode_ptr->next_ptr; func(current_dlnode_ptr, ud_ptr); } } /* ------------------------------------------------------------------------- */ bool bs_dllist_all( const bs_dllist_t *list_ptr, bool (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr) { for (bs_dllist_node_t *dlnode_ptr = list_ptr->head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { if (!func(dlnode_ptr, ud_ptr)) return false; } return true; } /* ------------------------------------------------------------------------- */ bool bs_dllist_any( const bs_dllist_t *list_ptr, bool (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr) { for (bs_dllist_node_t *dlnode_ptr = list_ptr->head_ptr; dlnode_ptr != NULL; dlnode_ptr = dlnode_ptr->next_ptr) { if (func(dlnode_ptr, ud_ptr)) return true; } return false; } /* ------------------------------------------------------------------------- */ void bs_dllist_sort( bs_dllist_t *list_ptr, int (*compare_fn)(const bs_dllist_node_t *, const bs_dllist_node_t*)) { _bs_dllist_sort_split_and_merge( list_ptr, bs_dllist_size(list_ptr), compare_fn); } /* == Local methods ======================================================== */ /* ------------------------------------------------------------------------- */ void assert_consistency(const bs_dllist_t *list_ptr) { const bs_dllist_node_t *node_ptr; // An empty list is consistent if both head/tail are NULL. if (NULL == list_ptr->head_ptr || NULL == list_ptr->tail_ptr) { BS_ASSERT(NULL == list_ptr->head_ptr); BS_ASSERT(NULL == list_ptr->tail_ptr); return; } // Head and tail don't continue further. BS_ASSERT(NULL == list_ptr->head_ptr->prev_ptr); BS_ASSERT(NULL == list_ptr->tail_ptr->next_ptr); for (node_ptr = list_ptr->head_ptr; node_ptr != NULL; node_ptr = node_ptr->next_ptr) { if (NULL != node_ptr->next_ptr) { BS_ASSERT(node_ptr == node_ptr->next_ptr->prev_ptr); } else { BS_ASSERT(node_ptr == list_ptr->tail_ptr); } } } /* ------------------------------------------------------------------------- */ /** Returns whether dlnode_ptr == ud_ptr. */ bool find_is(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { return dlnode_ptr == ud_ptr; } /* ------------------------------------------------------------------------- */ /** * Returns whether |dlnode_ptr| is orphaned, ie. not in any list. * * Note: This does not guarantee that dlnode_ptr is not member of a list, * merely that it refers no neighbours. The case of it being the single list * node cannot be distinguished. * * @param dlnode_ptr * * @reutrn Whether the list holds no references. */ bool node_orphaned(const bs_dllist_node_t *dlnode_ptr) { return (NULL == dlnode_ptr->prev_ptr) && (NULL == dlnode_ptr->next_ptr); } /* ------------------------------------------------------------------------- */ /** Implements merge sort for `list_ptr` with known size. */ void _bs_dllist_sort_split_and_merge( bs_dllist_t *list_ptr, size_t list_size, int (*compare_fn)(const bs_dllist_node_t *, const bs_dllist_node_t*)) { if (1 >= list_size) return; // Split the lists. Since n >= 2, we have non-zero elements on each side. bs_dllist_t left = {}, right = {}; size_t left_size = 0; bs_dllist_node_t *dln = list_ptr->head_ptr; for (; left_size < list_size / 2; ++left_size) dln = dln->next_ptr; left = (bs_dllist_t){ .head_ptr = list_ptr->head_ptr, .tail_ptr = dln->prev_ptr }; right = (bs_dllist_t){ .head_ptr = dln, .tail_ptr = list_ptr->tail_ptr }; left.tail_ptr->next_ptr = NULL; right.head_ptr->prev_ptr = NULL; _bs_dllist_sort_split_and_merge(&left, left_size, compare_fn); _bs_dllist_sort_split_and_merge(&right, list_size - left_size, compare_fn); *list_ptr = _bs_dllist_sort_merge(&left, &right, compare_fn); } /* ------------------------------------------------------------------------- */ /** Sorted merge lists. `left_ptr` and `right_ptr` expected to be sorted. */ bs_dllist_t _bs_dllist_sort_merge( bs_dllist_t *left_ptr, bs_dllist_t *right_ptr, int (*compare_fn)(const bs_dllist_node_t *, const bs_dllist_node_t*)) { bs_dllist_t merged = {}; while (NULL != left_ptr->head_ptr && NULL != right_ptr->head_ptr) { if (0 >= compare_fn(left_ptr->head_ptr, right_ptr->head_ptr)) { bs_dllist_push_back(&merged, bs_dllist_pop_front(left_ptr)); } else { bs_dllist_push_back(&merged, bs_dllist_pop_front(right_ptr)); } } bs_dllist_append(&merged, left_ptr); bs_dllist_append(&merged, right_ptr); return merged; } /* == Tests =============================================================== */ static void bs_dllist_test_back(bs_test_t *test_ptr); static void bs_dllist_test_front(bs_test_t *test_ptr); static void bs_dllist_test_remove(bs_test_t *test_ptr); static void bs_dllist_test_insert(bs_test_t *test_ptr); static void bs_dllist_test_append(bs_test_t *test_ptr); static void bs_dllist_test_find(bs_test_t *test_ptr); static void bs_dllist_test_for_each(bs_test_t *test_ptr); static void bs_dllist_test_for_each_dtor(bs_test_t *test_ptr); static void bs_dllist_test_all(bs_test_t *test_ptr); static void bs_dllist_test_any(bs_test_t *test_ptr); static void bs_dllist_test_sort(bs_test_t *test_ptr); static void bs_dllist_test_sort_random(bs_test_t *test_ptr); static void bs_dllist_test_iterator(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_dllist_test_cases[] = { { true, "push/pop back", bs_dllist_test_back }, { true, "push/pop front", bs_dllist_test_front }, { true, "remove", bs_dllist_test_remove }, { true, "insert", bs_dllist_test_insert }, { true, "append", bs_dllist_test_append }, { true, "find", bs_dllist_test_find }, { true, "for_each", bs_dllist_test_for_each }, { true, "for_each_dtor", bs_dllist_test_for_each_dtor }, { true, "all", bs_dllist_test_all }, { true, "any", bs_dllist_test_any }, { true, "sort", bs_dllist_test_sort }, { true, "sort_random", bs_dllist_test_sort_random }, { true, "iterator", bs_dllist_test_iterator }, { false, NULL, NULL } }; const bs_test_set_t bs_dllist_test_set = BS_TEST_SET( true, "dllist", bs_dllist_test_cases); /* ------------------------------------------------------------------------- */ /** * Tests that push_back and pop_back works. */ void bs_dllist_test_back(bs_test_t *test_ptr) { bs_dllist_t list1 = {}; bs_dllist_node_t node1 = {}, node2 = {}, node3 = {}; bs_dllist_push_back(&list1, &node1); bs_dllist_push_back(&list1, &node2); bs_dllist_push_back(&list1, &node3); BS_TEST_VERIFY_EQ(test_ptr, node1.prev_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, &node2); BS_TEST_VERIFY_EQ(test_ptr, node2.prev_ptr, &node1); BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node3); BS_TEST_VERIFY_EQ(test_ptr, node3.prev_ptr, &node2); BS_TEST_VERIFY_EQ(test_ptr, node3.next_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, &node3, bs_dllist_pop_back(&list1)); BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.next_ptr); BS_TEST_VERIFY_EQ(test_ptr, &node2, bs_dllist_pop_back(&list1)); BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.next_ptr); BS_TEST_VERIFY_EQ(test_ptr, &node1, bs_dllist_pop_back(&list1)); BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.next_ptr); BS_TEST_VERIFY_EQ(test_ptr, list1.head_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, list1.tail_ptr, NULL); } /* ------------------------------------------------------------------------- */ /** * Tests that push_front and pop_front works. */ void bs_dllist_test_front(bs_test_t *test_ptr) { bs_dllist_t list1 = {}; bs_dllist_node_t node1 = {}, node2 = {}, node3 = {}; bs_dllist_push_front(&list1, &node3); bs_dllist_push_front(&list1, &node2); bs_dllist_push_front(&list1, &node1); BS_TEST_VERIFY_EQ(test_ptr, node1.prev_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, node1.next_ptr, &node2); BS_TEST_VERIFY_EQ(test_ptr, node2.prev_ptr, &node1); BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node3); BS_TEST_VERIFY_EQ(test_ptr, node3.prev_ptr, &node2); BS_TEST_VERIFY_EQ(test_ptr, node3.next_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, &node1, bs_dllist_pop_front(&list1)); BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.next_ptr); BS_TEST_VERIFY_EQ(test_ptr, &node2, bs_dllist_pop_front(&list1)); BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.next_ptr); BS_TEST_VERIFY_EQ(test_ptr, &node3, bs_dllist_pop_front(&list1)); BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.next_ptr); BS_TEST_VERIFY_EQ(test_ptr, list1.head_ptr, NULL); BS_TEST_VERIFY_EQ(test_ptr, list1.tail_ptr, NULL); } /* ------------------------------------------------------------------------- */ void bs_dllist_test_remove(bs_test_t *test_ptr) { bs_dllist_t list = {}; bs_dllist_node_t node1 = {}, node2 = {}, node3 = {}, node4 = {}; BS_TEST_VERIFY_TRUE(test_ptr, node_orphaned(&node1)); // Sequence: 1, 2, 3, 4. bs_dllist_push_back(&list, &node1); assert_consistency(&list); bs_dllist_push_back(&list, &node2); assert_consistency(&list); bs_dllist_push_back(&list, &node3); assert_consistency(&list); bs_dllist_push_back(&list, &node4); assert_consistency(&list); BS_TEST_VERIFY_EQ(test_ptr, 4, bs_dllist_size(&list)); BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_empty(&list)); BS_TEST_VERIFY_FALSE(test_ptr, node_orphaned(&node1)); bs_dllist_remove(&list, &node3); // Now: 1, 2, 4. assert_consistency(&list); BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node3.next_ptr); bs_dllist_remove(&list, &node1); // Now: 2, 4. assert_consistency(&list); BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node1.next_ptr); BS_TEST_VERIFY_TRUE(test_ptr, node_orphaned(&node1)); bs_dllist_remove(&list, &node4); // Now: 2. assert_consistency(&list); BS_TEST_VERIFY_EQ(test_ptr, NULL, node4.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node4.next_ptr); bs_dllist_remove(&list, &node2); assert_consistency(&list); BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.prev_ptr); BS_TEST_VERIFY_EQ(test_ptr, NULL, node2.next_ptr); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&list)); BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_empty(&list)); } /* ------------------------------------------------------------------------- */ void bs_dllist_test_insert(bs_test_t *test_ptr) { bs_dllist_t list = {}; bs_dllist_node_t node1 = {}, node2 = {}, node3 = {}; BS_TEST_VERIFY_TRUE(test_ptr, node_orphaned(&node1)); bs_dllist_push_back(&list, &node1); bs_dllist_insert_node_before(&list, &node1, &node2); assert_consistency(&list); BS_TEST_VERIFY_EQ(test_ptr, list.head_ptr, &node2); BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node1); bs_dllist_insert_node_before(&list, &node1, &node3); assert_consistency(&list); BS_TEST_VERIFY_EQ(test_ptr, list.head_ptr, &node2); BS_TEST_VERIFY_EQ(test_ptr, node2.next_ptr, &node3); BS_TEST_VERIFY_EQ(test_ptr, node3.next_ptr, &node1); } /* ------------------------------------------------------------------------- */ void bs_dllist_test_append(bs_test_t *test_ptr) { bs_dllist_t l1 = {}, l2 = {}; bs_dllist_node_t n1 = {}, n2 = {}; bs_dllist_append(&l1, &l2); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&l1)); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&l2)); bs_dllist_push_back(&l2, &n1); bs_dllist_append(&l1, &l2); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&l1)); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&l2)); // Append again, must not change anything. bs_dllist_append(&l1, &l2); BS_TEST_VERIFY_EQ(test_ptr, 1, bs_dllist_size(&l1)); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&l2)); bs_dllist_push_back(&l2, &n2); bs_dllist_append(&l1, &l2); BS_TEST_VERIFY_EQ(test_ptr, 2, bs_dllist_size(&l1)); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&l2)); BS_TEST_VERIFY_EQ(test_ptr, &n1, l1.head_ptr); BS_TEST_VERIFY_EQ(test_ptr, &n2, l1.tail_ptr); } /* ------------------------------------------------------------------------- */ /** Function to use in the test for @ref bs_dllist_any. */ static bool test_find(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { return dlnode_ptr == ud_ptr; } /* ------------------------------------------------------------------------- */ void bs_dllist_test_find(bs_test_t *test_ptr) { bs_dllist_t list = {}; bs_dllist_node_t n1 = {}, n2 = {}; BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_dllist_find(&list, test_find, &n1)); BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_contains(&list, &n1)); BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_contains(&list, &n2)); bs_dllist_push_back(&list, &n1); BS_TEST_VERIFY_EQ(test_ptr, &n1, bs_dllist_find(&list, test_find, &n1)); BS_TEST_VERIFY_NEQ(test_ptr, &n2, bs_dllist_find(&list, test_find, &n2)); BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_contains(&list, &n1)); BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_contains(&list, &n2)); bs_dllist_push_back(&list, &n2); BS_TEST_VERIFY_EQ(test_ptr, &n1, bs_dllist_find(&list, test_find, &n1)); BS_TEST_VERIFY_EQ(test_ptr, &n2, bs_dllist_find(&list, test_find, &n2)); BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_contains(&list, &n1)); BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_contains(&list, &n2)); } /* ------------------------------------------------------------------------- */ /** Function to use in the test for @ref bs_dllist_for_each. */ static void test_for_each( __UNUSED__ bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { *((int*)ud_ptr) += 1; } /** Tests @ref bs_dllist_for_each. */ void bs_dllist_test_for_each(bs_test_t *test_ptr) { bs_dllist_t list = {}; bs_dllist_node_t n1 = {}, n2 = {}; int outcome = 0; bs_dllist_for_each(&list, test_for_each, &outcome); BS_TEST_VERIFY_EQ(test_ptr, 0, outcome); bs_dllist_push_back(&list, &n1); outcome = 0; bs_dllist_for_each(&list, test_for_each, &outcome); BS_TEST_VERIFY_EQ(test_ptr, 1, outcome); bs_dllist_push_back(&list, &n2); outcome = 0; bs_dllist_for_each(&list, test_for_each, &outcome); BS_TEST_VERIFY_EQ(test_ptr, 2, outcome); } /* ------------------------------------------------------------------------- */ /** Helper: dtor for the allocated @ref bs_dllist_node_t. */ static void test_for_each_dtor(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { bs_dllist_remove(ud_ptr, dlnode_ptr); free(dlnode_ptr); } /** Runs bs_dllist_for_each with a dtor, ensuring no invalid access. */ void bs_dllist_test_for_each_dtor(bs_test_t *test_ptr) { bs_dllist_t list = {}; for (int i = 0; i < 5; ++i) { bs_dllist_node_t *node_ptr = BS_ASSERT_NOTNULL( calloc(1, sizeof(bs_dllist_node_t))); bs_dllist_push_back(&list, node_ptr); } BS_TEST_VERIFY_EQ(test_ptr, 5, bs_dllist_size(&list)); bs_dllist_for_each(&list, test_for_each_dtor, &list); BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_empty(&list)); } /* ------------------------------------------------------------------------- */ /** Callback for @ref bs_dllist_all. */ static bool test_all(__UNUSED__ bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { int *i_ptr = ud_ptr; *i_ptr += 1; return *i_ptr != 2; } /** Tests @ref bs_dllist_all. */ void bs_dllist_test_all(bs_test_t *test_ptr) { bs_dllist_t list = {}; bs_dllist_node_t n1 = {}, n2 = {}, n3 = {}; int calls = 0; BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_all(&list, test_all, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 0, calls); bs_dllist_push_back(&list, &n1); BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_all(&list, test_all, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 1, calls); bs_dllist_push_back(&list, &n2); calls = 0; BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_all(&list, test_all, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 2, calls); bs_dllist_push_back(&list, &n3); calls = 0; BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_all(&list, test_all, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 2, calls); } /* ------------------------------------------------------------------------- */ /** Callback for @ref bs_dllist_any. Return true for 2nd call. */ static bool test_any(__UNUSED__ bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { int *i_ptr = ud_ptr; *i_ptr += 1; return *i_ptr == 2; } /** Tests @ref bs_dllist_any. */ void bs_dllist_test_any(bs_test_t *test_ptr) { bs_dllist_t list = {}; bs_dllist_node_t n1 = {}, n2 = {}, n3 = {}; int calls = 0; BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_any(&list, test_any, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 0, calls); bs_dllist_push_back(&list, &n1); BS_TEST_VERIFY_FALSE(test_ptr, bs_dllist_any(&list, test_any, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 1, calls); bs_dllist_push_back(&list, &n2); calls = 0; BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_any(&list, test_any, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 2, calls); bs_dllist_push_back(&list, &n3); calls = 0; BS_TEST_VERIFY_TRUE(test_ptr, bs_dllist_any(&list, test_any, &calls)); BS_TEST_VERIFY_EQ(test_ptr, 2, calls); } /* ------------------------------------------------------------------------- */ /** Test struct, for sorting. Node with a value. */ struct _bs_dllist_test_sort_node { /** List node. */ bs_dllist_node_t dlnode; /** Value. */ int i; /** Position. */ size_t pos; }; /** Comparator, for testing. */ static int _bs_dllist_test_sort_cmp( const bs_dllist_node_t *left_node_ptr, const bs_dllist_node_t *right_node_ptr) { struct _bs_dllist_test_sort_node *lnp = BS_CONTAINER_OF( left_node_ptr, struct _bs_dllist_test_sort_node, dlnode); struct _bs_dllist_test_sort_node *rnp = BS_CONTAINER_OF( right_node_ptr, struct _bs_dllist_test_sort_node, dlnode); if (lnp->i < rnp->i) { return -1; } else if (lnp->i > rnp->i) { return 1; } return 0; } /** Tests @ref bs_dllist_sort. */ void bs_dllist_test_sort(bs_test_t *test_ptr) { bs_dllist_t list = {}; struct _bs_dllist_test_sort_node n1 = { .i = 1 }, n2 = { .i = 2 }, n3 = { .i = 3 }, n4 = { .i = 4 }, other1 = { .i = 1 }; bs_dllist_sort(&list, _bs_dllist_test_sort_cmp); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_dllist_size(&list)); bs_dllist_push_back(&list, &n3.dlnode); bs_dllist_sort(&list, _bs_dllist_test_sort_cmp); BS_TEST_VERIFY_EQ(test_ptr, &n3.dlnode, bs_dllist_pop_front(&list)); bs_dllist_push_back(&list, &n4.dlnode); bs_dllist_push_back(&list, &n2.dlnode); bs_dllist_sort(&list, _bs_dllist_test_sort_cmp); BS_TEST_VERIFY_EQ(test_ptr, &n2.dlnode, bs_dllist_pop_front(&list)); BS_TEST_VERIFY_EQ(test_ptr, &n4.dlnode, bs_dllist_pop_front(&list)); bs_dllist_push_back(&list, &n4.dlnode); bs_dllist_push_back(&list, &n2.dlnode); bs_dllist_push_back(&list, &n3.dlnode); bs_dllist_push_back(&list, &n1.dlnode); bs_dllist_sort(&list, _bs_dllist_test_sort_cmp); BS_TEST_VERIFY_EQ(test_ptr, &n1.dlnode, bs_dllist_pop_front(&list)); BS_TEST_VERIFY_EQ(test_ptr, &n2.dlnode, bs_dllist_pop_front(&list)); BS_TEST_VERIFY_EQ(test_ptr, &n3.dlnode, bs_dllist_pop_front(&list)); BS_TEST_VERIFY_EQ(test_ptr, &n4.dlnode, bs_dllist_pop_front(&list)); bs_dllist_push_back(&list, &n1.dlnode); bs_dllist_push_back(&list, &other1.dlnode); bs_dllist_sort(&list, _bs_dllist_test_sort_cmp); BS_TEST_VERIFY_EQ(test_ptr, &n1.dlnode, bs_dllist_pop_front(&list)); BS_TEST_VERIFY_EQ(test_ptr, &other1.dlnode, bs_dllist_pop_front(&list)); } /* ------------------------------------------------------------------------- */ /** Tests randomized sort. */ void bs_dllist_test_sort_random(bs_test_t *test_ptr) { bs_dllist_t list = {}; static const size_t n = 1024; struct _bs_dllist_test_sort_node v[n]; srand(12345); for (size_t i = 0; i < n; ++i) { v[i] = (struct _bs_dllist_test_sort_node){ .i = rand(), .pos = i }; bs_dllist_push_back(&list, &v[i].dlnode); } bs_dllist_sort(&list, _bs_dllist_test_sort_cmp); bs_dllist_node_t *n1 = bs_dllist_pop_front(&list); for (bs_dllist_node_t *n2 = bs_dllist_pop_front(&list); n2 != NULL; n1 = n2, n2 = bs_dllist_pop_front(&list)) { struct _bs_dllist_test_sort_node *v1 = BS_CONTAINER_OF( n1, struct _bs_dllist_test_sort_node, dlnode); struct _bs_dllist_test_sort_node *v2 = BS_CONTAINER_OF( n2, struct _bs_dllist_test_sort_node, dlnode); BS_TEST_VERIFY_TRUE(test_ptr, v1->i <= v2->i); if (v1->pos == v2->pos) BS_TEST_VERIFY_EQ(test_ptr, v1->pos, v2->pos); } } /* ------------------------------------------------------------------------- */ void bs_dllist_test_iterator(bs_test_t *test_ptr) { bs_dllist_t list = {}; bs_dllist_node_t n1 = {}, n2 = {}; bs_dllist_push_back(&list, &n1); bs_dllist_push_back(&list, &n2); bs_dllist_node_iterator_t it = bs_dllist_node_iterator_forward; BS_TEST_VERIFY_EQ(test_ptr, NULL, it(NULL)); BS_TEST_VERIFY_EQ(test_ptr, &n2, it(&n1)); BS_TEST_VERIFY_EQ(test_ptr, NULL, it(&n2)); it = bs_dllist_node_iterator_backward; BS_TEST_VERIFY_EQ(test_ptr, NULL, it(NULL)); BS_TEST_VERIFY_EQ(test_ptr, NULL, it(&n1)); BS_TEST_VERIFY_EQ(test_ptr, &n1, it(&n2)); } /* == End of dllist.c ===================================================== */ wlmaker-0.8.1/submodules/libbase/src/time.c0000644000175100017510000000567215222270712020256 0ustar runnerrunner/* ========================================================================= */ /** * @file time.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /// clock_gettime(2) is a POSIX extension, at 199309L. _XOPEN_SOURCE 500 /// defines _POSIX_C_SOURCE at 199506L. /// gettimeofday(2) is a XSI extension, needing _XOPEN_SOURCE 500. #define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include #undef _XOPEN_SOURCE /* == Methods ============================================================== */ /* ------------------------------------------------------------------------- */ uint64_t bs_usec(void) { struct timeval tv; if (0 != gettimeofday(&tv, NULL)) { bs_log(BS_ERROR | BS_ERRNO, "Failed gettimeofday(%p, NULL)\n", (void*)&tv); return 0; } return (uint64_t)tv.tv_sec * UINT64_C(1000000) + (uint64_t)tv.tv_usec % UINT64_C(1000000); } /* ------------------------------------------------------------------------- */ uint64_t bs_mono_nsec(void) { struct timespec timespec; if (0 != clock_gettime(CLOCK_MONOTONIC, ×pec)) { bs_log(BS_ERROR | BS_ERRNO, "Failed clock_gettime(CLOCK_MONOTONIC, %p)", ×pec); return 0; } return timespec.tv_sec * UINT64_C(1000000000) + + timespec.tv_nsec; } /* == Unit tests =========================================================== */ static void bs_time_test_usec(bs_test_t *test_ptr); static void bs_time_test_nsec(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_time_test_cases[] = { { true, "time usec", bs_time_test_usec }, { true, "time_mono_nsec", bs_time_test_nsec }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bs_time_test_set = BS_TEST_SET( true, "time", bs_time_test_cases); /* ------------------------------------------------------------------------- */ void bs_time_test_usec(bs_test_t *test_ptr) { BS_TEST_VERIFY_NEQ(test_ptr, 0, bs_usec()); } /* ------------------------------------------------------------------------- */ void bs_time_test_nsec(bs_test_t *test_ptr) { uint64_t v1, v2; v1 = bs_mono_nsec(); v2 = bs_mono_nsec(); BS_TEST_VERIFY_TRUE(test_ptr, v1 <= v2); } /* == End of time.c ======================================================== */ wlmaker-0.8.1/submodules/libbase/src/test.c0000644000175100017510000010512615222270712020272 0ustar runnerrunner/* ========================================================================= */ /** * @file test.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** Information on current test case. */ struct _bs_test_t { /** Allocated pointers through the test, collected for later cleanup. */ bs_ptr_stack_t allocated_ptrs; /** Index of current test case (for information only). */ int case_idx; /** Current test case descriptor. */ const bs_test_case_t *case_ptr; /** Test set the case belongs to. */ const bs_test_set_t *set_ptr; /** Test environment. */ const struct bs_test_env *env_ptr; /** Tree holding environment variables and original values. */ bs_avltree_t *env_tree_ptr; /** Return value of @ref bs_test_set_t::setup. */ void *setup_context_ptr; /** Temporary path, see @ref bs_test_data_path. */ char *test_path_ptr; /** Test outcome: Failed? */ bool failed; /** Test report summary (through bs_test_succeed, bs_test_fail) */ char report[256]; }; /** Terminal codes for commands we're requiring. */ struct _bs_test_tcode { /** Buffer for storing all codes. */ char all_codes[2048]; /** Code for setting the foreground color. */ const char *code_set_foreground; /** Code for setting the background color. */ const char *code_set_background; /** Sets default colour-pair to the original one. */ const char *code_orig_pair; /** Code for entering bold mode. */ const char *code_enter_bold_mode; /** Code for clearing attributes. */ const char *code_exit_attribute_mode; }; /** Attributes for setting terminal attributes. */ enum _bs_test_attributes { BS_TEST_ATTR_SKIP = 0x0201, BS_TEST_ATTR_FAIL = 0x020C, BS_TEST_ATTR_SUCCESS = 0x020A, BS_TEST_ATTR_RESET = -1, }; /** Double-linked-list node, information about a failed test case. */ struct bs_test_fail_node { /** Node of the double-linked list. */ bs_dllist_node_t dlnode; /** Full name of the test (including test suite)*. */ char *full_name_ptr; }; /** Summary on a test run (either a case or a set). */ struct bs_test_report { /** Number of failed tests. */ int failed; /** Number of succeeded tests. */ int succeeded; /** Number of skipped tests. */ int skipped; /** Total number of tests. */ int total; }; /** Test environment. */ struct bs_test_env { /** Filter applied for selecting tests. See `--test_filter` arg. */ char *filter_ptr; /** * Directory where test data is stored. From `--test_data_directory`, or * passed through @ref bs_test_param_t. * * See @ref bs_test_data_path. */ char *data_dir_ptr; }; /** Node holding environment variable. */ struct bs_test_env_node { /** Tree node. */ bs_avltree_node_t avlnode; /** Name of the environment variable. */ char *name_ptr; /** Value originally held by this environment variable. */ char *value_ptr; }; /* Other helpers */ static void bs_test_tcode_init(void); static int bs_test_putc(int c); static void bs_test_puts(const char *fmt_ptr, ...) __ARG_PRINTF__(1, 2); static void bs_test_attr(int attr); /* Set helpers. */ static void bs_test_set_report(const bs_test_set_t *set_ptr, const struct bs_test_report *set_report_ptr); static int bs_test_set(const bs_test_set_t *set_ptr, const struct bs_test_env *env_ptr, bs_dllist_t *failed_tests_ptr); static int _bs_test_env_node_cmp( const bs_avltree_node_t *node_ptr, const void *key_ptr); static void _bs_test_env_node_destroy(bs_avltree_node_t *node_ptr); /* Testcase helpers. */ static bool bs_test_case_prepare(bs_test_t *test_ptr, const bs_test_set_t *set_ptr, const struct bs_test_env *env_ptr, int case_idx); static void bs_test_case_report(bs_test_t *test_ptr, const bs_test_set_t *set_ptr, bool enabled); static struct bs_test_fail_node *bs_test_case_fail_node_create( char *full_name_ptr); static void bs_test_case_fail_node_destroy( struct bs_test_fail_node *fnode_ptr); static char *bs_test_case_create_full_name( const bs_test_set_t *set_ptr, const bs_test_case_t *case_ptr); static const char *_bs_test_get_mkdtemp(bs_test_t *test_ptr); /* == Data ================================================================= */ /** Terminal codes. */ static struct _bs_test_tcode bs_test_tcode; /** Line separator between test cases. */ static const char *bs_test_linesep_ptr = "----------------------------------------" "---------------------------------------"; /** Line separator between test sets. */ static const char *bs_test_report_separator_ptr = "========================================" "======================================="; /* == Exported Functions =================================================== */ /* ------------------------------------------------------------------------- */ int bs_test_sets( const bs_test_set_t** test_set_ptrs, int argc, const char **argv, const bs_test_param_t *param_ptr) { struct bs_test_report report = {}; int i; bool run_set; bs_dllist_t failed_tests = {}; struct bs_test_env env = {}; const bs_arg_t bs_test_args[] = { BS_ARG_STRING( "test_filter", "Filter to apply for selecting tests. Uses fnmatch on the full name.", "*", &env.filter_ptr), BS_ARG_STRING( "test_data_directory", "Directory to use for test data. Setting this flag takes precedence " "over the parameter specified through the `bs_test_param_t` arg to " "bs_test().", NULL, &env.data_dir_ptr), BS_ARG_SENTINEL() }; if (NULL != argv && !bs_arg_parse(bs_test_args, BS_ARG_MODE_EXTRA_VALUES, &argc, argv)) { bs_arg_print_usage(stderr, bs_test_args); return -1; } if (NULL == env.data_dir_ptr && NULL != param_ptr && NULL != param_ptr->test_data_dir_ptr) { env.data_dir_ptr = logged_strdup(param_ptr->test_data_dir_ptr); if (NULL == env.data_dir_ptr) return -1; } bs_test_tcode_init(); const bs_test_set_t *set_ptr; while (NULL != (set_ptr = *test_set_ptrs++)) { // If any args are given, check if the name matches. run_set = 1 >= argc; for (i = 1; i < argc; i++) { if (0 == strcmp(argv[i], set_ptr->name_ptr)) { run_set = true;; } } if (run_set && set_ptr->enabled) { if (bs_test_set(set_ptr, &env, &failed_tests)) { report.failed++; } else { report.succeeded++; } } else { report.skipped++; } report.total++; } if (report.failed) { bs_test_attr(BS_TEST_ATTR_FAIL); bs_test_puts("FAILED: % 66d/% 3d\n", report.failed, report.total); /* Print all failed tests. */ struct bs_test_fail_node *fnode_ptr; while (NULL != (fnode_ptr = (struct bs_test_fail_node*) bs_dllist_pop_front(&failed_tests))) { bs_test_puts(" %s\n", fnode_ptr->full_name_ptr); bs_test_case_fail_node_destroy(fnode_ptr); } bs_test_attr(BS_TEST_ATTR_RESET); } else if (report.succeeded) { bs_test_attr(BS_TEST_ATTR_SUCCESS); bs_test_puts("SUCCESS: % 65d/% 3d\n", report.succeeded, report.total); bs_test_attr(BS_TEST_ATTR_RESET); } if (report.skipped) { bs_test_attr(BS_TEST_ATTR_SKIP); bs_test_puts("SKIPPED: % 65d/% 3d\n", report.skipped, report.total); bs_test_attr(BS_TEST_ATTR_RESET); } if (NULL != argv) { bs_arg_cleanup(bs_test_args); }; return report.failed; } /* ------------------------------------------------------------------------- */ void bs_test_succeed(bs_test_t *test, const char *fmt_ptr, ...) { va_list ap; if ((!test->failed) && ('\0' == test->report[0])) { va_start(ap, fmt_ptr); vsnprintf(test->report, sizeof(test->report), fmt_ptr, ap); va_end(ap); } } /* ------------------------------------------------------------------------- */ void bs_test_fail_at( bs_test_t *test, const char *fname_ptr, int line, const char *fmt_ptr, ...) { va_list ap; if (!test->failed) { test->failed = true; int pos = snprintf(&test->report[0], sizeof(test->report), "%s(%d): ", fname_ptr, line); if (0 > pos || (size_t)pos >= sizeof(test->report)) return; va_start(ap, fmt_ptr); vsnprintf(&test->report[pos], sizeof(test->report) - pos, fmt_ptr, ap); va_end(ap); } } /* ------------------------------------------------------------------------- */ bool bs_test_failed(bs_test_t *test_ptr) { return test_ptr->failed; } /* ------------------------------------------------------------------------- */ void *bs_test_context(bs_test_t *test_ptr) { return test_ptr->setup_context_ptr; } /* ------------------------------------------------------------------------- */ void bs_test_verify_streq_at( bs_test_t *test_ptr, const char *fname_ptr, const int line, const char *a_ptr, const char *hash_a_ptr, const char *b_ptr, const char *hash_b_ptr) { if (0 == strcmp(a_ptr, b_ptr)) { // TODO: report bs_test_succeed. return; } size_t pos = 0; while (a_ptr[pos] && b_ptr[pos] && a_ptr[pos] == b_ptr[pos]) { ++pos; } bs_test_fail_at( test_ptr, fname_ptr, line, "%s (\"%s\") not equal %s (\"%s\") at %zu (0x%02x != 0x%02x)", hash_a_ptr, a_ptr, hash_b_ptr, b_ptr, pos, a_ptr[pos], b_ptr[pos]); } /* ------------------------------------------------------------------------- */ void bs_test_verify_strmatch_at( bs_test_t *test_ptr, const char *fname_ptr, const int line, const char *a_ptr, const char *hash_a_ptr, const char *regex_ptr) { regex_t regex; int rv = regcomp(®ex, regex_ptr, REG_EXTENDED); if (0 != rv) { char err_buf[512]; regerror(rv, ®ex, err_buf, sizeof(err_buf)); BS_TEST_FAIL(test_ptr, "Failed regcomp(\"%s\"): %s", regex_ptr, err_buf); return; } regmatch_t matches[1]; rv = regexec(®ex, a_ptr, 1, matches, 0); regfree(®ex); if (REG_NOMATCH == rv) { bs_test_fail_at( test_ptr, fname_ptr, line, "%s (\"%s\") does not match \"%s\".", hash_a_ptr, a_ptr, regex_ptr); } } /* ------------------------------------------------------------------------- */ void bs_test_verify_memeq_at( bs_test_t *test_ptr, const char *fname_ptr, const int line, const void *a_ptr, const char *hash_a_ptr, const void *b_ptr, const char *hash_b_ptr, const size_t size) { if (0 != memcmp(a_ptr, b_ptr, size)) { bs_test_fail_at( test_ptr, fname_ptr, line, "Buffer at %p (%s) != at %p (%s) for %zu", a_ptr, hash_a_ptr, b_ptr, hash_b_ptr, size); } } /* ------------------------------------------------------------------------- */ const char *bs_test_data_path( bs_test_t *test_ptr, const char *fname_fmt_ptr, ...) { va_list ap; va_start(ap, fname_fmt_ptr); char *fname_ptr = bs_vstrdupf(fname_fmt_ptr, ap); va_end(ap); if (NULL == fname_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_vstrdupf(...)"); return NULL; } char *resolved_path_ptr = bs_file_join_resolve_path( test_ptr->env_ptr->data_dir_ptr, fname_ptr, NULL); if (NULL == resolved_path_ptr) { BS_TEST_FAIL( test_ptr, "Failed bs_file_join_resolve_path(\"%s\", \"%s\", NULL)", test_ptr->env_ptr->data_dir_ptr, fname_ptr); } free(fname_ptr); if (NULL == resolved_path_ptr) return NULL; if (!bs_ptr_stack_push(&test_ptr->allocated_ptrs, resolved_path_ptr)) { BS_TEST_FAIL(test_ptr, "Failed bs_ptr_stack_push(%p, %p)", &test_ptr->allocated_ptrs, resolved_path_ptr); free(resolved_path_ptr); return NULL; } return resolved_path_ptr; } /* ------------------------------------------------------------------------- */ const char *bs_test_temp_path( bs_test_t *test_ptr, const char *fname_fmt_ptr, ...) { const char *dir_ptr = _bs_test_get_mkdtemp(test_ptr); if (NULL == dir_ptr) return NULL; if (NULL == fname_fmt_ptr) return dir_ptr; va_list ap; va_start(ap, fname_fmt_ptr); char *fname_ptr = bs_vstrdupf(fname_fmt_ptr, ap); va_end(ap); char *path_ptr = bs_strdupf("%s/%s", dir_ptr, fname_ptr); free(fname_ptr); if (NULL != path_ptr) { if (bs_ptr_stack_push(&test_ptr->allocated_ptrs, path_ptr)) { return path_ptr; } BS_TEST_FAIL(test_ptr, "Failed bs_stack_push(%p, %p)", &test_ptr->allocated_ptrs, path_ptr); free(path_ptr); } else { BS_TEST_FAIL(test_ptr, "Failed bs_strdupf(...)"); } return NULL; } /* ------------------------------------------------------------------------- */ void bs_test_setenv( bs_test_t *test_ptr, const char *name_ptr, const char *value_fmt_ptr, ...) { if (NULL == bs_avltree_lookup(test_ptr->env_tree_ptr, name_ptr)) { struct bs_test_env_node *node_ptr = BS_ASSERT_NOTNULL( logged_calloc(1, sizeof(struct bs_test_env_node))); node_ptr->name_ptr = BS_ASSERT_NOTNULL(logged_strdup(name_ptr)); char *old_value_ptr = getenv(name_ptr); if (NULL != old_value_ptr) { node_ptr->value_ptr = BS_ASSERT_NOTNULL( logged_strdup(old_value_ptr)); } BS_ASSERT(bs_avltree_insert( test_ptr->env_tree_ptr, node_ptr->name_ptr, &node_ptr->avlnode, false)); } va_list ap; va_start(ap, value_fmt_ptr); char *value_ptr = BS_ASSERT_NOTNULL(bs_vstrdupf(value_fmt_ptr, ap)); va_end(ap); setenv(name_ptr, value_ptr, 1); free(value_ptr); } /* == Static (Local) Functions ============================================= */ /* ------------------------------------------------------------------------- */ /** * Initializes terminal escape codes. * * @param tcode_ptr */ void bs_test_tcode_init(void) { char *code_buf_ptr; char tc_buf[4096]; char *term_ptr = getenv("TERM"); if (NULL == term_ptr) { // Fallback: Use xterm, provides colors also on github workflows... term_ptr = "xterm"; } else if (0 == strcmp(term_ptr, "xterm-color") || 0 == strcmp(term_ptr, "xterm-256color")) { // Hack: Some Unixes use xterm-color, but fail to provide a termcap. term_ptr = "xterm"; } BS_ASSERT(0 < tgetent(tc_buf, term_ptr)); bs_test_tcode = (struct _bs_test_tcode){}; code_buf_ptr = bs_test_tcode.all_codes; // https://pubs.opengroup.org/onlinepubs/007908799/xcurses/terminfo.html. bs_test_tcode.code_set_background = tgetstr((char*)"Sb", &code_buf_ptr); bs_test_tcode.code_set_foreground = tgetstr((char*)"Sf", &code_buf_ptr); bs_test_tcode.code_orig_pair = tgetstr((char*)"op", &code_buf_ptr); bs_test_tcode.code_enter_bold_mode = tgetstr((char*)"md", &code_buf_ptr); bs_test_tcode.code_exit_attribute_mode = tgetstr((char*)"me", &code_buf_ptr); } /* ------------------------------------------------------------------------- */ /** * Print a character to terminal. Wrapper for terminal-code functions. * * @param c * * @return 0 on success. */ int bs_test_putc(int c) { char x = c; /* return 0 on success */ return 1 - write(1, &x, 1); } /* ------------------------------------------------------------------------- */ /** * Print to terminal. * * @param fmt_ptr Format string, as vsnprintf takes. * @param ... Additional arguments. */ void bs_test_puts(const char *fmt_ptr, ...) { char line_buf[1024]; int len; va_list ap; va_start(ap, fmt_ptr); len = vsnprintf(line_buf, sizeof(line_buf), fmt_ptr, ap); if ((size_t)len > sizeof(line_buf)) len = sizeof(line_buf); va_end(ap); int written_len = 0; for (written_len = 0; written_len < len; written_len = write(1, &line_buf[written_len], len)) ; } /* ------------------------------------------------------------------------- */ /** * Set terminal attributes. * * @param attr: - bit 0..2 : 7-bit foreground * - bit 3 : toggle bold mode * - bit 4..6 : 7-bit background * - bit 7 : ignored. * - bit 8 : do not set foreground * - bit 9 : do not set background * - a value of -1 resets attributes */ void bs_test_attr(int attr) { if (-1 == attr) { tputs(bs_test_tcode.code_orig_pair, 0, bs_test_putc); tputs(bs_test_tcode.code_exit_attribute_mode, 0, bs_test_putc); return; } if (0x100 != (0x100 & attr)) { tputs(tparm((char*)bs_test_tcode.code_set_foreground, attr & 0x07, 0, 0, 0, 0, 0, 0, 0, 0), 0, bs_test_putc); } if (attr & 0x08) { tputs(bs_test_tcode.code_enter_bold_mode, 0, bs_test_putc); } if (0x0200 != (0x0200 & attr)) { tputs(tparm((char*)bs_test_tcode.code_set_background, (attr & 0x70) >> 4, 0, 0, 0, 0, 0, 0, 0, 0), 0, bs_test_putc); } } /* ------------------------------------------------------------------------- */ /** * Print summary of test set. * * @param test_ptr Test state. * @param set_report_ptr Report. */ void bs_test_set_report(const bs_test_set_t *set_ptr, const struct bs_test_report *set_report_ptr) { bs_test_puts("%s\n %-65.65s ", bs_test_report_separator_ptr, set_ptr->name_ptr); bs_test_puts("OK"); bs_test_puts(": % 3d/% 3d\n%s\n", set_report_ptr->succeeded, set_report_ptr->total, bs_test_report_separator_ptr); } /* ------------------------------------------------------------------------- */ /** * Runs the test cases of the specified test set. * * @param test_ptr Test state. * @param env_ptr * @param set_ptr Set to run. * * @return 0, if all tests of the test reported success, or the number of * failed test cases. */ int bs_test_set(const bs_test_set_t *set_ptr, const struct bs_test_env *env_ptr, bs_dllist_t *failed_tests_ptr) { const bs_test_case_t *case_ptr; struct bs_test_fail_node *fnode_ptr; struct bs_test_report set_report = {}; int case_idx; bs_test_t test; bs_test_puts("%s\n Set: %-73.73s\n", bs_test_report_separator_ptr, set_ptr->name_ptr); /* step through descriptors and run each test */ for (case_idx = 0; NULL != set_ptr->cases_ptr[case_idx].name_ptr; set_report.total++, case_idx++) { case_ptr = set_ptr->cases_ptr + case_idx; bs_test_puts("%s\n", bs_test_linesep_ptr); bs_test_case_prepare(&test, set_ptr, env_ptr, case_idx); bool enabled = case_ptr->enabled; char *full_name_ptr = bs_test_case_create_full_name(set_ptr, case_ptr); if (NULL == full_name_ptr) { test.failed = true; } else { if (NULL != env_ptr->filter_ptr && fnmatch(env_ptr->filter_ptr, full_name_ptr, 0)) { enabled = false; } if (enabled) { case_ptr->test_fn(&test); } } if (!enabled) { set_report.skipped++; } else if (!test.failed) { set_report.succeeded++; } else { set_report.failed++; fnode_ptr = bs_test_case_fail_node_create(full_name_ptr); if (fnode_ptr) { bs_dllist_push_back(failed_tests_ptr, &fnode_ptr->dlnode); full_name_ptr = NULL; } } if (NULL != full_name_ptr) free(full_name_ptr); bs_test_case_report(&test, set_ptr, enabled); } bs_test_set_report(set_ptr, &set_report); return set_report.failed; } /* ------------------------------------------------------------------------- */ int _bs_test_env_node_cmp( const bs_avltree_node_t *avlnode_ptr, const void *key_ptr) { struct bs_test_env_node *node_ptr = BS_CONTAINER_OF( avlnode_ptr, struct bs_test_env_node, avlnode); return strcmp(node_ptr->name_ptr, key_ptr); } /* ------------------------------------------------------------------------- */ void _bs_test_env_node_destroy(bs_avltree_node_t *avlnode_ptr) { struct bs_test_env_node *node_ptr = BS_CONTAINER_OF( avlnode_ptr, struct bs_test_env_node, avlnode); if (NULL != node_ptr->name_ptr) { if (NULL != node_ptr->value_ptr) { setenv(node_ptr->name_ptr, node_ptr->value_ptr, 1); } else { unsetenv(node_ptr->name_ptr); } free(node_ptr->name_ptr); } if (NULL != node_ptr->value_ptr) free(node_ptr->value_ptr); free(node_ptr); } /* ------------------------------------------------------------------------- */ /** * Prepares test_ptr for this test case. * * @param test_ptr Test state. * @param set_ptr Test set. * @param env_ptr Environment. * @param case_idx Index of the test case within the set. */ bool bs_test_case_prepare(bs_test_t *test_ptr, const bs_test_set_t *set_ptr, const struct bs_test_env *env_ptr, int case_idx) { *test_ptr = (bs_test_t){ .case_idx = case_idx, .case_ptr = set_ptr->cases_ptr + case_idx, .failed = false, .env_tree_ptr = bs_avltree_create( _bs_test_env_node_cmp, _bs_test_env_node_destroy) }; if (NULL == test_ptr->env_tree_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_avltree_create(...)"); } if (set_ptr->setup) { test_ptr->setup_context_ptr = set_ptr->setup(); if (NULL == test_ptr->setup_context_ptr) { BS_TEST_FAIL(test_ptr, "Failed setup() [%p]", set_ptr->setup); } } test_ptr->set_ptr = set_ptr; test_ptr->env_ptr = env_ptr; return bs_ptr_stack_init(&test_ptr->allocated_ptrs); } /* ------------------------------------------------------------------------- */ /** * Reports the outcome of this particular testcase. * * @param test_ptr */ void bs_test_case_report(bs_test_t *test_ptr, const bs_test_set_t *set_ptr, bool enabled) { const char *outcome_ptr; int attr; if (NULL != set_ptr->teardown && NULL != test_ptr->setup_context_ptr) { set_ptr->teardown(test_ptr->setup_context_ptr); } if (NULL != test_ptr->test_path_ptr) { if (0 != rmdir(test_ptr->test_path_ptr)) { BS_TEST_FAIL(test_ptr, "Failed rmdir(\"%s\"): %s", test_ptr->test_path_ptr, strerror(errno)); } free(test_ptr->test_path_ptr); } if (NULL != test_ptr->env_tree_ptr) { bs_avltree_destroy(test_ptr->env_tree_ptr); test_ptr->env_tree_ptr = NULL; } void *p; while (NULL != (p = bs_ptr_stack_pop(&test_ptr->allocated_ptrs))) { free(p); } bs_ptr_stack_fini(&test_ptr->allocated_ptrs); if (!enabled) { attr = BS_TEST_ATTR_SKIP; outcome_ptr = "SKIP"; } else if (test_ptr->failed) { attr = BS_TEST_ATTR_FAIL; outcome_ptr = "FAIL"; } else { attr = BS_TEST_ATTR_SUCCESS; outcome_ptr = "SUCCESS"; } bs_test_puts(" % 3d: %-63.63s ", test_ptr->case_idx, test_ptr->case_ptr->name_ptr); bs_test_attr(attr); bs_test_puts("%8.8s", outcome_ptr); bs_test_attr(BS_TEST_ATTR_RESET); bs_test_puts("\n"); if ((enabled) && ('\0' != test_ptr->report[0])) { bs_test_puts(" %s\n", test_ptr->report); } bs_test_attr(BS_TEST_ATTR_RESET); } /* ------------------------------------------------------------------------- */ /** * Create a double-linked-list node with information on a failed test. * * @param full_name_ptr * * @return Pointer to the node or NULL on error. */ struct bs_test_fail_node *bs_test_case_fail_node_create(char *full_name_ptr) { struct bs_test_fail_node *fnode_ptr; fnode_ptr = calloc(1, sizeof(struct bs_test_fail_node)); if (NULL == fnode_ptr) return NULL; fnode_ptr->full_name_ptr = full_name_ptr; return fnode_ptr; } /* ------------------------------------------------------------------------- */ /** * Destroy a double-linked-list node with failed-test information. * * @see bs_test_case_fail_node_create * * @param fnode_ptr */ void bs_test_case_fail_node_destroy(struct bs_test_fail_node *fnode_ptr) { if (NULL != fnode_ptr->full_name_ptr) { free(fnode_ptr->full_name_ptr); } free(fnode_ptr); } /* ------------------------------------------------------------------------- */ /** * Creates the fully-qualified test name from the set and the case. * * @param set_ptr * @param case_ptr * * @return Pointer to the full name, or NULL on error. */ char *bs_test_case_create_full_name( const bs_test_set_t *set_ptr, const bs_test_case_t *case_ptr) { char *full_name_ptr = logged_malloc( strlen(set_ptr->name_ptr) + strlen(case_ptr->name_ptr) + 2); if (NULL == full_name_ptr) return NULL; strcpy(full_name_ptr, set_ptr->name_ptr); full_name_ptr[strlen(set_ptr->name_ptr)] = '.'; strcpy(full_name_ptr + strlen(set_ptr->name_ptr) + 1, case_ptr->name_ptr); return full_name_ptr; } /* ------------------------------------------------------------------------- */ const char *_bs_test_get_mkdtemp(bs_test_t *test_ptr) { if (NULL != test_ptr->test_path_ptr) return test_ptr->test_path_ptr; test_ptr->test_path_ptr = bs_strdupf( "/tmp/test-%p-%x-XXXXXX", test_ptr->set_ptr, test_ptr->case_idx); if (NULL != test_ptr->test_path_ptr) { if (NULL != mkdtemp(test_ptr->test_path_ptr)) { return test_ptr->test_path_ptr; } BS_TEST_FAIL(test_ptr, "Failed mkdtemp(\"%s\"): %s", test_ptr->test_path_ptr, strerror(errno)); free(test_ptr->test_path_ptr); test_ptr->test_path_ptr = NULL; } else { BS_TEST_FAIL(test_ptr, "Failed bs_strdupf(\"/tmp/test-%p.%x-XXXXXX\")", test_ptr->set_ptr, test_ptr->case_idx); } return NULL; } /* == Unit self-tests ====================================================== */ /** @cond TEST */ static void _bs_test_test_report(bs_test_t *test_ptr); static void _bs_test_eq_neq_tests(bs_test_t *test_ptr); static void _bs_test_context_setup_ok(bs_test_t *test_ptr); static void _bs_test_context_setup_fail(bs_test_t *test_ptr); static void _bs_test_temp_path(bs_test_t *test_ptr); static void _bs_test_set_env(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_test_test_cases[] = { { true, "succeed/fail reporting", _bs_test_test_report }, { true, "eq/neq tests", _bs_test_eq_neq_tests }, { true, "context_setup_ok", _bs_test_context_setup_ok }, { true, "context_setup_fail", _bs_test_context_setup_fail }, { true, "temp_path", _bs_test_temp_path }, { true, "set_env", _bs_test_set_env }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bs_test_test_set = BS_TEST_SET( true, "test", bs_test_test_cases); void _bs_test_test_fail(bs_test_t *test_ptr) { BS_TEST_FAIL(test_ptr, "fail"); } void _bs_test_test_succeed(bs_test_t *test_ptr) { bs_test_succeed(test_ptr, "success"); } void _bs_test_test_succeed_fail(bs_test_t *test_ptr) { bs_test_succeed(test_ptr, "success"); BS_TEST_FAIL(test_ptr, "fail"); } void _bs_test_test_fail_succeed(bs_test_t *test_ptr) { BS_TEST_FAIL(test_ptr, "fail"); bs_test_succeed(test_ptr, "success"); } /* ------------------------------------------------------------------------- */ /** Tests bs_test_fail and bs_test_succeed. */ void _bs_test_test_report(bs_test_t *test_ptr) { bs_test_t sub_test = {}; _bs_test_test_fail(&sub_test); BS_TEST_VERIFY_TRUE(test_ptr, sub_test.failed); BS_TEST_VERIFY_TRUE(test_ptr, bs_test_failed(&sub_test)); sub_test = (bs_test_t){}; _bs_test_test_succeed(&sub_test); BS_TEST_VERIFY_FALSE(test_ptr, sub_test.failed); BS_TEST_VERIFY_FALSE(test_ptr, bs_test_failed(&sub_test)); /** bs_test_fail takes precedence over bs_test_succeed. */ sub_test = (bs_test_t){}; _bs_test_test_succeed_fail(&sub_test); BS_TEST_VERIFY_TRUE(test_ptr, sub_test.failed); BS_TEST_VERIFY_TRUE(test_ptr, bs_test_failed(&sub_test)); sub_test = (bs_test_t){}; _bs_test_test_fail_succeed(&sub_test); BS_TEST_VERIFY_TRUE(test_ptr, sub_test.failed); BS_TEST_VERIFY_TRUE(test_ptr, bs_test_failed(&sub_test)); } /* ------------------------------------------------------------------------- */ /** Tests the EQ / NEQ macros. */ void _bs_test_eq_neq_tests(bs_test_t *test_ptr) { BS_TEST_VERIFY_EQ(test_ptr, 1, 1); BS_TEST_VERIFY_NEQ(test_ptr, 1, 2); BS_TEST_VERIFY_STREQ(test_ptr, "a", "a"); BS_TEST_VERIFY_STRMATCH(test_ptr, "asdf", "^[a-z]+$"); BS_TEST_VERIFY_MEMEQ(test_ptr, "asdf", "asdf", 4); } static int _teardown_calls = 0; static void *_bs_test_setup_succeed(void) { _teardown_calls = 0; return &_teardown_calls; }; static void *_bs_test_setup_fail(void) { _teardown_calls = 0; return NULL; }; static void _bs_test_teardown(void *ctx_ptr) { int *calls_ptr = BS_ASSERT_NOTNULL(ctx_ptr); *calls_ptr += 1; } static void _bs_test_verify_context_context(bs_test_t *test_ptr) { BS_TEST_VERIFY_EQ(test_ptr, &_teardown_calls, bs_test_context(test_ptr)); } /* ------------------------------------------------------------------------- */ void _bs_test_context_setup_ok(bs_test_t *test_ptr) { static const bs_test_case_t cases[] = { { true, "test", _bs_test_verify_context_context }, BS_TEST_CASE_SENTINEL() }; static const bs_test_set_t set = BS_TEST_SET_CONTEXT( true, "test_setup_teardown", cases, _bs_test_setup_succeed, _bs_test_teardown); static const bs_test_set_t *sets[] = { &set, NULL }; BS_TEST_VERIFY_EQ(test_ptr, 0, bs_test_sets(sets, 0, NULL, NULL)); BS_TEST_VERIFY_EQ(test_ptr, 1, _teardown_calls); } /* ------------------------------------------------------------------------- */ void _bs_test_context_setup_fail(bs_test_t *test_ptr) { static const bs_test_case_t cases[] = { { true, "test", _bs_test_verify_context_context }, BS_TEST_CASE_SENTINEL() }; static const bs_test_set_t set = BS_TEST_SET_CONTEXT( true, "test_setup_teardown", cases, _bs_test_setup_fail, _bs_test_teardown); static const bs_test_set_t *sets[] = { &set, NULL }; BS_TEST_VERIFY_EQ(test_ptr, 1, bs_test_sets(sets, 0, NULL, NULL)); BS_TEST_VERIFY_EQ(test_ptr, 0, _teardown_calls); } /* ------------------------------------------------------------------------- */ void _bs_test_temp_path(bs_test_t *test_ptr) { const char *tmp_path = bs_test_temp_path(test_ptr, NULL); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, tmp_path); BS_TEST_VERIFY_TRUE(test_ptr, bs_file_realpath_is(tmp_path, S_IFDIR)); } /* ------------------------------------------------------------------------- */ /** Set and verify an env variable. Used in @ref _bs_test_set_env. */ void _bs_test_set_and_verify_env(bs_test_t *test_ptr) { bs_test_setenv(test_ptr, "VAR", "value"); char *value_ptr = getenv("VAR"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, value_ptr); BS_TEST_VERIFY_STREQ(test_ptr, "value", value_ptr); } /** Use environment variables, and ensure they're cleared on teardown. */ void _bs_test_set_env(bs_test_t *test_ptr) { static const bs_test_case_t cases [] = { { true, "set_and_verify_env", _bs_test_set_and_verify_env }, BS_TEST_CASE_SENTINEL() }; static const bs_test_set_t set = BS_TEST_SET(true, "env", cases); static const bs_test_set_t *sets[] = { &set, NULL }; // Setup VAR. This will be over-written, verified & restored in the // test set. We will verify it is back to it's original value after. bs_test_setenv(test_ptr, "VAR", "original"); BS_TEST_VERIFY_EQ(test_ptr, 0, bs_test_sets(sets, 0, NULL, NULL)); BS_TEST_VERIFY_STREQ(test_ptr, "original", getenv("VAR")); } /** @endcond */ /* == End of test.c ======================================================== */ wlmaker-0.8.1/submodules/libbase/src/plist/0000755000175100017510000000000015222270712020275 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/src/plist/parse.c0000644000175100017510000002605115222270712021557 0ustar runnerrunner/* ========================================================================= */ /** * @file parse.c * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "grammar.h" #include "analyzer.h" #include "parser_context.h" #include #include #include #include #include /* == Declarations ========================================================= */ static bspl_object_t *_bspl_create_object_from_plist_scanner( yyscan_t scanner); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_create_object_from_plist_string(const char *buf_ptr) { yyscan_t scanner; yylex_init(&scanner); YY_BUFFER_STATE buf_state; buf_state = yy_scan_string(buf_ptr, scanner); yy_switch_to_buffer(buf_state, scanner); bspl_object_t *obj = _bspl_create_object_from_plist_scanner(scanner); yy_delete_buffer(buf_state, scanner); yylex_destroy(scanner); return obj; } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_create_object_from_plist_data( const uint8_t *data_ptr, size_t data_size) { yyscan_t scanner; yylex_init(&scanner); YY_BUFFER_STATE buf_state; buf_state = yy_scan_bytes((const char*)data_ptr, data_size, scanner); yy_switch_to_buffer(buf_state, scanner); bspl_object_t *obj = _bspl_create_object_from_plist_scanner(scanner); yy_delete_buffer(buf_state, scanner); yylex_destroy(scanner); return obj; } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_create_object_from_plist_file(const char *fname_ptr) { FILE *file_ptr = fopen(fname_ptr, "r"); if (NULL == file_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed fopen(%s, 'r')", fname_ptr); return NULL; } yyscan_t scanner; yylex_init(&scanner); YY_BUFFER_STATE buf_state; buf_state = yy_create_buffer(file_ptr, YY_BUF_SIZE, scanner); yy_switch_to_buffer(buf_state, scanner); bspl_object_t *obj = _bspl_create_object_from_plist_scanner(scanner); yy_delete_buffer(buf_state, scanner); yylex_destroy(scanner); fclose(file_ptr); return obj; } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_create_object_from_dynbuf(bs_dynbuf_t *dynbuf_ptr) { return bspl_create_object_from_plist_data( dynbuf_ptr->data_ptr, dynbuf_ptr->length); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Does a parser run, from the initialized scanner. */ bspl_object_t *_bspl_create_object_from_plist_scanner(yyscan_t scanner) { bspl_parser_context_t ctx = {}; if (!bs_ptr_stack_init(&ctx.object_stack)) return NULL; // TODO(kaeser@gubbe.ch): Clean up stack on error! yyset_extra(&ctx, scanner); int rv = yyparse(scanner, &ctx); bspl_object_t *object_ptr = bs_ptr_stack_pop(&ctx.object_stack); bs_ptr_stack_fini(&ctx.object_stack); if (0 != rv) { bspl_object_unref(object_ptr); object_ptr = NULL; } return object_ptr; } /* == Unit tests =========================================================== */ static void test_from_string(bs_test_t *test_ptr); static void test_from_file(bs_test_t *test_ptr); static void test_from_data(bs_test_t *test_ptr); static void test_from_dynbuf(bs_test_t *test_ptr); static void test_escaped_string(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bspl_plist_test_cases[] = { { true, "from_string", test_from_string }, { true, "from_file", test_from_file }, { true, "from_data", test_from_data }, { true, "from_dynbuf", test_from_dynbuf }, { true, "escaped_string", test_escaped_string }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bspl_plist_test_set = BS_TEST_SET( true, "plist", bspl_plist_test_cases); static const uint8_t _test_data[] = { 'v', 'a', 'l', 'u', 'e' }; /* ------------------------------------------------------------------------- */ /** Tests plist object creation from string. */ void test_from_string(bs_test_t *test_ptr) { bspl_object_t *object_ptr, *v_ptr; bspl_array_t *array_ptr; bspl_dict_t *dict_ptr; // A string. object_ptr = bspl_create_object_from_plist_string("value"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, object_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "value", bspl_string_value(bspl_string_from_object(object_ptr))); bspl_object_unref(object_ptr); // A string that should be quoted. object_ptr = bspl_create_object_from_plist_string("va:lue"); BS_TEST_VERIFY_EQ(test_ptr, NULL, object_ptr); // A dict. object_ptr = bspl_create_object_from_plist_string( "{key1=dict_value1;key2=dict_value2}"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, object_ptr); dict_ptr = bspl_dict_from_object(object_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); v_ptr = bspl_dict_get(dict_ptr, "key1"); BS_TEST_VERIFY_STREQ( test_ptr, "dict_value1", bspl_string_value(bspl_string_from_object(v_ptr))); v_ptr = bspl_dict_get(dict_ptr, "key2"); BS_TEST_VERIFY_STREQ( test_ptr, "dict_value2", bspl_string_value(bspl_string_from_object(v_ptr))); bspl_object_unref(object_ptr); // A dict, with semicolon at the end. object_ptr = bspl_create_object_from_plist_string( "{key1=dict_value1;key2=dict_value2;}"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, bspl_dict_from_object(object_ptr)); bspl_object_unref(object_ptr); // A dict with a duplicate key. Will return NULL, no need to unref. object_ptr = bspl_create_object_from_plist_string( "{key1=dict_value1;key1=dict_value2}"); BS_TEST_VERIFY_EQ(test_ptr, NULL, object_ptr); // An empty dict. object_ptr = bspl_create_object_from_plist_string("{}"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, object_ptr); bspl_object_unref(object_ptr); // An array. object_ptr = bspl_create_object_from_plist_string( "(elem0,elem1)"); array_ptr = bspl_array_from_object(object_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, array_ptr); if (NULL != array_ptr) { BS_TEST_VERIFY_STREQ( test_ptr, "elem0", bspl_string_value(bspl_string_from_object( bspl_array_at(array_ptr, 0)))); BS_TEST_VERIFY_STREQ( test_ptr, "elem1", bspl_string_value(bspl_string_from_object( bspl_array_at(array_ptr, 1)))); } bspl_object_unref(object_ptr); // An array with a comma at the end. object_ptr = bspl_create_object_from_plist_string( "(elem0,elem1,)"); array_ptr = bspl_array_from_object(object_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, array_ptr); bspl_object_unref(object_ptr); // An empty array. object_ptr = bspl_create_object_from_plist_string("()"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, object_ptr); array_ptr = bspl_array_from_object(object_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, array_ptr); BS_TEST_VERIFY_EQ(test_ptr, 0, bspl_array_size(array_ptr)); bspl_object_unref(object_ptr); } /* ------------------------------------------------------------------------- */ /** Tests plist object creation from string. */ void test_from_file(bs_test_t *test_ptr) { bspl_object_t *object_ptr, *v_ptr; object_ptr = bspl_create_object_from_plist_file( bs_test_data_path(test_ptr, "string.plist")); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, object_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "file_value", bspl_string_value(bspl_string_from_object(object_ptr))); bspl_object_unref(object_ptr); object_ptr = bspl_create_object_from_plist_file( bs_test_data_path(test_ptr, "dict.plist")); BS_TEST_VERIFY_NEQ(test_ptr, NULL, object_ptr); bspl_dict_t *dict_ptr = bspl_dict_from_object(object_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, dict_ptr); v_ptr = BS_ASSERT_NOTNULL(bspl_dict_get(dict_ptr, "key0")); BS_TEST_VERIFY_STREQ( test_ptr, "value0", bspl_string_value(bspl_string_from_object(v_ptr))); v_ptr = BS_ASSERT_NOTNULL(bspl_dict_get(dict_ptr, "quoted+key")); BS_TEST_VERIFY_STREQ( test_ptr, "value", bspl_string_value(bspl_string_from_object(v_ptr))); bspl_object_unref(object_ptr); object_ptr = bspl_create_object_from_plist_file( bs_test_data_path(test_ptr, "array.plist")); BS_TEST_VERIFY_NEQ(test_ptr, NULL, object_ptr); bspl_array_t *array_ptr = bspl_array_from_object(object_ptr); BS_TEST_VERIFY_NEQ(test_ptr, NULL, array_ptr); bspl_object_unref(object_ptr); } /* ------------------------------------------------------------------------- */ /** Tests plist object creation from a sized data buffer. */ void test_from_data(bs_test_t *test_ptr) { // A string. bspl_object_t *object_ptr = bspl_create_object_from_plist_data( _test_data, sizeof(_test_data)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, object_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "value", bspl_string_value(bspl_string_from_object(object_ptr))); bspl_object_unref(object_ptr); } /* ------------------------------------------------------------------------- */ /** Tests plist object creation from a dynamic data buffer. */ void test_from_dynbuf(bs_test_t *test_ptr) { bs_dynbuf_t dynbuf = { .data_ptr = (void*)_test_data, .length = sizeof(_test_data) }; bspl_object_t *object_ptr = bspl_create_object_from_dynbuf(&dynbuf); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, object_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "value", bspl_string_value(bspl_string_from_object(object_ptr))); bspl_object_unref(object_ptr); } /* ------------------------------------------------------------------------- */ void test_escaped_string(bs_test_t *test_ptr) { bspl_object_t *o; // A string with escaped backslash and double quote. o = bspl_create_object_from_plist_string("\"backslash\\\\dquote\\\"end\""); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, o); BS_TEST_VERIFY_STREQ( test_ptr, "backslash\\dquote\"end", bspl_string_value(bspl_string_from_object(o))); bspl_object_unref(o); } /* == End of plist.c ======================================================= */ wlmaker-0.8.1/submodules/libbase/src/plist/grammar.y0000644000175100017510000001217015222270712022116 0ustar runnerrunner/* ========================================================================= */ /** * @file grammar.y * * See https://www.gnu.org/software/bison/manual/bison.html. * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* == Prologue ============================================================= */ %{ #include "grammar.h" // IWYU pragma: keep #include "analyzer.h" // IWYU pragma: keep #include // IWYU pragma: keep #include #include #include #include %} /* == Bison declarations =================================================== */ %union{ char *string; } %locations %define parse.error verbose %define api.pure full %parse-param { void* scanner } %parse-param { bspl_parser_context_t *ctx_ptr } %lex-param { yyscan_t scanner } %code requires { #include "parser_context.h" } %code provides { extern int yyerror( YYLTYPE *loc_ptr, void* scanner, bspl_parser_context_t *ctx_ptr, const char* msg_ptr); } %token TK_LPAREN "(" %token TK_RPAREN ")" %token TK_LBRACE "{" %token TK_RBRACE "}" %token TK_COMMA "," %token TK_EQUAL "=" %token TK_SEMICOLON ";" %token TK_IDENTIFIER_STRING "identifier string" %token TK_QUOTED_STRING "quoted string" %destructor { free($$); } %% /* == Grammar rules ======================================================== */ /* See https://code.google.com/archive/p/networkpx/wikis/PlistSpec.wiki. */ start: object; object: string | dict | array; string: TK_IDENTIFIER_STRING { bspl_string_t *string_ptr = bspl_string_create($1); free($1); bs_ptr_stack_push(&ctx_ptr->object_stack, bspl_object_from_string(string_ptr)); } | TK_QUOTED_STRING { size_t len = strlen($1); BS_ASSERT(2 <= len); // It is supposed to be quoted. // Un-escape the escaped backslash and double quotes. char *dest_ptr = $1; for (size_t i = 1; i < len - 1; ++i, ++dest_ptr) { if ($1[i] == '\\') ++i; *dest_ptr = $1[i]; } *dest_ptr = '\0'; bspl_string_t *string_ptr = bspl_string_create($1); free($1); bs_ptr_stack_push(&ctx_ptr->object_stack, bspl_object_from_string(string_ptr)); }; dict: TK_LBRACE { bspl_dict_t *dict_ptr = bspl_dict_create(); bs_ptr_stack_push( &ctx_ptr->object_stack, bspl_object_from_dict(dict_ptr)); } kv_list TK_RBRACE; kv_list: kv TK_SEMICOLON kv_list | kv | %empty; kv: string TK_EQUAL object { bspl_object_t *object_ptr = bs_ptr_stack_pop(&ctx_ptr->object_stack); bspl_string_t *key_string_ptr = bspl_string_from_object( bs_ptr_stack_pop(&ctx_ptr->object_stack)); const char *key_ptr = bspl_string_value(key_string_ptr); bspl_dict_t *dict_ptr = bspl_dict_from_object( bs_ptr_stack_peek(&ctx_ptr->object_stack, 0)); bool rv = bspl_dict_add(dict_ptr, key_ptr, object_ptr); if (!rv) { // TODO(kaeser@gubbe.ch): Keep this as error in context. bs_log(BS_WARNING, "Failed bspl_dict_add(%p, %s, %p)", dict_ptr, key_ptr, object_ptr); } bspl_object_unref(object_ptr); bspl_string_unref(key_string_ptr); if (!rv) return -1; }; array: TK_LPAREN { bspl_array_t *array_ptr = bspl_array_create(); bs_ptr_stack_push( &ctx_ptr->object_stack, bspl_object_from_array(array_ptr)); } element_list TK_RPAREN; element_list: element TK_COMMA element_list | element | %empty; element: object { bspl_array_t *array_ptr = bspl_array_from_object( bs_ptr_stack_peek(&ctx_ptr->object_stack, 1)); bspl_object_t *object_ptr = bs_ptr_stack_pop(&ctx_ptr->object_stack); bool rv = bspl_array_push_back(array_ptr, object_ptr); if (!rv) { // TODO(kaeser@gubbe.ch): Keep this as error in context. bs_log(BS_WARNING, "Failed bspl_array_push_back(%p, %p)", array_ptr, object_ptr); } bspl_object_unref(object_ptr); }; %% /* == Epilogue ============================================================= */ #include int yyerror( YYLTYPE *loc_ptr, __UNUSED__ void* scanner, __UNUSED__ bspl_parser_context_t *ctx_ptr, const char* msg_ptr) { bs_log(BS_ERROR, "Parse error at %d,%d: %s", loc_ptr->first_line, loc_ptr->first_column, msg_ptr); return -1; } /* == End of grammar.y ===================================================== */ wlmaker-0.8.1/submodules/libbase/src/plist/conf.md0000644000175100017510000000226315222270712021547 0ustar runnerrunner # Configuration Files {#conf_page} Configuration files are in *text plist* (or: *ASCII plist*) format: https://code.google.com/archive/p/networkpx/wikis/PlistSpec.wiki ```plantuml class Object { {abstract}#void destroy() } class Dict { Object get(String key); #void destroy() } Dict <|-- Object class Array { size_t size() Object get(size_t idx) #void destroy() } Array <|-- Object class String { const char *get_value(); #void destroy() } String <|-- Object class DictItem { -String key -Object value } class ArrayElement { Object value } ``` wlmaker-0.8.1/submodules/libbase/src/plist/analyzer.l0000644000175100017510000000566315222270712022311 0ustar runnerrunner/* ========================================================================= */ /** * @file analyzer.l * * See https://westes.github.io/flex/manual/. * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ %{ #include #include // IWYU pragma: keep #include "grammar.h" struct yyguts_t; // For IWYU. /** Addresses valgrind unitialized memory warnings. */ #define YY_USER_INIT do { \ yylineno = 1; yycolumn = 0; yyleng = 0; \ memset(yylloc, 0, sizeof(*yylloc)); \ } while (0); /** Permits location tracking, for positioned error reports. */ #define YY_USER_ACTION \ do { \ yylloc->first_line = yylloc->last_line = yylineno; \ yylloc->first_column = yycolumn - 1; \ yylloc->last_column = yycolumn + yyleng - 1; \ yycolumn += yyleng; \ } while (0); %} /* == Definitions section ===================================================*/ %option batch %option bison-bridge %option bison-locations %option never-interactive %option nodefault %option noinput %option nounput %option noyywrap %option reentrant %option yylineno ws [[:blank:]\r\n] /* == Rules section ======================================================== */ %% {ws}+ { /* whitespace */ } "//".* { /* comment. */ } \"([^\"]|(\\\")|(\\\\))*\" { yylval->string = logged_strdup(yytext); return TK_QUOTED_STRING; } [a-zA-Z0-9_.$]+ { yylval->string = logged_strdup(yytext); return TK_IDENTIFIER_STRING; } "(" { return TK_LPAREN; } ")" { return TK_RPAREN; } "{" { return TK_LBRACE; } "}" { return TK_RBRACE; } "," { return TK_COMMA; } "=" { return TK_EQUAL; } ";" { return TK_SEMICOLON; } . { char msg[256]; snprintf(msg, sizeof(msg), "Unexpected character: '%s'", yytext); yyerror(yylloc, yyscanner, yyextra, msg); return YYerror; } %% /* == User code section ==================================================== */ /* == End of analyzer.l ==================================================== */ wlmaker-0.8.1/submodules/libbase/src/plist/decode.c0000644000175100017510000013736115222270712021677 0ustar runnerrunner/* ========================================================================= */ /** * @file decode.c * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** A pointer of type `value_type`, at `offset` behind `base_ptr`. */ #define BS_VALUE_AT(_value_type, _base_ptr, _offset) \ ((_value_type*)((uint8_t*)(_base_ptr) + (_offset))) static bool _bspl_init_defaults( const bspl_desc_t *desc_ptr, void *value_ptr); /** Enum descriptor for decoding bool. */ static const bspl_enum_desc_t _bspl_bool_desc[] = { BSPL_ENUM("True", true), BSPL_ENUM("False", false), BSPL_ENUM("Yes", true), BSPL_ENUM("No", false), BSPL_ENUM("Enabled", true), BSPL_ENUM("Disabled", false), BSPL_ENUM("On", true), BSPL_ENUM("Off", false), BSPL_ENUM_SENTINEL() }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool bspl_decode_dict( bspl_dict_t *dict_ptr, const bspl_desc_t *desc_ptr, void *value_ptr) { union bspl_desc_value dv = { .v_dict_desc_ptr = desc_ptr }; if (!_bspl_init_defaults(desc_ptr, value_ptr) || !bspl_decode_dict_without_init( bspl_object_from_dict(dict_ptr), &dv, value_ptr)) { bspl_decoded_destroy(desc_ptr, value_ptr); return false; } return true; } /* ------------------------------------------------------------------------- */ bspl_dict_t *bspl_encode_dict( const bspl_desc_t *desc_ptr, const void *src_ptr) { bspl_dict_t *dict_ptr = bspl_dict_create(); if (NULL == dict_ptr) return NULL; if (bspl_encode_into_dict(desc_ptr, src_ptr, dict_ptr)) return dict_ptr; bspl_dict_unref(dict_ptr); return NULL; } /* ------------------------------------------------------------------------- */ bool bspl_encode_into_dict( const bspl_desc_t *desc_ptr, const void *value_ptr, bspl_dict_t *dest_dict_ptr) { bspl_object_t *object_ptr; for (const bspl_desc_t *iter_desc_ptr = desc_ptr; iter_desc_ptr->key_ptr != NULL; ++iter_desc_ptr) { // Check presence field, but only if given. if (iter_desc_ptr->presence_ofs != iter_desc_ptr->field_ofs && !*BS_VALUE_AT(bool, value_ptr, iter_desc_ptr->presence_ofs)) { continue; } if (NULL == iter_desc_ptr->encode) { bs_log(BS_ERROR, "Missing encode method for key \"%s\"", iter_desc_ptr->key_ptr); return false; } object_ptr = iter_desc_ptr->encode( &iter_desc_ptr->v, BS_VALUE_AT(void, value_ptr, iter_desc_ptr->field_ofs)); if (NULL == object_ptr) return false; if (!bspl_dict_add( dest_dict_ptr, iter_desc_ptr->key_ptr, object_ptr)) { bs_log(BS_WARNING, "Failed bspl_dict_add(%p, \"%s\", %p)", dest_dict_ptr, iter_desc_ptr->key_ptr, object_ptr); return false; } bspl_object_unref(object_ptr); } return true; } /* ------------------------------------------------------------------------- */ void bspl_decoded_destroy( const bspl_desc_t *desc_ptr, void *value_ptr) { for (const bspl_desc_t *iter_desc_ptr = desc_ptr; iter_desc_ptr->key_ptr != NULL; ++iter_desc_ptr) { void *ptr = BS_VALUE_AT(void, value_ptr, iter_desc_ptr->field_ofs); switch (iter_desc_ptr->type) { case BSPL_TYPE_STRING: if (NULL != ptr) { char **str_ptr_ptr = ptr; free(*str_ptr_ptr); *str_ptr_ptr = NULL; } break; case BSPL_TYPE_DICT: bspl_decoded_destroy(iter_desc_ptr->v.v_dict_desc_ptr, ptr); break; case BSPL_TYPE_CUSTOM: if (NULL != iter_desc_ptr->v.v_custom.fini) { iter_desc_ptr->v.v_custom.fini(ptr); } break; case BSPL_TYPE_ARRAY: if (NULL != iter_desc_ptr->v.v_array.fini) { iter_desc_ptr->v.v_array.fini(ptr); } break; default: // Nothing. break; } } } /* ------------------------------------------------------------------------- */ bool bspl_enum_name_to_value( const bspl_enum_desc_t *enum_desc_ptr, const char *name_ptr, int *value_ptr) { if (NULL == name_ptr) return false; for (; NULL != enum_desc_ptr->name_ptr; ++enum_desc_ptr) { if (0 == strcmp(enum_desc_ptr->name_ptr, name_ptr)) { *value_ptr = enum_desc_ptr->value; return true; } } return false; } /* ------------------------------------------------------------------------- */ bool bspl_enum_value_to_name( const bspl_enum_desc_t *enum_desc_ptr, int value, const char **name_ptr_ptr) { for (; NULL != enum_desc_ptr->name_ptr; ++enum_desc_ptr) { if (value == enum_desc_ptr->value) { *name_ptr_ptr = enum_desc_ptr->name_ptr; return true; } } return false; } /* ------------------------------------------------------------------------- */ bool bspl_decode_uint64( bspl_object_t *obj_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value_from_object(obj_ptr); if (NULL == s) return false; uint64_t *uint64_ptr = value_ptr; return bs_strconvert_uint64(s, uint64_ptr, 10); } /* ------------------------------------------------------------------------- */ bool bspl_decode_int64( bspl_object_t *obj_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value_from_object(obj_ptr); if (NULL == s) return false; int64_t *int64_ptr = value_ptr; return bs_strconvert_int64(s, int64_ptr, 10); } /* ------------------------------------------------------------------------- */ bool bspl_decode_double( bspl_object_t *obj_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value_from_object(obj_ptr); if (NULL == s) return false; double *d_ptr = value_ptr; return bs_strconvert_double(s, d_ptr); } /* ------------------------------------------------------------------------- */ bool bspl_decode_argb32( bspl_object_t *obj_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value_from_object(obj_ptr); if (NULL == s) return false; uint32_t *argb32_ptr = value_ptr; int rv = sscanf(s, "argb32:%"PRIx32, argb32_ptr); if (1 != rv) { bs_log(BS_ERROR | BS_ERRNO, "Failed sscanf(\"%s\", \"argb32:%%"PRIx32", %p)", s, argb32_ptr); return false; } return true; } /* ------------------------------------------------------------------------- */ bool bspl_decode_bool( bspl_object_t *obj_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { static const union bspl_desc_value dv = { .v_enum.desc_ptr = _bspl_bool_desc }; int i; bool rv = bspl_decode_enum(obj_ptr, &dv, &i); *((bool*)value_ptr) = i; return rv; } /* ------------------------------------------------------------------------- */ bool bspl_decode_enum( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value_from_object(obj_ptr); if (NULL == s) return false; int *enum_value_ptr = value_ptr; const bspl_enum_desc_t *enum_desc_ptr = desc_value_ptr->v_enum.desc_ptr; for (; NULL != enum_desc_ptr->name_ptr; ++enum_desc_ptr) { if (0 == strcmp(enum_desc_ptr->name_ptr, s)) { *enum_value_ptr = enum_desc_ptr->value; return true; } } bs_log(BS_WARNING, "Failed to decode enum value \"%s\".", s); return false; } /* ------------------------------------------------------------------------- */ bool bspl_decode_string( bspl_object_t *obj_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value_from_object(obj_ptr); if (NULL == s) return false; char **str_ptr_ptr = value_ptr; if (NULL != *str_ptr_ptr) free(*str_ptr_ptr); *str_ptr_ptr = logged_strdup(s); return (NULL != *str_ptr_ptr); } /* ------------------------------------------------------------------------- */ bool bspl_decode_charbuf( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr) { const char *s = bspl_string_value_from_object(obj_ptr); if (NULL == s) return false; if (desc_value_ptr->v_charbuf.len < strlen(s) + 1) { bs_log(BS_WARNING, "Charbuf size %zu < %zu + 1 for \"%s\"", desc_value_ptr->v_charbuf.len, strlen(s), s); return false; } strcpy(value_ptr, s); return true; } /* ------------------------------------------------------------------------- */ bool bspl_decode_array( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr) { bspl_array_t *array_ptr = bspl_array_from_object(obj_ptr); if (NULL == array_ptr) { bs_log(BS_ERROR, "Requires object type ARRAY for %p", obj_ptr); return false; } bool rv = true; for (size_t i = 0; i < bspl_array_size(array_ptr); ++i) { bspl_object_t *o = bspl_array_at(array_ptr, i); if (!desc_value_ptr->v_array.decode_item(o, i, value_ptr)) rv = false; } return rv; } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_uint64( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { char buf[21]; // Length of the UINT64_MAX value plus 1 for NUL. const uint64_t *uint64_ptr = value_ptr; int rv = snprintf(buf, sizeof(buf), "%"PRIu64, *uint64_ptr); if (0 > rv || (size_t)rv >= sizeof(buf)) return NULL; return bspl_object_from_string(bspl_string_create(buf)); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_int64( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { char buf[22]; // Length of INT64_MIN value plus sign, plus 1 for NUL. const int64_t *i64_ptr = value_ptr; int rv = snprintf(buf, sizeof(buf), "%"PRId64, *i64_ptr); if (0 > rv || (size_t)rv >= sizeof(buf)) return NULL; return bspl_object_from_string(bspl_string_create(buf)); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_double( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { char buf[32]; // Surely enough for a double. const double *d_ptr = value_ptr; int rv = snprintf(buf, sizeof(buf), "%e", *d_ptr); if (0 > rv || (size_t)rv >= sizeof(buf)) return NULL; return bspl_object_from_string(bspl_string_create(buf)); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_argb32( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { char buf[16]; // Enough for "argb32:aarrggbb". const uint32_t *argb32_ptr = value_ptr; int rv = snprintf(buf, sizeof(buf), "argb32:%"PRIx32, *argb32_ptr); if (0 > rv || (size_t)rv >= sizeof(buf)) return NULL; return bspl_object_from_string(bspl_string_create(buf)); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_bool( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { const bool *b_ptr = value_ptr; static const union bspl_desc_value dv = { .v_enum.desc_ptr = _bspl_bool_desc }; int i = (*b_ptr != false); return bspl_encode_enum(&dv, &i); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_enum( const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { const int *i_ptr = value_ptr; const char *p; if (!bspl_enum_value_to_name( desc_value_ptr->v_enum.desc_ptr, *i_ptr, &p)) return NULL; return bspl_object_from_string(bspl_string_create(p)); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_string( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { if (NULL == value_ptr) return NULL; char *const *str_ptr_ptr = value_ptr; return bspl_object_from_string(bspl_string_create(*str_ptr_ptr)); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_charbuf( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { const char *charbuf_ptr = value_ptr; return bspl_object_from_string(bspl_string_create(charbuf_ptr)); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_encode_dict_as_object( const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { bspl_dict_t *dict_ptr = bspl_dict_create(); if (NULL == dict_ptr) return NULL; if (bspl_encode_into_dict( desc_value_ptr->v_dict_desc_ptr, value_ptr, dict_ptr)) { return bspl_object_from_dict(dict_ptr); } bspl_dict_unref(dict_ptr); return NULL; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Initializes default values at the destination, as described. * * @param desc_ptr * @param value_ptr */ bool _bspl_init_defaults(const bspl_desc_t *desc_ptr, void *value_ptr) { char **str_ptr_ptr; char *str_ptr; for (const bspl_desc_t *iter_desc_ptr = desc_ptr; iter_desc_ptr->key_ptr != NULL; ++iter_desc_ptr) { if (iter_desc_ptr->presence_ofs != iter_desc_ptr->field_ofs) { *BS_VALUE_AT(bool, value_ptr, iter_desc_ptr->presence_ofs) = false; } switch (iter_desc_ptr->type) { case BSPL_TYPE_UINT64: *BS_VALUE_AT(uint64_t, value_ptr, iter_desc_ptr->field_ofs) = iter_desc_ptr->v.v_uint64.default_value; break; case BSPL_TYPE_INT64: *BS_VALUE_AT(int64_t, value_ptr, iter_desc_ptr->field_ofs) = iter_desc_ptr->v.v_int64.default_value; break; case BSPL_TYPE_DOUBLE: *BS_VALUE_AT(double, value_ptr, iter_desc_ptr->field_ofs) = iter_desc_ptr->v.v_double.default_value; break; case BSPL_TYPE_ARGB32: *BS_VALUE_AT(uint32_t, value_ptr, iter_desc_ptr->field_ofs) = iter_desc_ptr->v.v_argb32.default_value; break; case BSPL_TYPE_BOOL: *BS_VALUE_AT(bool, value_ptr, iter_desc_ptr->field_ofs) = iter_desc_ptr->v.v_bool.default_value; break; case BSPL_TYPE_ENUM: *BS_VALUE_AT(int, value_ptr, iter_desc_ptr->field_ofs) = iter_desc_ptr->v.v_enum.default_value; break; case BSPL_TYPE_STRING: str_ptr_ptr = BS_VALUE_AT( char*, value_ptr, iter_desc_ptr->field_ofs); *str_ptr_ptr = logged_strdup( iter_desc_ptr->v.v_string.default_value_ptr); if (NULL == *str_ptr_ptr) return false; break; case BSPL_TYPE_CHARBUF: str_ptr = BS_VALUE_AT( char, value_ptr, iter_desc_ptr->field_ofs); if (NULL == iter_desc_ptr->v.v_charbuf.default_value_ptr) break; if (iter_desc_ptr->v.v_charbuf.len < strlen(iter_desc_ptr->v.v_charbuf.default_value_ptr) + 1) { bs_log(BS_ERROR, "Buffer size %zu < %zu + 1, default charbuf (\"%s\")", iter_desc_ptr->v.v_charbuf.len, strlen(iter_desc_ptr->v.v_charbuf.default_value_ptr), iter_desc_ptr->v.v_charbuf.default_value_ptr); return false; } strcpy(str_ptr, iter_desc_ptr->v.v_charbuf.default_value_ptr); break; case BSPL_TYPE_DICT: if (!_bspl_init_defaults( iter_desc_ptr->v.v_dict_desc_ptr, BS_VALUE_AT(void*, value_ptr, iter_desc_ptr->field_ofs))) { return false; } break; case BSPL_TYPE_CUSTOM: if (NULL != iter_desc_ptr->v.v_custom.init && !iter_desc_ptr->v.v_custom.init( BS_VALUE_AT(void*, value_ptr, iter_desc_ptr->field_ofs))) { return false; } break; case BSPL_TYPE_ARRAY: if (NULL != iter_desc_ptr->v.v_array.init && !iter_desc_ptr->v.v_array.init( BS_VALUE_AT(void*, value_ptr, iter_desc_ptr->field_ofs))) { return false; } break; default: bs_log(BS_ERROR, "Unsupported type %d.", iter_desc_ptr->type); return false; } } return true; } /* ------------------------------------------------------------------------- */ bool bspl_decode_dict_without_init( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr) { bspl_dict_t *dict_ptr = bspl_dict_from_object(obj_ptr); if (NULL == dict_ptr) { bs_log(BS_ERROR, "Requires object type DICT for %p", obj_ptr); return false; } for (const bspl_desc_t *iter_desc_ptr = desc_value_ptr->v_dict_desc_ptr; iter_desc_ptr->key_ptr != NULL; ++iter_desc_ptr) { bspl_object_t *item_obj_ptr = bspl_dict_get( dict_ptr, iter_desc_ptr->key_ptr); if (NULL == item_obj_ptr) { if (iter_desc_ptr->required) { bs_log(BS_ERROR, "Key \"%s\" not found in dict %p.", iter_desc_ptr->key_ptr, dict_ptr); return false; } continue; } if (NULL == iter_desc_ptr->decode) { bs_log(BS_ERROR, "Missing decode method for %p (key \"%s\")", item_obj_ptr, iter_desc_ptr->key_ptr); return false; } bool rv = iter_desc_ptr->decode( item_obj_ptr, &iter_desc_ptr->v, BS_VALUE_AT(void, value_ptr, iter_desc_ptr->field_ofs)); if (iter_desc_ptr->presence_ofs != iter_desc_ptr->field_ofs) { *BS_VALUE_AT(bool, value_ptr, iter_desc_ptr->presence_ofs) = rv; } if (!rv) { bs_log(BS_ERROR, "Failed to decode key \"%s\"", iter_desc_ptr->key_ptr); return false; } } return true; } /* == Unit tests =========================================================== */ static void test_init_defaults(bs_test_t *test_ptr); static void test_enum_translate(bs_test_t *test_ptr); static void test_decode_dict(bs_test_t *test_ptr); static void test_decode_number(bs_test_t *test_ptr); static void test_decode_argb32(bs_test_t *test_ptr); static void test_decode_bool(bs_test_t *test_ptr); static void test_decode_enum(bs_test_t *test_ptr); static void test_decode_string(bs_test_t *test_ptr); static void test_decode_charbuf(bs_test_t *test_ptr); static void test_encode_dict(bs_test_t *test_ptr); static void test_encode_number(bs_test_t *test_ptr); static void test_encode_argb32(bs_test_t *test_ptr); static void test_encode_bool(bs_test_t *test_ptr); static void test_encode_enum(bs_test_t *test_ptr); static void test_encode_string(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bspl_decode_test_cases[] = { { true, "init_defaults", test_init_defaults }, { true, "enum_translate", test_enum_translate }, { true, "dict", test_decode_dict }, { true, "number", test_decode_number }, { true, "argb32", test_decode_argb32 }, { true, "bool", test_decode_bool }, { true, "enum", test_decode_enum }, { true, "string", test_decode_string }, { true, "charbuf", test_decode_charbuf }, { true, "encode_dict", test_encode_dict }, { true, "encode_number", test_encode_number }, { true, "encode_argb32", test_encode_argb32 }, { true, "encode_bool", test_encode_bool }, { true, "encode_enum", test_encode_enum }, { true, "encode_string", test_encode_string }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bspl_decode_test_set = BS_TEST_SET( true, "decode", bspl_decode_test_cases); static bool _bspl_test_custom_decode( bspl_object_t *object_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); static bspl_object_t *_bspl_test_custom_encode( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); static void _bspl_test_array_item_destroy( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); static bool _bspl_test_custom_init(void *dst_ptr); static void _bspl_test_custom_fini(void *dst_ptr); static bool _bspl_test_array_decode( bspl_object_t *obj_ptr, size_t i, void *dst_ptr); static bspl_object_t *_bspl_test_array_encode_all( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); static bool _bspl_test_array_init(void *dst_ptr); static void _bspl_test_array_fini(void *dst_ptr); static bool _bspl_test_array_encode_dlnode( bs_dllist_node_t *dlnode_ptr, void *ud_ptr); /** Structure with test values. */ typedef struct { #ifndef DOXYGEN_SHOULD_SKIP_THIS char *value; #endif // DOXYGEN_SHOULD_SKIP_THIS } _test_subdict_value_t; /** Structure with test values. */ typedef struct { #ifndef DOXYGEN_SHOULD_SKIP_THIS uint64_t v_uint64; bool has_uint64; int64_t v_int64; bool has_int64; double v_double; bool has_double; uint32_t v_argb32; bool has_argb32; bool v_bool; bool has_bool; int v_enum; bool has_enum; char *v_string; bool has_string; char v_charbuf[10]; bool has_charbuf; _test_subdict_value_t subdict; bool has_subdict; void *v_custom_ptr; bool has_custom; bs_dllist_t *dllist_ptr; bool has_array; #endif // DOXYGEN_SHOULD_SKIP_THIS } _test_value_t; /** A decoded array item. */ typedef struct { /** Node of _test_value_t::dllist_ptr. */ bs_dllist_node_t dlnode; /** the character of the item. */ char c; } _test_array_item_t; /** An enum descriptor. */ static const bspl_enum_desc_t _test_enum_desc[] = { BSPL_ENUM("enum1", 1), BSPL_ENUM("enum2", 2), BSPL_ENUM_SENTINEL() }; /** Descriptor of a contained dict. */ static const bspl_desc_t _bspl_decode_test_subdesc[] = { BSPL_DESC_STRING("string", true, _test_subdict_value_t, value, value, "Other String"), BSPL_DESC_SENTINEL(), }; /** Test descriptor. */ static const bspl_desc_t _bspl_decode_test_desc[] = { BSPL_DESC_UINT64("u64", true, _test_value_t, v_uint64, has_uint64, 1234), BSPL_DESC_INT64("i64", true, _test_value_t, v_int64, has_int64, -1234), BSPL_DESC_DOUBLE("d", true, _test_value_t, v_double, has_double, 3.14), BSPL_DESC_ARGB32("argb32", true, _test_value_t, v_argb32, has_argb32, 0x01020304), BSPL_DESC_BOOL("bool", true, _test_value_t, v_bool, has_bool, true), BSPL_DESC_ENUM("enum", true, _test_value_t, v_enum, has_enum, 3, _test_enum_desc), BSPL_DESC_STRING("string", true, _test_value_t, v_string, has_string, "The String"), BSPL_DESC_CHARBUF("charbuf", true, _test_value_t, v_charbuf, has_charbuf, 10, "CharBuf"), BSPL_DESC_DICT("subdict", true, _test_value_t, subdict, has_subdict, _bspl_decode_test_subdesc), BSPL_DESC_CUSTOM("custom", true, _test_value_t, v_custom_ptr, has_custom, _bspl_test_custom_decode, _bspl_test_custom_encode, _bspl_test_custom_init, _bspl_test_custom_fini), BSPL_DESC_ARRAY("array", true, _test_value_t, dllist_ptr, has_array, _bspl_test_array_decode, _bspl_test_array_encode_all, _bspl_test_array_init, _bspl_test_array_fini), BSPL_DESC_SENTINEL(), }; /* ------------------------------------------------------------------------- */ /** A custom decoding function. Here: just decode a string. */ bool _bspl_test_custom_decode( bspl_object_t *object_ptr, __UNUSED__ const union bspl_desc_value *desc_value_ptr, void *value_ptr) { char** str_ptr_ptr = value_ptr; _bspl_test_custom_fini(value_ptr); const char *s = bspl_string_value_from_object(object_ptr); if (NULL == s) return false; *str_ptr_ptr = logged_strdup(s); return *str_ptr_ptr != NULL; } /* ------------------------------------------------------------------------- */ /** A custom encoding function. Here: just encode the string. */ bspl_object_t *_bspl_test_custom_encode( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { char *const *str_ptr_ptr = value_ptr; return bspl_object_from_string(bspl_string_create(*str_ptr_ptr)); } /* ------------------------------------------------------------------------- */ /** A custom decoding initializer. Here: Just create a string. */ bool _bspl_test_custom_init(void *dst_ptr) { char** str_ptr_ptr = dst_ptr; _bspl_test_custom_fini(dst_ptr); *str_ptr_ptr = logged_strdup("Custom Init"); return *str_ptr_ptr != NULL; } /* ------------------------------------------------------------------------- */ /** A custom decoding cleanup method. Frees the string. */ void _bspl_test_custom_fini(void *dst_ptr) { char** str_ptr_ptr = dst_ptr; if (NULL != *str_ptr_ptr) { free(*str_ptr_ptr); *str_ptr_ptr = NULL; } } /* ------------------------------------------------------------------------- */ /** Decode method for array item. */ bool _bspl_test_array_decode( bspl_object_t *obj_ptr, __UNUSED__ size_t i, void *dst_ptr) { bs_dllist_t **dllist_ptr_ptr = dst_ptr; _test_array_item_t *item_ptr = logged_calloc(1, sizeof(_test_array_item_t)); if (NULL == item_ptr) return false; bspl_string_t *string_ptr = bspl_string_from_object(obj_ptr); if (NULL == string_ptr) { free(item_ptr); return false; } item_ptr->c = bspl_string_value(string_ptr)[0]; bs_dllist_push_back(*dllist_ptr_ptr, &item_ptr->dlnode); return true; } /* ------------------------------------------------------------------------- */ /** Encodes all items of the array, and returns the plist array object. */ bspl_object_t *_bspl_test_array_encode_all( __UNUSED__ const union bspl_desc_value *desc_value_ptr, const void *value_ptr) { bspl_array_t *array_ptr = bspl_array_create(); if (NULL == array_ptr) return NULL; bs_dllist_t *const *dllist_ptr_ptr = value_ptr; if (bs_dllist_all(*dllist_ptr_ptr, _bspl_test_array_encode_dlnode, array_ptr)) { return bspl_object_from_array(array_ptr); } bspl_array_unref(array_ptr); return NULL; } /* ------------------------------------------------------------------------- */ void _bspl_test_array_item_destroy( bs_dllist_node_t *dlnode_ptr, __UNUSED__ void *ud_ptr) { _test_array_item_t *item_ptr = BS_CONTAINER_OF( dlnode_ptr, _test_array_item_t, dlnode); free(item_ptr); } /* ------------------------------------------------------------------------- */ bool _bspl_test_array_init(void *dst_ptr) { bs_dllist_t **dllist_ptr_ptr = dst_ptr; *dllist_ptr_ptr = logged_calloc(1, sizeof(bs_dllist_t)); if (NULL == *dllist_ptr_ptr) return false; return true; } /* ------------------------------------------------------------------------- */ void _bspl_test_array_fini(void *dst_ptr) { bs_dllist_t **dllist_ptr_ptr = dst_ptr; bs_dllist_for_each(*dllist_ptr_ptr, _bspl_test_array_item_destroy, NULL); if (NULL != *dllist_ptr_ptr) { free(*dllist_ptr_ptr); *dllist_ptr_ptr = NULL; } } /* ------------------------------------------------------------------------- */ /** @ref bs_dllist_for_each callback: Encodes one node, appends to `ud_ptr`. */ bool _bspl_test_array_encode_dlnode(bs_dllist_node_t *dlnode_ptr, void *ud_ptr) { _test_array_item_t *item_ptr = BS_CONTAINER_OF( dlnode_ptr, _test_array_item_t, dlnode); char s[2] = { item_ptr->c, '\0' }; bspl_object_t *object_ptr = bspl_object_from_string(bspl_string_create(s)); if (NULL == object_ptr) return false; bspl_array_t *array_ptr = ud_ptr; bool rv = bspl_array_push_back(array_ptr, object_ptr); bspl_object_unref(object_ptr); return rv; } /* ------------------------------------------------------------------------- */ /** Tests initialization of default values. */ void test_init_defaults(bs_test_t *test_ptr) { _test_value_t val; memset(&val, 1, sizeof(_test_value_t)); val.v_custom_ptr = NULL; BS_TEST_VERIFY_TRUE( test_ptr, _bspl_init_defaults(_bspl_decode_test_desc, &val)); BS_TEST_VERIFY_EQ(test_ptr, 1234, val.v_uint64); BS_TEST_VERIFY_FALSE(test_ptr, val.has_uint64); BS_TEST_VERIFY_EQ(test_ptr, -1234, val.v_int64); BS_TEST_VERIFY_FALSE(test_ptr, val.has_int64); BS_TEST_VERIFY_EQ(test_ptr, 0x01020304, val.v_argb32); BS_TEST_VERIFY_FALSE(test_ptr, val.has_argb32); BS_TEST_VERIFY_EQ(test_ptr, true, val.v_bool); BS_TEST_VERIFY_FALSE(test_ptr, val.has_bool); BS_TEST_VERIFY_EQ(test_ptr, 3, val.v_enum); BS_TEST_VERIFY_FALSE(test_ptr, val.has_enum); BS_TEST_VERIFY_STREQ(test_ptr, "The String", val.v_string); BS_TEST_VERIFY_FALSE(test_ptr, val.has_string); BS_TEST_VERIFY_STREQ(test_ptr, "CharBuf", val.v_charbuf); BS_TEST_VERIFY_FALSE(test_ptr, val.has_charbuf); BS_TEST_VERIFY_STREQ(test_ptr, "Other String", val.subdict.value); BS_TEST_VERIFY_FALSE(test_ptr, val.has_subdict); BS_TEST_VERIFY_STREQ(test_ptr, "Custom Init", val.v_custom_ptr); BS_TEST_VERIFY_FALSE(test_ptr, val.has_custom); BS_TEST_VERIFY_FALSE(test_ptr, val.has_array); bspl_decoded_destroy(_bspl_decode_test_desc, &val); } /* ------------------------------------------------------------------------- */ /** Tests @ref bspl_enum_name_to_value and @ref bspl_enum_value_to_name. */ void test_enum_translate(bs_test_t *test_ptr) { const bspl_enum_desc_t *d = _bspl_bool_desc; int v; BS_TEST_VERIFY_TRUE(test_ptr, bspl_enum_name_to_value(d, "True", &v)); BS_TEST_VERIFY_EQ(test_ptr, 1, v); BS_TEST_VERIFY_TRUE(test_ptr, bspl_enum_name_to_value(d, "On", &v)); BS_TEST_VERIFY_EQ(test_ptr, 1, v); BS_TEST_VERIFY_TRUE(test_ptr, bspl_enum_name_to_value(d, "Off", &v)); BS_TEST_VERIFY_EQ(test_ptr, 0, v); BS_TEST_VERIFY_FALSE(test_ptr, bspl_enum_name_to_value(d, "Bad", &v)); BS_TEST_VERIFY_FALSE(test_ptr, bspl_enum_name_to_value(d, NULL, &v)); const char *n_ptr; BS_TEST_VERIFY_TRUE(test_ptr, bspl_enum_value_to_name(d, true, &n_ptr)); BS_TEST_VERIFY_STREQ(test_ptr, "True", n_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_enum_value_to_name(d, false, &n_ptr)); BS_TEST_VERIFY_STREQ(test_ptr, "False", n_ptr); BS_TEST_VERIFY_FALSE(test_ptr, bspl_enum_value_to_name(d, 42, &n_ptr)); } /* ------------------------------------------------------------------------- */ /** Tests dict decoding. */ void test_decode_dict(bs_test_t *test_ptr) { _test_value_t val = {}; const char *plist_string_ptr = ("{" "u64 = \"100\";" "i64 = \"-101\";" "d = \"-1.414\";" "argb32 = \"argb32:0204080c\";" "bool = Disabled;" "enum = enum1;" "string = TestString;" "charbuf = TestBuf;" "subdict = { string = OtherTestString };" "array = (a, b);" "custom = CustomThing" "}"); bspl_dict_t *dict_ptr; dict_ptr = bspl_dict_from_object( bspl_create_object_from_plist_string(plist_string_ptr)); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dict_ptr); BS_TEST_VERIFY_TRUE_OR_RETURN( test_ptr, bspl_decode_dict(dict_ptr, _bspl_decode_test_desc, &val)); BS_TEST_VERIFY_EQ(test_ptr, 100, val.v_uint64); BS_TEST_VERIFY_TRUE(test_ptr, val.has_uint64); BS_TEST_VERIFY_EQ(test_ptr, -101, val.v_int64); BS_TEST_VERIFY_TRUE(test_ptr, val.has_int64); BS_TEST_VERIFY_EQ(test_ptr, -1.414, val.v_double); BS_TEST_VERIFY_TRUE(test_ptr, val.has_double); BS_TEST_VERIFY_EQ(test_ptr, 0x0204080c, val.v_argb32); BS_TEST_VERIFY_TRUE(test_ptr, val.has_argb32); BS_TEST_VERIFY_EQ(test_ptr, false, val.v_bool); BS_TEST_VERIFY_TRUE(test_ptr, val.has_bool); BS_TEST_VERIFY_EQ(test_ptr, 1, val.v_enum); BS_TEST_VERIFY_TRUE(test_ptr, val.has_enum); BS_TEST_VERIFY_STREQ(test_ptr, "TestString", val.v_string); BS_TEST_VERIFY_TRUE(test_ptr, val.has_string); BS_TEST_VERIFY_STREQ(test_ptr, "TestBuf", val.v_charbuf); BS_TEST_VERIFY_TRUE(test_ptr, val.has_charbuf); BS_TEST_VERIFY_STREQ(test_ptr, "OtherTestString", val.subdict.value); // No presence flag there. BS_TEST_VERIFY_STREQ(test_ptr, "CustomThing", val.v_custom_ptr); BS_TEST_VERIFY_TRUE(test_ptr, val.has_custom); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, val.dllist_ptr); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 2, bs_dllist_size(val.dllist_ptr)); BS_TEST_VERIFY_TRUE(test_ptr, val.has_array); _test_array_item_t *item_ptr = BS_CONTAINER_OF( val.dllist_ptr->head_ptr, _test_array_item_t, dlnode); BS_TEST_VERIFY_EQ(test_ptr, 'a', item_ptr->c); item_ptr = BS_CONTAINER_OF( item_ptr->dlnode.next_ptr, _test_array_item_t, dlnode); BS_TEST_VERIFY_EQ(test_ptr, 'b', item_ptr->c); bspl_dict_unref(dict_ptr); bspl_decoded_destroy(_bspl_decode_test_desc, &val); dict_ptr = bspl_dict_from_object( bspl_create_object_from_plist_string("{anything=value}")); BS_TEST_VERIFY_FALSE( test_ptr, bspl_decode_dict(dict_ptr, _bspl_decode_test_desc, &val)); bspl_dict_unref(dict_ptr); } /* ------------------------------------------------------------------------- */ /** Tests number decoding. */ void test_decode_number(bs_test_t *test_ptr) { bspl_object_t *obj_ptr; int64_t i64; uint64_t u64; obj_ptr = bspl_create_object_from_plist_string("42"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_uint64(obj_ptr, NULL, &u64)); BS_TEST_VERIFY_EQ(test_ptr, 42, u64); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_string("\"-1234\""); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_FALSE(test_ptr, bspl_decode_uint64(obj_ptr, NULL, &u64)); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_string("42"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_int64(obj_ptr, NULL, &i64)); BS_TEST_VERIFY_EQ(test_ptr, 42, i64); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_string("\"-1234\""); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_int64(obj_ptr, NULL, &i64)); BS_TEST_VERIFY_EQ(test_ptr, -1234, i64); bspl_object_unref(obj_ptr); double d; obj_ptr = bspl_create_object_from_plist_string("\"3.14\""); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_double(obj_ptr, NULL, &d)); BS_TEST_VERIFY_EQ(test_ptr, 3.14, d); bspl_object_unref(obj_ptr); } /* ------------------------------------------------------------------------- */ /** Tests argb32 decoding. */ void test_decode_argb32(bs_test_t *test_ptr) { bspl_object_t *obj_ptr = bspl_create_object_from_plist_string( "\"argb32:01020304\""); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); uint32_t argb32; BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_argb32(obj_ptr, NULL, &argb32)); BS_TEST_VERIFY_EQ(test_ptr, 0x01020304, argb32); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_string( "{c=\"argb32:ffa0b0c0\"}"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); _test_value_t v; bspl_desc_t desc[] = { BSPL_DESC_ARGB32("c", false, _test_value_t , v_argb32, v_argb32, 0x01020304), BSPL_DESC_SENTINEL() }; BS_TEST_VERIFY_TRUE( test_ptr, bspl_decode_dict(bspl_dict_from_object(obj_ptr), desc, &v)); BS_TEST_VERIFY_EQ(test_ptr, 0xffa0b0c0, v.v_argb32); bspl_object_unref(obj_ptr); } /* ------------------------------------------------------------------------- */ /** Tests bool decoding. */ void test_decode_bool(bs_test_t *test_ptr) { bool value; bspl_object_t *obj_ptr; obj_ptr = bspl_create_object_from_plist_string("Yes"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_bool(obj_ptr, NULL, &value)); BS_TEST_VERIFY_TRUE(test_ptr, value); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_string("Disabled"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_bool(obj_ptr, NULL, &value)); BS_TEST_VERIFY_FALSE(test_ptr, value); bspl_object_unref(obj_ptr); } /* ------------------------------------------------------------------------- */ /** Tests enum decoding. */ void test_decode_enum(bs_test_t *test_ptr) { int value; bspl_object_t *obj_ptr; static const union bspl_desc_value dv = { .v_enum.desc_ptr = _test_enum_desc }; obj_ptr = bspl_create_object_from_plist_string("enum2"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_enum(obj_ptr, &dv, &value)); BS_TEST_VERIFY_EQ(test_ptr, 2, value); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_string("\"enum2\""); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_enum(obj_ptr, &dv, &value)); BS_TEST_VERIFY_EQ(test_ptr, 2, value); bspl_object_unref(obj_ptr); obj_ptr = bspl_create_object_from_plist_string("INVALID"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_FALSE(test_ptr, bspl_decode_enum(obj_ptr, &dv, &value)); bspl_object_unref(obj_ptr); } /* ------------------------------------------------------------------------- */ /** Tests string decoding. */ void test_decode_string(bs_test_t *test_ptr) { char *v_ptr = NULL; bspl_object_t *obj_ptr; obj_ptr = bspl_create_object_from_plist_string("TheString"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_string(obj_ptr, NULL, &v_ptr)); BS_TEST_VERIFY_STREQ(test_ptr, "TheString", v_ptr); bspl_object_unref(obj_ptr); free(v_ptr); v_ptr = NULL; obj_ptr = bspl_create_object_from_plist_string("1234"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_string(obj_ptr, NULL, &v_ptr)); BS_TEST_VERIFY_STREQ(test_ptr, "1234", v_ptr); bspl_object_unref(obj_ptr); // Not free-ing v_ptr => the next 'decode' call has to do that. obj_ptr = bspl_create_object_from_plist_string("\"quoted string\""); BS_TEST_VERIFY_NEQ(test_ptr, NULL, obj_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_string(obj_ptr, NULL, &v_ptr)); BS_TEST_VERIFY_STREQ(test_ptr, "quoted string", v_ptr); bspl_object_unref(obj_ptr); free(v_ptr); } /* ------------------------------------------------------------------------- */ /** Tests string decoding into a char buf. */ void test_decode_charbuf(bs_test_t *test_ptr) { char b[10]; bspl_object_t *o; static const union bspl_desc_value dv = { .v_charbuf.len = sizeof(b) }; o = bspl_create_object_from_plist_string("123456789"); BS_TEST_VERIFY_TRUE(test_ptr, bspl_decode_charbuf(o, &dv, b)); BS_TEST_VERIFY_STREQ(test_ptr, b, "123456789"); bspl_object_unref(o); o = bspl_create_object_from_plist_string("1234567890"); BS_TEST_VERIFY_FALSE(test_ptr, bspl_decode_charbuf(o, &dv, b)); bspl_object_unref(o); } /* ------------------------------------------------------------------------- */ /** Tests encoding a dict. */ void test_encode_dict(bs_test_t *t) { bs_dllist_t dllist = {}; _test_array_item_t i0 = { .c = 'a' }, i1 = { .c = 'b' }; _test_value_t val = { .v_uint64 = 64, .has_uint64 = true, .v_int64 = -2, .has_int64 = true, .v_double = 3.14, .has_double = true, .v_argb32 = 0x10203040, .has_argb32 = true, .v_bool = true, .has_bool = true, .v_enum = 1, .has_enum = true, .v_string = "TheStr", .has_string = true, .v_charbuf = { 'a', 'b' }, .has_charbuf = true, .subdict.value = "SubVal", .has_subdict = true, .v_custom_ptr = "Custom", .has_custom = true, .dllist_ptr = &dllist, .has_array = true, }; bs_dllist_push_back(&dllist, &i0.dlnode); bs_dllist_push_back(&dllist, &i1.dlnode); bspl_dict_t *d = bspl_encode_dict(_bspl_decode_test_desc, &val); BS_TEST_VERIFY_NEQ_OR_RETURN(t, NULL, d); BS_TEST_VERIFY_STREQ(t, "64", bspl_dict_get_string_value(d, "u64")); BS_TEST_VERIFY_STREQ(t, "-2", bspl_dict_get_string_value(d, "i64")); BS_TEST_VERIFY_STREQ( t, "3.140000e+00", bspl_dict_get_string_value(d, "d")); BS_TEST_VERIFY_STREQ( t, "argb32:10203040", bspl_dict_get_string_value(d, "argb32")); BS_TEST_VERIFY_STREQ(t, "True", bspl_dict_get_string_value(d, "bool")); BS_TEST_VERIFY_STREQ(t, "enum1", bspl_dict_get_string_value(d, "enum")); BS_TEST_VERIFY_STREQ(t, "TheStr", bspl_dict_get_string_value(d, "string")); BS_TEST_VERIFY_STREQ(t, "ab", bspl_dict_get_string_value(d, "charbuf")); bspl_dict_t *sd = bspl_dict_get_dict(d, "subdict"); BS_TEST_VERIFY_NEQ_OR_RETURN(t, NULL, sd); BS_TEST_VERIFY_STREQ(t, "SubVal", bspl_dict_get_string_value(sd, "string")); BS_TEST_VERIFY_STREQ(t, "Custom", bspl_dict_get_string_value(d, "custom")); bspl_array_t *a = bspl_dict_get_array(d, "array"); BS_TEST_VERIFY_NEQ_OR_RETURN(t, NULL, a); BS_TEST_VERIFY_EQ(t, 2, bspl_array_size(a)); BS_TEST_VERIFY_STREQ(t, "a", bspl_array_string_value_at(a, 0)); BS_TEST_VERIFY_STREQ(t, "b", bspl_array_string_value_at(a, 1)); bspl_dict_unref(d); } /* ------------------------------------------------------------------------- */ /** Tests number encoding. */ void test_encode_number(bs_test_t *t) { bspl_string_t *s; uint64_t u = UINT64_MAX; s = bspl_string_from_object(bspl_encode_uint64(NULL, &u)); BS_TEST_VERIFY_STREQ(t, "18446744073709551615", bspl_string_value(s)); bspl_string_unref(s); int64_t i = INT64_MAX; s = bspl_string_from_object(bspl_encode_int64(NULL, &i)); BS_TEST_VERIFY_STREQ(t, "9223372036854775807", bspl_string_value(s)); bspl_string_unref(s); i = INT64_MIN; s = bspl_string_from_object(bspl_encode_int64(NULL, &i)); BS_TEST_VERIFY_STREQ(t, "-9223372036854775808", bspl_string_value(s)); bspl_string_unref(s); double d = 0.2; s = bspl_string_from_object(bspl_encode_double(NULL, &d)); BS_TEST_VERIFY_STREQ(t, "2.000000e-01", bspl_string_value(s)); bspl_string_unref(s); d = DBL_MIN; s = bspl_string_from_object(bspl_encode_double(NULL, &d)); BS_TEST_VERIFY_STREQ(t, "2.225074e-308", bspl_string_value(s)); bspl_string_unref(s); d = DBL_MAX; s = bspl_string_from_object(bspl_encode_double(NULL, &d)); BS_TEST_VERIFY_STREQ(t, "1.797693e+308", bspl_string_value(s)); bspl_string_unref(s); } /* ------------------------------------------------------------------------- */ /** Tests argb32 encoding. */ void test_encode_argb32(bs_test_t *test_ptr) { bspl_string_t *s; uint32_t argb32 = 0x10203040; s = bspl_string_from_object(bspl_encode_argb32(NULL, &argb32)); BS_TEST_VERIFY_STREQ(test_ptr, "argb32:10203040", bspl_string_value(s)); bspl_string_unref(s); } /* ------------------------------------------------------------------------- */ /** Tests bool encoding. */ void test_encode_bool(bs_test_t *test_ptr) { bspl_string_t *s; bool b = false; s = bspl_string_from_object(bspl_encode_bool(NULL, &b)); BS_TEST_VERIFY_STREQ(test_ptr, "False", bspl_string_value(s)); bspl_string_unref(s); b = true; s = bspl_string_from_object(bspl_encode_bool(NULL, &b)); BS_TEST_VERIFY_STREQ(test_ptr, "True", bspl_string_value(s)); bspl_string_unref(s); // Does not correspond to the defined enums, but must translate to True. b = 42; s = bspl_string_from_object(bspl_encode_bool(NULL, &b)); BS_TEST_VERIFY_STREQ(test_ptr, "True", bspl_string_value(s)); bspl_string_unref(s); } /* ------------------------------------------------------------------------- */ /** Tests enum encoding. */ void test_encode_enum(bs_test_t *test_ptr) { bspl_string_t *s; static const union bspl_desc_value dv = { .v_enum.desc_ptr = _test_enum_desc }; int i = 1; s = bspl_string_from_object(bspl_encode_enum(&dv, &i)); BS_TEST_VERIFY_STREQ(test_ptr, "enum1", bspl_string_value(s)); bspl_string_unref(s); i = 2; s = bspl_string_from_object(bspl_encode_enum(&dv, &i)); BS_TEST_VERIFY_STREQ(test_ptr, "enum2", bspl_string_value(s)); bspl_string_unref(s); i = 3; s = bspl_string_from_object(bspl_encode_enum(&dv, &i)); BS_TEST_VERIFY_EQ(test_ptr, NULL, s); } /* ------------------------------------------------------------------------- */ /** Tests string encoding. */ void test_encode_string(bs_test_t *test_ptr) { const char *x = "test"; bspl_string_t *s = bspl_string_from_object(bspl_encode_string(NULL, &x)); BS_TEST_VERIFY_STREQ(test_ptr, "test", bspl_string_value(s)); bspl_string_unref(s); s = bspl_string_from_object(bspl_encode_string(NULL, NULL)); BS_TEST_VERIFY_EQ(test_ptr, NULL, s); s = bspl_string_from_object(bspl_encode_charbuf(NULL, x)); BS_TEST_VERIFY_STREQ(test_ptr, "test", bspl_string_value(s)); bspl_string_unref(s); } /* == End of decode.c ====================================================== */ wlmaker-0.8.1/submodules/libbase/src/plist/parser_context.h0000644000175100017510000000233715222270712023513 0ustar runnerrunner/* ========================================================================= */ /** * @file parser_context.h * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __BSPL_PARSER_CONTEXT_H__ #define __BSPL_PARSER_CONTEXT_H__ #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Context for parser, to be used in the grammar file. */ typedef struct { /** Stack of objects, used throughout parsing. */ bs_ptr_stack_t object_stack; } bspl_parser_context_t; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __BSPL_PARSER_CONTEXT_H__ */ /* == End of parser_context.h ============================================== */ wlmaker-0.8.1/submodules/libbase/src/plist/CMakeLists.txt0000644000175100017510000000467715222270712023053 0ustar runnerrunner# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. find_package(FLEX 2.6 REQUIRED) flex_target( analyzer analyzer.l "${CMAKE_CURRENT_BINARY_DIR}/analyzer.c" DEFINES_FILE "${CMAKE_CURRENT_BINARY_DIR}/analyzer.h") find_package(BISON 3.0 REQUIRED) bison_target( grammar grammar.y "${CMAKE_CURRENT_BINARY_DIR}/grammar.c" DEFINES_FILE "${CMAKE_CURRENT_BINARY_DIR}/grammar.h") add_flex_bison_dependency(analyzer grammar) set(public_header_files "${PROJECT_SOURCE_DIR}/include/libbase/plist/decode.h" "${PROJECT_SOURCE_DIR}/include/libbase/plist/model.h" "${PROJECT_SOURCE_DIR}/include/libbase/plist/parse.h") add_library(libbase_plist STATIC) target_sources(libbase_plist PRIVATE decode.c model.c parse.c "${CMAKE_CURRENT_BINARY_DIR}/analyzer.c" "${CMAKE_CURRENT_BINARY_DIR}/grammar.c") target_include_directories( libbase_plist PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}" PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/") # TODO(kaeser@gubbe.ch):Remove, once updating post bison 3.8.2 (Aug 2022). # See: https://github.com/phkaeser/wlmaker/issues/53 target_compile_options(libbase_plist PRIVATE -Wno-unused-but-set-variable) if(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU") set_source_files_properties( "${CMAKE_CURRENT_BINARY_DIR}/analyzer.c" "${CMAKE_CURRENT_BINARY_DIR}/grammar.c" PROPERTIES COMPILE_OPTIONS "-Wno-error") endif() target_link_libraries(libbase_plist PRIVATE libbase libbase_compiler_flags) set_target_properties( libbase_plist PROPERTIES VERSION 1.0 PUBLIC_HEADER "${public_header_files}") if(iwyu_path_and_options) set_target_properties( libbase_plist PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() # Add 'install' target, but only if `libbase` is the toplevel project. if(CMAKE_PROJECT_NAME STREQUAL libbase) install( TARGETS libbase_plist LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libbase/plist") endif() wlmaker-0.8.1/submodules/libbase/src/plist/model.c0000644000175100017510000010402315222270712021541 0ustar runnerrunner/* ========================================================================= */ /** * @file model.c * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** Base type of a config object. */ struct _bspl_object_t { /** Type of this object. */ bspl_type_t type; /** References held to this object. */ int references; /** Abstract virtual method: Writes the object into dynbuf_ptr. */ bool (*write_fn)( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level); /** Abstract virtual method: Destroys the object. */ void (*destroy_fn)(bspl_object_t *object_ptr); }; /** Config object: A string. */ struct _bspl_string_t { /** The string's superclass: An object. */ bspl_object_t super_object; /** Value of the string. */ char *value_ptr; }; /** Config object: A dict. */ struct _bspl_dict_t { /** The dict's superclass: An object. */ bspl_object_t super_object; /** Implemented as a tree. Benefit: Keeps sorting order. */ bs_avltree_t *tree_ptr; }; /** An item in the dict. Internal class. */ typedef struct { /** Node in the tree. */ bs_avltree_node_t avlnode; /** The lookup key. */ char *key_ptr; /** The value, is an object. */ bspl_object_t *value_object_ptr; } bspl_dict_item_t; /** Array object. A vector of pointers to the objects. */ struct _bspl_array_t { /** The array's superclass: An object. */ bspl_object_t super_object; /** Vector holding pointers to each object. */ bs_ptr_vector_t object_vector; }; /** Argument to _bspl_dict_item_write(). */ typedef struct { /** The dynbuf to write into. */ bs_dynbuf_t *dynbuf_ptr; /** Indentation. */ size_t indent; /** Level of indentation. */ size_t level; } bspl_dict_item_write_arg_t; static bool _bspl_object_init( bspl_object_t *object_ptr, bspl_type_t type, bool (*write_fn)( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level), void (*destroy_fn)(bspl_object_t *object_ptr)); static bool _bspl_string_object_write( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level); static bool _bspl_string_write( const char *str_ptr, bs_dynbuf_t *dynbuf_ptr); static void _bspl_string_object_destroy(bspl_object_t *object_ptr); static bool _bspl_dict_object_write( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level); static void _bspl_dict_object_destroy(bspl_object_t *object_ptr); static bool _bspl_array_object_write( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level); static void _bspl_array_object_destroy(bspl_object_t *object_ptr); static bspl_dict_item_t *_bspl_dict_item_create( const char *key_ptr, bspl_object_t *object_ptr); static void _bspl_dict_item_destroy( bspl_dict_item_t *item_ptr); static bool _bspl_dict_item_write( const char *key_ptr, bspl_object_t *object_ptr, void *userdata_ptr); static int _bspl_dict_item_node_cmp( const bs_avltree_node_t *node_ptr, const void *key_ptr); static void _bspl_dict_item_node_destroy( bs_avltree_node_t *node_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_object_ref(bspl_object_t *object_ptr) { if (NULL == object_ptr) return NULL; ++object_ptr->references; return object_ptr; } /* ------------------------------------------------------------------------- */ void bspl_object_unref(bspl_object_t *object_ptr) { if (NULL == object_ptr) return; // Check references. Warn on potential double-free. BS_ASSERT(0 < object_ptr->references); if (0 < --object_ptr->references) return; object_ptr->destroy_fn(object_ptr); } /* ------------------------------------------------------------------------- */ bspl_type_t bspl_object_type(bspl_object_t *object_ptr) { return object_ptr->type; } /* ------------------------------------------------------------------------- */ bool bspl_object_write(bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr) { if (bspl_object_write_indented(object_ptr, dynbuf_ptr, 2, 0)) { return ( bs_dynbuf_append_char(dynbuf_ptr, '\n') || (bs_dynbuf_grow(dynbuf_ptr) && bs_dynbuf_append_char(dynbuf_ptr, '\n'))); } return false; } /* ------------------------------------------------------------------------- */ bool bspl_object_write_indented( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level) { BS_ASSERT(NULL != object_ptr->write_fn); size_t backup_pos = dynbuf_ptr->length; while (!object_ptr->write_fn(object_ptr, dynbuf_ptr, indent, level)) { dynbuf_ptr->length = backup_pos; if (!bs_dynbuf_grow(dynbuf_ptr)) return false; } return true; } /* ------------------------------------------------------------------------- */ bspl_string_t *bspl_string_create(const char *value_ptr) { if (NULL == value_ptr) return NULL; bspl_string_t *string_ptr = logged_calloc(1, sizeof(bspl_string_t)); if (NULL == string_ptr) return NULL; if (!_bspl_object_init(&string_ptr->super_object, BSPL_STRING, _bspl_string_object_write, _bspl_string_object_destroy)) { free(string_ptr); return NULL; } string_ptr->value_ptr = logged_strdup(value_ptr); if (NULL == string_ptr->value_ptr) { _bspl_string_object_destroy(bspl_object_from_string(string_ptr)); return NULL; } return string_ptr; } /* ------------------------------------------------------------------------- */ const char *bspl_string_value_from_object(bspl_object_t *object_ptr) { return bspl_string_value(bspl_string_from_object(object_ptr)); } /* ------------------------------------------------------------------------- */ const char *bspl_string_value(const bspl_string_t *string_ptr) { if (NULL == string_ptr) return NULL; // Guard clause. return string_ptr->value_ptr; } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_object_from_string(bspl_string_t *string_ptr) { if (NULL == string_ptr) return NULL; return &string_ptr->super_object; } /* ------------------------------------------------------------------------- */ bspl_string_t *bspl_string_from_object(bspl_object_t *object_ptr) { if (NULL == object_ptr || BSPL_STRING != object_ptr->type) return NULL; return BS_CONTAINER_OF(object_ptr, bspl_string_t, super_object); } /* ------------------------------------------------------------------------- */ bspl_dict_t *bspl_dict_create(void) { bspl_dict_t *dict_ptr = logged_calloc(1, sizeof(bspl_dict_t)); if (NULL == dict_ptr) return NULL; if (!_bspl_object_init(&dict_ptr->super_object, BSPL_DICT, _bspl_dict_object_write, _bspl_dict_object_destroy)) { free(dict_ptr); return NULL; } dict_ptr->tree_ptr = bs_avltree_create( _bspl_dict_item_node_cmp, _bspl_dict_item_node_destroy); if (NULL == dict_ptr->tree_ptr) { _bspl_dict_object_destroy(bspl_object_from_dict(dict_ptr)); return NULL; } return dict_ptr; } /* ------------------------------------------------------------------------- */ bool bspl_dict_add( bspl_dict_t *dict_ptr, const char *key_ptr, bspl_object_t *object_ptr) { bspl_dict_item_t *item_ptr = _bspl_dict_item_create( key_ptr, object_ptr); if (NULL == item_ptr) return false; bool rv = bs_avltree_insert( dict_ptr->tree_ptr, item_ptr->key_ptr, &item_ptr->avlnode, false); if (!rv) _bspl_dict_item_destroy(item_ptr); return rv; } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_dict_get( bspl_dict_t *dict_ptr, const char *key_ptr) { if (NULL == dict_ptr) return NULL; bs_avltree_node_t *node_ptr = bs_avltree_lookup( dict_ptr->tree_ptr, key_ptr); if (NULL == node_ptr) return NULL; bspl_dict_item_t *item_ptr = BS_CONTAINER_OF( node_ptr, bspl_dict_item_t, avlnode); return item_ptr->value_object_ptr; } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_object_from_dict(bspl_dict_t *dict_ptr) { if (NULL == dict_ptr) return NULL; return &dict_ptr->super_object; } /* ------------------------------------------------------------------------- */ bspl_dict_t *bspl_dict_from_object(bspl_object_t *object_ptr) { if (NULL == object_ptr || BSPL_DICT != object_ptr->type) return NULL; return BS_CONTAINER_OF(object_ptr, bspl_dict_t, super_object); } /* ------------------------------------------------------------------------- */ bool bspl_dict_foreach( bspl_dict_t *dict_ptr, bool (*fn)(const char *key_ptr, bspl_object_t *object_ptr, void *userdata_ptr), void *userdata_ptr) { for (bs_avltree_node_t *node_ptr = bs_avltree_min(dict_ptr->tree_ptr); NULL != node_ptr; node_ptr = bs_avltree_node_next(dict_ptr->tree_ptr, node_ptr)) { bspl_dict_item_t *item_ptr = BS_CONTAINER_OF( node_ptr, bspl_dict_item_t, avlnode); if (!fn(item_ptr->key_ptr, item_ptr->value_object_ptr, userdata_ptr)) { return false; } } return true; } /* == Array methods ======================================================== */ /* ------------------------------------------------------------------------- */ bspl_array_t *bspl_array_create(void) { bspl_array_t *array_ptr = logged_calloc(1, sizeof(bspl_array_t)); if (NULL == array_ptr) return NULL; if (!_bspl_object_init(&array_ptr->super_object, BSPL_ARRAY, _bspl_array_object_write, _bspl_array_object_destroy)) { free(array_ptr); return NULL; } if (!bs_ptr_vector_init(&array_ptr->object_vector)) { _bspl_array_object_destroy(bspl_object_from_array(array_ptr)); return NULL; } return array_ptr; } /* ------------------------------------------------------------------------- */ bool bspl_array_push_back( bspl_array_t *array_ptr, bspl_object_t *object_ptr) { bspl_object_t *new_object_ptr = bspl_object_ref(object_ptr); if (NULL == new_object_ptr) return false; return bs_ptr_vector_push_back(&array_ptr->object_vector, new_object_ptr); } /* ------------------------------------------------------------------------- */ size_t bspl_array_size(bspl_array_t *array_ptr) { return bs_ptr_vector_size(&array_ptr->object_vector); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_array_at( bspl_array_t *array_ptr, size_t index) { if (index >= bs_ptr_vector_size(&array_ptr->object_vector)) return NULL; return bs_ptr_vector_at(&array_ptr->object_vector, index); } /* ------------------------------------------------------------------------- */ bspl_object_t *bspl_object_from_array(bspl_array_t *array_ptr) { if (NULL == array_ptr) return NULL; return &array_ptr->super_object; } /* ------------------------------------------------------------------------- */ bspl_array_t *bspl_array_from_object(bspl_object_t *object_ptr) { if (NULL == object_ptr || BSPL_ARRAY != object_ptr->type) return NULL; return BS_CONTAINER_OF(object_ptr, bspl_array_t, super_object); } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Initializes the object base class. * * @param object_ptr * @param type * @param write_fn * @param destroy_fn * * @return true on success. */ bool _bspl_object_init( bspl_object_t *object_ptr, bspl_type_t type, bool (*write_fn)( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level), void (*destroy_fn)(bspl_object_t *object_ptr)) { BS_ASSERT(NULL != object_ptr); BS_ASSERT(NULL != write_fn); BS_ASSERT(NULL != destroy_fn); *object_ptr = (bspl_object_t){ .type = type, .write_fn = write_fn, .destroy_fn = destroy_fn, .references = 1, }; return true; } /* ------------------------------------------------------------------------- */ /** * Implementation of @ref bspl_object_t::write_fn. Writes the string. * * @param object_ptr * @param dynbuf_ptr * @param indent * * @return true on success. */ bool _bspl_string_object_write( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, __UNUSED__ size_t indent, __UNUSED__ size_t level) { bspl_string_t *string_ptr = BS_ASSERT_NOTNULL( bspl_string_from_object(object_ptr)); BS_ASSERT(NULL != string_ptr->value_ptr); return _bspl_string_write(string_ptr->value_ptr, dynbuf_ptr); } /* ------------------------------------------------------------------------- */ /** * Writes the actual C string, with escaping as needed. * * @param str_ptr * @param dynbuf_ptr * * @return true on success. */ bool _bspl_string_write( const char *str_ptr, bs_dynbuf_t *dynbuf_ptr) { static const char *identifier_pattern = "[a-zA-Z0-9_.$]+"; static thread_local regex_t identifier_regex; static thread_local bool regex_initialized = false; if (!regex_initialized) { int rv = regcomp(&identifier_regex, identifier_pattern, REG_EXTENDED); if (0 != rv) { char err_buf[512]; regerror(rv, &identifier_regex, err_buf, sizeof(err_buf)); bs_log(BS_ERROR, "Failed regcomp(%p, \"%s \", REG_EXTENDED): %s", &identifier_regex, identifier_pattern, err_buf); return false; } regex_initialized = true; } // Space check: At least the unescaped string needs to fit. size_t len = strlen(str_ptr); if (len > dynbuf_ptr->capacity || dynbuf_ptr->length + len > dynbuf_ptr->capacity) { return false; } // If it matches an identifier: Write without quotes. regmatch_t match = { .rm_eo = len }; if (0 == regexec(&identifier_regex, str_ptr, 1, &match, 0) && match.rm_so == 0 && (size_t)match.rm_eo == len) { return bs_dynbuf_append(dynbuf_ptr, str_ptr, len); } // Otherwise: Write it, and escape any backslash or double quotes. const char *pos = str_ptr; const char *c = pos; if (!bs_dynbuf_append_char(dynbuf_ptr, '"')) return false; for (; *c != '\0'; ++c) { if (*c == '\\' || *c == '\"') { if (!bs_dynbuf_append(dynbuf_ptr, pos, c - pos) || !bs_dynbuf_append_char(dynbuf_ptr, '\\') || !bs_dynbuf_append_char(dynbuf_ptr, *c)) { return false; } pos = c + 1; } } return (bs_dynbuf_append(dynbuf_ptr, pos, c - pos) && bs_dynbuf_append_char(dynbuf_ptr, '"')); } /* ------------------------------------------------------------------------- */ /** * Implementation of @ref bspl_object_t::destroy_fn. Destroys the string. * * @param object_ptr */ void _bspl_string_object_destroy(bspl_object_t *object_ptr) { bspl_string_t *string_ptr = BS_ASSERT_NOTNULL( bspl_string_from_object(object_ptr)); if (NULL != string_ptr->value_ptr) { free(string_ptr->value_ptr); string_ptr->value_ptr = NULL; } free(string_ptr); } /* ------------------------------------------------------------------------- */ bool _bspl_dict_object_write( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level) { bspl_dict_t *dict_ptr = BS_ASSERT_NOTNULL( bspl_dict_from_object(object_ptr)); bspl_dict_item_write_arg_t arg = { .dynbuf_ptr = dynbuf_ptr, .indent = indent, .level = level }; bool nonempty = bs_avltree_size(dict_ptr->tree_ptr) > 0; return ( bs_dynbuf_append_char(dynbuf_ptr, '{') && bs_dynbuf_maybe_append_char(dynbuf_ptr, nonempty, '\n') && bs_dynbuf_maybe_indent(dynbuf_ptr, nonempty, indent * level) && bspl_dict_foreach(dict_ptr, _bspl_dict_item_write, &arg) && bs_dynbuf_append_char(dynbuf_ptr, '}')); } /* ------------------------------------------------------------------------- */ /** * Implementation of @ref bspl_object_t::destroy_fn. Destroys the dict, * including all contained elements. * * @param object_ptr */ void _bspl_dict_object_destroy(bspl_object_t *object_ptr) { bspl_dict_t *dict_ptr = BS_ASSERT_NOTNULL( bspl_dict_from_object(object_ptr)); if (NULL != dict_ptr->tree_ptr) { bs_avltree_destroy(dict_ptr->tree_ptr); dict_ptr->tree_ptr = NULL; } free(dict_ptr); } /* ------------------------------------------------------------------------- */ /** Ctor: Creates a dict item. Copies the key, and duplicates the object. */ bspl_dict_item_t *_bspl_dict_item_create( const char *key_ptr, bspl_object_t *object_ptr) { bspl_dict_item_t *item_ptr = logged_calloc( 1, sizeof(bspl_dict_item_t)); if (NULL == item_ptr) return NULL; item_ptr->key_ptr = logged_strdup(key_ptr); if (NULL == item_ptr->key_ptr) { _bspl_dict_item_destroy(item_ptr); return NULL; } item_ptr->value_object_ptr = bspl_object_ref(object_ptr); if (NULL == item_ptr->value_object_ptr) { _bspl_dict_item_destroy(item_ptr); return NULL; } return item_ptr; } /* ------------------------------------------------------------------------- */ /** Dtor: Destroys the dict item. */ void _bspl_dict_item_destroy(bspl_dict_item_t *item_ptr) { if (NULL != item_ptr->value_object_ptr) { bspl_object_unref(item_ptr->value_object_ptr); item_ptr->value_object_ptr = NULL; } if (NULL != item_ptr->key_ptr) { free(item_ptr->key_ptr); item_ptr->key_ptr = NULL; } free(item_ptr); } /* ------------------------------------------------------------------------- */ /** * Writes the dict item into the dynamic buffer. * * @param key_ptr * @param object_ptr * @param userdata_ptr Points to a @ref bspl_dict_item_write_arg_t. * * @return true on success. */ bool _bspl_dict_item_write( const char *key_ptr, bspl_object_t *object_ptr, void *userdata_ptr) { bspl_dict_item_write_arg_t *a = userdata_ptr; return ( bs_dynbuf_maybe_indent(a->dynbuf_ptr, true, a->indent) && _bspl_string_write(key_ptr, a->dynbuf_ptr) && bs_dynbuf_append(a->dynbuf_ptr, " = ", 3) && bspl_object_write_indented( object_ptr, a->dynbuf_ptr, a->indent, a->level + 1)) && bs_dynbuf_append_char(a->dynbuf_ptr, ';') && bs_dynbuf_append_char(a->dynbuf_ptr, '\n') && bs_dynbuf_maybe_indent(a->dynbuf_ptr, true, a->indent * a->level); } /* ------------------------------------------------------------------------- */ /** Comparator for the dictionary item with another key. */ int _bspl_dict_item_node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr) { bspl_dict_item_t *item_ptr = BS_CONTAINER_OF( node_ptr, bspl_dict_item_t, avlnode); return strcmp(item_ptr->key_ptr, key_ptr); } /* ------------------------------------------------------------------------- */ /** Destroy dict item by avlnode. Forward to @ref _bspl_dict_item_destroy. */ void _bspl_dict_item_node_destroy(bs_avltree_node_t *node_ptr) { bspl_dict_item_t *item_ptr = BS_CONTAINER_OF( node_ptr, bspl_dict_item_t, avlnode); _bspl_dict_item_destroy(item_ptr); } /* ------------------------------------------------------------------------- */ /** * Implements @ref bspl_object_t::write_fn. Writes the array. * * @param object_ptr * @param dynbuf_ptr * * @return true on success */ bool _bspl_array_object_write( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level) { bspl_array_t *array_ptr = BS_ASSERT_NOTNULL( bspl_array_from_object(object_ptr)); bool two_or_more = 1 < bs_ptr_vector_size(&array_ptr->object_vector); // print bracket. if >1 element: newline, otherwise: print elem if (!bs_dynbuf_append_char(dynbuf_ptr, '(') || !bs_dynbuf_maybe_append_char(dynbuf_ptr, two_or_more, '\n') || !bs_dynbuf_maybe_indent(dynbuf_ptr, two_or_more, indent * level)) { return false; } for (size_t i = 0; i < bs_ptr_vector_size(&array_ptr->object_vector); ++i) { bool not_last = i + 1 < bs_ptr_vector_size(&array_ptr->object_vector); if (!bs_dynbuf_maybe_indent( dynbuf_ptr, two_or_more, //0 < bs_ptr_vector_size(&array_ptr->object_vector), indent) || !bspl_object_write_indented( bs_ptr_vector_at(&array_ptr->object_vector, i), dynbuf_ptr, indent, level + 1) || !bs_dynbuf_maybe_append_char(dynbuf_ptr, not_last, ',') || !bs_dynbuf_maybe_append_char(dynbuf_ptr, two_or_more, '\n') || !bs_dynbuf_maybe_indent(dynbuf_ptr, two_or_more, indent * level)) { return false; } } if (!bs_dynbuf_append_char(dynbuf_ptr, ')')) return false; return true; } /* ------------------------------------------------------------------------- */ /** * Implementation of @ref bspl_object_t::destroy_fn. Destroys the array, * including all contained elements. * * @param object_ptr */ void _bspl_array_object_destroy(bspl_object_t *object_ptr) { bspl_array_t *array_ptr = BS_ASSERT_NOTNULL( bspl_array_from_object(object_ptr)); while (0 < bs_ptr_vector_size(&array_ptr->object_vector)) { bspl_object_t *object_ptr = bs_ptr_vector_at( &array_ptr->object_vector, bs_ptr_vector_size(&array_ptr->object_vector) - 1); bs_ptr_vector_erase( &array_ptr->object_vector, bs_ptr_vector_size(&array_ptr->object_vector) - 1); bspl_object_unref(object_ptr); } bs_ptr_vector_fini(&array_ptr->object_vector); free(array_ptr); } /* == Unit tests =========================================================== */ static void test_string(bs_test_t *test_ptr); static void test_dict(bs_test_t *test_ptr); static void test_array(bs_test_t *test_ptr); static void test_write_string(bs_test_t *test_ptr); static void test_write_dict(bs_test_t *test_ptr); static void test_write_array(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bspl_model_test_cases[] = { { true, "string", test_string }, { true, "dict", test_dict }, { true, "array", test_array }, { true, "write_string", test_write_string }, { true, "write_dict", test_write_dict }, { true, "write_array", test_write_array }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bspl_model_test_set = BS_TEST_SET( true, "model", bspl_model_test_cases); /* ------------------------------------------------------------------------- */ /** Test helper: A callback for @ref bspl_dict_foreach. */ static bool foreach_callback( const char *key_ptr, __UNUSED__ bspl_object_t *object_ptr, void *userdata_ptr) { int *map = userdata_ptr; if (strcmp(key_ptr, "key0") == 0) { *map |= 1; } else if (strcmp(key_ptr, "key1") == 0) { *map |= 2; } return true; } /* ------------------------------------------------------------------------- */ /** Tests the bspl_string_t methods. */ void test_string(bs_test_t *test_ptr) { bspl_string_t *string_ptr; BS_TEST_VERIFY_EQ(test_ptr, NULL, bspl_string_create(NULL)); BS_TEST_VERIFY_EQ(test_ptr, NULL, bspl_string_ref(NULL)); string_ptr = bspl_string_create("a test"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, string_ptr); BS_TEST_VERIFY_STREQ(test_ptr, "a test", string_ptr->value_ptr); bspl_object_t *object_ptr = bspl_object_from_string(string_ptr); BS_TEST_VERIFY_EQ( test_ptr, string_ptr, bspl_string_from_object(object_ptr)); BS_TEST_VERIFY_EQ( test_ptr, BSPL_STRING, bspl_object_type(object_ptr)); BS_TEST_VERIFY_STREQ( test_ptr, "a test", bspl_string_value_from_object(object_ptr)); BS_TEST_VERIFY_EQ(test_ptr, NULL, bspl_string_value_from_object(NULL)); bspl_object_unref(object_ptr); object_ptr = bspl_object_from_array(bspl_array_create()); BS_TEST_VERIFY_EQ(test_ptr, NULL, bspl_string_value_from_object(NULL)); bspl_object_unref(object_ptr); } /* ------------------------------------------------------------------------- */ /** Tests the bspl_dict_t methods. */ void test_dict(bs_test_t *test_ptr) { BS_TEST_VERIFY_EQ(test_ptr, NULL, bspl_dict_ref(NULL)); bspl_dict_t *dict_ptr = bspl_dict_create(); bspl_object_t *obj0_ptr = BS_ASSERT_NOTNULL( bspl_object_from_string(bspl_string_create("val0"))); BS_TEST_VERIFY_TRUE( test_ptr, bspl_dict_add(dict_ptr, "key0", obj0_ptr)); bspl_object_unref(obj0_ptr); bspl_object_t *obj1_ptr = BS_ASSERT_NOTNULL( bspl_object_from_string(bspl_string_create("val1"))); BS_TEST_VERIFY_FALSE( test_ptr, bspl_dict_add(dict_ptr, "key0", obj1_ptr)); BS_TEST_VERIFY_TRUE( test_ptr, bspl_dict_add(dict_ptr, "key1", obj1_ptr)); bspl_object_unref(obj1_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "val0", bspl_string_value(bspl_string_from_object( bspl_dict_get(dict_ptr, "key0")))); BS_TEST_VERIFY_STREQ( test_ptr, "val1", bspl_string_value(bspl_string_from_object( bspl_dict_get(dict_ptr, "key1")))); int val = 0; BS_TEST_VERIFY_TRUE( test_ptr, bspl_dict_foreach(dict_ptr, foreach_callback, &val)); BS_TEST_VERIFY_EQ(test_ptr, 3, val); BS_TEST_VERIFY_EQ( test_ptr, BSPL_DICT, bspl_object_type(bspl_object_from_dict(dict_ptr))); bspl_dict_unref(dict_ptr); } /* ------------------------------------------------------------------------- */ /** Tests the bspl_array_t methods. */ void test_array(bs_test_t *test_ptr) { BS_TEST_VERIFY_EQ(test_ptr, NULL, bspl_array_ref(NULL)); bspl_array_t *array_ptr = bspl_array_create(); bspl_object_t *obj0_ptr = BS_ASSERT_NOTNULL( bspl_object_from_string(bspl_string_create("val0"))); BS_TEST_VERIFY_TRUE( test_ptr, bspl_array_push_back(array_ptr, obj0_ptr)); bspl_object_unref(obj0_ptr); bspl_object_t *obj1_ptr = BS_ASSERT_NOTNULL( bspl_object_from_string(bspl_string_create("val1"))); BS_TEST_VERIFY_TRUE( test_ptr, bspl_array_push_back(array_ptr, obj1_ptr)); bspl_object_unref(obj1_ptr); BS_TEST_VERIFY_EQ(test_ptr, obj0_ptr, bspl_array_at(array_ptr, 0)); BS_TEST_VERIFY_EQ(test_ptr, obj1_ptr, bspl_array_at(array_ptr, 1)); BS_TEST_VERIFY_EQ(test_ptr, NULL, bspl_array_at(array_ptr, 2)); BS_TEST_VERIFY_EQ( test_ptr, BSPL_ARRAY, bspl_object_type(bspl_object_from_array(array_ptr))); bspl_array_unref(array_ptr); } /* ------------------------------------------------------------------------- */ /** Tests correct output of a string. */ void test_write_string(bs_test_t *test_ptr) { bspl_object_t *object_ptr; bspl_string_t *string_ptr; bs_dynbuf_t dynbuf; char output[16]; bs_dynbuf_init_unmanaged(&dynbuf, output, sizeof(output)); string_ptr = bspl_string_create("test"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, string_ptr); object_ptr = bspl_object_from_string(string_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(object_ptr, &dynbuf)); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 5, dynbuf.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "test\n", dynbuf.data_ptr, 5); bspl_object_unref(object_ptr); bs_dynbuf_clear(&dynbuf); string_ptr = bspl_string_create("test1.$_"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, string_ptr); object_ptr = bspl_object_from_string(string_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(object_ptr, &dynbuf)); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 9, dynbuf.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "test1.$_\n", dynbuf.data_ptr, 9); bspl_object_unref(object_ptr); bs_dynbuf_clear(&dynbuf); string_ptr = bspl_string_create(""); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, string_ptr); object_ptr = bspl_object_from_string(string_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(object_ptr, &dynbuf)); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 3, dynbuf.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "\"\"\n", dynbuf.data_ptr, 3); bspl_object_unref(object_ptr); bs_dynbuf_clear(&dynbuf); string_ptr = bspl_string_create(",1"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, string_ptr); object_ptr = bspl_object_from_string(string_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(object_ptr, &dynbuf)); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 5, dynbuf.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "\",1\"\n", dynbuf.data_ptr, 5); bspl_object_unref(object_ptr); bs_dynbuf_clear(&dynbuf); string_ptr = bspl_string_create("x\\y\"z"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, string_ptr); object_ptr = bspl_object_from_string(string_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(object_ptr, &dynbuf)); BS_TEST_VERIFY_EQ_OR_RETURN(test_ptr, 10, dynbuf.length); BS_TEST_VERIFY_MEMEQ(test_ptr, "\"x\\\\y\\\"z\"\n", dynbuf.data_ptr, 10); bspl_object_unref(object_ptr); } /* ------------------------------------------------------------------------- */ /** Tests correct output of a dictionary. */ void test_write_dict(bs_test_t *test_ptr) { bspl_dict_t *dict_ptr = bspl_dict_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dict_ptr); bspl_object_t *o = bspl_object_from_dict(dict_ptr); char output[32]; bs_dynbuf_t dynbuf; // Empty dict, insufficient space. bs_dynbuf_init_unmanaged(&dynbuf, output, 1); BS_TEST_VERIFY_FALSE(test_ptr, bspl_object_write(o, &dynbuf)); // Empty dict, sufficient space. bs_dynbuf_init_unmanaged(&dynbuf, output, 3); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(o, &dynbuf)); BS_TEST_VERIFY_MEMEQ(test_ptr, "{}\n", dynbuf.data_ptr, 3); // Add an element. Test with both insufficient, then: sufficient space. bspl_object_t *i = bspl_object_from_string(bspl_string_create("1")); bspl_dict_add(dict_ptr, "a", i); bspl_object_unref(i); BS_TEST_VERIFY_FALSE(test_ptr, bspl_object_write(o, &dynbuf)); bs_dynbuf_init_unmanaged(&dynbuf, output, 13); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(o, &dynbuf)); BS_TEST_VERIFY_MEMEQ(test_ptr, "{\n a = 1;\n}\n", dynbuf.data_ptr, 13); // Add another element. One that needs escaping. Then verify. i = bspl_object_from_string(bspl_string_create("2")); bspl_dict_add(dict_ptr, " ", i); // Yep. A space. Valid. bspl_object_unref(i); bs_dynbuf_init_unmanaged(&dynbuf, output, 32); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(o, &dynbuf)); BS_TEST_VERIFY_MEMEQ( test_ptr, "{\n \" \" = 2;\n a = 1;\n}\n", dynbuf.data_ptr, 24); bspl_object_unref(o); } /* ------------------------------------------------------------------------- */ /** Tests correct output of an array. */ void test_write_array(bs_test_t *test_ptr) { bspl_array_t *array_ptr = bspl_array_create(); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, array_ptr); bspl_object_t *o = bspl_object_from_array(array_ptr); char output[16]; bs_dynbuf_t dynbuf; // Empty array, insufficient space. bs_dynbuf_init_unmanaged(&dynbuf, output, 1); BS_TEST_VERIFY_FALSE(test_ptr, bspl_object_write(o, &dynbuf)); // Sufficient space. bs_dynbuf_init_unmanaged(&dynbuf, output, 3); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(o, &dynbuf)); BS_TEST_VERIFY_MEMEQ(test_ptr, "()\n", dynbuf.data_ptr, 3); // Array with one element. Insufficent space. bspl_object_t *s = bspl_object_from_string(bspl_string_create("a")); BS_TEST_VERIFY_TRUE(test_ptr, bspl_array_push_back(array_ptr, s)); bspl_object_unref(s); bs_dynbuf_init_unmanaged(&dynbuf, output, 3); BS_TEST_VERIFY_FALSE(test_ptr, bspl_object_write(o, &dynbuf)); bs_dynbuf_init_unmanaged(&dynbuf, output, 16); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(o, &dynbuf)); BS_TEST_VERIFY_MEMEQ(test_ptr, "(a)\n", dynbuf.data_ptr, 4); // Two elements. s = bspl_object_from_string(bspl_string_create("b")); BS_TEST_VERIFY_TRUE(test_ptr, bspl_array_push_back(array_ptr, s)); bspl_object_unref(s); bs_dynbuf_init_unmanaged(&dynbuf, output, 4); BS_TEST_VERIFY_FALSE(test_ptr, bspl_object_write(o, &dynbuf)); bs_dynbuf_init_unmanaged(&dynbuf, output, 13); BS_TEST_VERIFY_TRUE(test_ptr, bspl_object_write(o, &dynbuf)); BS_TEST_VERIFY_MEMEQ(test_ptr, "(\n a,\n b\n)\n", dynbuf.data_ptr, 13); bspl_array_unref(array_ptr); } /* == End of model.c ======================================================= */ wlmaker-0.8.1/submodules/libbase/src/subprocess.c0000644000175100017510000010353215222270712021502 0ustar runnerrunner/* ========================================================================= */ /** * @file subprocess.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /// kill(2) is a POSIX extension, and we can't do IPC without it. #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef _POSIX_C_SOURCE /* == Definitions ========================================================== */ /** Local type, describes an environment variable. Non-const. */ typedef struct { /** Name of the variable. */ char *name_ptr; /** Value of the variable. */ char *value_ptr; } _env_var_t; /** Subprocess handle. */ struct _bs_subprocess_t { /** Name of the executable to execute in the subprocess. */ char *file_ptr; /** * A NULL-terminated array of strings, holding the arguments. * Note: argv_ptr[0] points to file_ptr. */ char **argv_ptr; /** Environment variables. */ _env_var_t *env_vars_ptr; /** Will be non-zero when a child process is running. */ pid_t pid; /** Will hold the exit status, if the process terminated normally. */ int exit_status; /** Will hold the signal number, if the process was killed by a signal. */ int signal_number; /** File descriptor for stdin (write) */ int stdin_write; /** File descriptor for stdout (read) */ int stdout_read; /** File descriptor for stderr (read) */ int stderr_read; }; /** Deterministic Finite Automation transition. */ typedef struct { /** Next state, for the automaton. */ int8_t next_state; /** Outcome: Add char to token (1), ignore (0) or terminate (-1). */ int8_t output; } dfa_transition_t; static bs_subprocess_t *_subprocess_create_argv( const char *file_ptr, char **argv_ptr, _env_var_t *env_vars_ptr); static int _waitpid_nointr(bs_subprocess_t *subprocess_ptr, bool wait); static void _close_fd(int *fd_ptr); static bool _create_pipe_fds(int *read_fd_ptr, int *write_fd_ptr); static void _subprocess_child( bs_subprocess_t *subprocess_ptr, int stdin_read, int stdout_write, int stderr_write); static char **_split_command( const char *cmd_ptr, _env_var_t **env_var_ptr_ptr); static char *_split_next_token(const char *word_ptr, const char **next_ptr); static void _free_argv_list(char **argv_ptr); static void _free_env_var_list(_env_var_t *env_var_ptr); static bool _populate_env_var( _env_var_t *env_var_ptr, const char *name_ptr, size_t name_len, const char *value_ptr); static bool _is_variable_assignment( const char *word_ptr, _env_var_t *env_variable_ptr); /* == Data ================================================================ */ /** character types, for the DFA table. */ static const int8_t char_type_alpha = 0; static const int8_t char_type_blank = 1; static const int8_t char_type_escape = 2; static const int8_t char_type_double_quote = 3; static const int8_t char_type_end_of_string = 4; static const int8_t char_type_single_quote = 5; /* * Transition table and parsing is modelled closely after libdockapp. * * From https://repo.or.cz/dockapps.git/blob_plain/HEAD:/libdockapp/COPYING: * * Copyright (c) 1999 Alfredo K. Kojima * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ static const dfa_transition_t state_transition_table[9][7] = { /* alpha blank escape dblquote NUL sglquote */ /* initial state, token has not started yet */ { { 3, 1 }, { 0, 0 }, { 4, 0 }, { 1, 0 }, { 8, 0 }, { 6, 0 } }, /* double-quoted */ { { 1, 1 }, { 1, 1 }, { 2, 0 }, { 3, 0 }, { 5, 0 }, { 1, 1 } }, /* escaped while double-quoted */ { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, { 5, 0 }, { 1, 1 } }, /* processing token (has started) */ { { 3, 1 }, { 5, 0 }, { 4, 0 }, { 1, 0 }, { 5, 0 }, { 6, 0 } }, /* escaped, part of token */ { { 3, 1 }, { 3, 1 }, { 3, 1 }, { 3, 1 }, { 5, 0 }, { 3, 1 } }, /* final state */ { {-1,-1 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, /* single-quoted */ { { 6, 1 }, { 6, 1 }, { 7, 0 }, { 6, 1 }, { 5, 0 }, { 3, 0 } }, /* escaped while single-quoted */ { { 6, 1 }, { 6, 1 }, { 6, 1 }, { 6, 1 }, { 5, 0 }, { 6, 1 } }, /* also final. */ { {-1,-1 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, // final. }; /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bs_subprocess_t *bs_subprocess_create( const char *file_ptr, const char *const *argv_ptr, const bs_subprocess_environment_variable_t *env_vars_ptr) { char **copied_argv_ptr; size_t argv_size = 0; while (NULL != argv_ptr[argv_size]) ++argv_size; // Don't forget to alloc space for the sentinel NULL. copied_argv_ptr = (char**)logged_calloc(argv_size + 1, sizeof(char*)); if (NULL == copied_argv_ptr) return NULL; for (size_t i = 0; i < argv_size; ++i) { copied_argv_ptr[i] = logged_strdup(argv_ptr[i]); if (NULL == copied_argv_ptr[i]) { _free_argv_list(copied_argv_ptr); return NULL; } } _env_var_t *env_var_ptr = NULL; if (NULL != env_vars_ptr) { size_t env_var_size = 0; while (NULL != env_vars_ptr[env_var_size].name_ptr) ++env_var_size; env_var_ptr = logged_calloc(env_var_size + 1, sizeof(_env_var_t)); if (NULL == env_var_ptr) { _free_argv_list(copied_argv_ptr); return NULL; } for (size_t i = 0; i < env_var_size; ++i) { if (!_populate_env_var( env_var_ptr + i, env_vars_ptr[i].name_ptr, strlen(env_vars_ptr[i].name_ptr), env_vars_ptr[i].value_ptr)) { _free_env_var_list(env_var_ptr); _free_argv_list(copied_argv_ptr); return NULL; } } } return _subprocess_create_argv(file_ptr, copied_argv_ptr, env_var_ptr); } /* ------------------------------------------------------------------------- */ bs_subprocess_t *bs_subprocess_create_cmdline( const char *cmdline_ptr) { _env_var_t *env_var_ptr = NULL; char **argv_ptr = _split_command(cmdline_ptr, &env_var_ptr); if (NULL == argv_ptr) { bs_log(BS_ERROR, "Failed _split_command(%s)", cmdline_ptr); return NULL; } return _subprocess_create_argv(argv_ptr[0], argv_ptr, env_var_ptr); } /* ------------------------------------------------------------------------- */ void bs_subprocess_destroy(bs_subprocess_t *subprocess_ptr) { if (0 != subprocess_ptr->pid) { bs_subprocess_stop(subprocess_ptr); } if (NULL != subprocess_ptr->env_vars_ptr) { _free_env_var_list(subprocess_ptr->env_vars_ptr); subprocess_ptr->env_vars_ptr = NULL; } if (NULL != subprocess_ptr->argv_ptr) { _free_argv_list(subprocess_ptr->argv_ptr); } if (NULL != subprocess_ptr->file_ptr) { free(subprocess_ptr->file_ptr); } free(subprocess_ptr); } /* ------------------------------------------------------------------------- */ bool bs_subprocess_start(bs_subprocess_t *subprocess_ptr) { int stdin_read = -1, stdout_write = -1, stderr_write = -1; if (0 != subprocess_ptr->pid) { bs_log(BS_ERROR, "Already a running subprocess, pid %d", subprocess_ptr->pid); return false; } if (_create_pipe_fds(&stdin_read, &subprocess_ptr->stdin_write) && _create_pipe_fds(&subprocess_ptr->stdout_read, &stdout_write) && _create_pipe_fds(&subprocess_ptr->stderr_read, &stderr_write)) { subprocess_ptr->pid = fork(); if (0 == subprocess_ptr->pid) { _subprocess_child(subprocess_ptr, stdin_read, stdout_write, stderr_write); // Will never return, hence not reach this line. abort(); } } // No matter what, we clean up descriptors for the child side. if (-1 != stdin_read) _close_fd(&stdin_read); if (-1 != stdout_write) _close_fd(&stdout_write); if (-1 != stderr_write) _close_fd(&stderr_write); // All worked -- go ahead. if (0 < subprocess_ptr->pid) { bs_sock_set_blocking(subprocess_ptr->stdout_read, false); bs_sock_set_blocking(subprocess_ptr->stderr_read, false); return true; } // There was a failure. Clean up descriptors on the parent side. if (-1 != subprocess_ptr->stdin_write) { _close_fd(&subprocess_ptr->stdin_write); } if (-1 != subprocess_ptr->stdout_read) { _close_fd(&subprocess_ptr->stdout_read); } if (-1 != subprocess_ptr->stderr_read) { _close_fd(&subprocess_ptr->stderr_read); } return false; } /* ------------------------------------------------------------------------- */ void bs_subprocess_stop(bs_subprocess_t *subprocess_ptr) { if (0 != subprocess_ptr->pid) { // The child process is apparently still up. Send a KILL signal. if (0 != kill(subprocess_ptr->pid, SIGKILL)) { bs_log(BS_ERROR | BS_ERRNO, "Failed kill(%d, SIGKILL)", subprocess_ptr->pid); } // Now, wait for the child process to terminate. Shouldn't last long. int rv = _waitpid_nointr(subprocess_ptr, true); if (0 == rv) { bs_log(BS_ERROR, "Process %d did unexpecedly NOT change status", subprocess_ptr->pid); } else if (subprocess_ptr->pid != rv) { bs_log(BS_ERROR, "Unexpected return value %d for waitpid(%d, ...)", rv, subprocess_ptr->pid); } subprocess_ptr->pid = 0; } // Here, the process is gone. Flush stdout & stderr, close sockets. _close_fd(&subprocess_ptr->stdin_write); _close_fd(&subprocess_ptr->stdout_read); _close_fd(&subprocess_ptr->stderr_read); } /* ------------------------------------------------------------------------- */ bool bs_subprocess_terminated(bs_subprocess_t *subprocess_ptr, int *exit_status_ptr, int *signal_number_ptr) { if (0 != subprocess_ptr->pid) { if (0 >= _waitpid_nointr(subprocess_ptr, false)) { // An error or no signal. Assume it's still up. return false; } subprocess_ptr->pid = 0; } if (NULL != exit_status_ptr) { *exit_status_ptr = subprocess_ptr->exit_status; } if (NULL != signal_number_ptr) { *signal_number_ptr = subprocess_ptr->signal_number; } return true; } /* ------------------------------------------------------------------------- */ void bs_subprocess_get_fds(bs_subprocess_t *subprocess_ptr, int *stdin_write_fd_ptr, int *stdout_read_fd_ptr, int *stderr_read_fd_ptr) { if (NULL != stdin_write_fd_ptr) { *stdin_write_fd_ptr = subprocess_ptr->stdin_write; } if (NULL != stdout_read_fd_ptr) { *stdout_read_fd_ptr = subprocess_ptr->stdout_read; } if (NULL != stderr_read_fd_ptr) { *stderr_read_fd_ptr = subprocess_ptr->stderr_read; } } /* ------------------------------------------------------------------------- */ pid_t bs_subprocess_pid(bs_subprocess_t *subprocess_ptr) { return subprocess_ptr->pid; } /* == Local methods ======================================================== */ /* ------------------------------------------------------------------------- */ /** * Creates the subprocess from |argv_ptr|. * * @param file_ptr Will create a copy of file_ptr * @param argv_ptr Takes ownership of it. * @param env_vars_ptr Takes ownership of it. * * @return The subprocess handle or NULL on err.r */ bs_subprocess_t *_subprocess_create_argv( const char *file_ptr, char **argv_ptr, _env_var_t *env_vars_ptr) { bs_subprocess_t *subprocess_ptr; subprocess_ptr = (bs_subprocess_t*)logged_calloc( 1, sizeof(bs_subprocess_t)); if (NULL == subprocess_ptr) return NULL; subprocess_ptr->file_ptr = logged_strdup(file_ptr); if (NULL == subprocess_ptr->file_ptr) { free(subprocess_ptr); return NULL; } subprocess_ptr->argv_ptr = argv_ptr; subprocess_ptr->env_vars_ptr = env_vars_ptr; subprocess_ptr->stdin_write = -1; subprocess_ptr->stdout_read = -1; subprocess_ptr->stderr_read = -1; return subprocess_ptr; } /* ------------------------------------------------------------------------- */ /** * Runs waitpid() and updates |exit_status| and |signal_number| of * |subprocess_ptr|. Waits for the job's termination if |wait| is set. * Will re-try waitpid() when encountering EINTR, and logs all errors. * * @param subprocess_ptr * @param wait * * @return See waitpid(). */ int _waitpid_nointr(bs_subprocess_t *subprocess_ptr, bool wait) { int options = 0, status = 0, rv = 0; if (!wait) options |= WNOHANG; // Catch EINTR and keep calling. do { rv = waitpid(subprocess_ptr->pid, &status, options); } while (0 > rv && EINTR == errno); if (0 > rv) { bs_log(BS_ERROR | BS_ERRNO, "Failed waitpid(%d, %p, 0x%x)", subprocess_ptr->pid, &status, options); } else if (0 < rv) { // Process did terminate. Update status accordingly. BS_ASSERT(rv == subprocess_ptr->pid); if (WIFEXITED(status)) { subprocess_ptr->exit_status = WEXITSTATUS(status); subprocess_ptr->signal_number = 0; } else if (WIFSIGNALED(status)) { subprocess_ptr->exit_status = INT_MIN; subprocess_ptr->signal_number = WTERMSIG(status); } else { bs_log(BS_FATAL, "Unhandled waitpid() status for %d: 0x%x", subprocess_ptr->pid, status); } } return rv; } /* ------------------------------------------------------------------------- */ /** Helper: Close socket, log error, and invalidate |*fd_ptr|. */ void _close_fd(int *fd_ptr) { if (0 != close(*fd_ptr)) { bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d)", *fd_ptr); } *fd_ptr = -1; } /* ------------------------------------------------------------------------- */ /** Helper: Creates a pipe, with errors logged & file descriptors stored */ bool _create_pipe_fds(int *read_fd_ptr, int *write_fd_ptr) { int fds[2]; if (0 != pipe(fds)) { bs_log(BS_ERROR | BS_ERRNO, "Failed pipe(%p)", fds); return false; } *read_fd_ptr = fds[0]; *write_fd_ptr = fds[1]; return true; } /* ------------------------------------------------------------------------- */ /** Wraps the child process: setup stdin, stdout, stderr and launches job. */ void _subprocess_child( bs_subprocess_t *subprocess_ptr, int stdin_read, int stdout_write, int stderr_write) { // Close the parent's descriptors for stdin, stdout & stderr. _close_fd(&subprocess_ptr->stdin_write); _close_fd(&subprocess_ptr->stdout_read); _close_fd(&subprocess_ptr->stderr_read); // Setup stdin, stderr and stdout proper. if (0 != dup2(stdin_read, 0)) { bs_log(BS_FATAL | BS_ERRNO, "Failed dup2(%d, 0)", stdin_read); } if (0 != close(stdin_read)) { bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d)", stdin_read); } if (1 != dup2(stdout_write, 1)) { bs_log(BS_FATAL | BS_ERRNO, "Failed dup2(%d, 1)", stdout_write); } if (0 != close(stdout_write)) { bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d)", stdout_write); } if (2 != dup2(stderr_write, 2)) { bs_log(BS_FATAL | BS_ERRNO, "Failed dup2(%d, 2)", stderr_write); } if (0 != close(stderr_write)) { bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d)", stderr_write); } for (const _env_var_t *var_ptr = subprocess_ptr->env_vars_ptr; NULL != var_ptr && NULL != var_ptr->name_ptr; ++var_ptr) { if (0 != setenv(var_ptr->name_ptr, var_ptr->value_ptr, 1)) { bs_log(BS_WARNING | BS_ERRNO, "Failed setenv(\"%s\", \"%s\")", var_ptr->name_ptr, var_ptr->value_ptr); abort(); } } if (0 > execvp(subprocess_ptr->file_ptr, subprocess_ptr->argv_ptr)) { bs_log(BS_ERROR | BS_ERRNO, "Failed execvp(%s, %p)", subprocess_ptr->file_ptr, subprocess_ptr->argv_ptr); } // We only get here on error, and cannot do anything about. abort(); } /* ------------------------------------------------------------------------- */ /** Splits the commandline into a NULL-terminated list of strings. */ char **_split_command(const char *cmd_ptr, _env_var_t **env_var_ptr_ptr) { char *token_ptr; const char*line_ptr; char **argv_ptr; size_t argv_size = 0; argv_ptr = (char**)logged_calloc(1, (argv_size + 1) * sizeof(char*)); if (NULL == argv_ptr) return NULL; _env_var_t *env_var_ptr; env_var_ptr = logged_calloc(1, sizeof(_env_var_t)); if (NULL == env_var_ptr) { free(argv_ptr); return NULL; } size_t env_var_size = 0; line_ptr = cmd_ptr; do { token_ptr = _split_next_token(line_ptr, &line_ptr); if (NULL != token_ptr) { if (0 == argv_size && _is_variable_assignment(token_ptr, &env_var_ptr[env_var_size])) { free(token_ptr); void *tmp_ptr = realloc( env_var_ptr, (env_var_size + 2) * sizeof(_env_var_t)); if (NULL == tmp_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)", env_var_ptr, (env_var_size + 2) * sizeof(_env_var_t)); _free_env_var_list(env_var_ptr); _free_argv_list(argv_ptr); return NULL; } env_var_ptr = tmp_ptr; ++env_var_size; memset(&env_var_ptr[env_var_size], 0, sizeof(_env_var_t)); } else { argv_ptr[argv_size++] = token_ptr; void *tmp_ptr = realloc(argv_ptr, (argv_size + 1) * sizeof(char*)); if (NULL == tmp_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)", argv_ptr, (argv_size + 1) * sizeof(char*)); _free_env_var_list(env_var_ptr); _free_argv_list(argv_ptr); return NULL; } argv_ptr = (char**)tmp_ptr; argv_ptr[argv_size] = NULL; } } } while (NULL != token_ptr && NULL != line_ptr); *env_var_ptr_ptr = env_var_ptr; return argv_ptr; } /* ------------------------------------------------------------------------- */ /** Returns a copy of the next token from |word_ptr|. Must be free()-ed. */ char *_split_next_token(const char *word_ptr, const char **next_ptr) { char *token_ptr, *orig_token_ptr; int state, char_type; if (*word_ptr == '\0') return NULL; token_ptr = malloc(strlen(word_ptr) + 1); if (NULL == token_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed malloc(%zu)", strlen(word_ptr) + 1); return NULL; } orig_token_ptr = token_ptr; state = 0; *token_ptr = '\0'; while (true) { switch (*word_ptr) { case '\0': char_type = char_type_end_of_string; break; case '\\': char_type = char_type_escape; break; case '"' : char_type = char_type_double_quote; break; case '\'': char_type = char_type_single_quote; break; case ' ' : case '\t': char_type = char_type_blank; break; default: char_type = char_type_alpha; break; } if (state_transition_table[state][char_type].output) { *token_ptr = *word_ptr; ++token_ptr; *token_ptr = '\0'; } state = state_transition_table[state][char_type].next_state; word_ptr++; if (0 > state_transition_table[state][0].output) break; } // The token might be shorter than what we had reserved, update that. token_ptr = logged_strdup(orig_token_ptr); free(orig_token_ptr); if (char_type == char_type_end_of_string) { *next_ptr = NULL; } else { *next_ptr = word_ptr; } return token_ptr; } /* ------------------------------------------------------------------------- */ /** Frees up strings and the list of a NULL-terminated list of strings. */ void _free_argv_list(char **argv_ptr) { for (char **iter_ptr = argv_ptr; *iter_ptr != NULL; ++iter_ptr) { free(*iter_ptr); } free(argv_ptr); } /* ------------------------------------------------------------------------- */ /** Frees up the variable names and values, as well as the list itself. */ void _free_env_var_list(_env_var_t *env_var_ptr) { for (_env_var_t *iter_ptr = env_var_ptr; iter_ptr->name_ptr != NULL; ++iter_ptr) { free(iter_ptr->name_ptr); free(iter_ptr->value_ptr); } free(env_var_ptr); } /* ------------------------------------------------------------------------- */ /** Populates the entry from name and value. */ bool _populate_env_var(_env_var_t *env_var_ptr, const char *name_ptr, size_t name_len, const char *value_ptr) { env_var_ptr->name_ptr = logged_malloc(name_len + 1); if (NULL == env_var_ptr->name_ptr) return false; memcpy(env_var_ptr->name_ptr, name_ptr, name_len); env_var_ptr->name_ptr[name_len] = '\0'; env_var_ptr->value_ptr = logged_strdup(value_ptr); if (NULL == env_var_ptr->value_ptr) { free(env_var_ptr->name_ptr); env_var_ptr->name_ptr = NULL; return false; } return true; } /* ------------------------------------------------------------------------- */ /** * Returns if `word_ptr` is a variable assignmend and gives name and value. * * A variable assignment will start with the variable name, located at the * beginning of `word_ptr`. The variable name must start with a letter or * underscore, and may only contain only letters, numbers or underscore. * The variable name must be succeeded by a '=' sign. All that follows will be * considered the value. * * @param word_ptr The token to analyse. * @param name_ptr_ptr If `word_ptr` is a variable assignment, then * *name_ptr_ptr will be set to point to a newly allocated copy of the * variable name. Will only be set if the function returns true. If so, * the allocated memory must be free()-ed. * @param value_ptr_ptr If `word_ptr` is a variable assignent, then * *value_ptr_ptr will be set to point to a newly allocated copy of the * variable's value. Will only be set if the function return true. I fos, * the allocated memory must be free()-ed. * * @return true if `word_ptr` is a variable, and false if it's not a varaible * or if an error occurred. *name_ptr_ptr and *value_ptr_ptr will be set * only on success. */ bool _is_variable_assignment( const char *word_ptr, _env_var_t *env_variable_ptr) { static const char *name_regexp_ptr = "^[a-zA-Z_][a-zA-Z_0-9]*="; regex_t regex; regmatch_t matches[1]; int rv; if (0 != (rv = regcomp(®ex, name_regexp_ptr, REG_EXTENDED))) { bs_log(BS_ERROR, "Failed regcomp(%p, \"%s\", REG_EXTENDED): %d", ®ex, name_regexp_ptr, rv); return false; } rv = regexec(®ex, word_ptr, 1, matches, 0); regfree(®ex); if (REG_NOMATCH == rv) return false; if (0 != rv) { bs_log(BS_ERROR, "Failed regexec(%p, \"%s\", 1, %p): %d", ®ex, word_ptr, matches, rv); return false; } // A valid match must start at the beginning of the word, and it must at // least hold two chars: the variable name, and the '=' sign. if (matches[0].rm_so != 0 || matches[0].rm_eo < 2) return false; if (!_populate_env_var(env_variable_ptr, word_ptr, matches[0].rm_eo - matches[0].rm_so - 1, word_ptr + matches[0].rm_eo)) { return false; } return true; } /* == Unit tests =========================================================== */ /** @cond TEST */ static void test_is_variable_assignment(bs_test_t *test_ptr); static void test_split_command(bs_test_t *test_ptr); static void test_hang(bs_test_t *test_ptr); static void test_nonexisting(bs_test_t *test_ptr); /** Unit test cases. */ const bs_test_case_t bs_subprocess_test_cases[] = { { true, "is_variable_assignment", test_is_variable_assignment }, { true, "split_command", test_split_command }, { true, "hang", test_hang }, { true, "nonexisting", test_nonexisting }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bs_subprocess_test_set = BS_TEST_SET( true, "bs_subprocess", bs_subprocess_test_cases); static const char *test_args[] = { NULL, "alpha", NULL }; /* ------------------------------------------------------------------------- */ /** Helper: Verify two NULL-terminated pointer string lists are equal. */ void test_verify_eq_arglist( bs_test_t *test_ptr, char **a1_ptr, char **a2_ptr) { for (; *a1_ptr != NULL && *a2_ptr != NULL; a1_ptr++, a2_ptr++) { BS_TEST_VERIFY_STREQ(test_ptr, *a1_ptr, *a2_ptr); } BS_TEST_VERIFY_EQ(test_ptr, *a1_ptr, *a2_ptr); } /* ------------------------------------------------------------------------- */ /** Helper: Verify two env variable lists are equal. */ void test_verify_eq_envlist( bs_test_t *test_ptr, const bs_subprocess_environment_variable_t *exp_ptr, _env_var_t *var_ptr) { for (; exp_ptr->name_ptr != NULL && var_ptr->name_ptr != NULL; exp_ptr++, var_ptr++) { BS_TEST_VERIFY_STREQ(test_ptr, exp_ptr->value_ptr, var_ptr->value_ptr); BS_TEST_VERIFY_STREQ(test_ptr, exp_ptr->value_ptr, var_ptr->value_ptr); } BS_TEST_VERIFY_EQ(test_ptr, exp_ptr->name_ptr, var_ptr->name_ptr); } /* ------------------------------------------------------------------------- */ void test_is_variable_assignment(bs_test_t *test_ptr) { _env_var_t var; BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("a=value", &var)); BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a"); BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value"); free(var.name_ptr); free(var.value_ptr); BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("a1=value", &var)); BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a1"); BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value"); free(var.name_ptr); free(var.value_ptr); BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("_=value", &var)); BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "_"); BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value"); free(var.name_ptr); free(var.value_ptr); BS_TEST_VERIFY_TRUE( test_ptr, _is_variable_assignment("SILLY_2_LONG_VARIABLE_42=value", &var)); BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "SILLY_2_LONG_VARIABLE_42"); BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value"); free(var.name_ptr); free(var.value_ptr); BS_TEST_VERIFY_TRUE( test_ptr, _is_variable_assignment("a=value\" with more\"", &var)); BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a"); BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, "value\" with more\""); free(var.name_ptr); free(var.value_ptr); BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("a= value", &var)); BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a"); BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, " value"); free(var.name_ptr); free(var.value_ptr); BS_TEST_VERIFY_TRUE(test_ptr, _is_variable_assignment("a=", &var)); BS_TEST_VERIFY_STREQ(test_ptr, var.name_ptr, "a"); BS_TEST_VERIFY_STREQ(test_ptr, var.value_ptr, ""); free(var.name_ptr); free(var.value_ptr); BS_TEST_VERIFY_FALSE(test_ptr, _is_variable_assignment("a", &var)); BS_TEST_VERIFY_FALSE(test_ptr, _is_variable_assignment("1a=b", &var)); } /* ------------------------------------------------------------------------- */ void test_split_command(bs_test_t *test_ptr) { char **argv_ptr; _env_var_t *env_var_ptr; char *expected1[] = {"command", "arg1", "arg2", NULL }; argv_ptr = _split_command("command arg1 arg2", &env_var_ptr); test_verify_eq_arglist(test_ptr, expected1, argv_ptr); _free_argv_list(argv_ptr); _free_env_var_list(env_var_ptr); char *expected2[] = {"command", "arg1 arg2", "arg3", NULL }; argv_ptr = _split_command("command \"arg1 arg2\" arg3", &env_var_ptr); test_verify_eq_arglist(test_ptr, expected2, argv_ptr); _free_argv_list(argv_ptr); _free_env_var_list(env_var_ptr); char *expected3[] = {"command", "arg1 arg2", "arg3", NULL }; argv_ptr = _split_command("command \'arg1 arg2\' arg3", &env_var_ptr); test_verify_eq_arglist(test_ptr, expected3, argv_ptr); _free_argv_list(argv_ptr); _free_env_var_list(env_var_ptr); char *expected4[] = {"command", "arg1 \'arg2", "arg3", NULL }; argv_ptr = _split_command("command \"arg1 \'arg2\" arg3\'", &env_var_ptr); test_verify_eq_arglist(test_ptr, expected4, argv_ptr); _free_argv_list(argv_ptr); _free_env_var_list(env_var_ptr); char *expected5[] = {"command", "\"arg1", "arg2\"", "arg3", NULL }; argv_ptr = _split_command("command \\\"arg1 arg2\\\" arg3", &env_var_ptr); test_verify_eq_arglist(test_ptr, expected5, argv_ptr); _free_argv_list(argv_ptr); _free_env_var_list(env_var_ptr); char *expected6[] = {"command", "arg1", NULL }; const bs_subprocess_environment_variable_t expected_env6[] = { { "var1", "1" }, { "var2", "2" }, { NULL, NULL } }; argv_ptr = _split_command("var1=1 var2=2 command arg1", &env_var_ptr); test_verify_eq_arglist(test_ptr, expected6, argv_ptr); test_verify_eq_envlist(test_ptr, expected_env6, env_var_ptr); _free_argv_list(argv_ptr); _free_env_var_list(env_var_ptr); } /* ------------------------------------------------------------------------- */ void test_hang(bs_test_t *test_ptr) { test_args[0] = "./subprocess_test_hang"; bs_subprocess_t *sp_ptr = bs_subprocess_create( "./subprocess_test_hang", test_args, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr)); int exit_status, signal_number; BS_TEST_VERIFY_FALSE( test_ptr, bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)); bs_subprocess_stop(sp_ptr); BS_TEST_VERIFY_TRUE( test_ptr, bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)); BS_TEST_VERIFY_EQ(test_ptr, INT_MIN, exit_status); BS_TEST_VERIFY_EQ(test_ptr, SIGKILL, signal_number); bs_subprocess_destroy(sp_ptr); } /* ------------------------------------------------------------------------- */ void test_nonexisting(bs_test_t *test_ptr) { test_args[0] = "./subprocess_test_does_not_exist"; bs_subprocess_t *sp_ptr = bs_subprocess_create( "./subprocess_test_does_not_exist", test_args, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr)); int exit_status, signal_number; while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) { // Don't busy-loop -- just wait a little. poll(NULL, 0, 10); } BS_TEST_VERIFY_EQ(test_ptr, INT_MIN, exit_status); BS_TEST_VERIFY_EQ(test_ptr, SIGABRT, signal_number); int fd; bs_subprocess_get_fds(sp_ptr, NULL, NULL, &fd); bs_dynbuf_t buf; BS_TEST_VERIFY_TRUE(test_ptr, bs_dynbuf_init(&buf, 1024, SIZE_MAX)); bs_dynbuf_read(&buf, fd); BS_TEST_VERIFY_STRMATCH( test_ptr, buf.data_ptr, ".*ERROR.*Failed execvp\\(\\./subprocess_test_does_not_exist"); bs_dynbuf_fini(&buf); bs_subprocess_destroy(sp_ptr); } /** @endcond */ /* == End of subprocess.c ================================================== */ wlmaker-0.8.1/submodules/libbase/src/sock.c0000644000175100017510000001007315222270712020246 0ustar runnerrunner/* ========================================================================= */ /** * @file sock.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include /* == Methods ============================================================== */ /* ------------------------------------------------------------------------- */ bool bs_sock_set_blocking(int fd, bool blocking) { int flags, rv; flags = fcntl(fd, F_GETFL); if (-1 == flags) { bs_log(BS_ERROR | BS_ERRNO, "Failed fcntl(%d, F_GETFL)", fd); return false; } if (blocking) { flags &= ~O_NONBLOCK; } else { flags |= O_NONBLOCK; } rv = fcntl(fd, F_SETFL, flags); if (0 != rv) { bs_log(BS_ERROR | BS_ERRNO, "Failed fcntl(%d, F_SETFL, 0x%x)", fd, flags); return false; } return true; } /* ------------------------------------------------------------------------- */ int bs_sock_poll_read(int fd, int msec) { struct pollfd poll_fd; int rv; poll_fd.fd = fd; poll_fd.events = POLLIN; rv = poll(&poll_fd, 1, msec); if (0 > rv) { rv = errno; bs_log(BS_ERROR | BS_ERRNO, "Failed poll(%d, 1, %d)", fd, msec); errno = rv; return -1; } return rv; } /* ------------------------------------------------------------------------- */ ssize_t bs_sock_read(int fd, void *buf_ptr, size_t count, int msec) { ssize_t consumed_bytes, read_bytes; int rv; uint8_t *byte_buf_ptr; uint64_t start_msec; int timeout; /* catch boundary conditions on count */ if (INT32_MAX < count) { errno = EINVAL; return -1; } if (0 >= count) { return 0; } if (0 > msec) { start_msec = bs_usec() / 1000; } else { start_msec = 0; } timeout = msec; consumed_bytes = 0; byte_buf_ptr = (uint8_t*)buf_ptr; while ((size_t)consumed_bytes < count) { if (0 <= msec) { uint64_t now_msec = bs_usec() / 1000; if (start_msec + msec > now_msec) { timeout = BS_MIN(now_msec - (start_msec + msec), (uint64_t)INT32_MAX); } else { timeout = 0; } } rv = bs_sock_poll_read(fd, timeout); if (0 > rv) { return -1; } else if (0 == rv) { /* no more data */ return consumed_bytes; } read_bytes = read(fd, &byte_buf_ptr[consumed_bytes], count - consumed_bytes); if (0 == read_bytes) { /* connection was closed, return prescribed error. */ errno = EPIPE; return -1; } if ((0 > read_bytes) && (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { rv = errno; bs_log(BS_ERROR | BS_ERRNO, "Failed read(%d, %p, %zu)", fd, &byte_buf_ptr[consumed_bytes], count - consumed_bytes); errno = rv; return -1; } consumed_bytes += read_bytes; } return consumed_bytes; } /* == End of sock.c ======================================================== */ wlmaker-0.8.1/submodules/libbase/src/strutil.c0000644000175100017510000002746415222270712021031 0ustar runnerrunner/* ========================================================================= */ /** * @file strutil.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ size_t bs_strappendf( char *buf, size_t buf_size, size_t buf_pos, const char *fmt_ptr, ...) { va_list ap; va_start(ap, fmt_ptr); size_t rv = bs_vstrappendf(buf, buf_size, buf_pos, fmt_ptr, ap); va_end(ap); return rv; } /* ------------------------------------------------------------------------- */ size_t bs_vstrappendf( char *buf, size_t buf_size, size_t buf_pos, const char *fmt_ptr, va_list ap) { if (buf_pos >= buf_size) return buf_pos; ssize_t rv = vsnprintf(&buf[buf_pos], buf_size - buf_pos, fmt_ptr, ap); return buf_pos + BS_MAX(0, rv); } /* ------------------------------------------------------------------------- */ size_t bs_strappend( char *buf, size_t buf_size, size_t buf_pos, const char *str_ptr) { if (buf_pos >= buf_size) return buf_size; size_t len = strlen(str_ptr); if (buf_pos + len + 1 >= buf_size) len = buf_size - buf_pos - 1; memmove(buf + buf_pos, str_ptr, len); buf[buf_pos + len] = '\0'; return buf_pos + strlen(str_ptr);; } /* ------------------------------------------------------------------------- */ bool bs_strconvert_uint64( const char *string_ptr, uint64_t *value_ptr, int base) { unsigned long long tmp_value; char *invalid_ptr; if ('-' == *string_ptr) { bs_log(BS_ERROR, "Unexpected negative value \"%s\"", string_ptr); return false; } invalid_ptr = NULL; errno = 0; tmp_value = strtoull(string_ptr, &invalid_ptr, base); if (0 != errno) { bs_log(BS_ERROR | BS_ERRNO, "Failed strtoull for value \"%s\"", string_ptr); return false; } if ('\0' != *invalid_ptr && !isspace(*invalid_ptr)) { bs_log(BS_ERROR, "Failed strtoull for value \"%s\" at \"%s\"", string_ptr, invalid_ptr); return false; } *value_ptr = tmp_value; return true; } /* ------------------------------------------------------------------------- */ bool bs_strconvert_int64( const char *string_ptr, int64_t *value_ptr, int base) { long long tmp_value; char *invalid_ptr; invalid_ptr = NULL; errno = 0; tmp_value = strtoll(string_ptr, &invalid_ptr, base); if (0 != errno) { bs_log(BS_ERROR | BS_ERRNO, "Failed strtoll for value \"%s\"", string_ptr); return false; } if ('\0' != *invalid_ptr && !isspace(*invalid_ptr)) { bs_log(BS_ERROR, "Failed strtoll for value \"%s\" at \"%s\"", string_ptr, invalid_ptr); return false; } *value_ptr = tmp_value; return true; } /* ------------------------------------------------------------------------- */ bool bs_strconvert_double( const char *string_ptr, double *value_ptr) { char *invalid_ptr = NULL; errno = 0; double tmp_value = strtod(string_ptr, &invalid_ptr); if (0 != errno) { bs_log(BS_ERROR | BS_ERRNO, "Failed strtod(\"%s\", %p)", string_ptr, &invalid_ptr); return false; } if ('\0' != *invalid_ptr && !isspace(*invalid_ptr)) { bs_log(BS_ERROR, "Failed strtod(\"%s\", %p) at \"%s\"", string_ptr, &invalid_ptr, invalid_ptr); return false; } *value_ptr = tmp_value; return true; } /* ------------------------------------------------------------------------- */ bool bs_str_startswith(const char *string_ptr, const char *prefix_ptr) { return 0 == strncmp(string_ptr, prefix_ptr, strlen(prefix_ptr)); } /* ------------------------------------------------------------------------- */ char *bs_strdupf(const char *fmt_ptr, ...) { char *rv; va_list ap; va_start(ap, fmt_ptr); rv = bs_vstrdupf(fmt_ptr, ap); va_end(ap); return rv; } /* ------------------------------------------------------------------------- */ char *bs_vstrdupf(const char *fmt_ptr, va_list ap) { size_t size = 16; // No point in using a string shorter than a paragraph. while (true) { char *str = logged_malloc(size); if (NULL == str) return NULL; va_list iter_ap; va_copy(iter_ap, ap); int rv = vsnprintf(str, size, fmt_ptr, iter_ap); if (0 > rv) { bs_log(BS_WARNING, "Failed vsnprintf(%p, %zu, \"%s\", ...)", str, size, fmt_ptr); free(str); return NULL; } if ((size_t)rv < size) return str; free(str); size = (size_t)rv + 1; } return NULL; } /* == Test functions ======================================================= */ static void test_strappend(bs_test_t *test_ptr); static void strconvert_uint64_test(bs_test_t *test_ptr); static void strconvert_int64_test(bs_test_t *test_ptr); static void strconvert_double_test(bs_test_t *test_ptr); static void test_startswith(bs_test_t *test_ptr); static void test_strdupf(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_strutil_test_cases[] = { { true, "strappend", test_strappend }, { true, "strconvert_uint64", strconvert_uint64_test }, { true, "strconvert_int64", strconvert_int64_test }, { true, "strconvert_double", strconvert_double_test }, { true, "startswith", test_startswith }, { true, "strdupf", test_strdupf }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bs_strutil_test_set = BS_TEST_SET( true, "strutil", bs_strutil_test_cases); /* -- Append to string buffer ---------------------------------------------- */ void test_strappend(bs_test_t *test_ptr) { char buf[10]; size_t in = 0, out; out = bs_strappendf(buf, sizeof(buf), in, "asdf"); BS_TEST_VERIFY_EQ(test_ptr, out, 4); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdf"); in = out; out = bs_strappendf(buf, sizeof(buf), in, "qwer"); BS_TEST_VERIFY_EQ(test_ptr, out, 8); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwer"); in = out; out = bs_strappendf(buf, sizeof(buf), in, "j"); BS_TEST_VERIFY_EQ(test_ptr, out, 9); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerj"); out = bs_strappendf(buf, sizeof(buf), in, "jk"); BS_TEST_VERIFY_EQ(test_ptr, out, 10); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerj"); out = bs_strappendf(buf, sizeof(buf), in, "jkl"); BS_TEST_VERIFY_EQ(test_ptr, out, 11); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerj"); in = out; out = bs_strappendf(buf, sizeof(buf), in, "uiop"); BS_TEST_VERIFY_EQ(test_ptr, out, 11); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerj"); in = 0; out = bs_strappend(buf, sizeof(buf), in, "asdf"); BS_TEST_VERIFY_EQ(test_ptr, out, 4); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdf"); in = out; out = bs_strappend(buf, sizeof(buf), in, "qwer"); BS_TEST_VERIFY_EQ(test_ptr, out, 8); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwer"); out = bs_strappend(buf, sizeof(buf), 8, "g"); BS_TEST_VERIFY_EQ(test_ptr, out, 9); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerg"); out = bs_strappend(buf, sizeof(buf), 8, "gh"); BS_TEST_VERIFY_EQ(test_ptr, out, 10); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerg"); out = bs_strappend(buf, sizeof(buf), 8, "ghv"); BS_TEST_VERIFY_EQ(test_ptr, out, 11); BS_TEST_VERIFY_STREQ(test_ptr, buf, "asdfqwerg"); } /* -- Convert uint64_t ----------------------------------------------------- */ void strconvert_uint64_test(bs_test_t *test_ptr) { uint64_t value; BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_uint64("42", &value, 10)); BS_TEST_VERIFY_EQ(test_ptr, value, 42); BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_uint64("43 ", &value, 10)); BS_TEST_VERIFY_EQ(test_ptr, value, 43); BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_uint64("44\n", &value, 10)); BS_TEST_VERIFY_EQ(test_ptr, value, 44); BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_uint64("0", &value, 10)); BS_TEST_VERIFY_EQ(test_ptr, value, 0); BS_TEST_VERIFY_TRUE( test_ptr, bs_strconvert_uint64("18446744073709551615", &value, 10)); BS_TEST_VERIFY_EQ(test_ptr, value, 18446744073709551615u); BS_TEST_VERIFY_TRUE( test_ptr, bs_strconvert_uint64("0xffffffffffffffff", &value, 16)); BS_TEST_VERIFY_EQ(test_ptr, value, 18446744073709551615u); BS_TEST_VERIFY_FALSE( test_ptr, bs_strconvert_uint64("18446744073709551616", &value, 10)); BS_TEST_VERIFY_FALSE(test_ptr, bs_strconvert_uint64("42x", &value, 10)); BS_TEST_VERIFY_FALSE(test_ptr, bs_strconvert_uint64("-42", &value, 10)); } /* ------------------------------------------------------------------------- */ void strconvert_int64_test(bs_test_t *test_ptr) { int64_t value; BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_int64("0", &value, 10)); BS_TEST_VERIFY_EQ(test_ptr, value, 0); BS_TEST_VERIFY_TRUE( test_ptr, bs_strconvert_int64("9223372036854775807", &value, 10)); BS_TEST_VERIFY_EQ(test_ptr, value, INT64_MAX); BS_TEST_VERIFY_TRUE( test_ptr, bs_strconvert_int64("-9223372036854775808", &value, 10)); BS_TEST_VERIFY_EQ(test_ptr, value, INT64_MIN); BS_TEST_VERIFY_FALSE( test_ptr, bs_strconvert_int64("18446744073709551615", &value, 10)); } /* ------------------------------------------------------------------------- */ void strconvert_double_test(bs_test_t *test_ptr) { double value; BS_TEST_VERIFY_TRUE(test_ptr, bs_strconvert_double("0", &value)); BS_TEST_VERIFY_EQ(test_ptr, value, 0); BS_TEST_VERIFY_TRUE( test_ptr, bs_strconvert_double("2.2250738585072014e-308", &value)); BS_TEST_VERIFY_EQ(test_ptr, value, DBL_MIN); BS_TEST_VERIFY_TRUE( test_ptr, bs_strconvert_double("1.7976931348623158e+308", &value)); BS_TEST_VERIFY_EQ(test_ptr, value, DBL_MAX); BS_TEST_VERIFY_FALSE(test_ptr, bs_strconvert_double("badvalue", &value)); BS_TEST_VERIFY_FALSE(test_ptr, bs_strconvert_double("1e+400", &value)); } /* ------------------------------------------------------------------------- */ void test_startswith(bs_test_t *test_ptr) { BS_TEST_VERIFY_TRUE(test_ptr, bs_str_startswith("asdf", "asd")); BS_TEST_VERIFY_FALSE(test_ptr, bs_str_startswith("asdf", "asdfe")); BS_TEST_VERIFY_TRUE(test_ptr, bs_str_startswith("asdf", "")); BS_TEST_VERIFY_FALSE(test_ptr, bs_str_startswith("", "asdf")); } /* ------------------------------------------------------------------------- */ void test_strdupf(bs_test_t *test_ptr) { char *s = bs_strdupf("%d%s", 1, "a"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, s); BS_TEST_VERIFY_STREQ(test_ptr, "1a", s); free(s); } /* == End of strutil.c ===================================================== */ wlmaker-0.8.1/submodules/libbase/src/gfxbuf.c0000644000175100017510000004556315222270712020604 0ustar runnerrunner/* ========================================================================= */ /** * @file gfxbuf.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CAIRO #include #endif // HAVE_CAIRO /* == Declarations ========================================================= */ /** Internal handle of a graphics buffer. */ typedef struct { /** Publicly accessible elements. */ bs_gfxbuf_t public; /** Whether `data_ptr` is owned by @ref bs_gfxbuf_t `public`. */ bool managed; } bs_gfxbuf_internal_t; /** Returns the @ref bs_gfxbuf_internal_t for a @ref bs_gfxbuf_t. */ static inline bs_gfxbuf_internal_t *internal_from_gfxbuf( bs_gfxbuf_t *gfxbuf_ptr) { return BS_CONTAINER_OF(gfxbuf_ptr, bs_gfxbuf_internal_t, public); } #ifdef HAVE_CAIRO /** Image format used for @ref bs_gfxbuf_t, translated to Cairo terms. */ static const cairo_format_t bs_gfx_cairo_image_format = CAIRO_FORMAT_ARGB32; #endif // HAVE_CAIRO /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bs_gfxbuf_t *bs_gfxbuf_create(unsigned width, unsigned height) { uint32_t *data_ptr = (uint32_t*)logged_calloc(1, 4 * width * height); if (NULL == data_ptr) return NULL; bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create_unmanaged( width, height, width, data_ptr); if (NULL == gfxbuf_ptr) { free(data_ptr); } else { bs_gfxbuf_internal_t *gfxbuf_internal_ptr = internal_from_gfxbuf(gfxbuf_ptr); gfxbuf_internal_ptr->managed = true; } return gfxbuf_ptr; } /* ------------------------------------------------------------------------- */ bs_gfxbuf_t *bs_gfxbuf_create_unmanaged(unsigned width, unsigned height, unsigned pixels_per_line, uint32_t* data_ptr) { bs_gfxbuf_internal_t *buf_internal_ptr = logged_calloc( 1, sizeof(bs_gfxbuf_internal_t)); if (NULL == buf_internal_ptr) return NULL; buf_internal_ptr->public.width = width; buf_internal_ptr->public.height = height; buf_internal_ptr->public.pixels_per_line = pixels_per_line; buf_internal_ptr->public.data_ptr = data_ptr; buf_internal_ptr->managed = false; return &buf_internal_ptr->public; } /* ------------------------------------------------------------------------- */ void bs_gfxbuf_destroy(bs_gfxbuf_t *gfxbuf_ptr) { bs_gfxbuf_internal_t *gfxbuf_internal_ptr = internal_from_gfxbuf(gfxbuf_ptr); if (gfxbuf_internal_ptr->managed && NULL != gfxbuf_internal_ptr->public.data_ptr) { free(gfxbuf_internal_ptr->public.data_ptr); gfxbuf_ptr->data_ptr = NULL; } free(gfxbuf_internal_ptr); } /* ------------------------------------------------------------------------- */ void bs_gfxbuf_clear(bs_gfxbuf_t *gfxbuf_ptr, const uint32_t color) { uint32_t *pixel_ptr; if (color == 0) { for (unsigned y = 0; y < gfxbuf_ptr->height; ++y) { pixel_ptr = &gfxbuf_ptr->data_ptr[y * gfxbuf_ptr->pixels_per_line]; // On amd64, using memset is ca 3x faster than filling self. memset(pixel_ptr, 0, sizeof(uint32_t) * gfxbuf_ptr->width); } } else { for (unsigned y = 0; y < gfxbuf_ptr->height; ++y) { pixel_ptr = &gfxbuf_ptr->data_ptr[y * gfxbuf_ptr->pixels_per_line]; unsigned width = gfxbuf_ptr->width; while (width--) *pixel_ptr++ = color; } } } /* ------------------------------------------------------------------------- */ void bs_gfxbuf_copy(bs_gfxbuf_t *dest_gfxbuf_ptr, const bs_gfxbuf_t *src_gfxbuf_ptr) { BS_ASSERT(src_gfxbuf_ptr->width == dest_gfxbuf_ptr->width); BS_ASSERT(src_gfxbuf_ptr->height == dest_gfxbuf_ptr->height); uint32_t *src_pixel_ptr, *dest_pixel_ptr; for (unsigned y = 0; y < src_gfxbuf_ptr->height; ++y) { src_pixel_ptr = &src_gfxbuf_ptr->data_ptr[ y * src_gfxbuf_ptr->pixels_per_line]; dest_pixel_ptr = &dest_gfxbuf_ptr->data_ptr[ y * dest_gfxbuf_ptr->pixels_per_line]; #if 1 // On amd64, using memcpy is up to 3x faster. memcpy(dest_pixel_ptr, src_pixel_ptr, sizeof(uint32_t) * src_gfxbuf_ptr->width); #else unsigned width = src_gfxbuf_ptr->width; while (width--) *dest_pixel_ptr++ = *src_pixel_ptr++; #endif // 1 } } /* ------------------------------------------------------------------------- */ void bs_gfxbuf_copy_area( bs_gfxbuf_t *dest_gfxbuf_ptr, unsigned dest_x, unsigned dest_y, const bs_gfxbuf_t *src_gfxbuf_ptr, unsigned src_x, unsigned src_y, unsigned width, unsigned height) { // Sanity check, don't copy from/in outside the valid buffers. if (src_gfxbuf_ptr->width <= src_x || src_gfxbuf_ptr->height <= src_y || dest_gfxbuf_ptr->width <= dest_x || dest_gfxbuf_ptr->height <= dest_y) { return; } // Restrict area to buffer dimensions. width = BS_MIN(dest_gfxbuf_ptr->width - dest_x, BS_MIN(src_gfxbuf_ptr->width - src_x, width)); height = BS_MIN(dest_gfxbuf_ptr->height - dest_y, BS_MIN(src_gfxbuf_ptr->height - src_y, height)); uint32_t *src_data_ptr, *dest_data_ptr; for (unsigned y = 0; y < height; ++y) { dest_data_ptr = &dest_gfxbuf_ptr->data_ptr[ (dest_y + y) * dest_gfxbuf_ptr->pixels_per_line + dest_x]; src_data_ptr = &src_gfxbuf_ptr->data_ptr[ (src_y + y) * src_gfxbuf_ptr->pixels_per_line + src_x]; memcpy(dest_data_ptr, src_data_ptr, sizeof(uint32_t) * width); } } /* ------------------------------------------------------------------------- */ void bs_gfxbuf_argb8888_to_floats( const uint32_t argb8888, float *red_ptr, float *green_ptr, float *blue_ptr, float *alpha_ptr) { *red_ptr = BS_MIN(1.0, ((argb8888 & 0xff0000) >> 0x10) / 255.0); *green_ptr = BS_MIN(1.0, ((argb8888 & 0x00ff00) >> 0x8) / 255.0); *blue_ptr = BS_MIN(1.0, ((argb8888 & 0x0000ff)) / 255.0); if (NULL != alpha_ptr) { *alpha_ptr = BS_MIN(1.0, ((argb8888 & 0xff000000) >> 0x18) / 255.0); } } /* ------------------------------------------------------------------------- */ #ifdef HAVE_CAIRO cairo_t *cairo_create_from_bs_gfxbuf(const bs_gfxbuf_t *gfxbuf_ptr) { cairo_surface_t *cairo_surface_ptr = cairo_image_surface_create_for_data( (unsigned char*)gfxbuf_ptr->data_ptr, bs_gfx_cairo_image_format, gfxbuf_ptr->width, gfxbuf_ptr->height, gfxbuf_ptr->pixels_per_line * sizeof(uint32_t)); if (NULL == cairo_surface_ptr) { bs_log( BS_ERROR, "Failed cairo_image_surface_create_for_data(%p, %d, %u, %u, %zu)", gfxbuf_ptr->data_ptr, bs_gfx_cairo_image_format, gfxbuf_ptr->width, gfxbuf_ptr->height, gfxbuf_ptr->pixels_per_line * sizeof(uint32_t)); return NULL; } cairo_t *cairo_ptr = cairo_create(cairo_surface_ptr); cairo_surface_destroy(cairo_surface_ptr); if (NULL == cairo_ptr) { bs_log(BS_ERROR, "Failed cairo_create(%p)", cairo_surface_ptr); } return cairo_ptr; } /* ------------------------------------------------------------------------- */ void cairo_set_source_argb8888( cairo_t *cairo_ptr, uint32_t argb8888) { float r, g, b, alpha; bs_gfxbuf_argb8888_to_floats(argb8888, &r, &g, &b, &alpha); cairo_set_source_rgba(cairo_ptr, r, g, b, alpha); } /* ------------------------------------------------------------------------- */ /** TODO(kaeser@gubbe.ch): Change this to use libpng, and clean the code. */ void bs_test_gfxbuf_equals_png_at( bs_test_t *test_ptr, const char *fname_ptr, int line, const bs_gfxbuf_t *gfxbuf_ptr, const char *png_fname_ptr) { static int32_t png_iterator = 0; if (NULL == png_fname_ptr) { bs_test_fail_at( test_ptr, fname_ptr, line, "PNG file name is NULL"); return; } cairo_surface_t *png_surface_ptr = cairo_image_surface_create_from_png(png_fname_ptr); if (NULL == png_surface_ptr) { bs_test_fail_at( test_ptr, fname_ptr, line, "Failed cairo_image_surface_create_from_png(\"%s\")", png_fname_ptr); return; } if (CAIRO_STATUS_SUCCESS != cairo_surface_status(png_surface_ptr)) { bs_test_fail_at(test_ptr, fname_ptr, line, "Failed to load PNG surface from \"%s\"", png_fname_ptr); } const char *tested_gfxbuf_name_ptr = bs_test_temp_path( test_ptr, "out-%08"PRIx32".png", png_iterator++); if ((unsigned)cairo_image_surface_get_width(png_surface_ptr) != gfxbuf_ptr->width) { bs_test_fail_at( test_ptr, fname_ptr, line, "gfxbuf width %u != expected width %d. " "Expected output: \"%s\", gfxbuf under test: \"%s\"", gfxbuf_ptr->width, cairo_image_surface_get_width(png_surface_ptr), png_fname_ptr, tested_gfxbuf_name_ptr); } if ((unsigned)cairo_image_surface_get_height(png_surface_ptr) != gfxbuf_ptr->height) { bs_test_fail_at( test_ptr, fname_ptr, line, "gfxbuf height %u != expected height %d. " "Expected output: \"%s\", gfxbuf under test: \"%s\"", gfxbuf_ptr->width, cairo_image_surface_get_height(png_surface_ptr), png_fname_ptr, tested_gfxbuf_name_ptr); } if ((unsigned)cairo_image_surface_get_stride(png_surface_ptr) < gfxbuf_ptr->width * sizeof(uint32_t)) { bs_test_fail_at( test_ptr, fname_ptr, line, "gfxbuf bytes per line (%u * %zu) lower than PNG stride %d. " "Expected output: \"%s\", gfxbuf under test: \"%s\"", gfxbuf_ptr->width, sizeof(uint32_t), cairo_image_surface_get_stride(png_surface_ptr), png_fname_ptr, tested_gfxbuf_name_ptr); } if (!bs_test_failed(test_ptr)) { for (unsigned l = 0; l < gfxbuf_ptr->height; ++l) { if (0 != memcmp( bs_gfxbuf_pixel_at(gfxbuf_ptr, 0, l), cairo_image_surface_get_data(png_surface_ptr) + ( l * cairo_image_surface_get_stride(png_surface_ptr)), gfxbuf_ptr->width * sizeof(uint32_t))) { bs_test_fail_at( test_ptr, fname_ptr, line, "gfxbuf content at line %u differs from expected PNG. " "Expected output: \"%s\", gfxbuf under test: \"%s\"", l, png_fname_ptr, tested_gfxbuf_name_ptr); } } } cairo_surface_destroy(png_surface_ptr); if (!bs_test_failed(test_ptr)) return; cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(gfxbuf_ptr); if (NULL == cairo_ptr) { bs_log(BS_ERROR, "Failed cairo_create_from_bs_gfxbuf"); return; } cairo_surface_t *surface_ptr = cairo_get_target(cairo_ptr); if (NULL == cairo_ptr) { bs_log(BS_ERROR, "Failed cairo_get_target"); cairo_destroy(cairo_ptr); return; } cairo_status_t status = cairo_surface_write_to_png( surface_ptr, tested_gfxbuf_name_ptr); if (CAIRO_STATUS_SUCCESS != status) { bs_log(BS_ERROR, "Failed cairo_surface_write_to_png(%p, \"%s\")", surface_ptr, tested_gfxbuf_name_ptr); } cairo_destroy(cairo_ptr); } #endif // HAVE_CAIRO /* == Tests ================================================================ */ static void test_copy_area(bs_test_t *test_ptr); static void test_argb8888_to_floats(bs_test_t *test_ptr); #ifdef HAVE_CAIRO static void test_cairo(bs_test_t *test_ptr); static void test_equals_png(bs_test_t *test_ptr); #endif // HAVE_CAIRO /** Unit test cases. */ static const bs_test_case_t bs_gfxbuf_test_cases[] = { { true, "copy_area", test_copy_area }, { true, "argb8888_fo_floats", test_argb8888_to_floats }, #ifdef HAVE_CAIRO { true, "cairo", test_cairo }, { true, "equals_png", test_equals_png }, #endif // HAVE_CAIRO BS_TEST_CASE_SENTINEL(), }; const bs_test_set_t bs_gfxbuf_test_set = BS_TEST_SET( true, "gfxbuf", bs_gfxbuf_test_cases); /* ------------------------------------------------------------------------- */ void test_copy_area(bs_test_t *test_ptr) { bs_gfxbuf_t *buf1 = bs_gfxbuf_create(3, 3); bs_gfxbuf_clear(buf1, 0x10203040); bs_gfxbuf_t *buf2 = bs_gfxbuf_create(4, 4); BS_TEST_VERIFY_EQ(test_ptr, 0, *bs_gfxbuf_pixel_at(buf2, 1, 1)); bs_gfxbuf_copy_area(buf2, 1, 1, buf1, 1, 1, 3, 3); BS_TEST_VERIFY_EQ(test_ptr, 0, *bs_gfxbuf_pixel_at(buf2, 0, 0)); BS_TEST_VERIFY_EQ(test_ptr, 0X10203040, *bs_gfxbuf_pixel_at(buf2, 1, 1)); BS_TEST_VERIFY_EQ(test_ptr, 0X10203040, *bs_gfxbuf_pixel_at(buf2, 2, 2)); BS_TEST_VERIFY_EQ(test_ptr, 0, *bs_gfxbuf_pixel_at(buf2, 3, 3)); bs_gfxbuf_destroy(buf2); bs_gfxbuf_destroy(buf1); } /* ------------------------------------------------------------------------- */ /** Verifies that cairo_util_argb8888_to_floats behaves properly */ void test_argb8888_to_floats(bs_test_t *test_ptr) { float r, g, b, alpha; bs_gfxbuf_argb8888_to_floats(0, &r, &g, &b, &alpha); BS_TEST_VERIFY_EQ(test_ptr, 0, r); BS_TEST_VERIFY_EQ(test_ptr, 0, g); BS_TEST_VERIFY_EQ(test_ptr, 0, b); BS_TEST_VERIFY_EQ(test_ptr, 0, alpha); bs_gfxbuf_argb8888_to_floats(0xffffffff, &r, &g, &b, &alpha); BS_TEST_VERIFY_EQ(test_ptr, 1.0, r); BS_TEST_VERIFY_EQ(test_ptr, 1.0, g); BS_TEST_VERIFY_EQ(test_ptr, 1.0, b); BS_TEST_VERIFY_EQ(test_ptr, 1.0, alpha); // Floating point - we're fine with a near-enough value. bs_gfxbuf_argb8888_to_floats(0xffc08040, &r, &g, &b, NULL); BS_TEST_VERIFY_TRUE(test_ptr, 1e-3 > fabs(r - 0.7529)); BS_TEST_VERIFY_TRUE(test_ptr, 1e-3 > fabs(g - 0.5020)); BS_TEST_VERIFY_TRUE(test_ptr, 1e-3 > fabs(b - 0.2510)); } #ifdef HAVE_CAIRO /* ------------------------------------------------------------------------- */ void test_cairo(bs_test_t *test_ptr) { bs_gfxbuf_t *buf = bs_gfxbuf_create(1, 1); cairo_t *cairo_ptr = cairo_create_from_bs_gfxbuf(buf); BS_TEST_VERIFY_NEQ(test_ptr, NULL, cairo_ptr); // Color is 0 after initialization. BS_TEST_VERIFY_EQ(test_ptr, 0, *bs_gfxbuf_pixel_at(buf, 0, 0)); cairo_set_source_rgba(cairo_ptr, 1.0, 1.0, 1.0, 1.0); cairo_rectangle(cairo_ptr, 0, 0, 1, 1); cairo_fill(cairo_ptr); // Cairo should have filled it with white. BS_TEST_VERIFY_EQ(test_ptr, 0xffffffff, *bs_gfxbuf_pixel_at(buf, 0, 0)); cairo_destroy(cairo_ptr); bs_gfxbuf_destroy(buf); } /* ------------------------------------------------------------------------- */ void test_equals_png(bs_test_t *test_ptr) { bs_gfxbuf_t *buf = bs_gfxbuf_create(1, 1); bs_gfxbuf_clear(buf, 0xff804020); BS_TEST_VERIFY_GFXBUF_EQUALS_PNG(test_ptr, buf, "data/gfxbuf_equals.png"); bs_gfxbuf_destroy(buf); } #endif // HAVE_CAIRO /* == Benchmarks =========================================================== */ static void benchmark_clear(bs_test_t *test_ptr); static void benchmark_clear_nonblack(bs_test_t *test_ptr); static void benchmark_copy(bs_test_t *test_ptr); /* set benchmarks to last for 2.5s each */ static const uint64_t benchmark_duration = 2500000; /** Benchmark cases. */ static const bs_test_case_t bs_gfxbuf_benchmarks[] = { { true, "benchmark-gfxbuf_clear-black", benchmark_clear }, { true, "benchmark-gfxbuf_clear-nonblack", benchmark_clear_nonblack }, { true, "benchmark-gfxbuf_copy", benchmark_copy }, BS_TEST_CASE_SENTINEL(), }; const bs_test_set_t bs_gfxbuf_benchmarks_set = BS_TEST_SET( true, "bs_gfxbuf", bs_gfxbuf_benchmarks); /* ------------------------------------------------------------------------- */ static void benchmark_clear(bs_test_t *test_ptr) { bs_gfxbuf_t *buf_ptr = bs_gfxbuf_create(1024, 768); if (NULL == buf_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(1024, 768)"); return; } uint64_t usec = bs_usec(); unsigned iterations = 0; while (usec + benchmark_duration >= bs_usec()) { bs_gfxbuf_clear(buf_ptr, 0); iterations++; } usec = bs_usec() - usec; bs_test_succeed(test_ptr, "bs_gfxbuf_clear: %.3e pix/sec - %"PRIu64"us", (double)iterations * 1024 * 768 / (usec * 1e-6), usec); bs_gfxbuf_destroy(buf_ptr); } /* ------------------------------------------------------------------------- */ static void benchmark_clear_nonblack(bs_test_t *test_ptr) { bs_gfxbuf_t *buf_ptr = bs_gfxbuf_create(1024, 768); if (NULL == buf_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(1024, 768)"); return; } uint64_t usec = bs_usec(); unsigned iterations = 0; while (usec + benchmark_duration >= bs_usec()) { bs_gfxbuf_clear(buf_ptr, 0x204080ff); iterations++; } usec = bs_usec() - usec; bs_test_succeed(test_ptr, "bs_gfxbuf_clear: %.3e pix/sec - %"PRIu64"us", (double)iterations * 1024 * 768 / (usec * 1e-6), usec); bs_gfxbuf_destroy(buf_ptr); } /* ------------------------------------------------------------------------- */ static void benchmark_copy(bs_test_t *test_ptr) { bs_gfxbuf_t *buf_1_ptr = bs_gfxbuf_create(1024, 768); if (NULL == buf_1_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(1024, 768)"); return; } bs_gfxbuf_t *buf_2_ptr = bs_gfxbuf_create(1024, 768); if (NULL == buf_2_ptr) { BS_TEST_FAIL(test_ptr, "Failed bs_gfxbuf_create(1024, 768)"); bs_gfxbuf_destroy(buf_1_ptr); return; } uint64_t usec = bs_usec(); unsigned iterations = 0; while (usec + benchmark_duration >= bs_usec()) { bs_gfxbuf_copy(buf_2_ptr, buf_1_ptr); iterations++; } usec = bs_usec() - usec; bs_test_succeed(test_ptr, "bs_gfxbuf_copy: %.3e pix/sec", (double)iterations * 1024 * 768 / (usec * 1e-6)); bs_gfxbuf_destroy(buf_1_ptr); bs_gfxbuf_destroy(buf_2_ptr); } /* == End of gfxbuf.c ====================================================== */ wlmaker-0.8.1/submodules/libbase/src/gfxbuf_xpm.c0000644000175100017510000003646015222270712021464 0ustar runnerrunner/* ========================================================================= */ /** * @file gfxbuf_xpm.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** Node for storing & looking up colors from the XPM. */ typedef struct { /** Tree node. */ bs_avltree_node_t node; /** The characters that define this color. */ char *pixel_chars_ptr; /** Number of caracters that make up the pixel. */ unsigned chars_per_pixel; /** Corresponding color, in ARGB8888. */ uint32_t color; } bs_gfxbuf_xpm_color_node_t; static bool _bs_gfxbuf_xpm_copy_data( bs_gfxbuf_t *gfxbuf_ptr, char **xpm_data_ptr, unsigned dest_x, unsigned dest_y); static bool _bs_gfxbuf_xpm_parse_header_line( const char *header_line_ptr, unsigned *width_ptr, unsigned *height_ptr, unsigned *colors_ptr, unsigned *chars_per_pixel_ptr); static bool _bs_gfxbuf_xpm_parse_color_into_node( bs_gfxbuf_xpm_color_node_t *node_ptr, unsigned chars_per_pixel, const char* color_line_ptr); static bs_gfxbuf_xpm_color_node_t *_bs_gfxbuf_xpm_color_node_create( unsigned chars_per_pixel); static void _bs_gfxbuf_xpm_color_node_destroy( bs_avltree_node_t *node_ptr); static int _bs_gfxbuf_xpm_color_node_cmp( const bs_avltree_node_t *node_ptr, const void *key_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bs_gfxbuf_t *bs_gfxbuf_xpm_create_from_data(char **xpm_data_ptr) { unsigned width, height, colors, chars_per_pixel; if (!_bs_gfxbuf_xpm_parse_header_line( *xpm_data_ptr, &width, &height, &colors, &chars_per_pixel)) { return NULL; } bs_gfxbuf_t *gfxbuf_ptr = bs_gfxbuf_create(width, height); if (_bs_gfxbuf_xpm_copy_data(gfxbuf_ptr, xpm_data_ptr, 0, 0)) { return gfxbuf_ptr; } bs_gfxbuf_destroy(gfxbuf_ptr); return NULL; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** * Loads an XPM from included data at `xpm_data_ptr` to the position * `dest_x`, `dest_y` into `gfxbuf_ptr`. Will not overwrite pixels where the * XPM is transparent (color: None). * * @param gfxbuf_ptr * @param xpm_data_ptr * @param dest_x * @param dest_y * * @return true on success. */ bool _bs_gfxbuf_xpm_copy_data( bs_gfxbuf_t *gfxbuf_ptr, char **xpm_data_ptr, unsigned dest_x, unsigned dest_y) { unsigned width, height, colors, chars_per_pixel; bs_gfxbuf_xpm_color_node_t *color_node_ptr; // Guard clause. if (NULL == gfxbuf_ptr) return false; if (!_bs_gfxbuf_xpm_parse_header_line( *xpm_data_ptr, &width, &height, &colors, &chars_per_pixel)) { return false; } // Prevent the XPM from spilling beyond the buffer. if (width + dest_x > gfxbuf_ptr->width) { width = gfxbuf_ptr->width - dest_x; } if (height + dest_y > gfxbuf_ptr->height) { height = gfxbuf_ptr->height - dest_y; } xpm_data_ptr++; // Parse the colors and store htem in the lookup tree. bs_avltree_t *tree_ptr = bs_avltree_create( _bs_gfxbuf_xpm_color_node_cmp, _bs_gfxbuf_xpm_color_node_destroy); if (NULL == tree_ptr) { bs_log(BS_ERROR, "Failed bs_avltree_create"); return false; } for (unsigned color = 0; color < colors; ++color, ++xpm_data_ptr) { color_node_ptr = _bs_gfxbuf_xpm_color_node_create(chars_per_pixel); if (NULL != color_node_ptr) { if (_bs_gfxbuf_xpm_parse_color_into_node( color_node_ptr, chars_per_pixel, *xpm_data_ptr)) { if (bs_avltree_insert( tree_ptr, color_node_ptr->pixel_chars_ptr, &color_node_ptr->node, false)) { continue; } bs_log(BS_ERROR, "Color \"%s\" already exists", *xpm_data_ptr); } _bs_gfxbuf_xpm_color_node_destroy(&color_node_ptr->node); } bs_avltree_destroy(tree_ptr); return false; } // Now, parse the XPM pixels. bool outcome = true; for (unsigned y = 0; y < height; ++y, ++xpm_data_ptr) { if (width * chars_per_pixel > strlen(*xpm_data_ptr)) { bs_log(BS_ERROR, "Shorter than %u chars: \"%s\"", width * chars_per_pixel, *xpm_data_ptr); outcome = false; break; } for (unsigned x = 0; x < width; ++x) { bs_gfxbuf_xpm_color_node_t *color_node_ptr = (bs_gfxbuf_xpm_color_node_t*)bs_avltree_lookup( tree_ptr, *xpm_data_ptr + x * chars_per_pixel); // Ignore transparent pixels. if (color_node_ptr->color != 0x00000000) { bs_gfxbuf_set_pixel( gfxbuf_ptr, dest_x + x, dest_y + y, color_node_ptr->color); } } } bs_avltree_destroy(tree_ptr); return outcome; } /* ------------------------------------------------------------------------- */ /** Parses the XPM header line. */ bool _bs_gfxbuf_xpm_parse_header_line( const char *header_line_ptr, unsigned *width_ptr, unsigned *height_ptr, unsigned *colors_ptr, unsigned *chars_per_pixel_ptr) { int scanned_values; scanned_values = sscanf( header_line_ptr, "%u %u %u %u", width_ptr, height_ptr, colors_ptr, chars_per_pixel_ptr); if (0 > scanned_values) { bs_log(BS_ERROR | BS_ERRNO, "Failed sscanf(%s, \"%%u %%u %%u %%u\")", header_line_ptr); return false; } if (4 != scanned_values) { bs_log(BS_ERROR, "Failed to match & assign 4 input values for " "sscanf(%s, \"%%u %%u %%u %%u\")", header_line_ptr); return false; } return true; } /* ------------------------------------------------------------------------- */ /** * Parses an XPM color line into `color_node_ptr`. * * It has the format ( )+. */ bool _bs_gfxbuf_xpm_parse_color_into_node( bs_gfxbuf_xpm_color_node_t *color_node_ptr, unsigned chars_per_pixel, const char* color_line_ptr) { // Sanity check: String must at least hold the color. if (chars_per_pixel > strlen(color_line_ptr)) { bs_log(BS_ERROR, "shorter than %u chars: \"%s\"", chars_per_pixel, color_line_ptr); return false; } // Copy the component. BS_ASSERT(color_node_ptr->chars_per_pixel == chars_per_pixel); memcpy(color_node_ptr->pixel_chars_ptr, color_line_ptr, chars_per_pixel); // Then, skip over it and over succeeding whitespace. color_line_ptr += chars_per_pixel; if (!isspace(*color_line_ptr)) { bs_log(BS_ERROR, "Whitespace missing after "); return false; } while (*color_line_ptr && isspace(*color_line_ptr)) color_line_ptr++; // Check . We only support 'c' for "color". if ('c' != *color_line_ptr) { bs_log(BS_ERROR, "Unsupported color representation: \"%s\"", color_line_ptr); return false; } // Again, skip over it and over succeeding whitespace. color_line_ptr++; while (*color_line_ptr && isspace(*color_line_ptr)) color_line_ptr++; // The . If it's "None", it means the pixel is transparent. if (0 == strncmp(color_line_ptr, "None", 4)) { color_node_ptr->color = 0; return true; } // If the start with '#', it is a RGB representation. if (*color_line_ptr == '#') { size_t alnum_count = 0; while (isalnum(color_line_ptr[1 + alnum_count])) ++alnum_count; if (6 != alnum_count) { bs_log(BS_ERROR, "Not a #RRGGBB representation: %s", color_line_ptr + 1); return false; } uint64_t tmp_value; if (!bs_strconvert_uint64(color_line_ptr + 1, &tmp_value, 16)) { return false; } if (tmp_value > 0xffffff) { bs_log(BS_ERROR, "Color value out of range: 0x%"PRIx64, tmp_value); return false; } color_node_ptr->color = tmp_value | 0xff000000; return true; } bs_log(BS_ERROR, "Unsupported color encoding: %s", color_line_ptr + 1); return false; } /* ------------------------------------------------------------------------- */ bs_gfxbuf_xpm_color_node_t *_bs_gfxbuf_xpm_color_node_create( unsigned chars_per_pixel) { bs_gfxbuf_xpm_color_node_t *color_node_ptr = calloc( 1, sizeof(bs_gfxbuf_xpm_color_node_t)); if (NULL == color_node_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed calloc(1, %zu)", sizeof(bs_gfxbuf_xpm_color_node_t)); return NULL; } color_node_ptr->pixel_chars_ptr = calloc(1, chars_per_pixel); if (NULL == color_node_ptr->pixel_chars_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed calloc(1, %u)", chars_per_pixel); _bs_gfxbuf_xpm_color_node_destroy(&color_node_ptr->node); return NULL; } color_node_ptr->chars_per_pixel = chars_per_pixel; return color_node_ptr; } /* ------------------------------------------------------------------------- */ void _bs_gfxbuf_xpm_color_node_destroy(bs_avltree_node_t *node_ptr) { bs_gfxbuf_xpm_color_node_t *color_node_ptr = (bs_gfxbuf_xpm_color_node_t*)node_ptr; if (NULL != color_node_ptr->pixel_chars_ptr) { free(color_node_ptr->pixel_chars_ptr); color_node_ptr->pixel_chars_ptr = NULL; } free(color_node_ptr); } /* ------------------------------------------------------------------------- */ int _bs_gfxbuf_xpm_color_node_cmp(const bs_avltree_node_t *node_ptr, const void *key_ptr) { bs_gfxbuf_xpm_color_node_t *color_node_ptr = (bs_gfxbuf_xpm_color_node_t*)node_ptr; return memcmp(color_node_ptr->pixel_chars_ptr, (const char*)key_ptr, color_node_ptr->chars_per_pixel); } /* == Unit tests =========================================================== */ static void test_parse_color(bs_test_t *test_ptr); static void test_parse_xpm(bs_test_t *test_ptr); static void test_create_xpm(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_gfxbuf_xpm_test_cases[] = { { true, "parse_color", test_parse_color }, { true, "parse_xpm", test_parse_xpm }, { true, "create_xpm", test_create_xpm }, { false, NULL, NULL } }; const bs_test_set_t bs_gfxbuf_xpm_test_set = BS_TEST_SET( true, "gfxbuf_xpm", bs_gfxbuf_xpm_test_cases); static char *test_xpm_data[] = { "2 2 3 1", " c None", ". c #0000ff", "+ c #000000", ".+", "+ "}; /* ------------------------------------------------------------------------- */ void test_parse_color(bs_test_t *test_ptr) { bs_gfxbuf_xpm_color_node_t cnode; char pixel_chars[2]; cnode.pixel_chars_ptr = &pixel_chars[0]; cnode.chars_per_pixel = 2; BS_TEST_VERIFY_TRUE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "xy c #123456")); BS_TEST_VERIFY_EQ(test_ptr, 0xff123456, cnode.color); BS_TEST_VERIFY_EQ(test_ptr, 'x', cnode.pixel_chars_ptr[0]); BS_TEST_VERIFY_EQ(test_ptr, 'y', cnode.pixel_chars_ptr[1]); BS_TEST_VERIFY_TRUE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "xy c #ffffff")); BS_TEST_VERIFY_EQ(test_ptr, 0xffffffff, cnode.color); BS_TEST_VERIFY_EQ(test_ptr, 'x', cnode.pixel_chars_ptr[0]); BS_TEST_VERIFY_EQ(test_ptr, 'y', cnode.pixel_chars_ptr[1]); BS_TEST_VERIFY_TRUE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "ab c None")); BS_TEST_VERIFY_EQ(test_ptr, cnode.color, 0x00000000); BS_TEST_VERIFY_EQ(test_ptr, 'a', cnode.pixel_chars_ptr[0]); BS_TEST_VERIFY_EQ(test_ptr, 'b', cnode.pixel_chars_ptr[1]); BS_TEST_VERIFY_TRUE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "a c None")); BS_TEST_VERIFY_EQ(test_ptr, cnode.color, 0x00000000); BS_TEST_VERIFY_EQ(test_ptr, 'a', cnode.pixel_chars_ptr[0]); BS_TEST_VERIFY_EQ(test_ptr, ' ', cnode.pixel_chars_ptr[1]); BS_TEST_VERIFY_FALSE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "t c None")); BS_TEST_VERIFY_FALSE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "abc c None")); BS_TEST_VERIFY_FALSE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "ab c #12345")); BS_TEST_VERIFY_FALSE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "ab c #1234567")); BS_TEST_VERIFY_FALSE( test_ptr, _bs_gfxbuf_xpm_parse_color_into_node(&cnode, 2, "ab c #12xx56")); } /* ------------------------------------------------------------------------- */ void test_parse_xpm(bs_test_t *test_ptr) { bs_gfxbuf_t *buf_ptr = bs_gfxbuf_create(3, 3); bs_gfxbuf_clear(buf_ptr, 42); BS_TEST_VERIFY_TRUE( test_ptr, _bs_gfxbuf_xpm_copy_data(buf_ptr, test_xpm_data, 1, 1)); BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 0, 0)); BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 1, 0)); BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 2, 0)); BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 0, 1)); BS_TEST_VERIFY_EQ(test_ptr, 0xff0000ff, *bs_gfxbuf_pixel_at(buf_ptr, 1, 1)); BS_TEST_VERIFY_EQ(test_ptr, 0xff000000, *bs_gfxbuf_pixel_at(buf_ptr, 2, 1)); BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 0, 2)); BS_TEST_VERIFY_EQ(test_ptr, 0xff000000, *bs_gfxbuf_pixel_at(buf_ptr, 1, 2)); BS_TEST_VERIFY_EQ(test_ptr, 42, *bs_gfxbuf_pixel_at(buf_ptr, 2, 2)); bs_gfxbuf_destroy(buf_ptr); } /* ------------------------------------------------------------------------- */ void test_create_xpm(bs_test_t *test_ptr) { bs_gfxbuf_t *buf_ptr; buf_ptr = bs_gfxbuf_xpm_create_from_data(test_xpm_data); BS_TEST_VERIFY_NEQ(test_ptr, NULL, buf_ptr); if (buf_ptr != NULL) { BS_TEST_VERIFY_EQ(test_ptr, 2, buf_ptr->width); BS_TEST_VERIFY_EQ(test_ptr, 2, buf_ptr->height); bs_gfxbuf_destroy(buf_ptr); } } /* == End of gfxbuf_xpm.c ================================================== */ wlmaker-0.8.1/submodules/libbase/src/file.c0000644000175100017510000003646615222270712020244 0ustar runnerrunner/* ========================================================================= */ /** * @file file.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libbase/log_wrappers.h" /* == Declarations ========================================================= */ #if defined __linux__ static int _bs_file_walk_compare( const FTSENT **a, const FTSENT **b); #else static int _bs_file_walk_compare( const FTSENT * const *a, const FTSENT * const *b); #endif /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ ssize_t bs_file_read_buffer( const char *fname_ptr, char *buf_ptr, size_t buf_len) { ssize_t read_bytes; int fd; fd = open(fname_ptr, O_RDONLY); if (0 > fd) { bs_log(BS_WARNING | BS_ERRNO, "Failed open(%s, O_RDONLY)", fname_ptr); return -1; } read_bytes = read(fd, buf_ptr, buf_len); if (0 > read_bytes) { bs_log(BS_WARNING | BS_ERRNO, "Failed read(%d, %p, %zu) from %s", fd, buf_ptr, buf_len - 1, fname_ptr); } if ((size_t)read_bytes >= buf_len) { bs_log(BS_WARNING | BS_ERRNO, "Read %zd >= %zu bytes. Too much data in %s", read_bytes, buf_len, fname_ptr); read_bytes = -1; } else { buf_ptr[read_bytes] = '\0'; } if (0 != close(fd)) { bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d) for %s", fd, fname_ptr); return -1; } return read_bytes; } /* ------------------------------------------------------------------------- */ ssize_t bs_file_write_buffer( const char *fname_ptr, const char *buf_ptr, size_t buf_len) { ssize_t written_bytes; int fd; fd = open(fname_ptr, O_WRONLY); if (0 > fd) { bs_log(BS_WARNING | BS_ERRNO, "Failed open(%s, O_WRONLY)", fname_ptr); return -1; } written_bytes = write(fd, buf_ptr, buf_len); if (0 > written_bytes) { bs_log(BS_ERROR | BS_ERRNO, "Falied write(%d, %p, %zu) to %s", fd, buf_ptr, buf_len, fname_ptr); } else if ((size_t)written_bytes != buf_len) { bs_log(BS_ERROR, "Incomplete write(%d, %p, %zu): %zd bytes to %s", fd, buf_ptr, buf_len, written_bytes, fname_ptr); written_bytes = -1; } if (0 != close(fd)) { bs_log(BS_WARNING | BS_ERRNO, "Failed close(%d) for %s", fd, fname_ptr); return -1; } return written_bytes; } /* ------------------------------------------------------------------------- */ char *bs_file_resolve_path( const char *path_ptr, char *resolved_path_buf_ptr) { char expanded_path[PATH_MAX]; if (bs_str_startswith(path_ptr, "~/")) { const char *home_dir_ptr = getenv("HOME"); if (NULL == home_dir_ptr) { bs_log(BS_WARNING, "Failed getenv(\"HOME\") for path %s", path_ptr); } else { size_t i = bs_strappend(expanded_path, PATH_MAX, 0, home_dir_ptr); i = bs_strappend(expanded_path, PATH_MAX, i, "/"); i = bs_strappend(expanded_path, PATH_MAX, i, path_ptr + 2); if (i >= PATH_MAX) { errno = ENAMETOOLONG; return NULL; } path_ptr = &expanded_path[0]; } } return realpath(path_ptr, resolved_path_buf_ptr); } /* ------------------------------------------------------------------------- */ char *bs_file_join_resolve_path( const char *path_ptr, const char *fname_ptr, char *resolved_path_buf_ptr) { char path[PATH_MAX]; size_t pos = bs_strappend(path, PATH_MAX, 0, path_ptr); pos = bs_strappend(path, PATH_MAX, pos, "/"); pos = bs_strappend(path, PATH_MAX, pos, fname_ptr); if (pos > PATH_MAX) { errno = ENAMETOOLONG; return NULL; } return bs_file_resolve_path(path, resolved_path_buf_ptr); } /* ------------------------------------------------------------------------- */ char *bs_file_resolve_and_lookup_from_paths( const char *fname_ptr, const char **paths_ptr_ptr, int mode, char *resolved_path_buf_ptr) { for (; NULL != *paths_ptr_ptr; ++paths_ptr_ptr) { char *resolved_path_ptr = bs_file_join_resolve_path( *paths_ptr_ptr, fname_ptr, resolved_path_buf_ptr); if (NULL == resolved_path_ptr) continue; // Found something and not needed to check type? We have it. if (0 == mode) return resolved_path_ptr; // Otherwise, verify type. struct stat stat_buf; if (0 == stat(resolved_path_ptr, &stat_buf)) { if ((stat_buf.st_mode & S_IFMT) == (mode & S_IFMT)) { return resolved_path_ptr; } } if (NULL == resolved_path_buf_ptr) free(resolved_path_ptr); } return NULL; } /* ------------------------------------------------------------------------- */ bool bs_file_mkdir_p(const char *dirname_ptr, int mode) { if (0 == mkdir(dirname_ptr, mode)) return true; if (ENOENT != errno) { bs_log(BS_ERROR | BS_ERRNO, "Failed mkdir(\"%s\", %o)", dirname_ptr, mode); return false; } // A parent does not exist. Attempt to create the parent, then try again. char *parent_dirname_ptr = logged_strdup(dirname_ptr); if (NULL == parent_dirname_ptr) return false; bool rv = bs_file_mkdir_p(dirname(parent_dirname_ptr), mode); free(parent_dirname_ptr); if (!rv) return false; return 0 == mkdir(dirname_ptr, mode); } /* ------------------------------------------------------------------------- */ bool bs_file_realpath_is(const char *fname_ptr, int mode_type) { char *resolved_fname_ptr = realpath(fname_ptr, NULL); if (NULL == resolved_fname_ptr) { if (ENOENT != errno) { bs_log(BS_ERROR, "Failed realpath(\"%s\", NULL)", fname_ptr); } return false; } struct stat stat_buf; bool rv = (0 == stat(resolved_fname_ptr, &stat_buf)); if (rv) { rv = ((stat_buf.st_mode & S_IFMT) == (mode_t)mode_type); } else if (ENOENT != errno) { bs_log(BS_ERROR, "Failed stat(\"%s\", %p)", resolved_fname_ptr, &stat_buf); } free(resolved_fname_ptr); return rv; } /* ------------------------------------------------------------------------- */ bool bs_file_walk_tree( const char *path_ptr, const char *pattern_ptr, int mode_type, bool sorted, bool (*callback)(const char *, const FTSENT *, void *), void *ud_ptr) { char *const paths[2] = { (char*)path_ptr, NULL }; FTS *fts_ptr = fts_open( paths, FTS_PHYSICAL, sorted ? _bs_file_walk_compare : NULL); if (NULL == fts_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed fts_open({\"%s\", NULL}, FTS_PHYSICAL, NULL)", path_ptr); return false; } FTSENT *ftsent_ptr = NULL; bool rv = true; while (NULL != (ftsent_ptr = fts_read(fts_ptr))) { // Restrict to files & directories. Omit the FTS_DP post-pass, as it // would make directories show up twice. if (ftsent_ptr->fts_info != FTS_D && ftsent_ptr->fts_info != FTS_DEFAULT && ftsent_ptr->fts_info != FTS_SL && ftsent_ptr->fts_info != FTS_F) continue; // Restrict to requested mode, if given. if (0 != mode_type && (ftsent_ptr->fts_statp->st_mode & S_IFMT) != (mode_t)mode_type) { continue; } // Optional pattern match. if (NULL != pattern_ptr && 0 != fnmatch(pattern_ptr, ftsent_ptr->fts_name, 0)) continue; char *resolved_path_ptr = realpath(ftsent_ptr->fts_accpath, NULL); if (NULL == resolved_path_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed realpath(\"%s\", NULL)", ftsent_ptr->fts_accpath); rv = false; break; } rv &= callback(resolved_path_ptr, ftsent_ptr, ud_ptr); free(resolved_path_ptr); if (!rv) break; } fts_close(fts_ptr); return rv; } /* == Static (local) functions ============================================= */ /** Comparator to fts_open() (see fts(3)). Used when sorting is requested. */ #if defined __linux__ static int _bs_file_walk_compare( const FTSENT **a, const FTSENT **b) #else static int _bs_file_walk_compare( const FTSENT * const *a, const FTSENT * const *b) #endif { return strcmp((*a)->fts_name, (*b)->fts_name); } /* == Test Functions ======================================================= */ static void test_resolve_path(bs_test_t *test_ptr); static void test_join_resolve_path(bs_test_t *test_ptr); static void test_lookup(bs_test_t *test_ptr); static void test_mkdir_p(bs_test_t *test_ptr); static void test_realpath_is(bs_test_t *test_ptr); static void test_walk_tree(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_file_test_cases[] = { { true, "resolve_path", test_resolve_path }, { true, "join_resolve_path", test_join_resolve_path }, { true, "lookup", test_lookup }, { true, "mkdir_p", test_mkdir_p }, { true, "realpath_is", test_realpath_is }, { true, "walk_tree", test_walk_tree }, { false, NULL, NULL } // sentinel. }; const bs_test_set_t bs_file_test_set = BS_TEST_SET( true, "file", bs_file_test_cases); /* ------------------------------------------------------------------------- */ void test_resolve_path(bs_test_t *test_ptr) { char *p; p = bs_file_resolve_path("/etc/../etc/passwd", NULL); BS_TEST_VERIFY_STREQ(test_ptr, "/etc/passwd", p); free(p); char path[PATH_MAX]; p = bs_file_resolve_path("/etc/../etc/passwd", path); BS_TEST_VERIFY_STREQ(test_ptr, "/etc/passwd", p); BS_TEST_VERIFY_EQ(test_ptr, p, &path[0]); p = bs_file_resolve_path("~/", path); BS_TEST_VERIFY_NEQ(test_ptr, NULL, p); } /* ------------------------------------------------------------------------- */ void test_join_resolve_path(bs_test_t *test_ptr) { char *p; p = bs_file_join_resolve_path("/etc/../etc", "passwd", NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, p); free(p); } /* ------------------------------------------------------------------------- */ void test_lookup(bs_test_t *test_ptr) { const char *paths[] = { NULL, NULL, NULL }; char path[PATH_MAX]; char *p; #if defined(__LIBBASE_LINUX) // The '/proc/self' moniker works only on Linux. paths[0] = "/anywhere"; char self_path[PATH_MAX]; p = realpath("/proc/self/exe", self_path); BS_TEST_VERIFY_NEQ(test_ptr, NULL, p); paths[1] = dirname(p); p = bs_file_resolve_and_lookup_from_paths( "libbase_test", paths, 0, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, p); free(p); p = bs_file_resolve_and_lookup_from_paths( "libbase_test", paths, S_IFBLK, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, p); p = bs_file_resolve_and_lookup_from_paths( "does_not_exist", paths, 0, NULL); BS_TEST_VERIFY_EQ(test_ptr, NULL, p); // Lookup into provided |path| arg. p = bs_file_resolve_and_lookup_from_paths( "libbase_test", paths, 0, path); BS_TEST_VERIFY_NEQ(test_ptr, NULL, p); p = bs_file_resolve_and_lookup_from_paths( "libbase_test", paths, S_IFREG, path); BS_TEST_VERIFY_NEQ(test_ptr, NULL, p); #endif // __LIBBASE_LINUX paths[0] = "~/"; paths[1] = NULL; p = bs_file_resolve_and_lookup_from_paths( "", paths, S_IFDIR, path); BS_TEST_VERIFY_NEQ(test_ptr, NULL, p); } /* ------------------------------------------------------------------------- */ void test_mkdir_p(bs_test_t *test_ptr) { const char *dirname_ptr = bs_test_temp_path(test_ptr, "a/b"); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, dirname_ptr); // The temp dir was just created: First attempt must succeed. BS_TEST_VERIFY_TRUE(test_ptr, bs_file_mkdir_p(dirname_ptr, 0700)); // Second attempt already finds the directories, must fail. BS_TEST_VERIFY_FALSE(test_ptr, bs_file_mkdir_p(dirname_ptr, 0700)); rmdir(dirname_ptr); rmdir(bs_test_temp_path(test_ptr, "a")); } /* ------------------------------------------------------------------------- */ void test_realpath_is(bs_test_t *test_ptr) { const char *p = bs_test_temp_path(test_ptr, NULL); BS_TEST_VERIFY_TRUE(test_ptr, bs_file_realpath_is(p, S_IFDIR)); BS_TEST_VERIFY_FALSE(test_ptr, bs_file_realpath_is(p, S_IFREG)); char *fn = bs_strdupf("%s/a", p); BS_TEST_VERIFY_NEQ_OR_RETURN(test_ptr, NULL, fn); BS_TEST_VERIFY_FALSE(test_ptr, bs_file_realpath_is(fn, S_IFDIR)); BS_TEST_VERIFY_FALSE(test_ptr, bs_file_realpath_is(fn, S_IFREG)); int fd = creat(fn, 0600); close(fd); BS_TEST_VERIFY_TRUE(test_ptr, bs_file_realpath_is(fn, S_IFREG)); unlink(fn); free(fn); } /* ------------------------------------------------------------------------- */ /** Argument to @ref _test_walk_cb. */ struct _test_walk_arg { /** Number of calls. */ size_t count; /** Unit test. */ bs_test_t *test_ptr; }; /** Mirrors sorted contents of all plist files in the test directory. */ static const char* const _test_walk_files[] = { "array.plist", "dict.plist", "string.plist" }; /** Callback for @ref bs_file_walk_tree. */ bool _test_walk_cb(const char *path_ptr, const FTSENT *ftsent_ptr, void *ud_ptr) { struct _test_walk_arg *arg_ptr = ud_ptr; if (arg_ptr->count >= (sizeof(_test_walk_files) / sizeof(char*))) { return false; } int rv = strcmp(_test_walk_files[arg_ptr->count], ftsent_ptr->fts_name); if (rv) { BS_TEST_FAIL(arg_ptr->test_ptr, "Expected file \"%s\", found \"%s\" at %zu", path_ptr, ftsent_ptr->fts_name, arg_ptr->count); } ++arg_ptr->count; return 0 == rv; } /** Tests @ref bs_file_walk_tree. */ void test_walk_tree(bs_test_t *test_ptr) { struct _test_walk_arg arg = { .test_ptr = test_ptr }; BS_TEST_VERIFY_TRUE( test_ptr, bs_file_walk_tree(bs_test_data_path(test_ptr, "/"), "*.plist", 0, // no particular mode. true, _test_walk_cb, &arg)); BS_TEST_VERIFY_EQ( test_ptr, sizeof(_test_walk_files) / sizeof(char*), arg.count); } /* == End of file.c ======================================================== */ wlmaker-0.8.1/submodules/libbase/src/thread.c0000644000175100017510000001057615222270712020566 0ustar runnerrunner/* ========================================================================= */ /** * @file thread.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bool bs_mutex_init(pthread_mutex_t *mutex_ptr) { int rv = pthread_mutex_init(mutex_ptr, NULL); if (0 != rv) { errno = rv; bs_log(BS_ERROR | BS_ERRNO, "Failed pthread_mutex_init(%p, NULL)", mutex_ptr); return false; } return true; } /* ------------------------------------------------------------------------- */ void bs_mutex_destroy(pthread_mutex_t *mutex_ptr) { int rv = pthread_mutex_destroy(mutex_ptr); if (0 != rv) { errno = rv; bs_log(BS_ERROR | BS_ERRNO, "Failed pthread_mutex_destroy(%p)", mutex_ptr); BS_ABORT(); } } /* ------------------------------------------------------------------------- */ void bs_mutex_lock(pthread_mutex_t *mutex_ptr) { int rv = pthread_mutex_lock(mutex_ptr); if (0 != rv) { errno = rv; bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_mutex_lock(%p)", mutex_ptr); BS_ABORT(); } } /* ------------------------------------------------------------------------- */ void bs_mutex_unlock(pthread_mutex_t *mutex_ptr) { int rv = pthread_mutex_unlock(mutex_ptr); if (0 != rv) { errno = rv; bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_mutex_unlock(%p)", mutex_ptr); BS_ABORT(); } } /* ------------------------------------------------------------------------- */ bool bs_cond_init(pthread_cond_t *condition_ptr) { int rv = pthread_cond_init(condition_ptr, NULL); if (0 != rv) { errno = rv; bs_log(BS_ERROR | BS_ERRNO, "Failed pthread_cond_init(%p)", condition_ptr); return false; } return true; } /* ------------------------------------------------------------------------- */ void bs_cond_destroy(pthread_cond_t *condition_ptr) { int rv = pthread_cond_destroy(condition_ptr); if (0 != rv) { errno = rv; bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_cond_destroy(%p)", condition_ptr); BS_ABORT(); } } /* ------------------------------------------------------------------------- */ void bs_cond_broadcast(pthread_cond_t *condition_ptr) { int rv = pthread_cond_broadcast(condition_ptr); if (0 != rv) { errno = rv; bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_cond_broadcast(%p)", condition_ptr); BS_ABORT(); } } /* ------------------------------------------------------------------------- */ bool bs_cond_timedwait(pthread_cond_t *condition_ptr, pthread_mutex_t *mutex_ptr, uint64_t usec) { struct timeval tv; struct timespec ts; int rv; if (0 != gettimeofday(&tv, NULL)) { bs_log(BS_FATAL | BS_ERRNO, "Failed gettimeofday(%p, NULL)", &tv); BS_ABORT(); } ts.tv_sec = tv.tv_sec + (tv.tv_usec + usec) / 1000000; ts.tv_nsec = ((tv.tv_usec + usec) % 1000000) * 1000; rv = pthread_cond_timedwait(condition_ptr, mutex_ptr, &ts); if (0 != rv && ETIMEDOUT != rv) { errno = rv; bs_log(BS_FATAL | BS_ERRNO, "Failed pthread_cond_timedwait(%p, %p, %"PRIu64")", condition_ptr, mutex_ptr, usec); BS_ABORT(); } return rv == 0; } /* == End of thread.c ====================================================== */ wlmaker-0.8.1/submodules/libbase/src/ref.c0000644000175100017510000000433615222270712020070 0ustar runnerrunner/* ========================================================================= */ /** * @file ref.c * Copyright (c) 2026 Philipp Kaeser */ #include #include /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ void bs_ref_init(bs_ref_t *ref_ptr, void (*destroy_fn)(bs_ref_t *ref_ptr)) { *ref_ptr = (bs_ref_t){ .count = 1, .destroy_fn = destroy_fn }; } /* ------------------------------------------------------------------------- */ void bs_ref_retain(bs_ref_t *ref_ptr) { ++ref_ptr->count; } /* ------------------------------------------------------------------------- */ void bs_ref_release(bs_ref_t *ref_ptr) { if (0 < --ref_ptr->count) return; BS_ASSERT(0 == ref_ptr->count); ref_ptr->destroy_fn(ref_ptr); } /* == Unit tests =========================================================== */ static void bs_ref_test(bs_test_t *test_ptr); /** Test cases. */ static const bs_test_case_t bs_ref_test_cases[] = { { true, "ref", bs_ref_test }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bs_ref_test_set = BS_TEST_SET( true, "ref", bs_ref_test_cases); /* ------------------------------------------------------------------------- */ /** State used in tests. */ struct _bs_ref_test { /** Number of calls to dtor. */ int dtor_calls; /** Reference state. */ bs_ref_t ref; }; /** Dtor used in tests. */ static void _bs_ref_test_dtor(bs_ref_t *r) { struct _bs_ref_test *t = BS_CONTAINER_OF(r, struct _bs_ref_test, ref); ++t->dtor_calls; } /** Simple test, for ref and unref. */ void bs_ref_test(bs_test_t *test_ptr) { struct _bs_ref_test t = {}; bs_ref_init(&t.ref, _bs_ref_test_dtor); BS_TEST_VERIFY_EQ(test_ptr, 1, t.ref.count); bs_ref_retain(&t.ref); BS_TEST_VERIFY_EQ(test_ptr, 2, t.ref.count); bs_ref_release(&t.ref); BS_TEST_VERIFY_EQ(test_ptr, 1, t.ref.count); BS_TEST_VERIFY_EQ(test_ptr, 0, t.dtor_calls); bs_ref_release(&t.ref); BS_TEST_VERIFY_EQ(test_ptr, 0, t.ref.count); BS_TEST_VERIFY_EQ(test_ptr, 1, t.dtor_calls); } /* == End of ref.c ========================================================= */ wlmaker-0.8.1/submodules/libbase/src/atomic.c0000644000175100017510000000776015222270712020574 0ustar runnerrunner/* ========================================================================= */ /** * @file atomic.c * * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #if defined(__cplusplus) || defined(__clang__) const bs_test_set_t bs_atomic_test_set = BS_TEST_SET(false, NULL, NULL); #else // defined(__cplusplus) || !defined(__clang__) /* == Atomic tests ========================================================= */ static void bs_atomic_test_int32(bs_test_t *test_ptr); static void bs_atomic_test_int64(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_atomic_test_cases[] = { { true, "int32 unit tests", bs_atomic_test_int32 }, { true, "int64 unit tests", bs_atomic_test_int64 }, { false, NULL, NULL } }; const bs_test_set_t bs_atomic_test_set = BS_TEST_SET( true, "atomic", bs_atomic_test_cases); /* ------------------------------------------------------------------------- */ void bs_atomic_test_int32(bs_test_t *test_ptr) { bs_atomic_int32_t a = BS_ATOMIC_INT32_INIT(42); int32_t b; BS_TEST_VERIFY_EQ(test_ptr, 42, bs_atomic_int32_get(&a)); bs_atomic_int32_set(&a, 27972); BS_TEST_VERIFY_EQ(test_ptr, 27972, bs_atomic_int32_get(&a)); BS_TEST_VERIFY_EQ(test_ptr, 27900, bs_atomic_int32_add(&a, -72)); BS_TEST_VERIFY_EQ(test_ptr, 27900, bs_atomic_int32_get(&a)); b = 1234; bs_atomic_int32_xchg(&a, &b); BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int32_get(&a)); BS_TEST_VERIFY_EQ(test_ptr, 27900, b); // CAS, but with non-matching old_val. Must not swap. BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int32_cas(&a, 4321, 2222)); BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int32_get(&a)); // CAS, with matching old_val. Must swap. BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int32_cas(&a, 4321, 1234)); BS_TEST_VERIFY_EQ(test_ptr, 4321, bs_atomic_int32_get(&a)); } /* ------------------------------------------------------------------------- */ void bs_atomic_test_int64(bs_test_t *test_ptr) { bs_atomic_int64_t a = BS_ATOMIC_INT64_INIT(0x0102030405060708); int64_t b; BS_TEST_VERIFY_EQ(test_ptr, 0x0102030405060708, bs_atomic_int64_get(&a)); bs_atomic_int64_set(&a, 0x0807060504030201); BS_TEST_VERIFY_EQ(test_ptr, 0x0807060504030201, bs_atomic_int64_get(&a)); BS_TEST_VERIFY_EQ(test_ptr, 0x1827364554637281, bs_atomic_int64_add(&a, 0x1020304050607080)); BS_TEST_VERIFY_EQ(test_ptr, 0x1827364554637281, bs_atomic_int64_get(&a)); b = 1234; bs_atomic_int64_xchg(&a, &b); BS_TEST_VERIFY_EQ(test_ptr, 1234, bs_atomic_int64_get(&a)); BS_TEST_VERIFY_EQ(test_ptr, 0x1827364554637281, b); // CAS, but with non-matching old_val. Must not swap. bs_atomic_int64_set(&a, 0x0807060504030201); BS_TEST_VERIFY_EQ(test_ptr, 0x0807060504030201, bs_atomic_int64_cas(&a, 0x1122334455667788, 2222)); BS_TEST_VERIFY_EQ(test_ptr, 0x0807060504030201, bs_atomic_int64_get(&a)); // CAS, with matching old_val. Must swap. BS_TEST_VERIFY_EQ(test_ptr, 0x0807060504030201, bs_atomic_int64_cas(&a, 0x1122334455667788, 0x0807060504030201)); BS_TEST_VERIFY_EQ(test_ptr, 0x1122334455667788, bs_atomic_int64_get(&a)); } #endif /* == End of atomic.c ===================================================== */ wlmaker-0.8.1/submodules/libbase/src/ptr_stack.c0000644000175100017510000001543615222270712021311 0ustar runnerrunner/* ========================================================================= */ /** * @file ptr_stack.c * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include /* == Declarations ========================================================= */ /** Initial stack size, 1024 elements. */ #define INITIAL_SIZE 1024 static bool grow_stack(bs_ptr_stack_t *ptr_stack_ptr); /* == Exported methods ===================================================== */ /* ------------------------------------------------------------------------- */ bs_ptr_stack_t *bs_ptr_stack_create(void) { bs_ptr_stack_t *ptr_stack_ptr = logged_calloc(1, sizeof(bs_ptr_stack_t)); if (NULL == ptr_stack_ptr) return NULL; if (!bs_ptr_stack_init(ptr_stack_ptr)) { bs_ptr_stack_destroy(ptr_stack_ptr); return NULL; } return ptr_stack_ptr; } /* ------------------------------------------------------------------------- */ void bs_ptr_stack_destroy(bs_ptr_stack_t *ptr_stack_ptr) { bs_ptr_stack_fini(ptr_stack_ptr); free(ptr_stack_ptr); } /* ------------------------------------------------------------------------- */ bool bs_ptr_stack_init(bs_ptr_stack_t *ptr_stack_ptr) { ptr_stack_ptr->pos = 0; ptr_stack_ptr->size = 0; ptr_stack_ptr->data_ptr = NULL; return grow_stack(ptr_stack_ptr); } /* ------------------------------------------------------------------------- */ void bs_ptr_stack_fini(bs_ptr_stack_t *ptr_stack_ptr) { if (NULL != ptr_stack_ptr->data_ptr) { if (0 < ptr_stack_ptr->pos) { bs_log(BS_WARNING, "Destroying non-empty ptr_stack at %p", ptr_stack_ptr); } free(ptr_stack_ptr->data_ptr); ptr_stack_ptr->data_ptr = NULL; } ptr_stack_ptr->size = 0; ptr_stack_ptr->pos = 0; } /* ------------------------------------------------------------------------- */ bool bs_ptr_stack_push(bs_ptr_stack_t *ptr_stack_ptr, void *elem_ptr) { BS_ASSERT(NULL != elem_ptr); if (ptr_stack_ptr->pos + 1 >= ptr_stack_ptr->size) { if (!grow_stack(ptr_stack_ptr)) return false; } ptr_stack_ptr->data_ptr[ptr_stack_ptr->pos++] = elem_ptr; return true; } /* ------------------------------------------------------------------------- */ void *bs_ptr_stack_pop(bs_ptr_stack_t *ptr_stack_ptr) { if (0 >= ptr_stack_ptr->pos) return NULL; return ptr_stack_ptr->data_ptr[--ptr_stack_ptr->pos]; } /* ------------------------------------------------------------------------- */ void *bs_ptr_stack_peek(bs_ptr_stack_t *ptr_stack_ptr, size_t index) { if (index >= ptr_stack_ptr->pos) return NULL; return ptr_stack_ptr->data_ptr[ptr_stack_ptr->pos - index - 1]; } /* == Local (static) methods =============================================== */ /* ------------------------------------------------------------------------- */ /** Grows the stack by INITIAL_SIZE elements. */ bool grow_stack(bs_ptr_stack_t *ptr_stack_ptr) { size_t new_size = ptr_stack_ptr->size + INITIAL_SIZE; void *new_data_ptr = realloc( ptr_stack_ptr->data_ptr, new_size * sizeof(void*)); if (NULL == new_data_ptr) { bs_log(BS_ERROR | BS_ERRNO, "Failed realloc(%p, %zu)", ptr_stack_ptr->data_ptr, new_size * sizeof(void*)); return false; } ptr_stack_ptr->size = new_size; ptr_stack_ptr->data_ptr = new_data_ptr; return true; } /* == Unit tests =========================================================== */ static void basic_test(bs_test_t *test_ptr); static void large_test(bs_test_t *test_ptr); /** Unit test cases. */ static const bs_test_case_t bs_ptr_stack_test_cases[] = { { true, "basic", basic_test }, { true, "large", large_test }, BS_TEST_CASE_SENTINEL() }; const bs_test_set_t bs_ptr_stack_test_set = BS_TEST_SET( true, "ptr_stack", bs_ptr_stack_test_cases); /* ------------------------------------------------------------------------- */ /** Basic functionality: init, empty pop, push, pop, empty pop, fini. */ void basic_test(bs_test_t *test_ptr) { bs_ptr_stack_t ptr_stack; static void *elem_ptr = (void*)basic_test; // Just anything, actually. BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_stack_init(&ptr_stack)); BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_ptr_stack_pop(&ptr_stack)); BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_stack_push(&ptr_stack, elem_ptr)); BS_TEST_VERIFY_EQ(test_ptr, elem_ptr, bs_ptr_stack_peek(&ptr_stack, 0)); BS_TEST_VERIFY_EQ(test_ptr, elem_ptr, bs_ptr_stack_pop(&ptr_stack)); BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_ptr_stack_pop(&ptr_stack)); bs_ptr_stack_fini(&ptr_stack); } /* ------------------------------------------------------------------------- */ /** Basic functionality: init, empty pop, push, pop, empty pop, fini. */ void large_test(bs_test_t *test_ptr) { bs_ptr_stack_t ptr_stack; char elem[2 * INITIAL_SIZE]; BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_stack_init(&ptr_stack)); for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) { BS_TEST_VERIFY_TRUE(test_ptr, bs_ptr_stack_push(&ptr_stack, &elem[i])); } for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) { BS_TEST_VERIFY_EQ( test_ptr, &elem[2 * INITIAL_SIZE - 1 - i], bs_ptr_stack_peek(&ptr_stack, i)); } BS_TEST_VERIFY_EQ( test_ptr, NULL, bs_ptr_stack_peek(&ptr_stack, 2 * INITIAL_SIZE)); BS_TEST_VERIFY_EQ( test_ptr, NULL, bs_ptr_stack_peek(&ptr_stack, 2 * INITIAL_SIZE + 1)); for (size_t i = 0; i < 2 * INITIAL_SIZE; ++i) { void *ptr = bs_ptr_stack_pop(&ptr_stack); if (&elem[2 * INITIAL_SIZE - 1 - i] != ptr) { bs_log(BS_ERROR, "%zu: (expected) %p != %p (stacked)", i, &elem[2 * INITIAL_SIZE - 1 - i], ptr); } BS_TEST_VERIFY_EQ( test_ptr, &elem[2 * INITIAL_SIZE - 1 - i], ptr); // bs_ptr_stack_pop(&ptr_stack)); } BS_TEST_VERIFY_EQ(test_ptr, NULL, bs_ptr_stack_pop(&ptr_stack)); bs_ptr_stack_fini(&ptr_stack); } /* == End of ptr_stack.c =================================================== */ wlmaker-0.8.1/submodules/libbase/src/CMakeLists.txt0000644000175100017510000000421115222270712021700 0ustar runnerrunner# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set(public_header_files arg.h assert.h atomic.h avltree.h def.h dequeue.h dllist.h dynbuf.h file.h gfxbuf.h gfxbuf_xpm.h libbase.h log.h log_wrappers.h ptr_set.h ptr_stack.h ptr_vector.h ref.h sock.h strutil.h subprocess.h test.h thread.h time.h vector.h) set(sources arg.c atomic.c avltree.c dequeue.c dllist.c dynbuf.c file.c gfxbuf.c gfxbuf_xpm.c log.c ptr_set.c ptr_stack.c ptr_vector.c ref.c sock.c strutil.c subprocess.c test.c thread.c time.c) add_library(libbase STATIC) target_sources(libbase PRIVATE ${sources}) target_include_directories( libbase PUBLIC $ $) target_include_directories(libbase PRIVATE "${CURSES_INCLUDE_DIRS}") target_link_libraries(libbase PRIVATE "${CURSES_LIBRARIES}" libbase_compiler_flags) set_target_properties( libbase PROPERTIES VERSION 1.0 PUBLIC_HEADER "${public_header_files}" ) if(iwyu_path_and_options) set_target_properties( libbase PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() if(CAIRO_FOUND) target_compile_definitions(libbase PUBLIC HAVE_CAIRO) target_include_directories(libbase PUBLIC ${CAIRO_INCLUDE_DIRS}) target_link_libraries(libbase PUBLIC PkgConfig::CAIRO) endif() # Add 'install' target, but only if we're the toplevel project. if(CMAKE_PROJECT_NAME STREQUAL libbase) install( TARGETS libbase LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libbase") endif() wlmaker-0.8.1/submodules/libbase/libbase.pc.in0000644000175100017510000000156215222270712020711 0ustar runnerrunner# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ Name: @PROJECT_NAME@ Description: @PROJECT_DESCRIPTION@ Version: @PROJECT_VERSION@ Requires: Libs: -L${libdir} -l@PROJECT_NAME@ Cflags: -I${includedir} wlmaker-0.8.1/submodules/libbase/LICENSE0000644000175100017510000002613515222270712017367 0ustar runnerrunner Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.wlmaker-0.8.1/submodules/libbase/include/0000755000175100017510000000000015222270712017776 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/include/libbase/0000755000175100017510000000000015222270712021377 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/include/libbase/assert.h0000644000175100017510000000413115222270712023050 0ustar runnerrunner/* ========================================================================= */ /** * @file assert.h * Assertions. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_ASSERT_H__ #define __LIBBASE_ASSERT_H__ #include "log.h" #include #include #if defined(__GNUC__) && (defined(i386) || defined(__x86_64__)) #define BS_ABORT() { __asm __volatile ( "int $3;" ); } #else #include /** Triggers an abort, ie. fatal error. */ #define BS_ABORT() { raise(SIGTRAP); } #endif #if defined(BS_ASSERT) #undef BS_ASSERT #endif /** An assertion, triggers a fatal error if `_expr` is false. */ #define BS_ASSERT(_expr) do { \ if (!(_expr)) { \ bs_log(BS_FATAL, "ASSERT failed: %s", #_expr); \ BS_ABORT(); \ } \ } while (0) /** Asserts that _expr is not NULL, and returns the value of _expr. */ #define BS_ASSERT_NOTNULL(_expr) \ ({ \ __typeof__(_expr) __expr = (_expr); \ BS_ASSERT(NULL != __expr); \ __expr; \ }) #endif /* __LIBBASE_ASSERT_H__ */ /* == End of assert.h ====================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/dynbuf.h0000644000175100017510000001233715222270712023045 0ustar runnerrunner/* ========================================================================= */ /** * @file dynbuf.h * * @copyright * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * Copyright (c) 2025 by Philipp Kaeser */ #ifndef __DYNBUF_H__ #define __DYNBUF_H__ #include #include #include "test.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** A dynamically growing buffer. Useful for reading input. */ typedef struct { /** Points to the data area. */ void *data_ptr; /** Current length of actual data. */ size_t length; /** Capacity of the buffer. */ size_t capacity; /** Max permitted capacity of the buffer. */ size_t max_capacity; /** Whether it was initialized from an unmanaged data buffer. */ bool unmanaged; } bs_dynbuf_t; /** * Initializes the buffer. * * @param dynbuf_ptr * @param initial_capacity * @param max_capacity * * @return true on success. */ bool bs_dynbuf_init( bs_dynbuf_t *dynbuf_ptr, size_t initial_capacity, size_t max_capacity); /** * Initializes the buffer from an unowned and statically-sized data. * * @param dynbuf_ptr * @param data_ptr Must outlive dynbuf_ptr. * @param capacity */ void bs_dynbuf_init_unmanaged( bs_dynbuf_t *dynbuf_ptr, void *data_ptr, size_t capacity); /** * Un-initializes the buffer. Frees @ref bs_dynbuf_t::data_ptr. * * @param dynbuf_ptr */ void bs_dynbuf_fini(bs_dynbuf_t *dynbuf_ptr); /** * Allocates a buffer. Calls into @ref bs_dynbuf_init. * * @param initial_capacity * @param max_capacity * * @return Pointer to a @ref bs_dynbuf_t. Must be released by calling * @ref bs_dynbuf_destroy. */ bs_dynbuf_t *bs_dynbuf_create( size_t initial_capacity, size_t max_capacity); /** * Destroys the dynamic buffer. * * @param dynbuf_ptr */ void bs_dynbuf_destroy(bs_dynbuf_t *dynbuf_ptr); /** * Grows the dynamic buffer. Doubles current capacity. * * @param dynbuf_ptr * * @return true on success. */ bool bs_dynbuf_grow(bs_dynbuf_t *dynbuf_ptr); /** @return whether the buffer is full. */ bool bs_dynbuf_full(bs_dynbuf_t *dynbuf_ptr); /** * Clears the buffer's contents: Resets length. * * @param dynbuf_ptr */ void bs_dynbuf_clear(bs_dynbuf_t *dynbuf_ptr); /** * Reads from the file descriptor into the dynamic buffer. * * Grows the buffer as needed. Reads until reaching the end of the file, or * (in case of a non-blocking socket descriptor) until no more data is * currently available. * * @param dynbuf_ptr * @param fd * * @return 0 if having reached the end of the file, 1 the end of the file was * not reached yet, and -1 on error. */ int bs_dynbuf_read(bs_dynbuf_t *dynbuf_ptr, int fd); /** * Safely writes the dynamic buffer into the file at `fname_ptr`. * * First, creates a temporary file and writes the buffer contents there. If * that succeeds, checks if the target exists. If it exists and is writable, * creates a backup file by appending a ".old" extension, and removes any * already-existing backup flie. Then, it replaces the target atomically with * the temporary file. * If the base directory at `fname_ptr` does not exist yet, it will attempt to * create the parent hierarchy with @ref bs_file_mkdir_p and `mode` | S_IXUSR. * * * @param dynbuf_ptr * @param fname_ptr * @param mode * * @return true on success. */ bool bs_dynbuf_write_file( bs_dynbuf_t *dynbuf_ptr, const char *fname_ptr, int mode); /** * Appends data to the buffer. * * @param dynbuf_ptr * @param data_ptr * @param len * * @return true on success. */ bool bs_dynbuf_append( bs_dynbuf_t *dynbuf_ptr, const void *data_ptr, size_t len); /** * Appends a char to the buffer. * * @param dynbuf_ptr * @param c * * @return true on success. */ bool bs_dynbuf_append_char( bs_dynbuf_t *dynbuf_ptr, char c); /** * Conditionally appends a character. * * @param dynbuf_ptr * @param condition * @param c * * @return true on success. */ static inline bool bs_dynbuf_maybe_append_char( bs_dynbuf_t *dynbuf_ptr, bool condition, char c) { return (!condition || bs_dynbuf_append_char(dynbuf_ptr, c)); } static inline bool bs_dynbuf_maybe_indent( bs_dynbuf_t *dynbuf_ptr, bool condition, size_t indent) { if (!condition) return true; while (indent--) { if (!bs_dynbuf_append_char(dynbuf_ptr, ' ')) return false; } return true; } /** Unit test set. */ extern const bs_test_set_t bs_dynbuf_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __DYNBUF_H__ */ /* == End of dynbuf.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/subprocess.h0000644000175100017510000001217415222270712023745 0ustar runnerrunner/* ========================================================================= */ /** * @file subprocess.h * Methods to conveniently create sub-processes and handle I/O in a non- * blocking fashion. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_SUBPROCESS_H__ #define __LIBBASE_SUBPROCESS_H__ #include "test.h" #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Handle for a sub-process */ typedef struct _bs_subprocess_t bs_subprocess_t; /** Descriptor for an environment variable. */ typedef struct { /** Name for the environment variable. */ const char *name_ptr; /** Value of the environment variable. */ const char *value_ptr; } bs_subprocess_environment_variable_t; /** * Creates a sub-process. Does not start it. * * @param file_ptr Name of the executable file. The subprocess module will * invoke execvp() for execution, hence this will lookup the executable * from PATHs as the shell would. * @param argv_ptr Pointer to a NULL-terminated list of args. Like execvp(), * the first element of argv_ptr should point to the filename associated * with the file being executed. * @param env_vars_ptr Points to an array of @ref bs_subprocess_environment_variable_t * describing additional environment variables to set for the sub-process. * The array must be concluded with a sentinel element, where key_ptr is * NULL. `env_vars_ptr` may be NULL to indicate no envinroment variables * need to be set. * * @return A handle to the subprocess, to be cleaned by calling * |bs_subprocess_destroy|. */ bs_subprocess_t *bs_subprocess_create( const char *file_ptr, const char *const *argv_ptr, const bs_subprocess_environment_variable_t *env_vars_ptr); /** * Creates a sub-process. Does not start it. * * @param cmdline_ptr Commandline to execute. Will be tokenized. The leading * tokens may be environment variables. The first token thereafter is * is expected to identify an executable file; the rest of the tokens * will be handled as arguments. * * @return A handle to the subprocess, to be cleaned by calling * |bs_subprocess_destroy|. */ bs_subprocess_t *bs_subprocess_create_cmdline( const char *cmdline_ptr); /** * Destroys a sub-process. Will stop (|bs_subprocess_stop|), if still running. * * @param subprocess_ptr */ void bs_subprocess_destroy(bs_subprocess_t *subprocess_ptr); /** * Starts the sub-process. * * A started sub-process can be stopped by calling |bs_subprocess_stop| (and * then can be started again), or by |bs_subprocess_destroy|. * * @param subprocess_ptr * * @return Whether the call succeeded. */ bool bs_subprocess_start(bs_subprocess_t *subprocess_ptr); /** * Stops the sub-process. Will SIGKILL the process, if not terminated * already. * * @param subprocess_ptr */ void bs_subprocess_stop(bs_subprocess_t *subprocess_ptr); /** * Checks whether |subprocess_ptr| has terminated, and provide the information * in |*exit_status_ptr|, respectively |*signal_number_ptr|. * * @param subprocess_ptr * @param exit_status_ptr If the process terminated normally, this will hold * the exit status of the program. It will be INT_MIN, if the process * terminated abnormally. * @param signal_number_ptr If the process terminated abnormally, this will * hold the number of the signal that caused the abnormal termination. A * value of 0 indicates normal termination. * * @return Whether the process has terminated. */ bool bs_subprocess_terminated(bs_subprocess_t *subprocess_ptr, int *exit_status_ptr, int *signal_number_ptr); /** * Retrieves the parent's file descriptors for stdin, stdout and stderr. * * @param subprocess_ptr * @param stdin_write_fd_ptr May be NULL. * @param stdout_read_fd_ptr May be NULL. * @param stderr_read_fd_ptr May be NULL. */ void bs_subprocess_get_fds(bs_subprocess_t *subprocess_ptr, int *stdin_write_fd_ptr, int *stdout_read_fd_ptr, int *stderr_read_fd_ptr); /** * Returns the PID of the given subprocess. Will be 0 if not started or * terminated. * * @param subprocess_ptr * * @return PID, or 0. */ pid_t bs_subprocess_pid(bs_subprocess_t *subprocess_ptr); /** Unit test set. */ extern const bs_test_set_t bs_subprocess_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_SUBPROCESS_H__ */ /* == End of subprocess.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/plist.h0000644000175100017510000000255615222270712022713 0ustar runnerrunner/* ========================================================================= */ /** * @file plist.h * * Basic C library to parse Property List files, stored in text format. * * See https://en.wikipedia.org/wiki/Property_list for details. This library * supports basic types (String, Array, Dictionnary) only -- the Date and * Data types are (currently) missing. * * @copyright * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_PLIST_H__ #define __LIBBASE_PLIST_H__ #ifdef __cplusplus extern "C" { #endif // __cplusplus // IWYU pragma: begin_exports #include "plist/decode.h" #include "plist/model.h" #include "plist/parse.h" // IWYU pragma: end_exports #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_PLIST_H__ */ /* == End of plist.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/dllist.h0000644000175100017510000001345615222270712023054 0ustar runnerrunner/* ========================================================================= */ /** * @file dllist.h * Provides a double-linked list. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_DLLIST_H__ #define __LIBBASE_DLLIST_H__ #include "test.h" #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** A doubly-linked list. */ typedef struct _bs_dllist_t bs_dllist_t; /** A node in a doubly-linked list. */ typedef struct _bs_dllist_node_t bs_dllist_node_t; /** Details of the list. */ struct _bs_dllist_t { /** Head of the double-linked list. NULL if empty. */ bs_dllist_node_t *head_ptr; /** Tail of the double-linked list. NULL if empty. */ bs_dllist_node_t *tail_ptr; }; /** Details of said node. */ struct _bs_dllist_node_t { /** The previous node, or NULL if this is the only node. */ bs_dllist_node_t *prev_ptr; /** The next node, or NULL if this is the only node. */ bs_dllist_node_t *next_ptr; }; /** Returns the number of elements in |list_ptr|. */ size_t bs_dllist_size(const bs_dllist_t *list_ptr); /** Returns whether the list is empty. */ bool bs_dllist_empty(const bs_dllist_t *list_ptr); /** Adds |node_ptr| at the back of |list_ptr|. */ void bs_dllist_push_back(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr); /** Adds |node_ptr| at the front of |list_ptr|. */ void bs_dllist_push_front(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr); /** Retrieves the node from the back of |list_ptr|, or NULL if empty. */ bs_dllist_node_t *bs_dllist_pop_back(bs_dllist_t *list_ptr); /** Retrieves the node from the front of |list_ptr|, or NULL if empty. */ bs_dllist_node_t * bs_dllist_pop_front(bs_dllist_t *list_ptr); /** Removes the node from the list. */ void bs_dllist_remove(bs_dllist_t *list_ptr, bs_dllist_node_t *node_ptr); /** Inserts |new_node_ptr| into the list, before |reference_node_ptr|. */ void bs_dllist_insert_node_before( bs_dllist_t *list_ptr, bs_dllist_node_t *reference_node_ptr, bs_dllist_node_t *new_node_ptr); /** Appends `appendix_ptr` to `list_ptr`. */ void bs_dllist_append(bs_dllist_t *list_ptr, bs_dllist_t *appendix_ptr); /** Returns whether |list_ptr| contains |dlnode_ptr|. */ bool bs_dllist_contains( const bs_dllist_t *list_ptr, bs_dllist_node_t *dlnode_ptr); /** * Returns the node for which |func()| is true. * * @param list_ptr * @param func * @param ud_ptr * * @return A pointer to the corresponding @ref bs_dllist_node_t or NULL. */ bs_dllist_node_t *bs_dllist_find( const bs_dllist_t *list_ptr, bool (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr); /** * Runs |func()| for each of the nodes in |list_ptr|. * * The list iterator is kept safe, so it is permitted to remove the called-back * node from the list: Useful for eg. destroying all list elements. * * @param list_ptr * @param func * @param ud_ptr */ void bs_dllist_for_each( const bs_dllist_t *list_ptr, void (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr); /** * Runs |func()| and returns whether it is true for all nodes in |list_ptr|. * * Returns on the first |func()| call that returned false. * * @param list_ptr * @param func * @param ud_ptr * * @return Whether |func()| was true for all, or if |list_ptr| was empty. */ bool bs_dllist_all( const bs_dllist_t *list_ptr, bool (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr); /** * Runs |func()| and returns whether it is true for any node in |list_ptr|. * * Returns on the first |func()| call that returned true. * * @param list_ptr * @param func * @param ud_ptr * * @return Whether |func()| was true for for any. false if|list_ptr| was empty. */ bool bs_dllist_any( const bs_dllist_t *list_ptr, bool (*func)(bs_dllist_node_t *dlnode_ptr, void *ud_ptr), void *ud_ptr); /** * Sorts the list using the provided `compare_fn`. * * @param list_ptr * @param compare_fn Similar to strcmp(3). */ void bs_dllist_sort( bs_dllist_t *list_ptr, int (*compare_fn)(const bs_dllist_node_t *, const bs_dllist_node_t*)); /** * Defines an iterator for the node. * * @param dlnode_ptr * * @return The next node from iterating, or NULL if there are no more. */ typedef bs_dllist_node_t *(*bs_dllist_node_iterator_t)( bs_dllist_node_t *dlnode_ptr); /** * NULL-safe forward iterator: Returns `dlnode_ptr->next_ptr`. * * @param dlnode_ptr * * @return @ref bs_dllist_node_t::next_ptr for `dlnode_ptr` or NULL if * dlnode_ptr is NULL. */ static inline bs_dllist_node_t *bs_dllist_node_iterator_forward( bs_dllist_node_t *dlnode_ptr) { return dlnode_ptr ? dlnode_ptr->next_ptr : dlnode_ptr; } /** * NULL-safe backward iterator: Returns `dlnode_ptr->prev_ptr`. * * @param dlnode_ptr * * @return @ref bs_dllist_node_t::prev_ptr for `dlnode_ptr` or NULL if * dlnode_ptr is NULL. */ static inline bs_dllist_node_t *bs_dllist_node_iterator_backward( bs_dllist_node_t *dlnode_ptr) { return dlnode_ptr ? dlnode_ptr->prev_ptr : dlnode_ptr; } /** Unit test set. */ extern const bs_test_set_t bs_dllist_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_DLLIST_H__ */ /* == End of dllist.h ====================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/log.h0000644000175100017510000000602715222270712022336 0ustar runnerrunner/* ========================================================================= */ /** * @file log.h * Logging library, to stderr or a log file. Used for verbose error reporting. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_LOG_H__ #define __LIBBASE_LOG_H__ #include "def.h" #include "test.h" #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Maximum size of one log message, excluding terminating NUL. */ #define BS_LOG_MAX_BUF_SIZE 4096 /** Severity for logging. */ typedef enum { BS_DEBUG = 0, BS_INFO = 1, BS_WARNING = 2, BS_ERROR = 3, /** Always log. No matter the current severity. And: abort(). */ BS_FATAL = 4, /** Can be OR-ed to above values, to report strerror & errno. */ BS_ERRNO = 0x80 } bs_log_severity_t; /** Current severity for logging. Only equal-or-higher severity is logged. */ extern bs_log_severity_t bs_log_severity; /** Actually write a log message. Helper to bs_log. */ void bs_log_write(bs_log_severity_t severity, const char *file_name_ptr, int line_num, const char *fmt_ptr, ...) __ARG_PRINTF__(4, 5); /** Same as bs_log_write(), but with va_list arg. */ void bs_log_vwrite(bs_log_severity_t severity, const char *file_name_ptr, int line_num, const char *fmt_ptr, va_list ap) __ARG_PRINTF__(4, 0); /** Initialize logging system to write to |log_filename_ptr| at |severity|. */ bool bs_log_init_file(const char *log_filename_ptr, bs_log_severity_t severity); /** Returns whether log outut will happen for `severity`. */ static inline bool bs_will_log(bs_log_severity_t severity) { return ((severity & 0x7f) >= bs_log_severity || (severity & 0x7f) == BS_FATAL); } /** Write a log message, at specified severity. */ #define bs_log(_severity, ...) { \ bs_log_severity_t _tmp_sev = (_severity); \ if (bs_will_log(_tmp_sev)) { \ bs_log_write(_tmp_sev, __FILE__, __LINE__, __VA_ARGS__); \ } \ } /** Unit testset. */ extern const bs_test_set_t bs_log_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_LOG_H__ */ /* == End of log.h ========================================================= */ wlmaker-0.8.1/submodules/libbase/include/libbase/strutil.h0000644000175100017510000001125615222270712023263 0ustar runnerrunner/* ========================================================================= */ /** * @file strutil.h * Utility functions for working with strings in C. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_STRUTIL_H__ #define __LIBBASE_STRUTIL_H__ #include #include #include #include #include "def.h" #include "test.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Appends a formatted string to `buf` at `buf_pos`, respecting `buf_size`. * * @param buf Points to the buffer destined to hold the result. * The formatted string will be written to * `&buf[buf_pos]`. * @param buf_size Size of `buf`. The written bytes, inculding the * terminating NUL character, will not exceed * `buf_size`. * @param buf_pos Position where to write to, within `buf`. Use * this for conveniently chaining `strappendf`. * @param fmt_ptr Format string. See printf for documentation. * @param ... Further arguments. * * @return The position of where the trailining NUL character was written to. * The number of written characters is `buf_size` minus the return value. * If the buffer was too small for holding all output (including the NUL * terminator), the return value will be larger or equal than buf_size. * It is undefined by how much larger the return value is. */ size_t bs_strappendf( char *buf, size_t buf_size, size_t buf_pos, const char *fmt_ptr, ...) __ARG_PRINTF__(4, 5); /** Same as @ref bs_strappendf, with a va_list argument. */ size_t bs_vstrappendf( char *buf, size_t buf_size, size_t buf_pos, const char *fmt_ptr, va_list ap) __ARG_PRINTF__(4, 0); /** Appends a non-formatted string. See @ref bs_strappendf for arguments. */ size_t bs_strappend( char *buf, size_t buf_size, size_t buf_pos, const char *str_ptr); /** * Converts a uint64_t from |string_ptr| with |base| and stores it in * |value_ptr|. * * The string is considered valid if it is fully consumed, or if the conversion * ends parsing at a whitespace character. Unlike stroull(3), a leading minus * sign is detected and returned as failure. * * @param string_ptr * @param value_ptr * @param base * * @return true on success. */ bool bs_strconvert_uint64( const char *string_ptr, uint64_t *value_ptr, int base); /** * Converts a int64_t from |string_ptr| with |base| and stores it in * |value_ptr|. * * The string is considered valid if it is fully consumed, or if the conversion * ends parsing at a whitespace character. * * @param string_ptr * @param value_ptr * @param base * * @return true on success. */ bool bs_strconvert_int64( const char *string_ptr, int64_t *value_ptr, int base); /** * Converts a double from |string_ptr| and stores it in |value_ptr|. * * The string is considered valid if it is fully consumed, or if the conversion * ends parsing at a whitespace character. * * @param string_ptr * @param value_ptr * * @return true on success */ bool bs_strconvert_double( const char *string_ptr, double *value_ptr); /** @return Whether `string_ptr` starts with `prefix_ptr`. */ bool bs_str_startswith(const char *string_ptr, const char *prefix_ptr); /** * Creates a new string holding the format string and outputs. * * @param fmt_ptr * @param ... Further arguments. * * @return A pointer to a string of appropriate size, including terminating NUL * character. Must be released by calling free(). Upon error, NULL is * returned. */ char *bs_strdupf(const char *fmt_ptr, ...) __ARG_PRINTF__(1, 2); /** Same as @ref bs_strdupf, but with a `va_list` argument. */ char *bs_vstrdupf(const char *fmt_ptr, va_list ap) __ARG_PRINTF__(1, 0); /** Test set. */ extern const bs_test_set_t bs_strutil_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_STRUTIL_H__ */ /* == End of strutil.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/dequeue.h0000644000175100017510000000605315222270712023211 0ustar runnerrunner/* ========================================================================= */ /** * @file dequeue.h * Provides a double-ended queue. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_DEQUEUE_H__ #define __LIBBASE_DEQUEUE_H__ #include #include "assert.h" #include "test.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** A double-ended queue. */ typedef struct _bs_dequeue_t bs_dequeue_t; /** A node in a double-ended queue. */ typedef struct _bs_dequeue_node_t bs_dequeue_node_t; /** Details of the queue. */ struct _bs_dequeue_t { /** Head of the queue. */ bs_dequeue_node_t *head_ptr; /** Tail of the queue. */ bs_dequeue_node_t *tail_ptr; }; /** Details of the node. */ struct _bs_dequeue_node_t { /** Next node. */ bs_dequeue_node_t *next_ptr; }; /** Pushes |node_ptr| to the front of the double-ended |queue_ptr|. */ static inline void bs_dequeue_push_front( bs_dequeue_t *queue_ptr, bs_dequeue_node_t *node_ptr) { node_ptr->next_ptr = queue_ptr->head_ptr; queue_ptr->head_ptr = node_ptr; if (NULL == queue_ptr->tail_ptr) { BS_ASSERT(NULL == node_ptr->next_ptr); queue_ptr->tail_ptr = node_ptr; } } /** Pushes |node_ptr| to the back the double-ended |queue_ptr|. */ static inline void bs_dequeue_push_back( bs_dequeue_t *queue_ptr, bs_dequeue_node_t *node_ptr) { node_ptr->next_ptr = NULL; if (NULL != queue_ptr->tail_ptr) { BS_ASSERT(NULL == queue_ptr->tail_ptr->next_ptr); queue_ptr->tail_ptr->next_ptr = node_ptr; } else { BS_ASSERT(NULL == queue_ptr->head_ptr); queue_ptr->head_ptr = node_ptr; } queue_ptr->tail_ptr = node_ptr; } /** Pops the (head) node of the double-ended queue at |queue_ptr|. */ static inline bs_dequeue_node_t *bs_dequeue_pop(bs_dequeue_t *queue_ptr) { bs_dequeue_node_t *node_ptr = queue_ptr->head_ptr; if (NULL == queue_ptr->head_ptr) { BS_ASSERT(NULL == queue_ptr->tail_ptr); } else { queue_ptr->head_ptr = queue_ptr->head_ptr->next_ptr; if (queue_ptr->tail_ptr == node_ptr) { BS_ASSERT(NULL == queue_ptr->head_ptr); queue_ptr->tail_ptr = NULL; } node_ptr->next_ptr = NULL; } return node_ptr; } /** Unit tests set. */ extern const bs_test_set_t bs_dequeue_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_DEQUEUE_H__ */ /* == End of dequeue.h ===================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/ptr_vector.h0000644000175100017510000000500215222270712023734 0ustar runnerrunner/* ========================================================================= */ /** * @file ptr_vector.h * * Interface for a simple vector to store pointers. * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_PTR_VECTOR_H__ #define __LIBBASE_PTR_VECTOR_H__ #include #include #include "test.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** State of a vector that stores pointers. */ typedef struct { /** Current capacity of the vector. Required for re-sizing. */ size_t capacity; /** Currently consumed capacity of the bector. */ size_t consumed; /** The elements. */ void **elements_ptr; } bs_ptr_vector_t; /** * Initializes the vector. * * @param ptr_vector_ptr * * @return true on success. */ bool bs_ptr_vector_init(bs_ptr_vector_t *ptr_vector_ptr); /** * Un-initializes the vector. * * @param ptr_vector_ptr */ void bs_ptr_vector_fini(bs_ptr_vector_t *ptr_vector_ptr); /** @return the size of the vector, ie. @ref bs_ptr_vector_t::consumed. */ size_t bs_ptr_vector_size(bs_ptr_vector_t *ptr_vector_ptr); /** * Adds `data_ptr` at the end of the vector. * * @param ptr_vector_ptr * @param data_ptr * * @return true on success. */ bool bs_ptr_vector_push_back(bs_ptr_vector_t *ptr_vector_ptr, void *data_ptr); /** * Erases the element at pos. * * @param ptr_vector_ptr * @param pos * * @return true if pos was valid. */ bool bs_ptr_vector_erase(bs_ptr_vector_t *ptr_vector_ptr, size_t pos); /** @return the element at `pos`. It must be `pos` < size. */ void* bs_ptr_vector_at(bs_ptr_vector_t *ptr_vector_ptr, size_t pos); /** Unit test set. */ extern const bs_test_set_t bs_ptr_vector_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_PTR_VECTOR_H__ */ /* == End of ptr_vector.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/def.h0000644000175100017510000000462015222270712022310 0ustar runnerrunner/* ========================================================================= */ /** * @file def.h * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_DEF_H__ #define __LIBBASE_DEF_H__ // For offsetof. #include /* == Definitions for arguments et.al. ===================================== */ #if !defined(__UNUSED__) /** Compiler hint, indicating unused elements. */ #define __UNUSED__ __attribute__ ((unused)) #endif #if !defined(__ARG_PRINTF__) /** Compiler hint, indicating these are printf-style format args. */ #define __ARG_PRINTF__(_fmtidx, _paridx) \ __attribute__ ((format(printf, _fmtidx, _paridx))) #endif /* == Function-like definitions ============================================ */ /** Returns the lesser of (a, b) */ #if !defined(BS_MIN) #define BS_MIN(a, b) \ ({ \ __typeof__(a) __a = (a); \ __typeof__(b) __b = (b); \ __a < __b ? __a : __b; \ }) #endif // !defined(BS_MIN) /** Returns the greater of (a, b) */ #if !defined(BS_MAX) #define BS_MAX(a, b) \ ({ \ __typeof__(a) __a = (a); \ __typeof__(b) __b = (b); \ __a > __b ? __a : __b; \ }) #endif // !defined(BS_MAX) /** Helper to retrieve the base container, given a pointer to an element. */ #define BS_CONTAINER_OF(elem_ptr, container_type, elem_field) \ (container_type*)( \ (uint8_t*)(elem_ptr) - \ offsetof(container_type, elem_field)) #endif /* __LIBBASE_DEF_H__ */ /* == End of def.h ========================================================= */ wlmaker-0.8.1/submodules/libbase/include/libbase/gfxbuf_xpm.h0000644000175100017510000000265215222270712023722 0ustar runnerrunner/* ========================================================================= */ /** * @file gfxbuf_xpm.h * Implements a simple XPM loader for compiled-in XPM images. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __GFXBUF_XPM_H__ #define __GFXBUF_XPM_H__ #include "gfxbuf.h" #include "test.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Creates a @ref bs_gfxbuf_t from the XPM data at `xpm_data_ptr`. * * @param xpm_data_ptr * * @return A new @ref bs_gfxbuf_t, or NULL on error. Must be destroyed by * calling @ref bs_gfxbuf_destroy. */ bs_gfxbuf_t *bs_gfxbuf_xpm_create_from_data(char **xpm_data_ptr); /** Unit test set. */ extern const bs_test_set_t bs_gfxbuf_xpm_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __GFXBUF_XPM_H__ */ /* == End of gfxbuf_xpm.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/plist/0000755000175100017510000000000015222270712022532 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/include/libbase/plist/decode.h0000644000175100017510000004675115222270712024143 0ustar runnerrunner/* ========================================================================= */ /** * @file decode.h * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __BSPL_DECODE_H__ #define __BSPL_DECODE_H__ #include #include #include #include "model.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: Descriptor. */ typedef struct _bspl_desc_t bspl_desc_t; /** Enum descriptor. */ typedef struct { /** The string representation of the enum. */ const char *name_ptr; /** The corresponding numeric value. */ int value; } bspl_enum_desc_t; /** Sentinel for an enum descriptor sequence. */ #define BSPL_ENUM_SENTINEL() { .name_ptr = NULL } /** Helper to define an enum descriptor. */ #define BSPL_ENUM(_name, _value) { .name_ptr = (_name), .value = (_value) } /** Supported types to decode from a plist dict. */ typedef enum { BSPL_TYPE_UINT64, BSPL_TYPE_INT64, BSPL_TYPE_DOUBLE, BSPL_TYPE_ARGB32, BSPL_TYPE_BOOL, BSPL_TYPE_ENUM, BSPL_TYPE_STRING, BSPL_TYPE_CHARBUF, BSPL_TYPE_DICT, BSPL_TYPE_CUSTOM, BSPL_TYPE_ARRAY, } bspl_decode_type_t; /** A signed 64-bit integer. */ typedef struct { /** The default value, if not in the dict. */ int64_t default_value; } bspl_desc_int64_t; /** An unsigned 64-bit integer. */ typedef struct { /** The default value, if not in the dict. */ uint64_t default_value; } bspl_desc_uint64_t; /** A floating point value. */ typedef struct { /** The default value, if not in the dict. */ double default_value; } bspl_desc_double_t; /** A color, encoded as string in format 'argb32:aarrggbb'. */ typedef struct { /** The default value, if not in the dict. */ uint32_t default_value; } bspl_desc_argb32_t; /** A boolean value. */ typedef struct { /** The default value, if not in the dict. */ bool default_value; } bspl_desc_bool_t; /** An enum. */ typedef struct { /** The default value, if not in the dict. */ int default_value; /** The enum descriptor. */ const bspl_enum_desc_t *desc_ptr; } bspl_desc_enum_t; /** A string. Will be (re)created and must be free'd. */ typedef struct { /** The default value, if not in the dict. */ const char *default_value_ptr; } bspl_desc_string_t; /** A char buffer. Fixed size, no need to create. */ typedef struct { /** Size of the char buffer at the specified offset. */ size_t len; /** The default value, if not in the dict. */ const char *default_value_ptr; } bspl_desc_charbuf_t; /** An array, with a decoder for each item. */ typedef struct { /** Decoding method, for item at position `i`. */ bool (*decode_item)(bspl_object_t *obj_ptr, size_t i, void *value_ptr); /** Initializer method: Allocate or prepare `value_ptr`. May be NULL. */ bool (*init)(void *value_ptr); /** Cleanup method: Frees `value_ptr`. May be NULL.. */ void (*fini)(void *value_ptr); } bspl_desc_array_t; /** A custom decoder. */ typedef struct { /** Initializer method: Allocate or prepare `value_ptr`. May be NULL. */ bool (*init)(void *value_ptr); /** Cleanup method: Frees `value_ptr`. May be NULL.. */ void (*fini)(void *value_ptr); } bspl_desc_custom_t; /** The value-specific aspects of the plist dict descriptor. */ union bspl_desc_value { /** Value descriptor for signed 64-bit integer. */ bspl_desc_int64_t v_int64; /** Value descriptor for unsigned 64-bit integer. */ bspl_desc_uint64_t v_uint64; /** Value descriptor for double. */ bspl_desc_double_t v_double; /** Value descriptor for an ARGB32 color value. */ bspl_desc_argb32_t v_argb32; /** Value descriptor for a bool. */ bspl_desc_bool_t v_bool; /** Value descriptor for an enum. */ bspl_desc_enum_t v_enum; /** Value descriptor for a string. */ bspl_desc_string_t v_string; /** Value descriptor for a char buf. */ bspl_desc_charbuf_t v_charbuf; /** Value descriptor for a dict. */ const bspl_desc_t *v_dict_desc_ptr; /** Value descriptor for a custom element. */ bspl_desc_custom_t v_custom; /** Value descriptor for an array. */ bspl_desc_array_t v_array; }; /** Descriptor to decode a plist dict. */ struct _bspl_desc_t { /** Type of the value. */ bspl_decode_type_t type; /** The key used for the described value in the plist dict. */ const char *key_ptr; /** Whether the field is required. */ bool required; /** Offset of the field where to store the value. */ size_t field_ofs; /** * Offset of the field (boolean) where to store whether this field had been * set. If @ref bspl_desc_t::presence_ofs == @ref bspl_desc_t::field_ofs, * presence will not be recorded. */ size_t presence_ofs; /** Method to decode `object_ptr` into `value_ptr`. */ bool (*decode)(bspl_object_t *object_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Method to encode `value_ptr` and return as a plist object. */ bspl_object_t *(*encode)(const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** And the descriptor of the value. */ union bspl_desc_value v; }; /** Decodes an unsigned number, using uint64_t as carry-all. */ bool bspl_decode_uint64( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes a signed number, using int64_t as carry-all. */ bool bspl_decode_int64( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes a floating point number. */ bool bspl_decode_double( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes an ARGB32 color value. */ bool bspl_decode_argb32( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes a boolean. */ bool bspl_decode_bool( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes an enum. */ bool bspl_decode_enum( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes a string. */ bool bspl_decode_string( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes a charbuf. */ bool bspl_decode_charbuf( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes an array. */ bool bspl_decode_array( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Decodes the dictionary, without initializing the value. */ bool bspl_decode_dict_without_init( bspl_object_t *obj_ptr, const union bspl_desc_value *desc_value_ptr, void *value_ptr); /** Encodes an unsigned 64-bit value into a plist object (a string). */ bspl_object_t *bspl_encode_uint64( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Encodes a signed 64-bit value into a plist object (a string). */ bspl_object_t *bspl_encode_int64( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Encodes a double into a plist object (a string). */ bspl_object_t *bspl_encode_double( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Encodes an ARGB32 color value into a plist object (a string). */ bspl_object_t *bspl_encode_argb32( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Encodes a bool value into a plist object (a string). */ bspl_object_t *bspl_encode_bool( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Encodes an enum value into a plist object (a string). */ bspl_object_t *bspl_encode_enum( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Encodes a string into a plist object. */ bspl_object_t *bspl_encode_string( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Encodes a character buffer into a plist object. */ bspl_object_t *bspl_encode_charbuf( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Encodes into a dictionnary. */ bspl_object_t *bspl_encode_dict_as_object( const union bspl_desc_value *desc_value_ptr, const void *value_ptr); /** Descriptor sentinel. Put at end of a @ref bspl_desc_t sequence. */ #define BSPL_DESC_SENTINEL() { .key_ptr = NULL } /** Descriptor for an unsigned int64. */ #define BSPL_DESC_UINT64(_key, _required, _base, _field, _presence, _default) { \ .type = BSPL_TYPE_UINT64, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_uint64, \ .encode = bspl_encode_uint64, \ .v.v_uint64.default_value = _default \ } /** Descriptor for an signed int64. */ #define BSPL_DESC_INT64(_key, _required, _base, _field, _presence, _default) { \ .type = BSPL_TYPE_INT64, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_int64, \ .encode = bspl_encode_int64, \ .v.v_int64.default_value = _default \ } /** Descriptor for a floating point value. */ #define BSPL_DESC_DOUBLE(_key, _required, _base, _field, _presence, _default) { \ .type = BSPL_TYPE_DOUBLE, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_double, \ .encode = bspl_encode_double, \ .v.v_double.default_value = _default \ } /** Descriptor for an ARGB32 value. */ #define BSPL_DESC_ARGB32(_key, _required, _base, _field, _presence, _default) { \ .type = BSPL_TYPE_ARGB32, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_argb32, \ .encode = bspl_encode_argb32, \ .v.v_argb32.default_value = _default \ } /** Descriptor for a bool value. */ #define BSPL_DESC_BOOL(_key, _required, _base, _field, _presence, _default) { \ .type = BSPL_TYPE_BOOL, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_bool, \ .encode = bspl_encode_bool, \ .v.v_bool.default_value = _default \ } /** Descriptor for an enum value. */ #define BSPL_DESC_ENUM(_key, _required, _base, _field, _presence, _default, _desc_ptr) \ { \ .type = BSPL_TYPE_ENUM, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_enum, \ .encode = bspl_encode_enum, \ .v.v_enum.default_value = _default, \ .v.v_enum.desc_ptr = _desc_ptr \ } /** Descriptor for a string value value. */ #define BSPL_DESC_STRING(_key, _required, _base, _field, _presence, _default) { \ .type = BSPL_TYPE_STRING, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_string, \ .encode = bspl_encode_string, \ .v.v_string.default_value_ptr = _default, \ } /** Descriptor for a char buffer. */ #define BSPL_DESC_CHARBUF(_key, _required, _base, _field, _presence, _len, _default) { \ .type = BSPL_TYPE_CHARBUF, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_charbuf, \ .encode = bspl_encode_charbuf, \ .v.v_charbuf.len = _len, \ .v.v_charbuf.default_value_ptr = _default, \ } /** Descriptor for a dict sub-value. */ #define BSPL_DESC_DICT(_key, _required, _base, _field, _presence, _desc) { \ .type = BSPL_TYPE_DICT, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_dict_without_init, \ .encode = bspl_encode_dict_as_object, \ .v.v_dict_desc_ptr = _desc \ } /** Descriptor for a custom object decoder. */ #define BSPL_DESC_CUSTOM(_key, _required, _base, _field, _presence, _d ,_e, _i, _f) { \ .type = BSPL_TYPE_CUSTOM, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = _d, \ .encode = _e, \ .v.v_custom.init = _i, \ .v.v_custom.fini = _f, \ } /** Descriptor for an array, with an item-decoder. */ #define BSPL_DESC_ARRAY(_key, _required, _base, _field, _presence, _decode_item, _e, _i, _f) { \ .type = BSPL_TYPE_ARRAY, \ .key_ptr = (_key), \ .required = _required, \ .field_ofs = offsetof(_base, _field), \ .presence_ofs = offsetof(_base, _presence), \ .decode = bspl_decode_array, \ .encode = _e, \ .v.v_array.decode_item = _decode_item, \ .v.v_array.init = _i, \ .v.v_array.fini = _f, \ } /** * Decodes the plist `dict_ptr` into `value_ptr` as described. * * @param dict_ptr * @param desc_ptr * @param value_ptr * * @return true on success. */ bool bspl_decode_dict( bspl_dict_t *dict_ptr, const bspl_desc_t *desc_ptr, void *value_ptr); /** * Encodes the data at `src_ptr` according to description into a plist object. * * @param desc_ptr * @param src_ptr * * @return A new plist object with the encoded data, or NULL on error. */ bspl_dict_t *bspl_encode_dict( const bspl_desc_t *desc_ptr, const void *src_ptr); /** * Encodes the data at `src_ptr` into the already-existing plist dict. * * @param desc_ptr * @param src_ptr * @param dict_ptr * * @return true on success. */ bool bspl_encode_into_dict( const bspl_desc_t *desc_ptr, const void *src_ptr, bspl_dict_t *dict_ptr); /** * Destroys resources that were allocated during @ref bspl_decode_dict. * * @param desc_ptr * @param value_ptr */ void bspl_decoded_destroy( const bspl_desc_t *desc_ptr, void *value_ptr); /** * Translates from the given name of an enum to it's value. * * @param enum_desc_ptr * @param name_ptr * @param value_ptr * * @return true if `name_ptr` was a valid enum name. */ bool bspl_enum_name_to_value( const bspl_enum_desc_t *enum_desc_ptr, const char *name_ptr, int *value_ptr); /** * Translates from the given value of an enum to it's name. * * @param enum_desc_ptr * @param value * @param name_ptr_ptr * * @return true if `name_ptr` was a valid enum name. */ bool bspl_enum_value_to_name( const bspl_enum_desc_t *enum_desc_ptr, int value, const char **name_ptr_ptr); /** Unit test set. */ extern const bs_test_set_t bspl_decode_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __BSPL_DECODE_H__ */ /* == End of decode.h ====================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/plist/model.h0000644000175100017510000002060215222270712024003 0ustar runnerrunner/* ========================================================================= */ /** * @file model.h * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __BSPL_MODEL_H__ #define __BSPL_MODEL_H__ #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Forward declaration: Base object type. */ typedef struct _bspl_object_t bspl_object_t; /** Forward declaration: A string. */ typedef struct _bspl_string_t bspl_string_t; /** Forward declaration: A dict (key/value store for objects). */ typedef struct _bspl_dict_t bspl_dict_t; /** Forward declaration: An array (sequential store for objects). */ typedef struct _bspl_array_t bspl_array_t; /** Type of the object. */ typedef enum { BSPL_STRING, BSPL_DICT, BSPL_ARRAY } bspl_type_t; /** * Gets a reference to the object. Increases the reference count. * * The reference should be released by calling @ref bspl_object_unref. * * @param object_ptr * * @return Same as "object_ptr". */ bspl_object_t *bspl_object_ref(bspl_object_t *object_ptr); /** * Removes reference to the object. * * Once no more references are pending, will call all into the virtual dtor of * the implementation. * * @param object_ptr */ void bspl_object_unref(bspl_object_t *object_ptr); /** * Returns the type of `object_ptr`. * * @param object_ptr * * @return The type. */ bspl_type_t bspl_object_type(bspl_object_t *object_ptr); /** * Writes the object into the buffer. * * @param object_ptr * @param dynbuf_ptr * * @return true on success. */ bool bspl_object_write(bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr); /** * Writes the object into the buffer, with specified indentation & level. * * @param object_ptr * @param dynbuf_ptr * @param indent * @param level * * @return true on success. */ bool bspl_object_write_indented( bspl_object_t *object_ptr, bs_dynbuf_t *dynbuf_ptr, size_t indent, size_t level); /** * Creates a string object. * * @param value_ptr * * @return The string object, or NULL on error or if `value_ptr` was NULL. */ bspl_string_t *bspl_string_create(const char *value_ptr); /** Gets the superclass @ref bspl_object_t from the string. */ bspl_object_t *bspl_object_from_string(bspl_string_t *string_ptr); /** Gets the @ref bspl_string_t for `object_ptr`. NULL if not a string. */ bspl_string_t *bspl_string_from_object(bspl_object_t *object_ptr); /** * Returns value of the string object. * * Convenience wrapper for bspl_string_value(bspl_string_from_object(...)). * * @param object_ptr * * @return Pointer to the string object's value or NULL it not a string. */ const char *bspl_string_value_from_object(bspl_object_t *object_ptr); /** * Returns the value of the string. * * @param string_ptr * * @return Pointer to the string's value. */ const char *bspl_string_value(const bspl_string_t *string_ptr); /** * Creates a dict object. * * @return The dict object, or NULL on error. */ bspl_dict_t *bspl_dict_create(void); /** @return the superclass @ref bspl_object_t of the dict. */ bspl_object_t *bspl_object_from_dict(bspl_dict_t *dict_ptr); /** @return the @ref bspl_dict_t for `object_ptr`. NULL if not a dict. */ bspl_dict_t *bspl_dict_from_object(bspl_object_t *object_ptr); /** * Adds an object to the dict. * * @param dict_ptr * @param key_ptr * @param object_ptr The object to add. It will be duplicated by * calling @ref bspl_object_ref. * * @return true on success. Adding the object can fail if the key already * exists, or if memory could not get allocated. */ bool bspl_dict_add( bspl_dict_t *dict_ptr, const char *key_ptr, bspl_object_t *object_ptr); /** @return the given object from the dict. */ bspl_object_t *bspl_dict_get( bspl_dict_t *dict_ptr, const char *key_ptr); /** * Executes `fn` for each key and object of the dict. * * @param dict_ptr * @param fn * @param userdata_ptr * * @return true if all calls to `fn` returned true. The iteration will be * aborted on the first failed call to `fn`. */ bool bspl_dict_foreach( bspl_dict_t *dict_ptr, bool (*fn)(const char *key_ptr, bspl_object_t *object_ptr, void *userdata_ptr), void *userdata_ptr); /** * Creates an array object. * * @return The array object, or NULL on error. */ bspl_array_t *bspl_array_create(void); /** @return the superclass @ref bspl_object_t of the array. */ bspl_object_t *bspl_object_from_array(bspl_array_t *array_ptr); /** @return the @ref bspl_array_t for `object_ptr`. NULL if not an array. */ bspl_array_t *bspl_array_from_object(bspl_object_t *object_ptr); /** * Adds an object to the end of the array. * * @param array_ptr * @param object_ptr * * @return true on success. Adding the object can fail if the array does not * have space and fails to grow. */ bool bspl_array_push_back( bspl_array_t *array_ptr, bspl_object_t *object_ptr); /** @return Size of the array, ie. the number of contained objects. */ size_t bspl_array_size(bspl_array_t *array_ptr); /** * Returns the object at the position `index` of the array. * * @param array_ptr * @param index * * @return Pointer to the object at the specified position in the array. * Returns NULL if index is out of bounds. */ bspl_object_t *bspl_array_at( bspl_array_t *array_ptr, size_t index); /* -- Static & inlined methods: Convenience wrappers ----------------------- */ /** Gets a reference to `string_ptr`. */ static inline bspl_string_t *bspl_string_ref(bspl_string_t *string_ptr) { return bspl_string_from_object( bspl_object_ref(bspl_object_from_string(string_ptr))); } /** Unreferences the string. Wraps to @ref bspl_object_unref. */ static inline void bspl_string_unref(bspl_string_t *string_ptr) { bspl_object_unref(bspl_object_from_string(string_ptr)); } /** Gets a reference to `dict_ptr`. */ static inline bspl_dict_t *bspl_dict_ref(bspl_dict_t *dict_ptr) { return bspl_dict_from_object( bspl_object_ref(bspl_object_from_dict(dict_ptr))); } /** Unreferences the dict. Wraps to @ref bspl_object_unref. */ static inline void bspl_dict_unref(bspl_dict_t *dict_ptr) { bspl_object_unref(bspl_object_from_dict(dict_ptr)); } /** @return the dict value of the specified object, or NULL on error. */ static inline bspl_dict_t *bspl_dict_get_dict( bspl_dict_t *dict_ptr, const char *key_ptr) { return bspl_dict_from_object(bspl_dict_get(dict_ptr, key_ptr)); } /** @return the array value of the specified object, or NULL on error. */ static inline bspl_array_t *bspl_dict_get_array( bspl_dict_t *dict_ptr, const char *key_ptr) { return bspl_array_from_object(bspl_dict_get(dict_ptr, key_ptr)); } /** @return the string value of the specified object, or NULL on error. */ static inline const char *bspl_dict_get_string_value( bspl_dict_t *dict_ptr, const char *key_ptr) { return bspl_string_value( bspl_string_from_object(bspl_dict_get(dict_ptr, key_ptr))); } /** Gets a reference to `array_ptr`. */ static inline bspl_array_t *bspl_array_ref(bspl_array_t *array_ptr) { return bspl_array_from_object( bspl_object_ref(bspl_object_from_array(array_ptr))); } /** Unreferences the array. Wraps to @ref bspl_object_unref. */ static inline void bspl_array_unref(bspl_array_t *array_ptr) { bspl_object_unref(bspl_object_from_array(array_ptr)); } /** @return the value of the string object at `idx`, or NULL on error. */ static inline const char *bspl_array_string_value_at( bspl_array_t *array_ptr, size_t idx) { return bspl_string_value( bspl_string_from_object(bspl_array_at(array_ptr, idx))); } /** Unit test set for the config data model. */ extern const bs_test_set_t bspl_model_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __BSPL_MODEL_H__ */ /* == End of model.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/plist/parse.h0000644000175100017510000000410215222270712024012 0ustar runnerrunner/* ========================================================================= */ /** * @file parse.h * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __BSPL_PARSE_H__ #define __BSPL_PARSE_H__ #include #include "model.h" #include "decode.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Parses the plist string `buf_ptr` and returns the de-serialized object. * * @param buf_ptr * * @return The de-serialized object, or NULL on error. */ bspl_object_t *bspl_create_object_from_plist_string(const char *buf_ptr); /** * Parses the plist data with given size and returns the de-serialized object. * * @param data_ptr * @param data_size * * @return The de-serialized object, or NULL on error. */ bspl_object_t *bspl_create_object_from_plist_data( const uint8_t *data_ptr, size_t data_size); /** * Parses the plist data from the dynamic buffer. * * @param dynbuf_ptr * * @return The de-serialized object, or NULL on error. */ bspl_object_t *bspl_create_object_from_dynbuf(bs_dynbuf_t *dynbuf_ptr); /** * Parses the file `fname_ptr` and returns the de-serialized object. * * @param fname_ptr * * @return The de-serialized object, or NULL on error. */ bspl_object_t *bspl_create_object_from_plist_file(const char *fname_ptr); /** Unit test setfor the plist parser. */ extern const bs_test_set_t bspl_plist_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __BSPL_PARSE_H__ */ /* == End of parse.h ======================================================= */ wlmaker-0.8.1/submodules/libbase/include/libbase/time.h0000644000175100017510000000251115222270712022505 0ustar runnerrunner/* ========================================================================= */ /** * @file time.h * Methods for retrieving system time, respectively clock counter. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_TIME_H__ #define __LIBBASE_TIME_H__ #include "test.h" #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Returns the current time, in microseconds since epoch. */ uint64_t bs_usec(void); /** Returns a monotonous time counter in nsec, as CLOCK_MONOTONIC. */ uint64_t bs_mono_nsec(void); /** Unit test set. */ extern const bs_test_set_t bs_time_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_TIME_H__ */ /* == End of time.h ======================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/arg.h0000644000175100017510000001776715222270712022343 0ustar runnerrunner/* ========================================================================= */ /** * @file arg.h * Permits to declare commandline flags, define defaults and a constraints, * and provides parsing functions. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_ARG_H__ #define __LIBBASE_ARG_H__ #include "test.h" #include #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Parsing mode, whether to permit extra args or not. */ typedef enum { /** Expects that the defined args consume all of argc/argv. */ BS_ARG_MODE_NO_EXTRA, /** Permits extra values (but nothing with a "--" prefix). */ BS_ARG_MODE_EXTRA_VALUES, /** Permits any leftovers. */ BS_ARG_MODE_EXTRA_ARGS, } bs_arg_mode_t; /** Type to consider for the arg. */ typedef enum { /** Used for sentinel. */ BS_ARG_TYPE_UNDEFINED = 0, /** A boolean. */ BS_ARG_TYPE_BOOL, /** An enum, from a set of strings. */ BS_ARG_TYPE_ENUM, /** A string value. */ BS_ARG_TYPE_STRING, /** An unsigned 32-bit value. */ BS_ARG_TYPE_UINT32, } bs_arg_type_t; /** Holds the specification for a boolean argument. */ typedef struct { /** Default value, if not specified on the commandline. */ const bool default_value; /** Points to the boolean that will hold the value. */ bool *value_ptr; } bs_arg_bool_t; /** Lookup table for enum. */ typedef struct { /** The human-readable string of the enum. */ const char *name_ptr; /** Correspnding numeric value. */ int value; } bs_arg_enum_table_t; /** Holds the specification for a string argument. */ typedef struct { /** Default value, if not specified on the commandline. */ const char *default_value; /** Points to the string that will hold the value. */ char **value_ptr; } bs_arg_string_t; /** Holds the specification for an unsigned 32-bit argument. */ typedef struct { /** Default value, if not specified on the commandline. */ const uint32_t default_value; /** Minimum permitted value. Use 0 to permit all possible values. */ const uint32_t min_value; /** Maximum permitted value. Use UINT32_MAX to permit everything. */ const uint32_t max_value; /** Points to the uint32_t that will hold the value. */ uint32_t *value_ptr; } bs_arg_uint32_t; /** Holds the specification for an enum. */ typedef struct { /** Default value, if not specified on the commandline. */ const char *default_name_ptr; /** Lookup table. */ const bs_arg_enum_table_t *lookup_table; /** Points to the int that will hold the value. */ int *value_ptr; } bs_arg_enum_t; /** Holds specification for one argument. */ typedef struct { /** Type of the argument. */ bs_arg_type_t type; /** Name of the argument. */ const char *name_ptr; /** Description, may be NULL. */ const char *description_ptr; /** Specification for the type. */ union { void *v_sentinel; bs_arg_bool_t v_bool; bs_arg_enum_t v_enum; bs_arg_string_t v_string; bs_arg_uint32_t v_uint32; } v; } bs_arg_t ; /** Defines a boolean argument. */ #define BS_ARG_BOOL(_name, _desc, _default, _value_ptr) { \ .type = BS_ARG_TYPE_BOOL, \ .name_ptr = _name, \ .description_ptr = _desc, \ .v = { .v_bool = { \ .default_value = _default, \ .value_ptr = _value_ptr \ } } \ } /** Defines an enum argument. */ #define BS_ARG_ENUM(_name, _desc, _default, _lookup_table, _value_ptr) {\ .type = BS_ARG_TYPE_ENUM, \ .name_ptr = _name, \ .description_ptr = _desc, \ .v = { .v_enum = { \ .default_name_ptr = _default, \ .lookup_table = _lookup_table, \ .value_ptr = _value_ptr \ } } \ } /** Defines a string argument. */ #define BS_ARG_STRING(_name, _desc, _default, _value_ptr) { \ .type = BS_ARG_TYPE_STRING, \ .name_ptr = _name, \ .description_ptr = _desc, \ .v = { .v_string = { \ .default_value = _default, \ .value_ptr = _value_ptr \ } } \ } /** Defines an unsigned 32-bit integer argument. */ #define BS_ARG_UINT32(_name, _desc, _default, _min, _max, _value_ptr) { \ .type = BS_ARG_TYPE_UINT32, \ .name_ptr = _name, \ .description_ptr = _desc, \ .v = { .v_uint32 = { \ .default_value = _default, \ .min_value = _min, \ .max_value = _max, \ .value_ptr = _value_ptr \ } } \ } /** Defines a sentinel for the argument list. */ #define BS_ARG_SENTINEL() { \ .type = BS_ARG_TYPE_UNDEFINED, \ .name_ptr = NULL, \ .description_ptr = NULL, \ .v = { .v_sentinel = NULL } \ } /** * Parses the commandline. * * @param arg_ptr Specifies the array of arguments, ending in a sentinel. * @param mode How to treat extra arguments. * @param argc_ptr Pointer to the number of arguments. * @param argv_ptr Pointer to the list of argument values. It is assumed that * argv_ptr[0] holds the program's name. It will be skipped for parsing. * * @return true if the parsing succeeded. */ bool bs_arg_parse(const bs_arg_t *arg_ptr, const bs_arg_mode_t mode, int *argc_ptr, const char **argv_ptr); /** * Cleanup any allocated resources during parsing by |bs_arg_parse|. * * @param arg_ptr Specifies the array of arguments, ending in a sentinel. */ void bs_arg_cleanup(const bs_arg_t *arg_ptr); /** * Prints the arguments. * * @param stream_ptr * @param arg_ptr */ int bs_arg_print_usage(FILE *stream_ptr, const bs_arg_t *arg_ptr); /** Unit test set. */ extern const bs_test_set_t bs_arg_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_ARG_H__ */ /* == End of arg.h ========================================================= */ wlmaker-0.8.1/submodules/libbase/include/libbase/libbase.h0000644000175100017510000000503415222270712023153 0ustar runnerrunner/* ========================================================================= */ /** * @file libbase.h * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_H__ #define __LIBBASE_H__ // IWYU pragma: begin_exports #include "arg.h" #include "assert.h" #include "atomic.h" #include "avltree.h" #include "def.h" #include "dequeue.h" #include "dllist.h" #include "dynbuf.h" #include "file.h" #include "gfxbuf.h" #include "gfxbuf_xpm.h" #include "log.h" #include "log_wrappers.h" #include "ptr_set.h" #include "ptr_stack.h" #include "ptr_vector.h" #include "ref.h" #include "sock.h" #include "strutil.h" #include "subprocess.h" #include "test.h" #include "thread.h" #include "time.h" #include "vector.h" // IWYU pragma: end_exports // Hack to prevent IWYU from suggesting the sub-includes We don't want that. // IWYU pragma: no_include "libbase/arg.h" // IWYU pragma: no_include "libbase/assert.h" // IWYU pragma: no_include "libbase/atomic.h" // IWYU pragma: no_include "libbase/avltree.h" // IWYU pragma: no_include "libbase/def.h" // IWYU pragma: no_include "libbase/dequeue.h" // IWYU pragma: no_include "libbase/dllist.h" // IWYU pragma: no_include "libbase/dynbuf.h" // IWYU pragma: no_include "libbase/file.h" // IWYU pragma: no_include "libbase/gfxbuf.h" // IWYU pragma: no_include "libbase/gfxbuf_xpm.h" // IWYU pragma: no_include "libbase/log.h" // IWYU pragma: no_include "libbase/log_wrappers.h" // IWYU pragma: no_include "libbase/ptr_set.h" // IWYU pragma: no_include "libbase/ptr_stack.h" // IWYU pragma: no_include "libbase/ptr_vector.h" // IWYU pragma: no_include "libbase/ref.h" // IWYU pragma: no_include "libbase/sock.h" // IWYU pragma: no_include "libbase/strutil.h" // IWYU pragma: no_include "libbase/subprocess.h" // IWYU pragma: no_include "libbase/test.h" // IWYU pragma: no_include "libbase/thread.h" // IWYU pragma: no_include "libbase/time.h" // IWYU pragma: no_include "libbase/vector.h" #endif /* __LIBBASE_H__ */ /* == End of libbase.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/ptr_stack.h0000644000175100017510000000606315222270712023547 0ustar runnerrunner/* ========================================================================= */ /** * @file ptr_stack.h * * Interface for a simple stack to store pointers. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_PTR_STACK_H__ #define __LIBBASE_PTR_STACK_H__ #include #include #include "test.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** State of the pointer stack. */ typedef struct { /** Number of elements contained. Required for re-sizing. */ size_t size; /** Current position within the stack. */ size_t pos; /** Points to data array, holds @ref size elements of `void*`. */ void **data_ptr; } bs_ptr_stack_t; /** * Creates a pointer stack. * * @return Pointer to the stack, or NULL on error. Must be destroyed by calling * @ref bs_ptr_stack_destroy. */ bs_ptr_stack_t *bs_ptr_stack_create(void); /** * Destroys the pointer stack. * * @param ptr_stack_ptr */ void bs_ptr_stack_destroy(bs_ptr_stack_t *ptr_stack_ptr); /** * Initializes the pointer stack. Use this for static allocations of * @ref bs_ptr_stack_t. Associated resources need to be cleaned up by calling * @ref bs_ptr_stack_fini. * * @param ptr_stack_ptr * * @return true on success. */ bool bs_ptr_stack_init(bs_ptr_stack_t *ptr_stack_ptr); /** * Cleans up resources of associated `ptr_stack_ptr`. * * @param ptr_stack_ptr */ void bs_ptr_stack_fini(bs_ptr_stack_t *ptr_stack_ptr); /** * Pushes `elem_ptr` to the stack. * * @param ptr_stack_ptr * @param elem_ptr Pointer to the element to be pushed. Must not be * NULL. * * @return true on success. */ bool bs_ptr_stack_push(bs_ptr_stack_t *ptr_stack_ptr, void *elem_ptr); /** * Pops the topmost element from the stack and returns it. * * @return Pointer to the popped element, or NULL if the stack was empty. */ void *bs_ptr_stack_pop(bs_ptr_stack_t *ptr_stack_ptr); /** * Peeks at the stack value that is `index` items below the top. * * @param ptr_stack_ptr * @param index * * @return The stacked pointer, or NULL if `index` is too large or the stack is * empty. */ void *bs_ptr_stack_peek(bs_ptr_stack_t *ptr_stack_ptr, size_t index); /** Unit test set. */ extern const bs_test_set_t bs_ptr_stack_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_PTR_STACK_H__ */ /* == End of ptr_stack.h =================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/file.h0000644000175100017510000001452215222270712022473 0ustar runnerrunner/* ========================================================================= */ /** * @file file.h * Convenience methods for reading a buffer from a file, respectively writing * a buffer to a file. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_FILE_H__ #define __LIBBASE_FILE_H__ #include #include #include "test.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Reads the contents from |fname_ptr| into |buf_ptr|, up to |buf_len|-1 bytes. * It will add a trailing NUL character to buf_ptr, for convenience. * * @param fname_ptr * @param buf_ptr * @param buf_len * * @return A negative value on error, or the number of bytes read on success. * Any error will be logged. */ // TODO(kaeser@gubbe.ch): Merge with dynbuf. ssize_t bs_file_read_buffer( const char *fname_ptr, char *buf_ptr, size_t buf_len); /** * Writes |buf_len| bytes from |buf_ptr| into the file |fname_ptr|. * * @param fname_ptr * @param buf_ptr * @param buf_len * * @return A negative value on error, or the number of bytes written. * Any error will be logged. */ // TODO(kaeser@gubbe.ch): Merge with dynbuf and bs_dynbuf_write_file. ssize_t bs_file_write_buffer( const char *fname_ptr, const char *buf_ptr, size_t buf_len); /** * Resolves the real path to `path_ptr`, with home directory expansion. * * @param path_ptr Path to join from. Paths that start with "~/" * will be expanded with getenv("HOME"). * @param resolved_path_buf_ptr If specified as NULL, this method will allocate * a buffer of suitable length, and return the * pointer to it. The caller then needs to release * that buffer by calling free(3). * Otherwise, the resolved path is stored here. * * @return Upon success, a pointer to the path is returned. If * resolved_realpath_ptr was NULL, it must be released by calling free(3). * NULL is returned if the the path resolution (realpath) failed, or if * the expanded path was too long. errno will be set appropriately in both * cases (See realpath(3) for the former; ENAMETOOLONG for the latter). */ char *bs_file_resolve_path( const char *path_ptr, char *resolved_path_buf_ptr); /** * Joins and resolves `path_ptr` and `fname_ptr`, with home dir expansion. * * @param path_ptr Path to use for joining. Paths that start with * "~/" will be expanded with getenv("HOME"). * @param fname_ptr Filename to join the path on. * @param resolved_path_buf_ptr See the `resolved_path_buf_ptr` argument to * @ref bs_file_resolve_path. * * @return Upon success, a pointer to the joined, expanded and resolved path, * of NULL on error. See @ref bs_file_resolve_path for details. */ char *bs_file_join_resolve_path( const char *path_ptr, const char *fname_ptr, char *resolved_path_buf_ptr); /** * Looks up a file from the set of provided paths, with path resolutio and * home directory expansion.. * * @param fname_ptr Name of the file that shall be looked up. * @param paths_ptr_ptr A NULL-terminated array of paths to search. Paths * starting with "~/" will be expanded as described * by @ref bs_file_join_resolve_path. * @param mode Optional, indicates to only consider these types * of the file. Matches the `st_mode` field of stat. * @param resolved_path_buf_ptr See the `resolved_path_buf_ptr` argument to * @ref bs_file_resolve_path. * * @return Upon success, a pointer to the joined, expanded and resolved path of * the first file found from a path & file name combination. * Otherwise, NULL on error, with errno set appropriately and for the last * of the attempted paths. See @ref bs_file_resolve_path for details. */ char *bs_file_resolve_and_lookup_from_paths( const char *fname_ptr, const char **paths_ptr_ptr, int mode, char *resolved_path_buf_ptr); /** * Creates the directory (and, recursively the parents) with the given mode. * * @param dirname_ptr * @param mode * * @return true on success. If the directory exists already, errno is ENOENT. */ bool bs_file_mkdir_p(const char *dirname_ptr, int mode); /** * Checks if the entity at the realpath()-expanded location is of mode_type. * * @param fname_ptr * @param mode_type Desired mode of resolved entity, see inode(7). * * @return true if there is a file, directory (or ...) at `fname_ptr` with type * `mode_type`. */ bool bs_file_realpath_is(const char *fname_ptr, int mode_type); /** * Walks the directory tree from `path_ptr` and issues `callback` for all. * * @param path_ptr Root path for starting the directory walk. * @param pattern_ptr Optional: Restrict `callback` to only those files * that fnmatch the pattern. NULL to ignore. * @param mode_type Optional: Restrict `callback` to only the * specified file types (eg. S_IFDIR or S_IFREG). * Set to 0 to call for all types. * @param sorted Whether to sort all files. * @param callback * @param ud_ptr * * @return true on success. */ bool bs_file_walk_tree( const char *path_ptr, const char *pattern_ptr, int mode_type, bool sorted, bool (*callback)(const char *, const FTSENT *, void *), void *ud_ptr); /** Unit test set. */ extern const bs_test_set_t bs_file_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_FILE_H__ */ /* == End of file.h ======================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/sock.h0000644000175100017510000000441315222270712022511 0ustar runnerrunner/* ========================================================================= */ /** * @file sock.h * Some helper functions to work with sockets. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_SOCK_H__ #define __LIBBASE_SOCK_H__ #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** * Sets the blocking property of the file descriptor |fd|. * * @param fd * @param blocking Have O_NONBLOCK cleared if true, and set if false. * * @return Whether the call succeeded. */ bool bs_sock_set_blocking(int fd, bool blocking); /** * Waits up to |msec| until the file descriptor |fd| has data to read. * * @param fd * @param msec * * @return a positive value if data is available, 0 if the call timed out, and a * negative value if there was an error. The error is logged, and errno is set. */ int bs_sock_poll_read(int fd, int msec); /** * Read |count| bytes from |fd| into |buf_ptr|, respecting the timeout given * by |msec|. * * @param fd * @param buf_ptr * @param count * @param msec Timeout in milliseconds. A negative value * specifies an infinite timeout. * * @return Number of bytes read. The return value will always be less or equal * than |count|. Returns a negative value on error. Errors will be logged, but * errno is retained. * If |count| is non-positive, 0 is returned. * If the connection was closed, a negative value is returned and errno is * set to EPIPE. */ ssize_t bs_sock_read(int fd, void *buf_ptr, size_t count, int msec); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_SOCK_H__ */ /* == End of sock.h ======================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/ref.h0000644000175100017510000000252215222270712022325 0ustar runnerrunner/* ========================================================================= */ /** * @file ref.h * Copyright (c) 2026 Philipp Kaeser */ #ifndef __REF_H__ #define __REF_H__ #ifdef __cplusplus extern "C" { #endif // __cplusplus /* == Declarations ========================================================= */ /** Forward declaration. */ typedef struct _bs_ref_t bs_ref_t; /** State for reference counting. */ struct _bs_ref_t { /** Number of references. */ int count; /** dtor. */ void (*destroy_fn)(bs_ref_t *ref_ptr); }; /* == Exported methods ===================================================== */ /** * Initializes the reference counter state. * * @param ref_ptr * @param destroy_fn */ void bs_ref_init( bs_ref_t *ref_ptr, void (*destroy_fn)(bs_ref_t *ref_ptr)); /** * Retain a reference to `ref_ptr`. * * @param ref_ptr */ void bs_ref_retain(bs_ref_t *ref_ptr); /** * Releases a reference to `ref_ptr`. * * Calls @ref bs_ref_t::destroy_fn, if there are no other references. * * @param ref_ptr */ void bs_ref_release(bs_ref_t *ref_ptr); /** Unit tests. */ extern const bs_test_set_t bs_ref_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif // __REF_H__ /* == End of ref.h ========================================================= */ wlmaker-0.8.1/submodules/libbase/include/libbase/gfxbuf.h0000644000175100017510000001613715222270712023041 0ustar runnerrunner/* ========================================================================= */ /** * @file gfxbuf.h * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_GFXBUF_H__ #define __LIBBASE_GFXBUF_H__ #include #include "assert.h" #include "test.h" #ifdef HAVE_CAIRO #include #endif // HAVE_CAIRO #ifdef __cplusplus extern "C" { #endif // __cplusplus /** A graphics buffer. */ typedef struct { /** Width, in pixels. */ unsigned width; /** Height, in pixels. */ unsigned height; /** Pixels per line. */ unsigned pixels_per_line; /** The pixels buffer. Has `height` * `pixels_per_line` elements. */ uint32_t *data_ptr; } bs_gfxbuf_t; /** * Creates a graphics buffer @ref bs_gfxbuf_t. * * @param width * @param height * * @return A pointer to @ref bs_gfxbuf_t. Must be free'd by * @ref bs_gfxbuf_destroy(). */ bs_gfxbuf_t *bs_gfxbuf_create(unsigned width, unsigned height); /** * Creates a graphics buffer @ref bs_gfxbuf_t. * * Will use the existing pixels buffer at `data_ptr`, and expects it to remain * available throughout the lifetime of the created @ref bs_gfxbuf_t. * * @param width * @param height * @param pixels_per_line * @param data_ptr */ bs_gfxbuf_t *bs_gfxbuf_create_unmanaged(unsigned width, unsigned height, unsigned pixels_per_line, uint32_t* data_ptr); /** * Destroys the graphics buffer. * * @param gfxbuf_ptr */ void bs_gfxbuf_destroy(bs_gfxbuf_t *gfxbuf_ptr); /** * Clears the graphics buffer with the specified `color`. * * @param gfxbuf_ptr * @param color The fill color, as an ARGB 8888. */ void bs_gfxbuf_clear(bs_gfxbuf_t *gfxbuf_ptr, const uint32_t color); /** * Copies the contents of `src_gfxbuf_ptr` to `dest_gfxbuf_ptr`. * * Expects width and height of `src_gfxbuf_ptr` and `dest_gfxbuf_ptr` the same. * * @param src_gfxbuf_ptr * @param dest_gfxbuf_ptr */ void bs_gfxbuf_copy(bs_gfxbuf_t *dest_gfxbuf_ptr, const bs_gfxbuf_t *src_gfxbuf_ptr); /** * Copies a rectangular area between graphics buffers. * * The width and height of the rectangular area will be truncated to fit both * source and destination buffers. * * @param dest_gfxbuf_ptr Destination graphics buffer. * @param dest_x Destination coordinate. * @param dest_y Destination coordinate. * @param src_gfxbuf_ptr Source graphics buffere. * @param src_x Source coordinate. * @param src_y Source coordinate. * @param width Width of the rectangle to copy. * @param height Height of the rectangle to copy. */ void bs_gfxbuf_copy_area( bs_gfxbuf_t *dest_gfxbuf_ptr, unsigned dest_x, unsigned dest_y, const bs_gfxbuf_t *src_gfxbuf_ptr, unsigned src_x, unsigned src_y, unsigned width, unsigned height); /** * Returns a pointer to the pixel at the given coordinates. * * @param gfxbuf_ptr * @param x * @param y * * @return A pointer to the pixel at the specified position. */ static inline uint32_t *bs_gfxbuf_pixel_at( const bs_gfxbuf_t *gfxbuf_ptr, unsigned x, unsigned y) { BS_ASSERT(x < gfxbuf_ptr->width); BS_ASSERT(y < gfxbuf_ptr->height); return &gfxbuf_ptr->data_ptr[y * gfxbuf_ptr->pixels_per_line + x]; } /** * Colors the pixel at the given coordinates. * * @param gfxbuf_ptr * @param x * @param y * @param color Color, in ARGB8888 format. */ static inline void bs_gfxbuf_set_pixel( bs_gfxbuf_t *gfxbuf_ptr, unsigned x, unsigned y, uint32_t color) { *bs_gfxbuf_pixel_at(gfxbuf_ptr, x, y) = color; } /** * Computes the red, green, blue and alpha components as floating points from * the given `argb8888` value. * * @param argb8888 Color, in ARGB 8888 format. * @param red_ptr Output value, will be clamped to [0, 1]. * @param blue_ptr Output value, will be clamped to [0, 1]. * @param green_ptr Output value, will be clamped to [0, 1]. * @param alpha_ptr Output value, will be clamped to [0, 1]. */ void bs_gfxbuf_argb8888_to_floats( const uint32_t argb8888, float *red_ptr, float *green_ptr, float *blue_ptr, float *alpha_ptr); #ifdef HAVE_CAIRO /** * Creates a Cairo drawing context for the @ref bs_gfxbuf_t. * * This is a convenience function that permits drawing into the graphics buffer * using the Cairo 2D graphics library (https://cairographics.org/). * * @param gfxbuf_ptr Graphics buffer. Must outlive the returned * `cairo_t`. * * @return A cairo drawing context, or NULL on error. The returned context must * be destroyed by calling cairo_destroy(). */ cairo_t *cairo_create_from_bs_gfxbuf(const bs_gfxbuf_t *gfxbuf_ptr); /** * Sets the source color for the cairo at `cairo_ptr`. * * @param cairo_ptr * @param argb8888 Color, in ARGB 8888 format. */ void cairo_set_source_argb8888( cairo_t *cairo_ptr, uint32_t argb8888); /** * Tests whether the graphics is equal to the contents of the PNG file. * * @param test_ptr * @paran fname_ptr * @paran line * @param gfxbuf_ptr * @param png_fname_ptr Name of the PNG file. The test will report as * failed if `png_fname_ptr` is NULL. */ void bs_test_gfxbuf_equals_png_at( bs_test_t *test_ptr, const char *fname_ptr, int line, const bs_gfxbuf_t *gfxbuf_ptr, const char *png_fname_ptr); /** * Tests whether @ref bs_gfxbuf_t equals the PNG file. * * @param _test The @ref bs_test_t of the current test case. * @param _gfxbuf_ptr A @ref bs_gfxbuf_t, the graphics buffer to test. * @param _png_name Path to the PNG file name. Relative paths will be * resolved using @ref bs_test_resolve_path. */ #define BS_TEST_VERIFY_GFXBUF_EQUALS_PNG(_test, _gfxbuf_ptr, _png_name) { \ bs_test_gfxbuf_equals_png_at( \ (_test), __FILE__, __LINE__, (_gfxbuf_ptr), \ bs_test_data_path(_test, _png_name)); \ } #endif // HAVE_CAIRO /** Unit tests set. */ extern const bs_test_set_t bs_gfxbuf_test_set; /** Benchmarks, in the form of an unit test. */ extern const bs_test_set_t bs_gfxbuf_benchmarks_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_GFXBUF_H__ */ /* == End of gfxbuf.h ================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/avltree.h0000644000175100017510000001230015222270712023206 0ustar runnerrunner/* ========================================================================= */ /** * @file avltree.h * Implements an AVL tree, with nodes provided such they can be embedded in * the element's struct. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_AVLTREE_H__ #define __LIBBASE_AVLTREE_H__ #include "test.h" #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** The tree. */ typedef struct _bs_avltree_t bs_avltree_t; /** A tree node. */ typedef struct _bs_avltree_node_t bs_avltree_node_t; /** Actual struct of the tree node. */ struct _bs_avltree_node_t { /** Back-link to the parent node. NULL for the root node. */ bs_avltree_node_t *parent_ptr; /** Links to the left (smaller) node. NULL if this is the smallest node. */ bs_avltree_node_t *left_ptr; /** Links to the left (greater) node. NULL if this is the greatest node. */ bs_avltree_node_t *right_ptr; /** Node balance. */ int8_t balance; }; /** * Functor type to compare two avltree nodes. * * @param node_ptr * @param key_ptr * * @return * - a negative value if node_ptr is less than key_ptr * - 0 if node equals key * - a positive value if node_ptr is greater than key_ptr */ typedef int (*bs_avltree_node_cmp_t)(const bs_avltree_node_t *node_ptr, const void *key_ptr); /** * Functor type to destroy an avltree node. * * @param node_ptr */ typedef void (*bs_avltree_node_destroy_t)(bs_avltree_node_t *node_ptr); /** * Creates a tree. * * @param cmp * @param destroy Optionally, a functor to destroy nodes. If destroy is NULL, * the tree will not destroy any nodes when overwriting or flushing. * * @return A pointer to the tree, or NULL on error. */ bs_avltree_t *bs_avltree_create(bs_avltree_node_cmp_t cmp, bs_avltree_node_destroy_t destroy); /** * Destroys the tree. Also destroys all remaining elements. * * @param tree_ptr */ void bs_avltree_destroy(bs_avltree_t *tree_ptr); /** Returns the node matching |key_ptr| in the tree. */ bs_avltree_node_t *bs_avltree_lookup(bs_avltree_t *tree_ptr, const void *key_ptr); /** * Inserts a node into the tree. * * @param tree_ptr * @param key_ptr The key for this node, required for insert. * @param node_ptr The node. * @param do_overwrite Whether to overwrite any already-existing node at the * specified |key_ptr|. The overwritten node will be destroyed, if a * destroy method was specified. * * @return Whether the insert succeeded. It can fail if |do_overwrite| is false * and a node at |key_ptr| already exists. */ bool bs_avltree_insert(bs_avltree_t *tree_ptr, const void *key_ptr, bs_avltree_node_t *node_ptr, bool do_overwrite); /** * Deletes a node from the tree. * * @param tree_ptr * @param key_ptr The key for the node that is to be deleted from the tree. * * @return A pointer to the deleted node, or NULL if it was not found. * Note: The node will NOT be destroyed. */ bs_avltree_node_t *bs_avltree_delete(bs_avltree_t *tree_ptr, const void *key_ptr); /** Returns the minimum node of the tree. */ bs_avltree_node_t *bs_avltree_min(bs_avltree_t *tree_ptr); /** Returns the maximum node of the tree. */ bs_avltree_node_t *bs_avltree_max(bs_avltree_t *tree_ptr); /** Returns the size of the tree. */ size_t bs_avltree_size(const bs_avltree_t *tree_ptr); /** Returns the next-larger node from the tree, seen from |node_ptr|. */ bs_avltree_node_t *bs_avltree_node_next(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr); /** Returns the next-smaller node from the tree, seen from |node_ptr|. */ bs_avltree_node_t *bs_avltree_node_prev(bs_avltree_t *tree_ptr, bs_avltree_node_t *node_ptr); /** * Helper: Comparator to compare two pointers. * * To be used for @ref bs_avltree_node_cmp_t, after the comparison function * looks up the key from the `node_ptr` argument. * * @param node_key_ptr The key obtained for the node under comparison. * @param key_ptr As passed from @ref bs_avltree_node_cmp_t. * * @return See @ref bs_avltree_node_cmp_t. */ int bs_avltree_cmp_ptr(const void *node_key_ptr, const void *key_ptr); /** Unit tests set. */ extern const bs_test_set_t bs_avltree_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_AVLTREE_H__ */ /* == End of avltree.h ===================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/test.h0000644000175100017510000003266115222270712022537 0ustar runnerrunner/* ========================================================================= */ /** * @file test.h * Declarations for building unit tests. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_TEST_H__ #define __LIBBASE_TEST_H__ #include #include #include #include #include "def.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Overall state of test. */ typedef struct _bs_test_t bs_test_t; /** State of unit test. */ typedef struct _bs_test_case_t bs_test_case_t; /** A test set. */ typedef struct _bs_test_set_t bs_test_set_t; /** Test function. */ typedef void (*bs_test_fn_t)(bs_test_t *test_ptr); /** Descriptor for a test case. */ struct _bs_test_case_t { /** Whether the test is enabled. */ bool enabled; /** Name of the test, for informational purpose. */ const char *name_ptr; /** Test function for this testcase. */ bs_test_fn_t test_fn; }; /** Sentinel, to add at the end of a list of @ref bs_test_case_t. */ #define BS_TEST_CASE_SENTINEL() { .enabled = false, .name_ptr = NULL } /** Test set. */ struct _bs_test_set_t { /** Whether the set is enabled. */ bool enabled; /** Name of the test set. */ const char *name_ptr; /** Array of test cases for that set. */ const bs_test_case_t *cases_ptr; /** Optional: Method to setup environment. Executed before each case. */ void *(*setup)(void); /** Optional: Method to tear down environment. Executed after each case. */ void (*teardown)(void *setup_context_ptr); }; /** Defines a plain test set, no setup/teardown methods. */ #define BS_TEST_SET(_enabled ,_name, _cases) { \ .enabled = _enabled, \ .name_ptr = _name, \ .cases_ptr = _cases \ } /** A test set with methods to setup & teardown context for each case. */ #define BS_TEST_SET_CONTEXT(_enabled ,_name, _cases, _setup, _teardown) { \ .enabled = _enabled, \ .name_ptr = _name, \ .cases_ptr = _cases, \ .setup = _setup, \ .teardown = _teardown \ } /** Test parameters. */ typedef struct { /** Directory to data files. */ const char *test_data_dir_ptr; } bs_test_param_t; /** * Reports that the given test succeeded, and print the format string and * arguments to the test's report. Calling bs_test_succeed is optional, if * neither bs_test_fail or bs_test_succeed are called, the test counts as * succeeded. bs_test_fail takes precedence. * * @param test_ptr Test state. * @param fmt_ptr Format string for report message. * @param ... Additional arguments to format string. */ void bs_test_succeed(bs_test_t *test_ptr, const char *fmt_ptr, ...) __ARG_PRINTF__(2, 3); /** * Reports the given test as failed for the given position. * * @param test_ptr Test state. * @param fname_ptr Filename to report the failure for. * @param line line number. * @param fmt_ptr Format string for report message. * @param ... Additional arguments to format string. */ void bs_test_fail_at( bs_test_t *test_ptr, const char *fname_ptr, int line, const char *fmt_ptr, ...) __ARG_PRINTF__(4, 5); /** * Check failure status of test. * * @return Whether the test has failed. */ bool bs_test_failed(bs_test_t *test_ptr); /** * Returns the context returned by @ref bs_test_set_t::setup, or NULL. * * @param test_ptr * * @return The context, or NULL if @ref bs_test_set_t::setup was not provided. */ void *bs_test_context(bs_test_t *test_ptr); /** * Runs test sets. * * @param test_set_ptrs A NULL-terminated array of pointers to test sets. * @param argc * @param argv * @param param_ptr Optional, points to a @ref bs_test_param_t and * specifies parameters for the test environment. * * @return 0 on success or the number of failed test sets. */ int bs_test_sets( const bs_test_set_t **test_set_ptrs, int argc, const char **argv, const bs_test_param_t *param_ptr); /** * Tests whether the strings at `a_ptr` and `b_ptr` are equal. * * Helper method, you should use the BS_TEST_VERIFY_STREQ macro instead. * * @param test_ptr * @param fname_ptr * @param line * @param a_ptr * @param hash_a_ptr * @param b_ptr * @param hash_b_ptr */ void bs_test_verify_streq_at( bs_test_t *test_ptr, const char *fname_ptr, const int line, const char *a_ptr, const char *hash_a_ptr, const char *b_ptr, const char *hash_b_ptr); /** * Tests whether the string at `a_ptr` matches the regular expression. * * Helper method, you should use the BS_TEST_VERIFY_STRMATCH macro instead. * * @param fname_ptr * @param line * @param test_ptr * @param a_ptr * @param hash_a_ptr * @param regex_ptr */ void bs_test_verify_strmatch_at( bs_test_t *test_ptr, const char *fname_ptr, const int line, const char *a_ptr, const char *hash_a_ptr, const char *regex_ptr); /** * Tests whether the memory buffers at `a_ptr` and `b_ptr` are equal. * * Helper method, you should use the BS_TEST_VERIFY_MEMEQ macro instead. * * @param test_ptr * @param fname_ptr * @param line * @param a_ptr * @param hash_a_ptr * @param b_ptr * @param hash_b_ptr * @param size */ void bs_test_verify_memeq_at( bs_test_t *test_ptr, const char *fname_ptr, const int line, const void *a_ptr, const char *hash_a_ptr, const void *b_ptr, const char *hash_b_ptr, const size_t size); /** * Joins `fname_fmt_ptr` (relative to test data directory) into absolute path. * * @param test_ptr * @param fname_fmt_ptr Format string, to permit constructing the path. * * @return A pointer to the resolved path or NULL on error. The memory will * remain reserved throughout lifetime of `test_ptr`, and will be freed * automatically. * Upon error, `test_ptr` will be marked as failed. */ const char *bs_test_data_path( bs_test_t *test_ptr, const char *fname_fmt_ptr, ...) __ARG_PRINTF__(2, 3); /** * Formats and joins a file path, relative to test case's temporary directory. * * Constructs a temporary directory with a lifetime bound to `test_ptr` The * directory and name is cached, ie. further calls to @ref bs_test_temp_path * within the same @ref bs_test_t will re-use the same directory. * When the test completes, it will attempt to rmdir() the directory. The test * will get marked as failed, if rmdir() fails, eg. if the directory is not * empty. * * @param test_ptr * @param fname_fmt_ptr Format string of the file name to join, or NULL * to return just the test directory. * * @return The path name to the created directory, or NULL on error. If the * function fails, `test_ptr` is marked as failed. The path name will be * cleaned up during test teardown. */ const char *bs_test_temp_path( bs_test_t *test_ptr, const char *fname_fmt_ptr, ...) __ARG_PRINTF__(2, 3); /** * Sets an environment variable in the process. * * This will first backup the current value of the environment variable, then * call getenv(3) to change it. Upon test teardown, the original value of the * environment variable is kept. * Repeated calls to @ref bs_test_setenv will not overwrite the backup stored * during the first call. * * @param test_ptr * @param name_ptr * @param value_fmt_ptr */ void bs_test_setenv( bs_test_t *test_ptr, const char *name_ptr, const char *value_fmt_ptr, ...) __ARG_PRINTF__(3, 4); /* == Verification macros ================================================== */ /** * Reports the test as failed, at current position. * * @param _test */ #define BS_TEST_FAIL(_test, ...) { \ bs_test_fail_at((_test), __FILE__, __LINE__, __VA_ARGS__); \ } /** Verifies that _expr is true, and returns (stops tests) if not. */ #define BS_TEST_VERIFY_TRUE_OR_RETURN(_test, _expr) { \ BS_TEST_VERIFY_TRUE(_test, _expr); \ if (bs_test_failed(_test)) return; \ } /** * Verifies that _expr is true. * * @param _test * @param _expr */ #define BS_TEST_VERIFY_TRUE(_test, _expr) { \ if (!(_expr)) { \ bs_test_fail_at((_test), __FILE__, __LINE__, \ "%s not true.", #_expr); \ } \ } /** Verifies that _expr is false, and reutrns (stop tests) if not. */ #define BS_TEST_VERIFY_FALSE_OR_RETURN(_test, _expr) { \ BS_TEST_VERIFY_FALSE(_test, _expr); \ if (bs_test_failed(_test)) return; \ } /** * Verifies that _expr is false. * * @param _test * @param _expr */ #define BS_TEST_VERIFY_FALSE(_test, _expr) { \ if (_expr) { \ bs_test_fail_at((_test), __FILE__, __LINE__, \ "%s not false.", #_expr); \ } \ } /** Verifies that _a == _b, and reutrns (stop tests) if not. */ #define BS_TEST_VERIFY_EQ_OR_RETURN(_test, _a, _b) { \ BS_TEST_VERIFY_EQ(_test, _a, _b); \ if (bs_test_failed(_test)) return; \ } /** * Verifies that _a == _b * * @param _test * @param _a * @param _b */ #define BS_TEST_VERIFY_EQ(_test, _a, _b) { \ if (!((_a) == (_b))) { \ bs_test_fail_at((_test), __FILE__, __LINE__, \ "%s not equal %s.", #_a, #_b); \ } \ } /** Verifies that _a != _b, and reutrns (stop tests) if not. */ #define BS_TEST_VERIFY_NEQ_OR_RETURN(_test, _a, _b) { \ BS_TEST_VERIFY_NEQ(_test, _a, _b); \ if (bs_test_failed(_test)) return; \ } /** * Verifies that _a != _b * * @param _test * @param _a * @param _b */ #define BS_TEST_VERIFY_NEQ(_test, _a, _b) { \ if ((_a) == (_b)) { \ bs_test_fail_at((_test), __FILE__, __LINE__, \ "%s equal %s.", #_a, #_b); \ } \ } /** Verifies that the strings _a == _b. Returns (stop test) if not. */ #define BS_TEST_VERIFY_STREQ_OR_RETURN(_test, _a, _b) { \ BS_TEST_VERIFY_STREQ(_test, _a, _b); \ if (bs_test_failed(_test)) return; \ } /** * Verifies that the strings _a == _b. * * @param _test * @param _a * @param _b */ #define BS_TEST_VERIFY_STREQ(_test, _a, _b) { \ bs_test_verify_streq_at( \ (_test), __FILE__, __LINE__, _a, #_a, _b, #_b); \ } /** Verifies that the string _a matches _regex.. Returns (stop test) if not. */ #define BS_TEST_VERIFY_STRMATCH_OR_RETURN(_test, _a, _regex) { \ BS_TEST_VERIFY_STRMATCH(_test, _a, _regex); \ if (bs_test_failed(_test)) return; \ } /** * Verifies that the string _a matches the regular expression _regex. * * @param _test * @param _a * @param _regex */ #define BS_TEST_VERIFY_STRMATCH(_test, _a, _regex) { \ bs_test_verify_strmatch_at( \ (_test), __FILE__, __LINE__, _a, #_a, _regex); \ } /** Verifies that the memory buffers _a == _b. Returns (stop tests) if not. */ #define BS_TEST_VERIFY_MEMEQ_OR_RETURN(_test, _a, _b, _size) { \ BS_TEST_VERIFY_MEMEQ(_test, _a, _b, _size); \ if (bs_test_failed(_test)) return; \ } /** * Verifies that the memory buffers _a == _b. * * @param _test * @param _a * @param _b * @param _size */ #define BS_TEST_VERIFY_MEMEQ(_test, _a, _b, _size) { \ bs_test_verify_memeq_at( \ (_test), __FILE__, __LINE__, _a, #_a, _b, #_b, _size); \ } /** Unit test set. */ extern const bs_test_set_t bs_test_test_set; /** Test set with setup and teardown of a context. */ extern const bs_test_set_t bs_test_test_setup_teardown_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_TEST_H__ */ /* == End of test.h ===================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/ptr_set.h0000644000175100017510000000502715222270712023234 0ustar runnerrunner/* ========================================================================= */ /** * @file ptr_set.h * * Interface for a simple stack to store pointers. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_PTR_SET_H__ #define __LIBBASE_PTR_SET_H__ #include #include "test.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Handle for a set. */ typedef struct _bs_ptr_set_t bs_ptr_set_t; /** * Creates the set. * * @return Pointer to the set, or NULL on error . Must be freed by calling * @ref bs_ptr_set_destroy. */ bs_ptr_set_t *bs_ptr_set_create(void); /** * Destroys a set previously created with @ref bs_ptr_set_create. * * @param set_ptr */ void bs_ptr_set_destroy(bs_ptr_set_t *set_ptr); /** * Inserts `elem_ptr` into `set_ptr`. * * @param set_ptr * @param elem_ptr * * @return true iff the insert worked, false if there already exists the same * `elem_ptr` in the set or if there was an allocation error. */ bool bs_ptr_set_insert(bs_ptr_set_t *set_ptr, void *elem_ptr); /** * Erases `elem_ptr` from `set_ptr`. * * @param set_ptr * @param elem_ptr */ void bs_ptr_set_erase(bs_ptr_set_t *set_ptr, void *elem_ptr); /** * Returns whether `set_ptr` contains `elem_ptr`. * * @param set_ptr * @param elem_ptr * * @return whether `set_ptr` contains `elem_ptr`. */ bool bs_ptr_set_contains(bs_ptr_set_t *set_ptr, void *elem_ptr); /** * Returns any of the contained pointers. Can be used for deletion. * * @param set_ptr * * @return Any of the contained pointers, or NULL on error. */ void *bs_ptr_set_any(bs_ptr_set_t *set_ptr); /** * Returns whether `set_ptr` is empty. * * @param set_ptr * * @return Whether the set is empty. */ bool bs_ptr_set_empty(bs_ptr_set_t *set_ptr); /** Unit test set. */ extern const bs_test_set_t bs_ptr_set_test_set; #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_PTR_SET_H__ */ /* == End of ptr_set.h ===================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/atomic.h0000644000175100017510000003246015222270712023031 0ustar runnerrunner/* ========================================================================= */ /** * @file atomic.h * Methods for atomic access to a few basic types. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_ATOMIC_H__ #define __LIBBASE_ATOMIC_H__ #include "test.h" #include #if defined(__cplusplus) // C++ (gcc) is not compatible with . When using C++, please // resort to using C++ STL definitions instead. // // Therefore, the __cplusplus #if is just an empty block. #else // defined(__cplusplus) /* == Types and Definitions ================================================ */ #if (defined(__GNUC__) || defined(__clang__)) && defined(i386) #define __BS_ATOMIC_GCC_ASM_i386 #elif (defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__) #define __BS_ATOMIC_GCC_ASM_x86_64 #else /** C11 supports atomics. We expect support for at least 32-bit atomics. */ #define __BS_ATOMIC_C11_STDATOMIC #include #if defined(ATOMIC_LLONG_LOCK_FREE) /** We can use C11 definitions for 64-bit atomics. */ #define __BS_ATOMIC_C11_STDATOMIC_INT64 #else // defined(ATOMIC_LLONG_LOCK_FREE) /** fall back to use a mutex for 64-bit atomics. */ #define __BS_ATOMIC_INT64_MUTEX #endif // defined(ATOMIC_LLONG_LOCK_FREE) #endif // (defined(__GNUC__) || defined(__clang__)) && (i386 || __x86_64__). #if defined(__BS_ATOMIC_INT64_MUTEX) #include static pthread_mutex_t _bs_atomic_mutex = PTHREAD_MUTEX_INITIALIZER; #endif // defined(__BS_ATOMIC_INT64_MUTEX) /** * Initializes a bs_atomic_int32_t to value v. */ #define BS_ATOMIC_INT32_INIT(v) { (v) } /** * Initializes a bs_atomic_int64_t to value v. */ #define BS_ATOMIC_INT64_INIT(v) { (v) } /** An atomically accessible 32-bit integer. */ typedef struct { #if defined(__BS_ATOMIC_C11_STDATOMIC) /** The actual value. */ atomic_int_least32_t v; #else // defined (__BS_ATOMIC_C11_STDATOMIC) /** The actual value. */ int32_t v; #endif // defined (__BS_ATOMIC_C11_STDATOMIC) } bs_atomic_int32_t; /** An atomically accessible 64-bit integer. */ typedef struct { #if defined(__BS_ATOMIC_C11_STDATOMIC_INT64) /** The actual value . */ atomic_int_least64_t v; #else // defined (__BS_ATOMIC_C11_STDATOMIC_INT64) /** The actual value . */ int64_t v; #endif // defined (__BS_ATOMIC_C11_STDATOMIC_INT64) } bs_atomic_int64_t; /* == 32-bit integer ======================================================= */ /* ------------------------------------------------------------------------- */ /** Set atomic value to v. */ static inline void bs_atomic_int32_set(bs_atomic_int32_t *a_ptr, int32_t v) { #if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64) // A 32-bit assignment is atomic on i386 */ a_ptr->v = v; #elif defined(__BS_ATOMIC_C11_STDATOMIC) atomic_store(&a_ptr->v, v); #else #error "Unsupported compiler or architecture." #endif } /* ------------------------------------------------------------------------- */ /** Get value of atomic. */ static inline int32_t bs_atomic_int32_get(bs_atomic_int32_t *a_ptr) { #if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64) /* A 32-bit read is atomic on i386 */ return a_ptr->v; #elif defined(__BS_ATOMIC_C11_STDATOMIC) return atomic_load(&a_ptr->v); #else #error "Unsupported compiler or architecture." #endif } /* ------------------------------------------------------------------------- */ /** Add value to atomic. */ static inline int32_t bs_atomic_int32_add(bs_atomic_int32_t *a_ptr, int32_t v) { #if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64) int32_t old_v = v; __asm__ volatile ( "lock; \n" "xaddl %0, %1; \n" : "=r" (v) : "m" (a_ptr->v), "0" (v) : "memory" ); return v + old_v; #elif defined(__BS_ATOMIC_C11_STDATOMIC) return atomic_fetch_add(&a_ptr->v, v) + v; #else #error "Unsupported compiler or architecture." #endif } /* ------------------------------------------------------------------------- */ /** * Compare-And-Swap value with atomic. * * If the returned value is not equal to old_val, no exchange was done. * * @param a_ptr * @param new_val Value to assign to atomic. * @param old_val Value to compare for. * * @return value of atomic. */ static inline int32_t bs_atomic_int32_cas(bs_atomic_int32_t *a_ptr, int32_t new_val, int32_t old_val) { #if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64) int32_t curr_val; __asm__ volatile ( "lock; \n" "cmpxchgl %2, %1; \n" : "=a" (curr_val), "=m" (a_ptr->v) : "r" (new_val), "m" (a_ptr->v), "0" (old_val) ); return curr_val; #elif defined(__BS_ATOMIC_C11_STDATOMIC) if (atomic_compare_exchange_strong(&a_ptr->v, &old_val, new_val)) { return old_val; } else { return atomic_load(&a_ptr->v); } #else #error "Unsupported compiler or architecture." #endif } /* ------------------------------------------------------------------------- */ /** Exchange value with atomic. */ static inline void bs_atomic_int32_xchg(bs_atomic_int32_t *a_ptr, int32_t *v_ptr) { #if defined(__BS_ATOMIC_GCC_ASM_i386) || defined(__BS_ATOMIC_GCC_ASM_x86_64) __asm__ volatile ( "lock; \n" "xchgl %0, %%eax; \n" : "+m" (a_ptr->v), "=a" (*v_ptr) : "m" (a_ptr->v), "a" (*v_ptr) ); #elif defined(__BS_ATOMIC_C11_STDATOMIC) *v_ptr = atomic_exchange(&a_ptr->v, *v_ptr); #else #error "Unsupported compiler or architecture." #endif } /* == 64-bit integer ======================================================= */ /* ------------------------------------------------------------------------- */ /** Set atomic value to v. */ static inline void bs_atomic_int64_set(bs_atomic_int64_t *a_ptr, int64_t v) { #if defined(__BS_ATOMIC_GCC_ASM_i386) __asm__ volatile ( "movl %%ebx, %%esi; \n" /* backup ebx, because -fPIC */ "movl %%eax, %%ebx; \n" /* v goes into ebx:ecx */ "movl %%edx, %%ecx; \n" "1: \n" "movl (%%edi), %%eax; \n" /* load atom value into edx:eax */ "movl 4(%%edi), %%edx; \n" "lock cmpxchg8b (%%edi); \n" "jnz 1b; \n" /* spin until write was atomic. */ "movl %%esi, %%ebx; \n" : : "D" (&a_ptr->v), "A" (v) /* "A" is edx:eax */ : "ecx", "memory", "esi" ); #elif defined(__BS_ATOMIC_GCC_ASM_x86_64) __asm__ volatile ( "movq %1, %0; \n" : "=m" (a_ptr->v) : "r" (v) : "memory" ); #elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64) atomic_store(&a_ptr->v, v); #elif defined(__BS_ATOMIC_INT64_MUTEX) pthread_mutex_lock(&_bs_atomic_mutex); a_ptr->v = v; pthread_mutex_unlock(&_bs_atomic_mutex); #else #error "Unsupported compiler or architecture." #endif } /* ------------------------------------------------------------------------- */ /** Get value of atomic. */ static inline int64_t bs_atomic_int64_get(bs_atomic_int64_t *a_ptr) { #if defined(__BS_ATOMIC_GCC_ASM_i386) int64_t rv = 0; __asm __volatile ( "movl %%ebx, %%esi; \n" /* backup ebx, becuase -fPIC */ "1: \n" "movl (%%edi), %%eax; \n" /* load atom value into edx:eax */ "movl 4(%%edi), %%edx; \n" "movl %%eax, %%ebx; \n" /* store in ebx:ecx for verfication */ "movl %%edx, %%ecx; \n" "lock cmpxchg8b (%%edi); \n" "jnz 1b; \n" /* spin until read was atomic */ "movl %%esi, %%ebx; \n" : "=A" (rv) /* "A" is edx:eax */ : "D" (&a_ptr->v) : "ecx", "memory", "esi" ); return rv; #elif defined(__BS_ATOMIC_GCC_ASM_x86_64) int64_t rv = 0; __asm__ volatile ( "movq %1, %0; \n" : "=r" (rv) : "m" (a_ptr->v) ); return rv; #elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64) return atomic_load(&a_ptr->v); #elif defined(__BS_ATOMIC_INT64_MUTEX) int64_t rv; pthread_mutex_lock(&_bs_atomic_mutex); rv = a_ptr->v; pthread_mutex_unlock(&_bs_atomic_mutex); return rv; #else #error "Unsupported compiler or architecture." #endif } /* ------------------------------------------------------------------------- */ /** Add value to atomic. */ static inline int64_t bs_atomic_int64_add(bs_atomic_int64_t *a_ptr, int64_t v) { #if defined(__BS_ATOMIC_GCC_ASM_i386) int64_t rv; __asm__ volatile ( "pushl %%ebx; \n" /* must backup ebx, because -fPIC */ "1: \n" "movl (%%edi), %%eax; \n" /* current atomic into edx:eax */ "movl 4(%%edi), %%edx; \n" "movl %%eax, %%ebx; \n" /* for verification, copy to ecx:ebx */ "movl %%edx, %%ecx; \n" "addl (%%esi), %%ebx; \n" /* add 'v' */ "adcl 4(%%esi), %%ecx; \n" "lock cmpxchg8b (%%edi); \n" "jnz 1b; \n" /* spin until results op was atomic */ "movl %%ebx, %%eax; \n" /* copy result to edx:eax */ "movl %%ecx, %%edx; \n" "popl %%ebx; \n" : "=A" (rv) : "D" (&a_ptr->v), "S" (&v) : "ecx", "memory" ); return rv; #elif defined(__BS_ATOMIC_GCC_ASM_x86_64) int64_t old_v = v; __asm__ volatile ( "lock; \n" "xaddq %0, %1; \n" : "=r" (v) : "m" (a_ptr->v), "0" (v) : "memory" ); return v + old_v; #elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64) return atomic_fetch_add(&a_ptr->v, v) + v; #elif defined(__BS_ATOMIC_INT64_MUTEX) int64_t rv; pthread_mutex_lock(&_bs_atomic_mutex); a_ptr->v +=v; rv = a_ptr->v; pthread_mutex_unlock(&_bs_atomic_mutex); return rv; #else #error "Unsupported compiler or architecture." #endif } /* ------------------------------------------------------------------------- */ /** * Compare-And-Swap value with atomic. * * If the returned value is not equal to old_val, no exchange was done. * * @param a_ptr * @param new_val Value to assign to atomic. * @param old_val Value to compare for. * * @return value of atomic. * */ static inline int64_t bs_atomic_int64_cas(bs_atomic_int64_t *a_ptr, int64_t new_val, int64_t old_val) { #if defined(__BS_ATOMIC_GCC_ASM_i386) int64_t rv; __asm __volatile ( "movl 4(%%esi), %%ecx; \n" "movl (%%esi), %%esi; \n" "xchgl %%esi, %%ebx; \n" "lock cmpxchg8b (%%edi); \n" "xchgl %%esi, %%ebx; \n" : "=A" (rv) : "D" (&a_ptr->v), "A" (old_val), "S" (&new_val) : "ecx", "memory" ); return rv; #elif defined(__BS_ATOMIC_GCC_ASM_x86_64) int64_t curr_val; __asm__ volatile ( "lock; \n" "cmpxchgq %2, %1; \n" : "=a" (curr_val), "=m" (a_ptr->v) : "r" (new_val), "m" (a_ptr->v), "0" (old_val) : "memory" ); return curr_val; #elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64) if (atomic_compare_exchange_strong(&a_ptr->v, &old_val, new_val)) { return old_val; } else { return atomic_load(&a_ptr->v); } #elif defined(__BS_ATOMIC_INT64_MUTEX) int64_t rv; pthread_mutex_lock(&_bs_atomic_mutex); if (a_ptr->v == old_val) { a_ptr->v = new_val; rv = old_val; } else { rv = a_ptr->v; } pthread_mutex_unlock(&_bs_atomic_mutex); return rv; #else #error "Unsupported compiler or architecture." #endif } /* ------------------------------------------------------------------------- */ /** Exchange value with atomic. */ static inline void bs_atomic_int64_xchg(bs_atomic_int64_t *a_ptr, int64_t *v_ptr) { #if defined(__BS_ATOMIC_GCC_ASM_i386) // Exchange using CAS. int64_t curr_value; do { curr_value = bs_atomic_int64_get(a_ptr); } while (curr_value != bs_atomic_int64_cas(a_ptr, *v_ptr, curr_value)); *v_ptr = curr_value; #elif defined(__BS_ATOMIC_GCC_ASM_x86_64) __asm__ volatile ( "lock; \n" "xchgq %0, %%rax; \n" : "+m" (a_ptr->v), "=a" (*v_ptr) : "m" (a_ptr->v), "a" (*v_ptr) ); #elif defined(__BS_ATOMIC_C11_STDATOMIC_INT64) *v_ptr = atomic_exchange(&a_ptr->v, *v_ptr); #elif defined(__BS_ATOMIC_INT64_MUTEX) pthread_mutex_lock(&_bs_atomic_mutex); int64_t tmp = a_ptr->v; a_ptr->v = *v_ptr; *v_ptr = tmp; pthread_mutex_unlock(&_bs_atomic_mutex); #else #error "Unsupported compiler or architecture." #endif } #endif // defined(__cplusplus) /** Unit test set. */ extern const bs_test_set_t bs_atomic_test_set; #endif /* __LIBBASE_ATOMIC_H__ */ /* == End of atomic.h ====================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/vector.h0000644000175100017510000000370015222270712023052 0ustar runnerrunner/* ========================================================================= */ /** * @file vector.h * Methods and definitions to work a bit more conveniently with vectors in C. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_VECTOR_H__ #define __LIBBASE_VECTOR_H__ #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Two-dimension vector with floating point dimensions. */ typedef struct { /** X dimension. */ double x; /** Y dimension. */ double y; } bs_vector_2f_t; /** Initializer for the 2-dimensional vector with floating points. */ #define BS_VECTOR_2F(_x, _y) ((bs_vector_2f_t){ .x = _x, .y = _y }) /** * Adds two vectors. * * @param v1 * @param v2 * * @return v1 + v2. */ static inline bs_vector_2f_t bs_vec_add_2f(const bs_vector_2f_t v1, const bs_vector_2f_t v2) { return BS_VECTOR_2F(v1.x + v2.x, v1.y + v2.y); } /** * Scalar multiplication of a vector. * * @param scale * @param v * * @return scale * v. */ static inline bs_vector_2f_t bs_vec_mul_2f(const double scale, const bs_vector_2f_t v) { return BS_VECTOR_2F(scale * v.x, scale * v.y); } #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_VECTOR_H__ */ /* == End of vector.h ====================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/thread.h0000644000175100017510000000426115222270712023022 0ustar runnerrunner/* ========================================================================= */ /** * @file thread.h * Wrappers around libpthread, to consolidate logging and error handling. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_THREAD_H__ #define __LIBBASE_THREAD_H__ #include #include #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Initializes as PTHREAD_MUTEX_NORMAL mutex, with error handling. */ bool bs_mutex_init(pthread_mutex_t *mutex_ptr); /** Destroys the mutex, with error handling. Aborts on error. */ void bs_mutex_destroy(pthread_mutex_t *mutex_ptr); /** Locks the mutex, with error handling: aborts on error. */ void bs_mutex_lock(pthread_mutex_t *mutex_ptr); /** Unlocks the mutex, with error handling: aborts on error. */ void bs_mutex_unlock(pthread_mutex_t *mutex_ptr); /** Initializes the condition, with default attributes. With error handling. */ bool bs_cond_init(pthread_cond_t *condition_ptr); /** Destroys the condition, with error handling: abors on error. */ void bs_cond_destroy(pthread_cond_t *condition_ptr); /** Broadcasts the condition. */ void bs_cond_broadcast(pthread_cond_t *condition_ptr); /** * Waits for condition, with error handling. Returns true, if the condition * was signalled or broadcasted, and false if it timed out. */ bool bs_cond_timedwait(pthread_cond_t *condition_ptr, pthread_mutex_t *mutex_ptr, uint64_t usec); #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_THREAD_H__ */ /* == End of thread.h ====================================================== */ wlmaker-0.8.1/submodules/libbase/include/libbase/log_wrappers.h0000644000175100017510000000563015222270712024260 0ustar runnerrunner/* ========================================================================= */ /** * @file log_wrappers.h * Wraps common methods to conveniently log errors. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __LIBBASE_LOG_WRAPPERS_H__ #define __LIBBASE_LOG_WRAPPERS_H__ #include "log.h" #include #ifdef __cplusplus extern "C" { #endif // __cplusplus /** Helper: Calls calloc(3), and wraps errors for the given file & line. */ static inline void *_logged_calloc( const char *filename_ptr, int line_no, size_t nmemb, size_t size) { void *ptr = calloc(nmemb, size); if ((NULL == ptr) && (BS_ERROR >= bs_log_severity)) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed calloc(%zu, %zu)", nmemb, size); } return ptr; } /** Calls calloc(3) and logs error with ERROR severity. */ #define logged_calloc(nmemb, size) \ _logged_calloc(__FILE__, __LINE__, nmemb, size) /** Helper: Calls malloc(3) and logs errors for given file & line. */ static inline void *_logged_malloc( const char *filename_ptr, int line_no, size_t size) { void *ptr = malloc(size); if ((NULL == ptr) && (BS_ERROR >= bs_log_severity)) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed malloc(%zu)", size); } return ptr; } /** Calls malloc(3) and logs error with ERROR severity. */ #define logged_malloc(size) \ _logged_malloc(__FILE__, __LINE__, size) /** Helper: Acts like strdup(3) and logs errors for given file & line. */ static inline char *_logged_strdup( const char *filename_ptr, int line_no, const char *str) { size_t len = strlen(str) + 1; char *new_str = malloc(len); if (NULL != new_str) { memcpy(new_str, str, len); } else if (BS_ERROR >= bs_log_severity) { bs_log_write((bs_log_severity_t)(BS_ERROR | BS_ERRNO), filename_ptr, line_no, "Failed strdup(%s)", str); } return new_str; } /** Calls strdup(3) and logs error with ERROR severity. */ #define logged_strdup(str) \ _logged_strdup(__FILE__, __LINE__, str) #ifdef __cplusplus } // extern "C" #endif // __cplusplus #endif /* __LIBBASE_LOG_WRAPPERS_H__ */ /* == End of log_wrappers.h ================================================== */ wlmaker-0.8.1/submodules/libbase/.git0000644000175100017510000000005615222270711017137 0ustar runnerrunnergitdir: ../../.git/modules/submodules/libbase wlmaker-0.8.1/submodules/libbase/BACKLOG.md0000644000175100017510000000116115222270712017736 0ustar runnerrunner# libbase idea/feature backlog ## Overall * Split non-core functionality out of libbase/libbase.h and require explicit includes. While doing so, benchmark compilation time. ## bs_arg * Support "extra" arguments of no type, eg. for `--help` or `--version`. ## bs_file * Merge write_buffer and read_buffer with bs_dynbuf. ## bs_test * When skipping tests, don't clutter the output with the full skipped test. * Store the failure position for failed tests, include in test summary. * Make it easy to run a single case, or a single set. * Use bs_test_case_init(), bs_test_case_fini() for the overloaded prepare/report. wlmaker-0.8.1/submodules/libbase/CONTRIBUTING.md0000644000175100017510000000205415222270712020605 0ustar runnerrunner# How to Contribute We would love to accept your patches and contributions to this project. ## Before you begin ### Sign our Contributor License Agreement Contributions to this project must be accompanied by a [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project. If you or your current employer have already signed the Google CLA (even if it was for a different project), you probably don't need to do it again. Visit to see your current agreements or to sign a new one. ### Review our Community Guidelines This project follows [Google's Open Source Community Guidelines](https://opensource.google/conduct/). ## Contribution process ### Code Reviews All submissions, including submissions by project members, require review. We use [GitHub pull requests](https://docs.github.com/articles/about-pull-requests) for this purpose. wlmaker-0.8.1/submodules/libbase/tests/0000755000175100017510000000000015222270712017515 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/tests/subprocess_test_success.c0000644000175100017510000000261015222270712024637 0ustar runnerrunner/* ========================================================================= */ /** * @file subprocess_test_success.c * Test executable for subprocess module: Exit successfully, with stdout/err. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include /** A test program that returns a successful code, if args were all correct. */ int main(int argc, char** argv) { if (2 != argc) { fprintf(stderr, "Expecting 1 arguments.\n"); return EXIT_FAILURE; } const char *value_ptr = getenv("SUBPROCESS_ENV"); fprintf(stdout, "test stdout: %s\nenv: %s\n", basename(argv[0]), value_ptr ? value_ptr : "(null)"); fprintf(stderr, "test stderr: %s\n", argv[1]); return EXIT_SUCCESS; } /* == End of subprocess_test_success.c ===================================== */ wlmaker-0.8.1/submodules/libbase/tests/libbase_test.c0000644000175100017510000002321215222270712022321 0ustar runnerrunner/* ========================================================================= */ /** * @file libbase_test.c * Unit tests for libbase. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #if !defined(BS_TEST_DATA_DIR) /** Directory root for looking up test data. See @ref bs_test_resolve_path. */ #define BS_TEST_DATA_DIR "./" #endif // BS_TEST_DATA_DIR #if !defined(BS_TEST_BINARY_DIR) #define BS_TEST_BINARY_DIR "." #endif // BS_TEST_BINARY_DIR static void test_assert(bs_test_t *test_ptr); static void test_def(bs_test_t *test_ptr); /** Further tests of definitions, without .c file. */ static const bs_test_case_t bs_header_only_test_cases[] = { { true, "assert", test_assert }, { true, "def", test_def }, { false, NULL, NULL } }; /** Test set of the cases without .c file. */ static const bs_test_set_t bs_header_only_test_set = BS_TEST_SET( true, "header_only", bs_header_only_test_cases); static void test_failure(bs_test_t *test_ptr); static void test_sigpipe(bs_test_t *test_ptr); static void test_success(bs_test_t *test_ptr); static void test_success_cmdline(bs_test_t *test_ptr); static void test_success_twice(bs_test_t *test_ptr); static const char *test_args[] = { "binary", "alpha", NULL }; const bs_test_case_t bs_subprocess_extra_test_cases[] = { { true, "failure", test_failure }, { true, "sigpipe", test_sigpipe }, { true, "success", test_success }, { true, "success_cmdline", test_success_cmdline }, { true, "success_twice", test_success_twice }, { false , NULL, NULL } }; static const bs_test_set_t bs_subprocess_extra_test_set = BS_TEST_SET( true, "subprocess_extra", bs_subprocess_extra_test_cases); /* ------------------------------------------------------------------------- */ /** Tests the functions of 'assert.h' */ void test_assert(bs_test_t *test_ptr) { void *ptr = test_assert; BS_TEST_VERIFY_EQ(test_ptr, ptr, BS_ASSERT_NOTNULL(ptr)); } /* ------------------------------------------------------------------------- */ /** Tests the functions of 'def.h' */ void test_def(bs_test_t *test_ptr) { BS_TEST_VERIFY_EQ(test_ptr, 1, BS_MIN(1, 2)); BS_TEST_VERIFY_EQ(test_ptr, 2, BS_MAX(1, 2)); } /* ------------------------------------------------------------------------- */ void test_failure(bs_test_t *test_ptr) { test_args[0] = BS_TEST_BINARY_DIR "/subprocess_test_failure"; bs_subprocess_t *sp_ptr = bs_subprocess_create( test_args[0], test_args, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr)); int exit_status, signal_number; while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) { // Don't busy-loop -- just wait a little. poll(NULL, 0, 10); } BS_TEST_VERIFY_EQ(test_ptr, 42, exit_status); BS_TEST_VERIFY_EQ(test_ptr, 0, signal_number); bs_subprocess_destroy(sp_ptr); } /* ------------------------------------------------------------------------- */ void test_sigpipe(bs_test_t *test_ptr) { test_args[0] = BS_TEST_BINARY_DIR "/subprocess_test_sigpipe"; bs_subprocess_t *sp_ptr = bs_subprocess_create( test_args[0], test_args, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr)); int exit_status, signal_number; while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) { // Don't busy-loop -- just wait a little. poll(NULL, 0, 10); } BS_TEST_VERIFY_EQ(test_ptr, INT_MIN, exit_status); BS_TEST_VERIFY_EQ(test_ptr, SIGPIPE, signal_number); bs_subprocess_destroy(sp_ptr); } /* ------------------------------------------------------------------------- */ void test_success(bs_test_t *test_ptr) { const bs_subprocess_environment_variable_t envs[] = { { "SUBPROCESS_ENV", "WORKS" }, { NULL, NULL } }; test_args[0] = BS_TEST_BINARY_DIR "/subprocess_test_success"; bs_subprocess_t *sp_ptr = bs_subprocess_create( test_args[0], test_args, envs); BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr)); int exit_status, signal_number; while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) { // Don't busy-loop -- just wait a little. poll(NULL, 0, 10); } BS_TEST_VERIFY_EQ(test_ptr, 0, exit_status); BS_TEST_VERIFY_EQ(test_ptr, 0, signal_number); int stdout_fd, stderr_fd; bs_subprocess_get_fds(sp_ptr, NULL, &stdout_fd, &stderr_fd); bs_dynbuf_t stdout_buf; bs_dynbuf_init(&stdout_buf, 1024, SIZE_MAX); bs_dynbuf_read(&stdout_buf, stdout_fd); bs_dynbuf_t stderr_buf; bs_dynbuf_init(&stderr_buf, 1024, SIZE_MAX); bs_dynbuf_read(&stderr_buf, stderr_fd); BS_TEST_VERIFY_STREQ( test_ptr, "test stdout: subprocess_test_success\nenv: WORKS\n", stdout_buf.data_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "test stderr: alpha\n", stderr_buf.data_ptr); bs_subprocess_destroy(sp_ptr); bs_dynbuf_fini(&stderr_buf); bs_dynbuf_fini(&stdout_buf); } /* ------------------------------------------------------------------------- */ void test_success_cmdline(bs_test_t *test_ptr) { bs_subprocess_t *sp_ptr = bs_subprocess_create_cmdline( "SUBPROCESS_ENV=CMD " BS_TEST_BINARY_DIR "/subprocess_test_success alpha"); BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr); BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr)); int exit_status, signal_number; while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) { // Don't busy-loop -- just wait a little. poll(NULL, 0, 10); } BS_TEST_VERIFY_EQ(test_ptr, 0, exit_status); BS_TEST_VERIFY_EQ(test_ptr, 0, signal_number); int stdout_fd, stderr_fd; bs_subprocess_get_fds(sp_ptr, NULL, &stdout_fd, &stderr_fd); bs_dynbuf_t stdout_buf; bs_dynbuf_init(&stdout_buf, 1024, SIZE_MAX); bs_dynbuf_read(&stdout_buf, stdout_fd); bs_dynbuf_t stderr_buf; bs_dynbuf_init(&stderr_buf, 1024, SIZE_MAX); bs_dynbuf_read(&stderr_buf, stderr_fd); BS_TEST_VERIFY_STREQ( test_ptr, "test stdout: subprocess_test_success\nenv: CMD\n", stdout_buf.data_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "test stderr: alpha\n", stderr_buf.data_ptr); bs_subprocess_destroy(sp_ptr); bs_dynbuf_fini(&stderr_buf); bs_dynbuf_fini(&stdout_buf); } /* ------------------------------------------------------------------------- */ void test_success_twice(bs_test_t *test_ptr) { test_args[0] = BS_TEST_BINARY_DIR "/subprocess_test_success"; bs_subprocess_t *sp_ptr = bs_subprocess_create( test_args[0], test_args, NULL); BS_TEST_VERIFY_NEQ(test_ptr, NULL, sp_ptr); for (int i = 0; i < 2; ++i) { // Verify that starting the process again works. BS_TEST_VERIFY_TRUE(test_ptr, bs_subprocess_start(sp_ptr)); int exit_status, signal_number; while (!bs_subprocess_terminated(sp_ptr, &exit_status, &signal_number)) { // Don't busy-loop -- just wait a little. poll(NULL, 0, 10); } BS_TEST_VERIFY_EQ(test_ptr, 0, exit_status); BS_TEST_VERIFY_EQ(test_ptr, 0, signal_number); int stdout_fd, stderr_fd; bs_subprocess_get_fds(sp_ptr, NULL, &stdout_fd, &stderr_fd); bs_dynbuf_t stdout_buf; bs_dynbuf_init(&stdout_buf, 1024, SIZE_MAX); bs_dynbuf_read(&stdout_buf, stdout_fd); bs_dynbuf_t stderr_buf; bs_dynbuf_init(&stderr_buf, 1024, SIZE_MAX); bs_dynbuf_read(&stderr_buf, stderr_fd); BS_TEST_VERIFY_STREQ( test_ptr, "test stdout: subprocess_test_success\nenv: (null)\n", stdout_buf.data_ptr); BS_TEST_VERIFY_STREQ( test_ptr, "test stderr: alpha\n", stderr_buf.data_ptr); bs_dynbuf_fini(&stderr_buf); bs_dynbuf_fini(&stdout_buf); } bs_subprocess_destroy(sp_ptr); } /* ========================================================================= */ /** Main program, runs all unit tests. */ int main(int argc, const char **argv) { const bs_test_param_t params = { .test_data_dir_ptr = BS_TEST_DATA_DIR }; const bs_test_set_t *sets[] = { &bs_arg_test_set, &bs_atomic_test_set, &bs_avltree_test_set, &bs_dequeue_test_set, &bs_dllist_test_set, &bs_dynbuf_test_set, &bs_file_test_set, &bs_gfxbuf_test_set, &bs_gfxbuf_xpm_test_set, &bs_header_only_test_set, &bs_log_test_set, &bs_ptr_set_test_set, &bs_ptr_stack_test_set, &bs_ptr_vector_test_set, &bs_ref_test_set, &bs_strutil_test_set, &bs_subprocess_extra_test_set, &bs_subprocess_test_set, &bs_test_test_set, &bs_time_test_set, NULL }; return bs_test_sets(sets, argc, argv, ¶ms); } /* == End of libbase_test.c ================================================ */ wlmaker-0.8.1/submodules/libbase/tests/subprocess_test_hang.c0000644000175100017510000000202115222270712024100 0ustar runnerrunner/* ========================================================================= */ /** * @file subprocess_test_hang.c * Test executable for subprocess module: Keep looping, does not exit. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include /** A program that will never exit. */ int main() { while (true) { poll(NULL, 0, 100); } } /* == End of subprocess_test_hang.c ======================================== */ wlmaker-0.8.1/submodules/libbase/tests/subprocess_test_sigpipe.c0000644000175100017510000000173715222270712024640 0ustar runnerrunner/* ========================================================================= */ /** * @file subprocess_test_sigpipe.c * Test executable for subprocess module: Killed by (arbitrarily chosen) * SIGPIPE. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include /** A test program that raises a signal. */ int main() { raise(SIGPIPE); } /* == End of subprocess_test_sigpipe.c ===================================== */ wlmaker-0.8.1/submodules/libbase/tests/data/0000755000175100017510000000000015222270712020426 5ustar runnerrunnerwlmaker-0.8.1/submodules/libbase/tests/data/string.plist0000644000175100017510000000003515222270712023007 0ustar runnerrunner// With a comment. file_valuewlmaker-0.8.1/submodules/libbase/tests/data/array.plist0000644000175100017510000000023715222270712022623 0ustar runnerrunner// A toplevel array. We do currently NOT enforce the same type. ( elem0, // a string. (subelem0, subelem1), // A sub-array. {subkey0 = (val0, val1)} ) wlmaker-0.8.1/submodules/libbase/tests/data/abcd.txt0000644000175100017510000000000515222270712022053 0ustar runnerrunnerabcd wlmaker-0.8.1/submodules/libbase/tests/data/gfxbuf_equals.png0000644000175100017510000000012715222270712023767 0ustar runnerrunner‰PNG  IHDR�wSÞbKGDÿÿÿ ½§“ IDAT™chpP$áþDO‘IEND®B`‚wlmaker-0.8.1/submodules/libbase/tests/data/dict.plist0000644000175100017510000000025515222270712022430 0ustar runnerrunner// comment. { key0 = "value0"; // comment. // more comment. subdict = { // comment. key1 = value1 // comment. }; // comment. "quoted+key" = value; } //more.wlmaker-0.8.1/submodules/libbase/tests/subprocess_test_failure.c0000644000175100017510000000164615222270712024626 0ustar runnerrunner/* ========================================================================= */ /** * @file subprocess_test_failure.c * Test executable for subprocess module: Exit with failure code. * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** Returns an error status. */ int main() { return 42; } /* == End of subprocess_test_failure.c ===================================== */ wlmaker-0.8.1/submodules/libbase/tests/libbase_benchmark.c0000644000175100017510000000220215222270712023270 0ustar runnerrunner/* ========================================================================= */ /** * @file libbase_benchmark.c * Benchmarks for libbase (in the form of unit test cases). * * @copyright * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include /** Main program, runs all unit tests. */ int main(int argc, const char **argv) { const bs_test_param_t param = {}; const bs_test_set_t* sets[] = { &bs_gfxbuf_benchmarks_set, NULL }; return bs_test_sets(sets, argc, argv, ¶m); } /* == End of libbase_benchmark.c =========================================== */ wlmaker-0.8.1/submodules/libbase/tests/plist_test.c0000644000175100017510000000262715222270712022062 0ustar runnerrunner/* ========================================================================= */ /** * @file plist_test.c * * @copyright * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #if !defined(TEST_DATA_DIR) /** Directory root for looking up test data. See `bs_test_resolve_path`. */ #define TEST_DATA_DIR "./" #endif // TEST_DATA_DIR /** Main program, runs the unit tests. */ int main(__UNUSED__ int argc, __UNUSED__ const char **argv) { const bs_test_param_t params = { .test_data_dir_ptr = TEST_DATA_DIR }; const bs_test_set_t* set_ptrs[] = { &bspl_decode_test_set, &bspl_model_test_set, &bspl_plist_test_set, NULL }; return bs_test_sets(set_ptrs, argc, argv, ¶ms); } /* == End of plist_test.c ================================================== */ wlmaker-0.8.1/submodules/libbase/tests/CMakeLists.txt0000644000175100017510000000471715222270712022266 0ustar runnerrunner# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. add_executable( libbase_test libbase_test.c) target_link_libraries( libbase_test PRIVATE libbase libbase_compiler_flags) # Refer to the source path for relative testdata. target_compile_definitions( libbase_test PUBLIC BS_TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}") target_compile_definitions( libbase_test PUBLIC BS_TEST_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_executable( libbase_benchmark libbase_benchmark.c) target_link_libraries( libbase_benchmark PRIVATE libbase libbase_compiler_flags) add_executable( subprocess_test_hang subprocess_test_hang.c) target_link_libraries( subprocess_test_hang PRIVATE libbase_compiler_flags) add_executable( subprocess_test_failure subprocess_test_failure.c) target_link_libraries( subprocess_test_failure PRIVATE libbase_compiler_flags) add_executable( subprocess_test_sigpipe subprocess_test_sigpipe.c) target_link_libraries( subprocess_test_sigpipe PRIVATE libbase_compiler_flags) add_executable( subprocess_test_success subprocess_test_success.c) target_link_libraries( subprocess_test_success PRIVATE libbase_compiler_flags) add_executable( plist_test plist_test.c) target_link_libraries( plist_test PRIVATE libbase libbase_plist libbase_compiler_flags) target_include_directories(plist_test PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/") target_compile_definitions( plist_test PUBLIC TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data") add_test(NAME libbase_test COMMAND libbase_test) add_test(NAME plist_test COMMAND plist_test) if(iwyu_path_and_options) set_target_properties( libbase_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( libbase_benchmark PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") set_target_properties( plist_test PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}") endif() wlmaker-0.8.1/submodules/libbase/.gitignore0000644000175100017510000000030715222270712020343 0ustar runnerrunner*~ CMakeFiles/* build/* build-clang/* CMakeCache.txt cmake_install.cmake Makefile libbase.a libbase_test subprocess_test_failure subprocess_test_hang subprocess_test_sigpipe subprocess_test_success wlmaker-0.8.1/submodules/libbase/CODE_OF_CONDUCT.md0000644000175100017510000001062315222270712021154 0ustar runnerrunner# Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. This Code of Conduct also applies outside the project spaces when the Project Steward has a reasonable belief that an individual's behavior may have a negative impact on the project or its community. ## Conflict Resolution We do not believe that all conflict is bad; healthy debate and disagreement often yield positive results. However, it is never okay to be disrespectful or to engage in behavior that violates the project’s code of conduct. If you see someone violating the code of conduct, you are encouraged to address the behavior directly with those involved. Many issues can be resolved quickly and easily, and this gives people more control over the outcome of their dispute. If you are unable to resolve the matter for any reason, or if the behavior is threatening or harassing, report it. We are dedicated to providing an environment where participants feel welcome and safe. Reports should be directed to *[PROJECT STEWARD NAME(s) AND EMAIL(s)]*, the Project Steward(s) for *[PROJECT NAME]*. It is the Project Steward’s duty to receive and address reported violations of the code of conduct. They will then work with a committee consisting of representatives from the Open Source Programs Office and the Google Open Source Strategy team. If for any reason you are uncomfortable reaching out to the Project Steward, please email opensource@google.com. We will investigate every complaint, but you may not receive a direct response. We will use our discretion in determining when and how to follow up on reported incidents, which may range from not taking action to permanent expulsion from the project and project-sponsored spaces. We will notify the accused of the report and provide them an opportunity to discuss it before any action is taken. The identity of the reporter will be omitted from the details of the report supplied to the accused. In potentially harmful situations, such as ongoing harassment or threats to anyone's safety, we may take action without notice. ## Attribution This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/ wlmaker-0.8.1/submodules/libbase/iwyu-mappings.imp0000644000175100017510000000045515222270712021677 0ustar runnerrunner# -*- mode: python; -*- [ # Weird iwyu complaint to add "#include // for tm" { "symbol": ["tm", "public", "", "public" ]}, # Otherwise iwyu is asking for glibc-only { "symbol": ["__GLIBC__", "public", "\"analyzer.h\"", "public" ]}, ] wlmaker-0.8.1/submodules/libbase/CMakeLists.txt0000644000175100017510000001333515222270712021120 0ustar runnerrunner# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Suggested configuration: # cmake -DCMAKE_INSTALL_PREFIX:PATH=${HOME}/.local -Dconfig_WERROR=ON -Dconfig_DEBUG=ON -DIWYU_MODE=FAIL -B build cmake_minimum_required(VERSION 3.25) project(libbase VERSION 1.0 DESCRIPTION "A small standard library for C" LANGUAGES C) # Requires out-of-source builds. file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" location_path) if(EXISTS "${location_path}") message( FATAL_ERROR "You cannot build into a source directory (containing a CMakeLists.txt file).\n" "Please make a build subdirectory, for example:\n" "cmake -B build\n" "(cd build && make)") endif() # Defaults to /usr/local/lib for installation. include(CTest) include(GNUInstallDirs) include(FindCurses) if(NOT CURSES_FOUND) message( FATAL_ERROR "Failed to find curses nor ncurses file and library.") endif() # Configuration. Remove CMakeCache.txt to rerun... option(config_DEBUG "Include debugging information" ON) option(config_OPTIM "Optimizations" OFF) option(config_COVERAGE "Build with test coverage" OFF) option(config_WERROR "Make all compiler warnings into errors." OFF) set(IWYU_MODE OFF CACHE STRING "Whether to run `iwyu`. OFF, WARN or FAIL.") set_property(CACHE IWYU_MODE PROPERTY STRINGS "OFF" "WARN" "FAIL") # Toplevel compile options, for Clang and GCC. add_library(libbase_compiler_flags INTERFACE) if(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU") if(config_DEBUG) target_compile_options(libbase_compiler_flags INTERFACE -ggdb -DDEBUG) endif() if(config_OPTIM) target_compile_options(libbase_compiler_flags INTERFACE -O2) else() target_compile_options(libbase_compiler_flags INTERFACE -O0) endif() target_compile_options(libbase_compiler_flags INTERFACE -Wall -Wextra) if(config_WERROR) target_compile_options(libbase_compiler_flags INTERFACE -Werror) endif() # CMake provides absolute paths to GCC, hence the __FILE__ macro includes the # full path. This option resets it to a path relative to project source. target_compile_options(libbase_compiler_flags INTERFACE -fmacro-prefix-map=${PROJECT_SOURCE_DIR}/src=.) # Target system. if(LINUX) target_compile_options(libbase_compiler_flags INTERFACE -D__LIBBASE_LINUX) endif() # Run iwyu when available. if((IWYU_MODE STREQUAL "WARN") OR (IWYU_MODE STREQUAL "FAIL")) find_program(iwyu_executable NAMES include-what-you-use iwyu REQUIRED) if(IWYU_MODE STREQUAL "FAIL") set(iwyu_error "1") else() set(iwyu_error "0") endif() if(iwyu_executable) set(iwyu_path_and_options ${iwyu_executable} -Xiwyu --error=${iwyu_error} -Xiwyu --transitive_includes_only -Xiwyu --mapping_file=${PROJECT_SOURCE_DIR}/iwyu-mappings.imp) endif() message(STATUS "Configured iwyu (${iwyu_executable}) to run in mode \"${IWYU_MODE}\"") elseif(IWYU_MODE STREQUAL "OFF") # Permitted value, we don't set iwyu_path_and_options. else() message(FATAL_ERROR "Invalid value for IWYU_MODE: \"${IWYU_MODE}\". Must be OFF, WARN or FAIL") endif() endif() set(CMAKE_C_STANDARD 11) find_package(PkgConfig REQUIRED) pkg_check_modules(CAIRO REQUIRED IMPORTED_TARGET cairo>=1.16.0) # Create pkg-config file from the template. configure_file(libbase.pc.in ${CMAKE_BINARY_DIR}/libbase.pc @ONLY) add_subdirectory(src) add_subdirectory(src/plist) add_subdirectory(tools) # Add 'install' target, but only if we're the toplevel project. if(CMAKE_PROJECT_NAME STREQUAL libbase) install( FILES ${CMAKE_BINARY_DIR}/libbase.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" ) endif() # Add test target, but only if we're the toplevel project. if(CMAKE_PROJECT_NAME STREQUAL libbase) add_subdirectory(tests) endif() # Adds 'doc' target, if doxygen is installed. find_package(Doxygen) if(DOXYGEN_FOUND) # Set input and output files. set(doxygen_in ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) set(doxygen_out ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) # Configure the file. configure_file(${doxygen_in} ${doxygen_out} @ONLY) add_custom_target( libbase_doc COMMAND ${DOXYGEN_EXECUTABLE} ${doxygen_out} DEPENDS ${doxygen_out} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating API documentation with Doxygen." VERBATIM) message(NOTICE "Doxygen available, adding libbase documentation to 'doc' target.") # Add as dependency to an existing 'doc' target, or create it. if(TARGET doc) add_dependencies(doc libbase_doc) else() add_custom_target(doc DEPENDS libbase_doc) endif() else() message(NOTICE "Doxygen not found. Not adding 'doc' target to generate API documentation.") endif() # Adds 'coverage' target, if configured. Needs 'gcovr'. if(config_COVERAGE) find_program(GCOVR_FOUND gcovr OPTIONAL) if(GCOVR_FOUND) target_compile_options(libbase_compiler_flags INTERFACE --coverage) target_link_options(libbase_compiler_flags INTERFACE --coverage) message(NOTICE "Coverage configured, adding 'coverage' target.") add_custom_target(coverage COMMAND gcovr -r ${CMAKE_CURRENT_SOURCE_DIR} . --exclude _deps --print-summary --html-details --html-title "Unittest coverage" -o coverage.html) else() message(WARNING "Coverage enabled, but 'govr' not found.") endif() endif() wlmaker-0.8.1/submodules/libbase/Doxyfile.in0000644000175100017510000034032115222270712020471 0ustar runnerrunner# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "My Project" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = @PROJECT_BINARY_DIR@/doc # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # By default Python docstrings are displayed as preformatted text and doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the # doxygen's special commands can be used and the contents of the docstring # documentation blocks is shown as doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines (in the resulting output). You can put ^^ in the value part of an # alias to insert a newline as if a physical newline was in the original file. # When you need a literal { or } or , in the value part of an alias you have to # escape them by means of a backslash (\), this can lead to conflicts with the # commands \{ and \} for these it is advised to use the version @{ and @} or use # a double escape (\\{ and \\}) ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 5 # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, # which efficively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If this flag is set to YES, the name of an unnamed parameter in a declaration # will be determined by the corresponding definition. By default unnamed # parameters remain unnamed in the output. # The default value is: YES. RESOLVE_UNNAMED_PARAMS = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that # are not case sensitive the option should be be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. If # EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. # Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = YES # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = @PROJECT_SOURCE_DIR@/src \ @PROJECT_SOURCE_DIR@/src/plist \ @PROJECT_SOURCE_DIR@/include/libbase \ @PROJECT_SOURCE_DIR@/include/libbase/plist # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), # *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, # *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.idl \ *.ddl \ *.odl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.cs \ *.d \ *.php \ *.php4 \ *.php5 \ *.phtml \ *.inc \ *.m \ *.markdown \ *.md \ *.mm \ *.dox \ *.py \ *.pyw \ *.f90 \ *.f95 \ *.f03 \ *.f08 \ *.f18 \ *.f \ *.for \ *.vhd \ *.vhdl \ *.ucf \ *.qsf \ *.ice # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the # clang parser (see: # http://clang.llvm.org/) for more accurate parsing at the cost of reduced # performance. This can be particularly helpful with template rich C++ code for # which doxygen's built-in parser lacks the necessary type information. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to # YES then doxygen will add the directory of each input to the include path. # The default value is: YES. CLANG_ADD_INC_PATHS = YES # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = # If clang assisted parsing is enabled you can provide the clang parser with the # path to the directory containing a file called compile_commands.json. This # file is the compilation database (see: # http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the # options used when the source files were built. This is equivalent to # specifying the -p option to a clang tool, such as clang-check. These options # will then be passed to the parser. Any options specified with CLANG_OPTIONS # will be added as well. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse_libclang=ON option for CMake. CLANG_DATABASE_PATH = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To # create a documentation set, doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: # https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path # including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. # Possible values are: png (the default) and svg (looks nicer but requires the # pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. # The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , / Packaging status Alternatively, you can obtain the source code from it's [git repository](https://github.com/phkaeser/wlmaker). Please follow the [instructions](https://github.com/phkaeser/wlmakerdoc/BUILD.md) to build from source. ## Bugs and missing features Report bugs at https://github.com/phkaeser/wlmaker/issues. wlmaker-0.8.1/doc/CMakeLists.txt0000644000175100017510000000450315222270710016075 0ustar runnerrunner# Copyright (c) 2026 Philipp Kaeser (kaeser@gubbe.ch) # Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. cmake_minimum_required(VERSION 3.13) find_package(Doxygen) if(DOXYGEN_FOUND) find_file(PLANTUML_JAR NAMES plantuml.jar HINTS ENV{PLANTUML_JAR_PATH} PATHS /usr/global/share/java/plantuml/ /usr/local/share/java/plantuml/ /usr/share/java/ /usr/local/share/java/ /usr/share/plantuml/) if(PLANTUML_JAR) set(doxygen_plantuml_jar_file "${PLANTUML_JAR}") else() set(doxygen_plantuml_jar_file "") message( NOTICE "Did not find plantuml.jar -- Will not generate class diagrams.") endif() if(config_DOXYGEN_CRITICAL) set(doxygen_warn_as_error "YES") else() set(doxygen_warn_as_error "NO") endif() # set input and output files set(doxygen_in "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in") set(doxygen_out "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile") # request to configure the file configure_file("${doxygen_in}" "${doxygen_out}" @ONLY) message(NOTICE "Doxygen available, adding 'doc' target.") add_custom_target( doc COMMAND "${DOXYGEN_EXECUTABLE}" "${doxygen_out}" DEPENDS "${doxygen_out}" "${PROJECT_BINARY_DIR}/etc/RootMenuDebian.plist" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMENT "Generating API documentation with Doxygen." VERBATIM) file( COPY "${PROJECT_SOURCE_DIR}/tests/data/toolkit/primitive_close_icon.png" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/html") file( COPY "${PROJECT_SOURCE_DIR}/tests/data/toolkit/primitive_minimize_icon.png" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/html") file( COPY "${PROJECT_SOURCE_DIR}/share/wlmaker.svg" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/html") else() message(NOTICE "Doxygen not found. Not adding 'doc' target to generate API documentation.") endif() wlmaker-0.8.1/doc/Doxyfile.in0000644000175100017510000034243215222270710015456 0ustar runnerrunner# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "wlmaker" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = @PROJECT_BINARY_DIR@/doc # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # By default Python docstrings are displayed as preformatted text and doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the # doxygen's special commands can be used and the contents of the docstring # documentation blocks is shown as doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines (in the resulting output). You can put ^^ in the value part of an # alias to insert a newline as if a physical newline was in the original file. # When you need a literal { or } or , in the value part of an alias you have to # escape them by means of a backslash (\), this can lead to conflicts with the # commands \{ and \} for these it is advised to use the version @{ and @} or use # a double escape (\\{ and \\}) ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = plist=C # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 5 # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, # which efficively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If this flag is set to YES, the name of an unnamed parameter in a declaration # will be determined by the corresponding definition. By default unnamed # parameters remain unnamed in the output. # The default value is: YES. RESOLVE_UNNAMED_PARAMS = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that # are not case sensitive the option should be be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. If # EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. # Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = @doxygen_warn_as_error@ # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = \ @PROJECT_SOURCE_DIR@/apps/ \ @PROJECT_SOURCE_DIR@/apps/libwlclient \ @PROJECT_SOURCE_DIR@/doc/commandline.md \ @PROJECT_SOURCE_DIR@/doc/config.md \ @PROJECT_SOURCE_DIR@/doc/manual.md \ @PROJECT_SOURCE_DIR@/doc/protocols.md \ @PROJECT_SOURCE_DIR@/doc/root_menu.md \ @PROJECT_SOURCE_DIR@/etc/Config.plist \ @PROJECT_SOURCE_DIR@/etc/RootMenu.plist \ @PROJECT_BINARY_DIR@/etc/RootMenuDebian.plist \ @PROJECT_SOURCE_DIR@/include/backend \ @PROJECT_SOURCE_DIR@/include/toolkit \ @PROJECT_SOURCE_DIR@/src \ @PROJECT_SOURCE_DIR@/src/backend \ @PROJECT_SOURCE_DIR@/src/input \ @PROJECT_SOURCE_DIR@/src/toolkit \ @PROJECT_SOURCE_DIR@/tool/desktop-parser \ @PROJECT_SOURCE_DIR@/tool \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), # *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, # *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.idl \ *.ddl \ *.odl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.cs \ *.d \ *.php \ *.php4 \ *.php5 \ *.phtml \ *.inc \ *.m \ *.markdown \ *.md \ *.mm \ *.dox \ *.py \ *.pyw \ *.f90 \ *.f95 \ *.f03 \ *.f08 \ *.f18 \ *.f \ *.for \ *.vhd \ *.vhdl \ *.ucf \ *.qsf \ *.ice # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = \ @PROJECT_BINARY_DIR@ \ @PROJECT_SOURCE_DIR@ # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the # clang parser (see: # http://clang.llvm.org/) for more accurate parsing at the cost of reduced # performance. This can be particularly helpful with template rich C++ code for # which doxygen's built-in parser lacks the necessary type information. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse_libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to # YES then doxygen will add the directory of each input to the include path. # The default value is: YES. CLANG_ADD_INC_PATHS = YES # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = # If clang assisted parsing is enabled you can provide the clang parser with the # path to the directory containing a file called compile_commands.json. This # file is the compilation database (see: # http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the # options used when the source files were built. This is equivalent to # specifying the -p option to a clang tool, such as clang-check. These options # will then be passed to the parser. Any options specified with CLANG_OPTIONS # will be added as well. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse_libclang=ON option for CMake. CLANG_DATABASE_PATH = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @PROJECT_SOURCE_DIR@/doc/manual.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To # create a documentation set, doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: # https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path # including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. # Possible values are: png (the default) and svg (looks nicer but requires the # pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. # The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /