From 5bc1bbaab247b5d72e70abc9432a15275fd2d229 Mon Sep 17 00:00:00 2001 From: Jari Pennanen Date: Sun, 2 Jun 2019 21:21:10 +0300 Subject: [PATCH] exporting some alt-tab specific functions --- README.markdown | 21 +- .../TestVirtualDesktopAccessorWin32.cpp | 8 + VirtualDesktopAccessor/Win10Desktops.h | 38 +-- VirtualDesktopAccessor/dllmain.def | 12 +- VirtualDesktopAccessor/dllmain.h | 268 ++++++++++++++++++ VirtualDesktopAccessor/stdafx.h | 1 + x64/Release/VirtualDesktopAccessor.dll | Bin 25088 -> 30720 bytes 7 files changed, 327 insertions(+), 21 deletions(-) diff --git a/README.markdown b/README.markdown index 62e3f1a..c01427a 100644 --- a/README.markdown +++ b/README.markdown @@ -6,6 +6,15 @@ Download the VirtualDesktopAccessor.dll from directory x64\Release\VirtualDeskto You probably first need the [VS 2017 runtimes vc_redist.x64.exe and/or vc_redist.x86.exe](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads), if they are not installed already. I've built the DLL using VS 2017, and Microsoft is not providing those runtimes (who knows why) with Windows 10 yet. +## Change log + +* 02.06.2019: + + Exported the Alt+Tab functions prefixed with `View`, this should allow user of the DLL to create native feeling ALT+Tab switcher, since these functions uses the same `IApplicationView` functions as real Alt+Tab switcher itself. + + The function to get Alt+Tab windows is basically `ViewGetByLastActivationOrder`. + + ## AutoHotkey script as example: ```AutoHotkey @@ -96,7 +105,7 @@ VWMess(wParam, lParam, msg, hwnd) { WinGet, activeHwnd, ID, A isPinned := DllCall(IsPinnedWindowProc, UInt, activeHwnd) oldHwnd := activeWindowByDesktop[lParam] - isOnDesktop := DllCall(IsWindowOnCurrentVirtualDesktopProc, UInt, oldHwnd, UInt) + isOnDesktop := DllCall(IsWindowOnCurrentVirtualDesktopProc, UInt, oldHwnd, Int) if (isOnDesktop == 1 && isPinned != 1) { WinActivate, ahk_id %oldHwnd% } @@ -153,5 +162,15 @@ VWMess(wParam, lParam, msg, hwnd) { * int IsWindowOnDesktopNumber(HWND window, int number) / * void RestartVirtualDesktopAccessor() // Call this during taskbar created message + * int ViewIsShownInSwitchers(HWND hwnd) // Is the window shown in Alt+Tab list? + * int ViewIsVisible(HWND hwnd) // Is the window visible? + * HWND ViewGetThumbnailHwnd(HWND hwnd) // Get thumbnail handle for a window, possibly peek preview of Alt+Tab + * void ViewSetFocus(HWND hwnd) // Set focus like Alt+Tab switcher + * HWND ViewGetFocused() // Get focused window thumbnail handle + * void ViewSwitchTo(HWND hwnd) // Switch to window like Alt+Tab switcher + * uint ViewGetByZOrder(HWND *windows, UINT count, BOOL onlySwitcherWindows, BOOL onlyCurrentDesktop) // Get windows in Z-order (NOT alt-tab order) + * uint ViewGetByLastActivationOrder(HWND *windows, UINT count, BOOL onlySwitcherWindows, BOOL onlyCurrentDesktop) // Get windows in alt tab order + * uint ViewGetLastActivationTimestamp(HWND) // Get last activation timestamp + * void EnableKeepMinimized() // Deprecated, does nothing * void RestoreMinimized() // Deprecated, does nothing diff --git a/TestVirtualDesktopAccessorWin32/TestVirtualDesktopAccessorWin32.cpp b/TestVirtualDesktopAccessorWin32/TestVirtualDesktopAccessorWin32.cpp index b2cb209..a5fb4f3 100644 --- a/TestVirtualDesktopAccessorWin32/TestVirtualDesktopAccessorWin32.cpp +++ b/TestVirtualDesktopAccessorWin32/TestVirtualDesktopAccessorWin32.cpp @@ -87,12 +87,20 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, std::wcout << "Console Window's Desktop Number: " << GetWindowDesktopNumber(GetConsoleWindow()) << std::endl; std::wcout << "Current Desktop Number: " << GetCurrentDesktopNumber() << "\r\n"; + HWND arr[1024]; + UINT numberOfWindows = ViewGetByLastActivationOrder((HWND*)&arr, 1024, 1, 0); + std::wcout << "Number of windows: " << numberOfWindows << std::endl; + + HWND notepad = FindWindow(_T("Notepad"), NULL); if (notepad != 0) { int number = GetCurrentDesktopNumber(); std::wcout << "Is notepad on this desktop: " << IsWindowOnDesktopNumber(notepad, number) << std::endl; std::wcout << "Is notepad on current desktop: " << IsWindowOnCurrentVirtualDesktop(notepad) << std::endl; + std::wcout << "Is notepad shown on alt tab switcher: " << ViewIsShownInSwitchers(notepad) << std::endl; + std::wcout << "Is notepad visible: " << ViewIsVisible(notepad) << std::endl; + std::wcout << "Where is thumbnail of notepad: " << ViewGetThumbnailHwnd(notepad) << std::endl; // Test pinning it std::wcout << "Try pinning the notepad (the window)." << std::endl; diff --git a/VirtualDesktopAccessor/Win10Desktops.h b/VirtualDesktopAccessor/Win10Desktops.h index 6306c16..d53df78 100644 --- a/VirtualDesktopAccessor/Win10Desktops.h +++ b/VirtualDesktopAccessor/Win10Desktops.h @@ -55,21 +55,21 @@ DECLARE_INTERFACE_IID_(IApplicationView, IInspectable, "372E1D3B-38D3-42E4-A15B- /*** IApplicationView methods ***/ STDMETHOD(SetFocus)(THIS) PURE; STDMETHOD(SwitchTo)(THIS) PURE; - STDMETHOD(TryInvokeBack)(THIS_ IAsyncCallback*) PURE; - STDMETHOD(GetThumbnailWindow)(THIS_ HWND*) PURE; - STDMETHOD(GetMonitor)(THIS_ IImmersiveMonitor**) PURE; - STDMETHOD(GetVisibility)(THIS_ int*) PURE; - STDMETHOD(SetCloak)(THIS_ APPLICATION_VIEW_CLOAK_TYPE, int) PURE; - STDMETHOD(GetPosition)(THIS_ REFIID, void**) PURE; - STDMETHOD(SetPosition)(THIS_ IApplicationViewPosition*) PURE; - STDMETHOD(InsertAfterWindow)(THIS_ HWND) PURE; - STDMETHOD(GetExtendedFramePosition)(THIS_ RECT*) PURE; - STDMETHOD(GetAppUserModelId)(THIS_ PWSTR*) PURE; - STDMETHOD(SetAppUserModelId)(THIS_ PCWSTR) PURE; - STDMETHOD(IsEqualByAppUserModelId)(THIS_ PCWSTR, int*) PURE; - STDMETHOD(GetViewState)(THIS_ UINT*) PURE; - STDMETHOD(SetViewState)(THIS_ UINT) PURE; - STDMETHOD(GetNeediness)(THIS_ int*) PURE; + STDMETHOD(TryInvokeBack)(THIS_ IAsyncCallback*) PURE; // Proc8 + STDMETHOD(GetThumbnailWindow)(THIS_ HWND*) PURE; // Proc9 + STDMETHOD(GetMonitor)(THIS_ IImmersiveMonitor**) PURE; // Proc10 + STDMETHOD(GetVisibility)(THIS_ int*) PURE; // Proc11 + STDMETHOD(SetCloak)(THIS_ APPLICATION_VIEW_CLOAK_TYPE, int) PURE; // Proc12 + STDMETHOD(GetPosition)(THIS_ REFIID, void**) PURE; // Proc13 + STDMETHOD(SetPosition)(THIS_ IApplicationViewPosition*) PURE; // Proc14 + STDMETHOD(InsertAfterWindow)(THIS_ HWND) PURE; // Proc15 + STDMETHOD(GetExtendedFramePosition)(THIS_ RECT*) PURE; // Proc16 + STDMETHOD(GetAppUserModelId)(THIS_ PWSTR*) PURE; // Proc17 + STDMETHOD(SetAppUserModelId)(THIS_ PCWSTR) PURE; // Proc18 + STDMETHOD(IsEqualByAppUserModelId)(THIS_ PCWSTR, int*) PURE; // Proc19 + STDMETHOD(GetViewState)(THIS_ UINT*) PURE; // Proc20 + STDMETHOD(SetViewState)(THIS_ UINT) PURE; // Proc21 + STDMETHOD(GetNeediness)(THIS_ int*) PURE; // Proc22 STDMETHOD(GetLastActivationTimestamp)(THIS_ ULONGLONG*) PURE; STDMETHOD(SetLastActivationTimestamp)(THIS_ ULONGLONG) PURE; STDMETHOD(GetVirtualDesktopId)(THIS_ GUID*) PURE; @@ -92,11 +92,11 @@ DECLARE_INTERFACE_IID_(IApplicationView, IInspectable, "372E1D3B-38D3-42E4-A15B- STDMETHOD(IsInHighZOrderBand)(THIS_ BOOL*) PURE; STDMETHOD(IsSplashScreenPresented)(THIS_ BOOL*) PURE; STDMETHOD(Flash)(THIS) PURE; - STDMETHOD(GetRootSwitchableOwner)(THIS_ IApplicationView**) PURE; - STDMETHOD(EnumerateOwnershipTree)(THIS_ IObjectArray**) PURE; + STDMETHOD(GetRootSwitchableOwner)(THIS_ IApplicationView**) PURE; // proc45 + STDMETHOD(EnumerateOwnershipTree)(THIS_ IObjectArray**) PURE; // proc46 - STDMETHOD(GetEnterpriseId)(THIS_ PWSTR*) PURE; - STDMETHOD(IsMirrored)(THIS_ BOOL*) PURE; + STDMETHOD(GetEnterpriseId)(THIS_ PWSTR*) PURE; // proc47 + STDMETHOD(IsMirrored)(THIS_ BOOL*) PURE; // STDMETHOD(Unknown1)(THIS_ int*) PURE; STDMETHOD(Unknown2)(THIS_ int*) PURE; diff --git a/VirtualDesktopAccessor/dllmain.def b/VirtualDesktopAccessor/dllmain.def index bdd1b88..0c4de2a 100644 --- a/VirtualDesktopAccessor/dllmain.def +++ b/VirtualDesktopAccessor/dllmain.def @@ -22,4 +22,14 @@ EXPORTS EnableKeepMinimized @19 RestoreMinimized @20 IsWindowOnDesktopNumber @21 - RestartVirtualDesktopAccessor @22 \ No newline at end of file + RestartVirtualDesktopAccessor @22 + + ViewIsShownInSwitchers @23 + ViewIsVisible @24 + ViewGetThumbnailHwnd @25 + ViewSetFocus @26 + ViewSwitchTo @27 + ViewGetByZOrder @28 + ViewGetFocused @29 + ViewGetLastActivationTimestamp @30 + ViewGetByLastActivationOrder @31 \ No newline at end of file diff --git a/VirtualDesktopAccessor/dllmain.h b/VirtualDesktopAccessor/dllmain.h index e17d802..8bf4593 100644 --- a/VirtualDesktopAccessor/dllmain.h +++ b/VirtualDesktopAccessor/dllmain.h @@ -447,6 +447,274 @@ void DllExport UnPinApp(HWND hwnd) { } } +int DllExport ViewIsShownInSwitchers(HWND hwnd) { + + //// Iterate views for fun + //IObjectArray* arr = nullptr; + //UINT count; + //viewCollection->GetViews(&arr); + //arr->GetCount(&count); + + //for (int i = 0; i < count; i++) + //{ + // IApplicationView* app2; + // HRESULT getAtResult = arr->GetAt(i, IID_IApplicationView, (void**)&app2); + // if (app2 != nullptr && getAtResult == S_OK) { + // PWSTR modelId; + // app2->GetAppUserModelId(&modelId); + + // BOOL showInSwitchers = 0; + // app2->GetShowInSwitchers(&showInSwitchers); + + // BOOL isVisible = 0; + // app2->GetVisibility(&isVisible); + + // int unknown1 = 0; + // HRESULT res1 = app2->Unknown1(&unknown1); + // int unknown2 = 0; + // HRESULT res2 = app2->Unknown2(&unknown2); + // int unknown3 = 0; + // HRESULT res3 = app2->Unknown3(&unknown3); + // int unknown5 = 0; + // HRESULT res5 = app2->Unknown5(&unknown5); + // int unknown8 = 0; + // HRESULT res8 = app2->Unknown8(&unknown8); + + // /* E_NOTIMPL + // BOOL isInHighZOrderBand = 0; + // HRESULT zres = app2->IsInHighZOrderBand(&isInHighZOrderBand); + // */ + + // /* Access violation + // BOOL isTray = 0; + // HRESULT isTrayRes = app2->IsTray(&isTray); + // */ + + // wprintf(L"modelId: %s switcher: %d visible: %d %d %d %d %d %d\n", modelId, showInSwitchers, isVisible, unknown1, unknown2, unknown3, unknown5, unknown8); + + // /* Seems to be always nullptr + // HSTRING className; + // app2->GetRuntimeClassName(&className); + // */ + + // /* + // Seems to be always 0xcccccccc00000000 + // IApplicationView* app2Owner; + // if (app2->GetRootSwitchableOwner(&app2Owner) == S_OK && app2Owner != (IApplicationView*) 0xcccccccc00000000) { + // PWSTR modelIdOwner; + // app2Owner->GetAppUserModelId(&modelIdOwner); + // wprintf(L"modelId owner: %s \n", modelIdOwner); + // app2Owner->Release(); + // } + // */ + // } + // app2->Release(); + //} + + + _RegisterService(); + IApplicationView* view = _GetApplicationViewForHwnd(hwnd); + int result = -1; + if (view != nullptr) { + BOOL show = 0; + if (view->GetShowInSwitchers(&show) == S_OK) { + result = show; + } + view->Release(); + } + return result; +} + +int DllExport ViewIsVisible(HWND hwnd) { + _RegisterService(); + IApplicationView* view = _GetApplicationViewForHwnd(hwnd); + int result = -1; + if (view != nullptr) { + int show = 0; + if (view->GetVisibility(&show) == S_OK) { + result = show; + } + view->Release(); + } + return result; +} + +HWND DllExport ViewGetThumbnailHwnd(HWND hwnd) { + _RegisterService(); + IApplicationView* view = _GetApplicationViewForHwnd(hwnd); + HWND result = 0; + if (view != nullptr) { + if (view->GetThumbnailWindow(&result) != S_OK) { + result = 0; + } + view->Release(); + } + return result; +} + +HRESULT DllExport ViewSetFocus(HWND hwnd) { + _RegisterService(); + IApplicationView* view = _GetApplicationViewForHwnd(hwnd); + HRESULT result = -1; + if (view != nullptr) { + result = view->SetFocus(); + view->Release(); + } + return result; +} + +HWND DllExport ViewGetFocused() { + _RegisterService(); + IApplicationView* view; + HRESULT getAtResult = viewCollection->GetViewInFocus(&view); + HWND ret = 0; + if (view != nullptr && getAtResult == S_OK) { + HWND wnd = 0; + if (view->GetThumbnailWindow(&wnd) == S_OK && wnd != 0) { + ret = wnd; + } + view->Release(); + } + return ret; +} + +HRESULT DllExport ViewSwitchTo(HWND hwnd) { + _RegisterService(); + IApplicationView* view = _GetApplicationViewForHwnd(hwnd); + HRESULT result = -1; + if (view != nullptr) { + result = view->SwitchTo(); + view->Release(); + } + return result; +} + +UINT DllExport ViewGetByZOrder(HWND *windows, UINT count, BOOL onlySwitcherWindows, BOOL onlyCurrentDesktop) { + _RegisterService(); + IObjectArray* arr = nullptr; + UINT countViews; + IApplicationView* view; + int fill = 0; + if (viewCollection->GetViewsByZOrder(&arr) != S_OK) { + return 0; + } + arr->GetCount(&countViews); + if (countViews > count) { + arr->Release(); + return 0; + } + + for (UINT i = 0; i < count; i++) + { + HRESULT getAtResult = arr->GetAt(i - 1, IID_IApplicationView, (void**)&view); + + if (view != nullptr && getAtResult == S_OK) { + HWND wnd = 0; + BOOL showInSwitchers = false; + BOOL isOnCurrentDesktop = false; + if (onlySwitcherWindows && (view->GetShowInSwitchers(&showInSwitchers) != S_OK || !showInSwitchers)) { + view->Release(); + continue; + } + if (view->GetThumbnailWindow(&wnd) != S_OK || wnd == 0) { + view->Release(); + continue; + } + if (onlyCurrentDesktop && (pDesktopManager->IsWindowOnCurrentVirtualDesktop(wnd, &isOnCurrentDesktop) != S_OK || !isOnCurrentDesktop)) { + view->Release(); + continue; + } + windows[fill] = wnd; + fill++; + view->Release(); + } + } + arr->Release(); + return fill; +} + +struct TempWindowEntry { + HWND hwnd; + ULONGLONG lastActivationTimestamp; +}; + +UINT DllExport ViewGetByLastActivationOrder(HWND *windows, UINT count, BOOL onlySwitcherWindows, BOOL onlyCurrentDesktop) { + _RegisterService(); + IObjectArray* arr = nullptr; + UINT countViews; + IApplicationView* view; + if (viewCollection->GetViews(&arr) != S_OK) { + return 0; + } + arr->GetCount(&countViews); + if (countViews > count) { + arr->Release(); + return 0; + } + + std::vector unsorted; + for (UINT i = 0; i < count; i++) + { + HRESULT getAtResult = arr->GetAt(i - 1, IID_IApplicationView, (void**)&view); + if (view != nullptr && getAtResult == S_OK) { + HWND wnd = 0; + ULONGLONG lastActivationTimestamp = 0; + BOOL showInSwitchers = false; + BOOL isOnCurrentDesktop = false; + + if (onlySwitcherWindows && (view->GetShowInSwitchers(&showInSwitchers) != S_OK || !showInSwitchers)) { + view->Release(); + continue; + } + if (view->GetThumbnailWindow(&wnd) != S_OK || wnd == 0) { + view->Release(); + continue; + } + + if (onlyCurrentDesktop && (pDesktopManager->IsWindowOnCurrentVirtualDesktop(wnd, &isOnCurrentDesktop) != S_OK || !isOnCurrentDesktop)) { + view->Release(); + continue; + } + + if (view->GetLastActivationTimestamp(&lastActivationTimestamp) != S_OK) { + view->Release(); + continue; + } + TempWindowEntry entry; + entry.hwnd = wnd; + entry.lastActivationTimestamp = lastActivationTimestamp; + unsorted.push_back(entry); + view->Release(); + } + } + arr->Release(); + + std::sort(unsorted.begin(), unsorted.end(), [](auto const& lhs, auto const& rhs) { + return lhs.lastActivationTimestamp > rhs.lastActivationTimestamp; + }); + + UINT i = 0; + for (auto entry : unsorted) { + windows[i] = entry.hwnd; + i++; + } + + return i; +} + +ULONGLONG DllExport ViewGetLastActivationTimestamp(HWND hwnd) { + _RegisterService(); + IApplicationView* view = _GetApplicationViewForHwnd(hwnd); + ULONGLONG result = 0; + if (view != nullptr) { + if (view->GetLastActivationTimestamp(&result) != S_OK) { + result = 0; + } + view->Release(); + } + return result; +} + class _Notifications : public IVirtualDesktopNotification { private: ULONG _referenceCount; diff --git a/VirtualDesktopAccessor/stdafx.h b/VirtualDesktopAccessor/stdafx.h index 8bd4a3e..d8ba090 100644 --- a/VirtualDesktopAccessor/stdafx.h +++ b/VirtualDesktopAccessor/stdafx.h @@ -21,6 +21,7 @@ #include #include #include +#include // TODO: reference additional headers your program requires here diff --git a/x64/Release/VirtualDesktopAccessor.dll b/x64/Release/VirtualDesktopAccessor.dll index 6403222c84c375213d0afad201844af1984729b4..d6af0adcc3d7c850f156846552646349566af736 100644 GIT binary patch delta 15655 zcmeHudt8*q_4n)piwlCgsLNGgk*k7;-~~j?f}qdF-GnG2@roA=-U%#tcTHB3kf)mx zr)|HF({??3PJ z`J6p7XU?2CbLPyMGtUFNj_G#w>Ds1o>-69UtyWje&aMTElAa0;z`Q#qg;t|JY}1p5 zB?AAC!6EQIL#4n^8>qc-%RbRwxaDcXLev)}JsnyDJbBY|fY%QTd6U2-@RijHKqSVyynb6y^sv&?4{3$R%8NuM>ucGGF zYR!kPNMSl|2{P>o)@R)dd9o*BFGiDF^>^M2p$v&Ae>)d%&^Gm8^_hpN$6j$BL zbA$7A6?{eTHrG9ZyG=!Rs;EIljVjs>)T+N3zrDj`cX1gC%-B+dDd(T?n|D?Kzb1S1 z<3P82BEFkT0?Se@P^A>*GEZr+-&_MOS&0Y*kKA&iDH`Q@x1#KEnRng>>^@d-X@)w>M_q$ z$Dp~&{+bxV9jE!ikc8+RXwN@JR?E#l;hXuL-wzod?&d!ZDUDq_o`yUocO3TPkrjJt zuAG0`S2>=46f!1mdw)+^R=z^#XTBAkZM*rnA+^%j+xSn0?2kb2kGLTcFEw1zYzhaS;=&E>USW(1m##YJ3oqe)m9x|I7ho(xGzTsVA z$-HW4q4d~T{@~DT`%Q=%+7e=hUny@ucJDR6UpeI~BP+X$*SUn~^G)L*|DX(ZkNE;b z`(BL01{yGP+E^YMULcKd@fqRi($_or+VI2?jhp(#YgSI?kA`Puq>1TjMywtKCik&_ zGop12|0Fyq=F{eW8JW;-?^R)`lASy)V%lw|GSR3k!fLpWG2w7UQgMSlIB`vs_R}PyXy|bO) zJ8b&6J2HftHA^~9i-p95^eG6wfgK<#XAvTtt6-*;+xh3i(xsX#9%mdM{eHUW*zc8M zAH15*Ym5n!%gJ3vb7u6zq?Fjc5H=8V3|k{~?7ds{c1_kI;E3Y(!R;UGQVm zeE2;%imX(3q+tO?LM|4kY8nGcgbjSWdA%_Q%Kq0S9hQjic${LUOrFAJ>ZuaIS> z5=)?*fnLUP*k9C!2s2_?dR;;CAX6rha@dg7q~TKG2mN91^^N_?X`Xyn-;E~Cq@emw zI#02kRUg1c{y`srS~xr511=E*{Z1UTLxjS)%6WCUp`y~!tJe;o?WyQu$D}dD*8U;L zg#AvMKcFBTXdiqk_E{6f64kFTqAG+edcrxr#oLtSB2!w30Y)MQ5W{IC*zyD{c0nCg ziS*Bb;|)07Rg0J=TwPTFQ_&yV@O=v{BOM0q5~2Pd)vl_AT;~vk{_iIGzJ&_@cMC1( zA3``;t1~r5oHp}HDof^-RF%M)E~`hWOYdK=X5>1SO`du&J*(QqahjrhM!Ydn0hcIm zmA70m?*22;(9tv3Z+Ced-AKBImJ$hlunLZDnBKVi0dTc=jV(yt7{=`>YhCXHTrO|< zz!?u-4C@O7h^AZ03fKFbSY31$O+D&TmdeV!9=YW;XJI+EMiTbWGUUc~Px)n4;}(~< zX$1OSc194V|G3;5ExS7nnjp-fH%u*)1Nb{iirzNfU5XF@BK%bK3UzkManP=jf4-AQ zLo72YcC*->>?BREZs^S%Evy2^*xm8){=j%fTUi;b|DakrjYlPI}Y9Ms8E z<+pnr?_kLmS}8%M^?FCFS2xtB_3EN&wC?y6kkq>4YU_@#%UeEnj#ibp8dx!7%g?}9 zSnj%N-0h(rBu41@sk(C8upXb%$~Dk}$6<26rsIw>_42RP$yFDYf9;$*&@;*3c33FU zG+kE8p+I>ja1!ttd(pp~kw#N_r!g`}c6Y$Gi;cZ85aj~P>$1l&#P^sHtENx_zSyYA zgoXUaONy=!w?wl6$@huCg8{*w_87o-0soTy%@`;EG+oX z-bXwUlT@k_%q6TYVN3aP}3^S(YRu0Yn)@lr6%<*WDc zoDnGrFAs1W9N@tHocVH9j%@=R^#dFdarAx2e>);cI`bhvGa@na@jLp>Sp)0&Ht_F8 z+>(_Glgb`ST%be>RfH1Q$CE#VvAuNK)X-wiishR|T3o*a6^ZX+oGi>a$6cIBg(lle z@DS$9jwNeR_#8$jPp8!YDnhmXURX(Pl`w1?HPBX%Q6fG@pr_4FyVQ2FHR3G_{J5(R z9P^f!sTsG2>7>jF-;^qK_;(FHUntT8>IZB1(da*oOasMN0Me0MLGSVzV-N9|4}zn|)uN?a1fzJZ@r-ZeNd7=V zwshRc|CsQ1DP|j%Nsg!qxY@S(&2d1xy-i^@<*$D8 zxNQDllGR18YAORSYjk$yb6L65V9)B4v-)f=VL*C}a{G6|4y8v{K0>NsEBeg1+X0V} z6`M)M(V5j{$1>K*?Oz4klyiJ-Ty)ZAoZ00Y8iCQ^M!V^-kv-KJvitoW zti`q7k#tlbN}Tk47)~0U@0If3EHRpbQ`NY7s>B!>oM=~OVHu)rMZPAzJ?m)iD7*3( zVBhfs+yki5=DEdW&-#n3{9O(@)p_G1=g28+fmU9uRyRGo$B?xS#m@a(>viPu?Aj>9=? zo&6XlHu@x`ikYTtl=gP{_665bL$XV?+ zrBBAWrLH0z++uuyk}MoV#s@w?0SDSC3Q`tW2bTsNIFK`$?eMD?OEq+?bqSg zCv9iHav=45;7y2kcox7RFWAn0Ew{gq#d~KJ2Athz^PiAC3;P@jtaw6<<|sO~qgYLm zJR?KlfpX9ZSt)a!$&?Gr&cw*^Cmcau+1)P5K__Kwiy$w&va=NK*lCB9qEp+u@Y9q~ za^e)s9H`&;^e~0PD67XNkzYaV3!O{ zMUU+lRC0JHP4349WP0~?*ti^f*X~)|r`FD3(%3LV9m**jokkPVn=|<*Y}m|Vn%F-< zHqY&TyYjm2>~%-WYdfc|YLf@g_{Isl{_~7a(u_|w#g$xX*ENlF1d)MCG2b)ld~qlT zrrYA(IZ>OgnnS4u{NWjE6K?UkXu>WSBD~Y)?uF-ckdwK+CUP{p(k(p9V<2C{hTKfI zRZ7ok2g;tAaOO_AsB+4+<|tzlT!tAVb+XbWyM0L5Dh!)GHb#bu6w-Yhgtv?>Tf{rX z?+H5X()Ox#ZWV1m?49d20tb8WJy3%40$C&S zn`?4WsbSO51`PWVwKnw1MW-60?MR7Mwb=_xw6=D%ooc|IYPZ7)T}2&@Ih1}9o4v?t z*c9E6h*0bdrzx32F)L%F0e*>d43;VCX!wV{=uGnzho=PIU4brh44YybQgIgc*^0WI z!{8g}7iNr<;0gwt`;y59lh}&BYUoDCrhJHSZqo+P8BH$YgsISr22p!b)+#T?yxME$`|tIEeoV}iAN+qoR~qoUqOH#a(tEnYuniyEzVLK zGN19Gydyay@glN9oX0^6fLiI~{_1ur$yEI0c5WD*?CM0eg*CP*UI)A*t6hs?nZ^gK zVqaD05LJR4${RMUuT3cnGdOVk+1lUN%S9!I=HZwDW`UVR!oxysiq&8%EE{TQP6w4X zV)#%r1=9}(EkUEz&>A*oX7X27D!v?@qeaHkxn?cIn6CSdJBIfEkpW+k9VX!C&eXlXZl3x zQ3roEy=eG+2NK#7B0)Enb=;?)<=|J-3#D)e&&x8c8Hs78xsgR>d{ZxhzBbnkz6yHfcH%3O>i?+{=CfrYqk@~wuB{JfE;N$vA zJO3ynZTO##`29^=6dEKJOHjBgS?{a__5&?a4NH&XjA(cYbF>Du&aY3-=Ehd5x83u(ZS9)v7N;yKf4U-{CuT|@A~s}K6(z;<8LZ7PQkSm}<2MG3<@ z?OenK);rQ~{&ft0KWk!n`Fr?ObDqAxx&6AP0&q=4zLNhMr7L65)?cxU6Gs0Rx^lI6 zox=hResf+7UzDAesHiPXp`L0T-KKg+f<@##e)Ff%{PFA!Y0*)%5U2d+C=~o=CaPUW zLW7xCES&65`@qce#-*(7oQ26f3ssTD;LBO7{i_a@Y;H-Mcu zttf|s)Tw`OCSxSGQ~yttXwsefi(2WhfdsGh(lWk1lY7TSrG%7|9DA@O(1ws4aj8rr zf-fPOe>HB@s6UJZNjY>40`kS}9yi4rq@;fHlOy?roJrEFBY9m;iu6Pj_v9o>&qwj! zu9-W=kClFB=3B;(k{&YihsMv; z`T5)9lcYN`xPQD^$~5z+T+6WEma3n}^D)z3mh!UPHBxaYKbSi;N`m{8xVv;^t@^7l zN$-#{MgNJsXF|MmR^|y4#>eah)p+FM$i@ww`uj>$F`fFmOZdtOxz&{*xVsFU`WYzD z%ZFBd2}&hbIxNl+?k-EGek`hb(U6G}IdB2_@jBtcCQ4-}_T_sE^`p>k{HeFYou-%h zKa<2N_pm6@!R<1J;m$#33{n&OowP`OSmdmI+EUSZ_F%r7j} zlQ?KA%U8SME}u+C6?<+m@~((bcnF;Ey{A%ws2x&Kd*O z4!S-X)YQt8;KI8v*;Bk3sCT%m1kpAL@tZSEeAUDh*Ic#L{=L@Aq`tcYr_4JIm((aA z0zlxF$I)ax@x(6pc-u&VOYOU*kC#+u7ZOZKhvhukD_c`T~4fk#3 zRh#ma-7_QDw+;!BQXZVT>dN`UIvBA$-dl3@d|8mUtYt>9Gt#b{anRKWuMz+1`-%AU zFM{AYY;q9^aov+?X-bxKg!t1GoFx3OlhRY_A?nf>=lp)Mk?grYQ`8Zw0_BI_;~|qX zavw)n$L&CG?Lm`RVZ3duOf}6@Z=xUJB))1r{Jq2UaQtC^Xl-{v&MJk6Mk;8CH=41h~mAGL(%yP zpdA9pOQ5N5Nbrj5($f%q==nt{I@faC^yz8V@XvOMD%-B%$r`TE@G1?D)^Lc1KiB%d ztzoZ*_iMOK!%TawY!?L=TfR-D98d{tjfPV-{DIbfRKvS991s-qz4{wF)Rt}yAJOn$ z4d18XdV!(U5{+iq*~nor-UQt~t$X#Ya1f2ZLvjsI1x{(^>oreUXs7ZAoq zX0k@OsNrx82L_m^&9LlyGp!lUc9Ac)&E+o?ZkW3F4z>D|nJNx+Klq?puh^sFm^u|7 zwySuWgD;qxIJNEH$ZGTQWh*Vq)~(yPVwrRG#trxrs@31`e&~g%Il2+ewJV$(8>UrG zw>UR$w5;2>;SR>%EPBjTl#ny!^@0ydpICVR3(s%}CS-5AD$JCjVvB|= zG`vg0UJbKMmCvf-noR!Qw4C8qdugYGArs5MRc~VMa;AgYUJ6|*9Wz+UHGfG zk>Q7zsfP3dgJ9;?o5J~9)9dMv;JWate(xO*CdYvoJh=z8z|%LVOtb=U2Ob1iKb5V3 zt~>a%H^q}IE2a=r7m!s=6Vjf#L1iNC`VX?Kb&wSoR*mMv=+Y*H)mVe4cMlTaL;3px zNhtLS7eb$mG^=0bca^~dbu2==9>_XiDdd`eFm8sQ~Ct~+?-$)c&;rxxX1thHJ2AYjJ zGvAFg!Pj>KtwQi!yOAdN*m6<^yI<9Y(pL}!v7aUwO`x?Q;N&#W%xKdwmWsHb)>9F^ z)a^fUfVOq()|Ja?7hYSh&A7q2BGpFuFdo=hSb=CR+X69&VKA{$Wi)|i0)GxMR;qj! z(E3Q3&8jjcC+fr_>ZzMae&Ek@b81x{`ZL`h5s$hwdBkGhqQ)kdM$9K-4Tyu*a3ay@ z;66YDs+}hjjlok3fO1s%BEV$ov^oq@gUw|{XwF&Qh)?@?_NspuoKvrLIH>+nZ%#vF zY{QDw+zG@}1L7_c($Gk>JRTX8%f029uI+OW)4>p%7{ZJzhcRPi1T*d#T2FKt^klun z^b-+mN8nGH0TnP&GD#d%k2Omyq(RE1KBT!WESQB&3}In;A@!ndkJ@HMo2buG>zT+P zSImqQI#i%dj8zxHYHAhZ*_yj3=<<+A56V*G%TTN zdP6<7FoI<{6IjN|Q7mO*Jd4(iWZ`{bJ%;WOZ*T>62WDJwNRg4^Ojn9p9)w!JM zG*~YLknmV_K`H`@>cT@|ECNdH9yJBYql0K1?}GD!uEq3uRG z{tUmL4`D6V2$#o-(g1C+#4c=IQs9^B7G|*#Iu^Vf(F5&uX#bnmt}_{!X`+#t=7^=L zA5s%ip)&+CL!MA9H#Rne#bO(dgaRRZqI!nSf#pQADKpc zjK!f&>mW=qCq$TnBoQq%7}||y>>Eg@`jm3Ujsh&`bF`dKwM`vOO=R*#vlu%CC_ocE z2?z?AfPzZ?Gh5;<0k(RO9st~`vC;dXmj=~C<3d;*<{j4%)ibhwL``7i&%krR#@Jf) zyI|we%?)F9v0*IM845EPSZw7G7K=y{n`iD3!{cYaT`lE0`7PEcm+Zv9KL7?lCR8Zx zp@&w5a17u8-Zp;!3FKUZ`POMe4CW0Cd-%q&_?yg|iEZ{vrFW@R5ax=~xz;u9PGiJ69g}VR`1D*mL z+RSI#b0QO2JR61NDF!bgqggDAJJe`@QRk{!UAt*kt+RBCeOaTkWQB8ev$%oX+OV>= zf%P{oxS>9CMI@Y>jj4Hg4Kr z+c0;NYqfJlU2Q|7V74_@t!`Ysd|j=m&8>Bcfz-+-VyxWAEL&kx?a9R1?yW&((3Q0s z<-N;rtJ)Ia!mFSixU7LMcTnH!pmo=RMdw|EXr-W8K%?X$@Eq8MQVl%u z|G(JyfBRyCviSf0#m1rK6;e=YAzQk7L-VqAt5+_qU)Hc}eXX;$Vd;jAt2eCL2-HyP zY--qWX!+dxrO;xwzIOfkjm@=qiB+uIQ&k`pEay|J5(zD+N+)!;ik_{?q1qd2+jW&V zdj3Th<+~OH%QFD_GqP^r1qdGnC{sB43}8ihaI!~9;CFyJ&|LxIO_OttrU*01tys!8Q|tmC6)k>2G}ZqD;Z|M~LoJM$l#17F9mQ-qU~=L8sVz zs|!JRB`Oqqqo)}WQ~Hi`WSKvs9*l@j|_tKhW~D+W*lUM;a|012uA9)e?}8iSdDw*!by zN6Qz0b)*E2q$+?DWjdbj$AI>-MRefgJgomgRC4q1muCPPxDEJ4 z01e;;{`Ewd9XtcMmzt~PO$P6sj1evbJcBafn*d&vt-x~tVgkU=0J=dx2>ceH59OZ8 zSpVv&NRk_%HSilnjEzFM2YARdbp|Hj`eLjz=!E|VAcWq8S6`(nj}>?s;9>Br1m3IB z310vZ|386EGt@HSG%CB$kcmQt)je-~IFhc}04h@g z_Jmf(J6d)fK>qO!@F)k?5rPO$)5;aVp|`?UK(_#|sld!pb^=>&gZWXm1K$ClN!9}& zxDD%1LJy$g15lYRU_S>C)1$yw0hiI4E>oN4!sAhH1KtDhp-eXx%Dn6ER54}H_cfqD zFs00YM42$<#iPJam~vezuQj9a1YkER^w#GsfMo-&=G*zOB}Ksp8QX#Pk|Pe)EO|m# zUV}fv!x)F3@IKM~MDD(Vee%AFebxJF_SNmXZ(rNKhxZ-acX*$7U-$m=`+N5L_V?|- ewx2y|c+&J_%#+uiGCdvhG&;8(npty1_kRG~xdn&- delta 10128 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 P?6aucdS=Ok=QRHfj&AKm