From 95051d304a6502e7a526e24e458a77375f9e4fdb Mon Sep 17 00:00:00 2001 From: LAGonauta Date: Fri, 26 Mar 2021 08:22:55 -0300 Subject: [PATCH] Initial code for using winrt headers --- plugins/windows-notification/CMakeLists.txt | 72 ++++---- .../api/include/ComPtr.hpp | 18 ++ .../api/include/converter.hpp | 7 + .../api/include/shortcutcreator.h | 14 ++ .../windows-notification/api/include/win32.h | 1 + .../api/include/win32.hpp | 25 +++ .../windows-notification/api/include/winrt.h | 14 ++ .../api/src/converter.cpp | 53 ++++++ .../api/src/shortcutcreator.cpp | 160 ++++++++++++++++++ .../windows-notification/api/src/win32.cpp | 60 +++++++ .../windows-notification/api/src/winrt.cpp | 2 + .../src/DinoWinToastLib_AMD64.lib | Bin 6068 -> 0 bytes .../src/DinoWinToastLib_x86.lib | Bin 6132 -> 0 bytes plugins/windows-notification/src/plugin.vala | 21 ++- .../vapi/DinoWinToastLib.vapi | 48 ------ .../vapi/DinoWinToastTemplate.vapi | 65 ------- .../vapi/shortcutcreator.vapi | 5 + plugins/windows-notification/vapi/win32.vapi | 6 + plugins/windows-notification/vapi/winrt.vapi | 5 + 19 files changed, 422 insertions(+), 154 deletions(-) create mode 100644 plugins/windows-notification/api/include/ComPtr.hpp create mode 100644 plugins/windows-notification/api/include/converter.hpp create mode 100644 plugins/windows-notification/api/include/shortcutcreator.h create mode 100644 plugins/windows-notification/api/include/win32.h create mode 100644 plugins/windows-notification/api/include/win32.hpp create mode 100644 plugins/windows-notification/api/include/winrt.h create mode 100644 plugins/windows-notification/api/src/converter.cpp create mode 100644 plugins/windows-notification/api/src/shortcutcreator.cpp create mode 100644 plugins/windows-notification/api/src/win32.cpp create mode 100644 plugins/windows-notification/api/src/winrt.cpp delete mode 100644 plugins/windows-notification/src/DinoWinToastLib_AMD64.lib delete mode 100644 plugins/windows-notification/src/DinoWinToastLib_x86.lib delete mode 100644 plugins/windows-notification/vapi/DinoWinToastLib.vapi delete mode 100644 plugins/windows-notification/vapi/DinoWinToastTemplate.vapi create mode 100644 plugins/windows-notification/vapi/shortcutcreator.vapi create mode 100644 plugins/windows-notification/vapi/win32.vapi create mode 100644 plugins/windows-notification/vapi/winrt.vapi diff --git a/plugins/windows-notification/CMakeLists.txt b/plugins/windows-notification/CMakeLists.txt index eacc8405..4d569394 100644 --- a/plugins/windows-notification/CMakeLists.txt +++ b/plugins/windows-notification/CMakeLists.txt @@ -1,7 +1,4 @@ -set(GETTEXT_PACKAGE "dino-windows-notifications") -find_package(Gettext) -include(${GETTEXT_USE_FILE}) -gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../main/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) +project(windows-notification) find_packages(WINDOWS_NOTIFICATION_PACKAGES REQUIRED Gee @@ -15,55 +12,54 @@ vala_precompile(WINDOWS_NOTIFICATION_VALA_C SOURCES src/plugin.vala src/register_plugin.vala - src/win_notification_provider.vala CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi ${CMAKE_BINARY_DIR}/exports/dino.vapi ${CMAKE_BINARY_DIR}/exports/qlite.vapi - ${CMAKE_CURRENT_SOURCE_DIR}/vapi/DinoWinToastLib.vapi - ${CMAKE_CURRENT_SOURCE_DIR}/vapi/DinoWinToastTemplate.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/win32.vapi + ${CMAKE_CURRENT_SOURCE_DIR}/vapi/shortcutcreator.vapi PACKAGES ${WINDOWS_NOTIFICATION_PACKAGES} ) -add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/DinoWinToastLib.h" -COMMAND - cp "${CMAKE_CURRENT_SOURCE_DIR}/src/DinoWinToastLib.h" "${CMAKE_BINARY_DIR}/exports/DinoWinToastLib.h" -DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/src/DinoWinToastLib.h" -COMMENT - Copy header file DinoWinToastLib.h +set(WINDOWS_API_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/win32.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/converter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/api/src/shortcutcreator.cpp ) -add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/DinoWinToastTemplate.h" -COMMAND - cp "${CMAKE_CURRENT_SOURCE_DIR}/src/DinoWinToastTemplate.h" "${CMAKE_BINARY_DIR}/exports/DinoWinToastTemplate.h" -DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/src/DinoWinToastTemplate.h" -COMMENT - Copy header file DinoWinToastTemplate.h +add_library(windows-notification SHARED ${WINDOWS_NOTIFICATION_VALA_C} ${WINDOWS_API_SOURCES}) + +target_include_directories(windows-notification + PRIVATE + ${PROJECT_SOURCE_DIR}/api/include ) -add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/exports/DinoWinToastDllExport.h" -COMMAND - cp "${CMAKE_CURRENT_SOURCE_DIR}/src/DinoWinToastDllExport.h" "${CMAKE_BINARY_DIR}/exports/DinoWinToastDllExport.h" -DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/src/DinoWinToastDllExport.h" -COMMENT - Copy header file DinoWinToastDllExport.h -) +find_library(shlwapi_LIBRARY shlwapi libshlwapi libshlwapi.a HINTS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) +if(NOT shlwapi_LIBRARY) + message(FATAL_ERROR "shlwapi library not found") +endif(NOT shlwapi_LIBRARY) -add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\") -add_library(windows-notification SHARED ${WINDOWS_NOTIFICATION_VALA_C} ${CMAKE_BINARY_DIR}/exports/DinoWinToastLib.h ${CMAKE_BINARY_DIR}/exports/DinoWinToastTemplate.h ${CMAKE_BINARY_DIR}/exports/DinoWinToastDllExport.h) -add_dependencies(omemo ${GETTEXT_PACKAGE}-translations) +find_library(propsys_LIBRARY propsys libpropsys libpropsys.a HINTS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) +if(NOT propsys_LIBRARY) + message(FATAL_ERROR "propsys library not found") +endif(NOT propsys_LIBRARY) -if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(WINTOASTLIB "${CMAKE_CURRENT_SOURCE_DIR}/src/DinoWinToastLib_AMD64.lib") -elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(WINTOASTLIB "${CMAKE_CURRENT_SOURCE_DIR}/src/DinoWinToastLib_x86.lib") -endif() +find_library(ntdll_LIBRARY ntdll libntdll libntdll.a HINTS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) +if(NOT ntdll_LIBRARY) + message(FATAL_ERROR "ntdll library not found") +endif(NOT ntdll_LIBRARY) + +find_library(mincore_LIBRARY mincore libmincore libmincore.a HINTS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) +if(NOT mincore_LIBRARY) + message(FATAL_ERROR "mincore library not found") +endif(NOT mincore_LIBRARY) + +target_link_libraries(windows-notification libdino ${shlwapi_LIBRARY} ${propsys_LIBRARY} ${ntdll_LIBRARY} ${mincore_LIBRARY} ${WINDOWS_NOTIFICATION_PACKAGES}) +target_compile_features(windows-notification PRIVATE cxx_std_20) +# target_compile_options(windows-notification PRIVATE -municode -DUNICODE -fcoroutines -iquote ../../include/winrt/yolort_impl) +target_compile_options(windows-notification PRIVATE -municode -DUNICODE -fcoroutines) -target_link_libraries(windows-notification libdino ${WINDOWS_NOTIFICATION_PACKAGES} ${WINTOASTLIB}) set_target_properties(windows-notification PROPERTIES PREFIX "") set_target_properties(windows-notification PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins/) diff --git a/plugins/windows-notification/api/include/ComPtr.hpp b/plugins/windows-notification/api/include/ComPtr.hpp new file mode 100644 index 00000000..4850b34b --- /dev/null +++ b/plugins/windows-notification/api/include/ComPtr.hpp @@ -0,0 +1,18 @@ +#pragma once + +template +struct ComPtr +{ + T *p{}; + + ~ComPtr() { if (p != nullptr) p->Release(); } + + T &operator*() const { return *p; } + T **operator&() const { return &p; } + T **operator&() { return &p; } + T *operator->() const { return p; } + + template + HRESULT As( U **const pp ) const + { return p->QueryInterface(pp); } +}; \ No newline at end of file diff --git a/plugins/windows-notification/api/include/converter.hpp b/plugins/windows-notification/api/include/converter.hpp new file mode 100644 index 00000000..743b8011 --- /dev/null +++ b/plugins/windows-notification/api/include/converter.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +std::wstring char_to_wstr(const gchar* str); +char* wstr_to_char(const std::wstring& wstr); \ No newline at end of file diff --git a/plugins/windows-notification/api/include/shortcutcreator.h b/plugins/windows-notification/api/include/shortcutcreator.h new file mode 100644 index 00000000..1c6c950b --- /dev/null +++ b/plugins/windows-notification/api/include/shortcutcreator.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + gboolean TryCreateShortcut(gchar* aumid); + +#ifdef __cplusplus +} +#endif diff --git a/plugins/windows-notification/api/include/win32.h b/plugins/windows-notification/api/include/win32.h new file mode 100644 index 00000000..ccf090f7 --- /dev/null +++ b/plugins/windows-notification/api/include/win32.h @@ -0,0 +1 @@ +#include "win32.hpp" \ No newline at end of file diff --git a/plugins/windows-notification/api/include/win32.hpp b/plugins/windows-notification/api/include/win32.hpp new file mode 100644 index 00000000..5619335e --- /dev/null +++ b/plugins/windows-notification/api/include/win32.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef __cplusplus + +#include +#include +#include +#include + +std::optional GetCurrentModulePath(); +std::optional GetShortcutPath(); + +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + gboolean SupportsModernNotifications(); + gboolean SetAppModelID(gchar* aumid); +#ifdef __cplusplus +} +#endif diff --git a/plugins/windows-notification/api/include/winrt.h b/plugins/windows-notification/api/include/winrt.h new file mode 100644 index 00000000..e14dabad --- /dev/null +++ b/plugins/windows-notification/api/include/winrt.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + gboolean Initialize(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/plugins/windows-notification/api/src/converter.cpp b/plugins/windows-notification/api/src/converter.cpp new file mode 100644 index 00000000..93b85479 --- /dev/null +++ b/plugins/windows-notification/api/src/converter.cpp @@ -0,0 +1,53 @@ +#include + +#include "converter.hpp" + +// Convert a wide Unicode string to an UTF8 string +std::string wstr_to_str(const std::wstring& wstr) +{ + if(wstr.empty()) + { + return std::string(); + } + int final_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr); + std::string strTo(final_size, 0); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), strTo.data(), final_size, nullptr, nullptr); + return strTo; +} + +char* wstr_to_char(const std::wstring& wstr) +{ + if(wstr.empty()) + { + return nullptr; + } + int final_size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr); + char* strTo = new char[final_size]; + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), strTo, final_size, nullptr, nullptr); + return strTo; +} + +// Convert an UTF8 string to a wide Unicode String +std::wstring std_to_wstr(const std::string &str) +{ + if(str.empty()) + { + return std::wstring(); + } + int final_size = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0); + std::wstring wstrTo(final_size, 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), wstrTo.data(), final_size); + return wstrTo; +} + +std::wstring char_to_wstr(const char* str) // TODO: how to be safe from non-null terminated strings? +{ + if(str == nullptr) + { + return std::wstring(); + } + int final_size = MultiByteToWideChar(CP_UTF8, 0, str, strlen(str), nullptr, 0); + std::wstring wstrTo(final_size, 0); + MultiByteToWideChar(CP_UTF8, 0, str, strlen(str), wstrTo.data(), final_size); + return wstrTo; +} \ No newline at end of file diff --git a/plugins/windows-notification/api/src/shortcutcreator.cpp b/plugins/windows-notification/api/src/shortcutcreator.cpp new file mode 100644 index 00000000..1469785d --- /dev/null +++ b/plugins/windows-notification/api/src/shortcutcreator.cpp @@ -0,0 +1,160 @@ +#include +#include + +#include "shortcutcreator.h" +#include "win32.hpp" +#include "converter.hpp" + +#ifdef UNICODE + #define _UNICODE +#endif + +#include // magic +#include // COM stuff +#include // IShellLink +#include // InitPropVariantFromString +#include // PKEY_AppUserModel_ID +#include // 'cause iostreams are bloat +#include + +#include "ComPtr.hpp" + +// Not available in MINGW headers for some reason +PSSTDAPI PropVariantToString(_In_ REFPROPVARIANT propvar, _Out_writes_(cch) PWSTR psz, _In_ UINT cch); + +int32_t InstallShortcut(const std::wstring& exe_path, const std::wstring& aumid, const std::wstring& shortcut_path) +{ + ComPtr shellLink; + auto hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + + if (SUCCEEDED(hr)) + { + hr = shellLink->SetPath(exe_path.c_str()); + if (SUCCEEDED(hr)) + { + hr = shellLink->SetArguments(TEXT("")); + if (SUCCEEDED(hr)) + { + hr = shellLink->SetWorkingDirectory(exe_path.c_str()); + if (SUCCEEDED(hr)) + { + ComPtr propertyStore; + hr = shellLink.As(&propertyStore); + if (SUCCEEDED(hr)) + { + PROPVARIANT appIdPropVar; + hr = InitPropVariantFromString(aumid.c_str(), &appIdPropVar); + if (SUCCEEDED(hr)) + { + hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar); + if (SUCCEEDED(hr)) + { + hr = propertyStore->Commit(); + if (SUCCEEDED(hr)) + { + ComPtr persistFile; + hr = shellLink.As(&persistFile); + if (SUCCEEDED(hr)) + { + hr = persistFile->Save(shortcut_path.c_str(), TRUE); + } + } + } + PropVariantClear(&appIdPropVar); + } + } + } + } + } + } + return hr; +} + +int32_t ValidateShortcut(const std::wstring& shortcut_path, const std::wstring& currentAumid) +{ + bool wasChanged = false; + + ComPtr shellLink; + auto hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + + if (SUCCEEDED(hr)) + { + ComPtr persistFile; + hr = shellLink.As(&persistFile); + if (SUCCEEDED(hr)) + { + hr = persistFile->Load(shortcut_path.c_str(), STGM_READWRITE); + if (SUCCEEDED(hr)) + { + ComPtr propertyStore; + hr = shellLink.As(&propertyStore); + if (SUCCEEDED(hr)) + { + PROPVARIANT appIdPropVar; + hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &appIdPropVar); + if (SUCCEEDED(hr)) + { + std::array AUMI; + hr = PropVariantToString(appIdPropVar, AUMI.data(), AUMI.size()); + if (FAILED(hr) || currentAumid != std::wstring(AUMI.data())) + { + // AUMI Changed for the same app, let's update the current value! =) + wasChanged = true; + PropVariantClear(&appIdPropVar); + hr = InitPropVariantFromString(currentAumid.c_str(), &appIdPropVar); + if (SUCCEEDED(hr)) + { + hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar); + if (SUCCEEDED(hr)) + { + hr = propertyStore->Commit(); + if (SUCCEEDED(hr) && SUCCEEDED(persistFile->IsDirty())) + { + hr = persistFile->Save(shortcut_path.c_str(), TRUE); + } + } + } + } + PropVariantClear(&appIdPropVar); + } + } + } + } + } + return hr; +} + +int32_t TryCreateShortcutInternal(const std::wstring& aumid) +{ + auto exePath = GetCurrentModulePath(); + auto shortcutPath = GetShortcutPath(); + + if (shortcutPath && exePath) + { + auto path = shortcutPath.value() + LR"(\Microsoft\Windows\Start Menu\Programs\Testando.lnk)"; + if (!std::filesystem::exists(path)) + { + return InstallShortcut(exePath.value(), aumid, path); + } + else + { + return ValidateShortcut(path, aumid); + } + + return S_OK; + } + return S_FALSE; +} + +extern "C" +{ + gboolean TryCreateShortcut(gchar* aumid) + { + auto result = char_to_wstr(aumid); + if (result.empty()) + { + return FALSE; + } + return SUCCEEDED(TryCreateShortcutInternal(result)); + } +} \ No newline at end of file diff --git a/plugins/windows-notification/api/src/win32.cpp b/plugins/windows-notification/api/src/win32.cpp new file mode 100644 index 00000000..9d68737d --- /dev/null +++ b/plugins/windows-notification/api/src/win32.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include "win32.hpp" +#include "converter.hpp" + +std::optional GetCurrentModulePath() +{ + std::array exePath; + auto charWritten = GetModuleFileName(nullptr, exePath.data(), exePath.size()); + if (charWritten > 0) + { + return std::wstring(exePath.data()); + } + return std::nullopt; +} + +std::optional GetShortcutPath() +{ + std::array shortcutPath; + auto charWritten = GetEnvironmentVariable(L"APPDATA", shortcutPath.data(), shortcutPath.size()); + if (charWritten > 0) + { + return std::wstring(shortcutPath.data()); + } + return std::nullopt; +} + +bool SetAppModelIDInternal(const std::wstring& aumid) +{ + return SUCCEEDED(SetCurrentProcessExplicitAppUserModelID(aumid.c_str())); +} + +extern "C" +{ + // Not available in mingw headers, but linking works. + NTSTATUS NTAPI RtlGetVersion(PRTL_OSVERSIONINFOW); + + gboolean SupportsModernNotifications() + { + RTL_OSVERSIONINFOW rovi = { 0 }; + rovi.dwOSVersionInfoSize = sizeof(rovi); + if (S_OK == RtlGetVersion(&rovi)) + { + return rovi.dwMajorVersion > 6; + } + return FALSE; + } + + gboolean SetAppModelID(gchar* aumid) + { + auto result = char_to_wstr(aumid); + if (result.empty()) + { + return FALSE; + } + return SetAppModelIDInternal(result); + } +} + diff --git a/plugins/windows-notification/api/src/winrt.cpp b/plugins/windows-notification/api/src/winrt.cpp new file mode 100644 index 00000000..6eafe395 --- /dev/null +++ b/plugins/windows-notification/api/src/winrt.cpp @@ -0,0 +1,2 @@ +#include "winrt.h" + diff --git a/plugins/windows-notification/src/DinoWinToastLib_AMD64.lib b/plugins/windows-notification/src/DinoWinToastLib_AMD64.lib deleted file mode 100644 index cebd9481fb60aeac72bed91911fb9578a029d06e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6068 zcmcIoO>7fK6n=IBDJDNoOoG&=l951wM4iNm<3wsJyO{3$W&aanseWU1}7>`jwi zQVtQP_RvdiRIS9V>a`~-QB@TODpUbIptjsl;Dnk=IiO1Ed)~jXcXrn_*(v??N(cZ>0EG4d`ceS>TL6RC4B5jH zC0y2J;28i(*pQJ?0FvPyU5;%7kPKCIiJ(rB;eK64ZXq7!_&HsMu#Y6NNg@c@`K86> zoRGP&a(+3xlv`XDGD=zfSSjaJv8K%_c@Tt!%X4#1Abj40)N#3smlrMxnRG6VBb8D` zkQ(&~%W_HmR9;XuWmPGNnxdAW1qff)%9^?{FV||~RoS70?cUGPw4_j_r<)kwJb6VOjD zeeIH{t<~*;uhD||=*pJRl)ZJ?5FcIiksm5@QF7;HL%eiJy4|~|F?+oO2 z05GoVPfU{Y>sL>%7*)vP%NJ(FC{qyrAf`y7CZ3F&T^PdQca-NwLl>4Q7{6oSrj2GG zTo2A?;;20Z(K|sb-^55xPlWTKry&yV!M@+>2s|fX6n3gZ^Gczr*3?xkdS>}-bS}Ga z$>?t=da*`?NB|EIdc*Sv4-$Ig%m%4~TB;!TooXO(aKMhlA$Sy#m`jiXsMB9FTk0RH z&u3t!2!n&b!X)e#YCl6i#zWcx(ngJnAms7)l@2gp!wj`+N%6!CV@W~y^-JmQPj#|H z8GvU=lHO(gGL|%HHj;&WZ&r+wEa|mcNGtpOHj;6@bhW#BljCt_^}1m7o16bU#R>Gy z=dYfJA12HNBR?iTWSc@X)W}bC^f8x7) zFDG#3AZO-=9HS!)lID@a?7*pm*yAKJ=;AQoBr+Z0F>MAchDYG%+TJ+w=;rWXvn3vZ zwk^?gd1n3}U;Of%obr${zgI@q5e`V!Z diff --git a/plugins/windows-notification/src/DinoWinToastLib_x86.lib b/plugins/windows-notification/src/DinoWinToastLib_x86.lib deleted file mode 100644 index 3d72e2500a618b5e8d7382415f90a3bc6cb6d4a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6132 zcmcIoO=ufO6n>K8+L3=M|I^kjGNfr5s2$6eWQP!>MhR6c%f?m+1j>3Xt>dM#R+ZLu z8UnrL(4RvIAR`P0Ai91p4DXN8S0}97d7c01E|S1*4u)R zo?Dn-%n7N}OQ#mo3%U74AtjfT^KvPth*dQsuYe$A&t)=pMfhx=rlyzs;9PcANF{Sg zoGGuD1)*RC6c(j*nh3(CTvF+DRB63j6je#6O6uuLWx2v^ zrl*nzUTVV=%~bKgN^TTnWxi~lG?$!3%MDR16q0#2Fh;=*2X`&0sug)8cfR?vLG38+5ohEb9> zo#xX}^Ko{v9%uV7&_+4|u5<(3=>gd41vreIH$ng-B-p+%1n^)GU}pf}CXPk0K8x*V z*k2q580!bvM!|p$KJY_3yaF9?0NS7ndZ7=xp%Z!_2nXR+2ziVKFebi0#B~U$jfNYy zpym{8n45qbxXpuQH67xv-3uCY)yD0}PVw!&AaTM2*ICC>0HgAYQQtWCIUd4z1!4jf@v z3vg6o&fZbS4CtPD+xWha>y_C?j1)J#986i0y|F^E2;=o}E&uhxi$&5hLAiZ{6&(Nd(1cZ0|Sig>Oo=QLGMc;w`P&-SwKC<|3 zIFruKYQxswE^N|70sy>T=m~Eqyk_W$vl=Aw%6b{OU$6Lldwa&RG?NOE^@Fhm3B2E^ zXU*uTy)3O4FnXp)2RlrisM$mPN9ac{gawe)TU7aB1%F@Ydh;~QV6&DKiB2mH<5tj2V2v-H=`#~x*uM> zSA6&@E}$JgfA?JcFsg4<&A7=A*~0kYl+jP7{Mcyd^-J01Mt1gXB;aYF&F*mA*kIgY z0CM7t@3-$R|D}bZ4lONqy`Xu4p;;EOi0&i|i?{a9>>kBe_F>eueVx~mfL5#=uip&j z`yUN|vr8e{&s*7&Xtabu%>M3Mm;b&$jSK=j2ApK36+B`yb{n zIs0TBIkY=-p!~8W>rZq1@%-(lznsMNJDk_&B(Xd?%|?f~afLF`VdvV3AC4oNgYImY zM7bqQIKCQ4{r-@AHR#QxmW;QAhmoE#J90Pr+COpR(do*gp78T%H1Dw-JI!A^et!me xbh+|izA9S7gmW_0)o<_7$#lCiF+W{+M4Gl$4v)5